Add parser for ffmpeg and HLS stream mapping

This commit is contained in:
Ingo Oppermann
2023-09-26 11:40:48 +02:00
parent d2016fff70
commit ab7c9e448b
3 changed files with 405 additions and 7 deletions

View File

@@ -204,7 +204,9 @@ func (p *parser) Parse(line string) uint64 {
isDefaultProgress := strings.HasPrefix(line, "frame=") isDefaultProgress := strings.HasPrefix(line, "frame=")
isFFmpegInputs := strings.HasPrefix(line, "ffmpeg.inputs:") isFFmpegInputs := strings.HasPrefix(line, "ffmpeg.inputs:")
isFFmpegOutputs := strings.HasPrefix(line, "ffmpeg.outputs:") isFFmpegOutputs := strings.HasPrefix(line, "ffmpeg.outputs:")
isFFmpegMapping := strings.HasPrefix(line, "ffmpeg.mapping:")
isFFmpegProgress := strings.HasPrefix(line, "ffmpeg.progress:") isFFmpegProgress := strings.HasPrefix(line, "ffmpeg.progress:")
isHLSStreamMap := strings.HasPrefix(line, "hls.streammap:")
isAVstreamProgress := strings.HasPrefix(line, "avstream.progress:") isAVstreamProgress := strings.HasPrefix(line, "avstream.progress:")
p.lock.log.Lock() p.lock.log.Lock()
@@ -233,7 +235,7 @@ func (p *parser) Parse(line string) uint64 {
} }
if isFFmpegInputs { if isFFmpegInputs {
if err := p.parseIO("input", strings.TrimPrefix(line, "ffmpeg.inputs:")); err != nil { if err := p.parseFFmpegIO("input", strings.TrimPrefix(line, "ffmpeg.inputs:")); err != nil {
p.logger.WithFields(log.Fields{ p.logger.WithFields(log.Fields{
"line": line, "line": line,
"error": err, "error": err,
@@ -243,8 +245,17 @@ func (p *parser) Parse(line string) uint64 {
return 0 return 0
} }
if isHLSStreamMap {
if err := p.parseHLSStreamMap(strings.TrimPrefix(line, "hls.streammap:")); err != nil {
p.logger.WithFields(log.Fields{
"line": line,
"error": err,
}).Error().Log("Failed parsing HSL stream mapping")
}
}
if isFFmpegOutputs { if isFFmpegOutputs {
if err := p.parseIO("output", strings.TrimPrefix(line, "ffmpeg.outputs:")); err != nil { if err := p.parseFFmpegIO("output", strings.TrimPrefix(line, "ffmpeg.outputs:")); err != nil {
p.logger.WithFields(log.Fields{ p.logger.WithFields(log.Fields{
"line": line, "line": line,
"error": err, "error": err,
@@ -273,6 +284,17 @@ func (p *parser) Parse(line string) uint64 {
} }
} }
if isFFmpegMapping {
if err := p.parseFFmpegMapping(strings.TrimPrefix(line, "ffmpeg.mapping:")); err != nil {
p.logger.WithFields(log.Fields{
"line": line,
"error": err,
}).Error().Log("Failed parsing mapping")
}
return 0
}
if !isDefaultProgress && !isFFmpegProgress && !isAVstreamProgress { if !isDefaultProgress && !isFFmpegProgress && !isAVstreamProgress {
// Write the current non-progress line to the log // Write the current non-progress line to the log
p.addLog(line) p.addLog(line)
@@ -491,7 +513,7 @@ func (p *parser) parseDefaultProgress(line string) error {
return nil return nil
} }
func (p *parser) parseIO(kind, line string) error { func (p *parser) parseFFmpegIO(kind, line string) error {
processIO := []ffmpegProcessIO{} processIO := []ffmpegProcessIO{}
err := json.Unmarshal([]byte(line), &processIO) err := json.Unmarshal([]byte(line), &processIO)
@@ -518,6 +540,32 @@ func (p *parser) parseIO(kind, line string) error {
return nil return nil
} }
func (p *parser) parseFFmpegMapping(line string) error {
mapping := ffmpegStreamMapping{}
err := json.Unmarshal([]byte(line), &mapping)
if err != nil {
return err
}
p.process.mapping = mapping
return nil
}
func (p *parser) parseHLSStreamMap(line string) error {
mapping := ffmpegHLSStreamMap{}
err := json.Unmarshal([]byte(line), &mapping)
if err != nil {
return err
}
p.process.hlsMapping = &mapping
return nil
}
func (p *parser) parseFFmpegProgress(line string) error { func (p *parser) parseFFmpegProgress(line string) error {
progress := ffmpegProgress{} progress := ffmpegProgress{}

View File

@@ -922,6 +922,165 @@ func TestParserProgressPlayout(t *testing.T) {
}, progress) }, progress)
} }
func TestParserStreamMapping(t *testing.T) {
parser := New(Config{
LogLines: 20,
}).(*parser)
parser.Parse(`ffmpeg.inputs:[{"url":"https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8","format":"hls","index":0,"stream":0,"type":"video","codec":"h264","coder":"h264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":25.000000,"pix_fmt":"yuvj420p","width":1280,"height":720},{"url":"https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk_1440.m3u8","format":"hls","index":1,"stream":0,"type":"video","codec":"h264","coder":"h264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":25.000000,"pix_fmt":"yuvj420p","width":2560,"height":1440},{"url":"anullsrc=r=44100:cl=mono","format":"lavfi","index":2,"stream":0,"type":"audio","codec":"pcm_u8","coder":"pcm_u8","bitrate_kbps":352,"duration_sec":0.000000,"language":"und","sampling_hz":44100,"layout":"mono","channels":1}]`)
parser.Parse(`hls.streammap:{"address":"http://127.0.0.1:8080/memfs/live/%v.m3u8","variants":[{"variant":0,"address":"http://127.0.0.1:8080/memfs/live/0.m3u8","streams":[0,2]},{"variant":1,"address":"http://127.0.0.1:8080/memfs/live/1.m3u8","streams":[1,3]}]}`)
parser.Parse(`ffmpeg.outputs:[{"url":"http://127.0.0.1:8080/memfs/live/%v.m3u8","format":"hls","index":0,"stream":0,"type":"video","codec":"h264","coder":"copy","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":25.000000,"pix_fmt":"yuvj420p","width":1280,"height":720},{"url":"http://127.0.0.1:8080/memfs/live/%v.m3u8","format":"hls","index":0,"stream":1,"type":"video","codec":"h264","coder":"copy","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":25.000000,"pix_fmt":"yuvj420p","width":2560,"height":1440},{"url":"http://127.0.0.1:8080/memfs/live/%v.m3u8","format":"hls","index":0,"stream":2,"type":"audio","codec":"aac","coder":"aac","bitrate_kbps":69,"duration_sec":0.000000,"language":"und","sampling_hz":44100,"layout":"mono","channels":1},{"url":"http://127.0.0.1:8080/memfs/live/%v.m3u8","format":"hls","index":0,"stream":3,"type":"audio","codec":"aac","coder":"aac","bitrate_kbps":69,"duration_sec":0.000000,"language":"und","sampling_hz":44100,"layout":"mono","channels":1}]`)
parser.Parse(`ffmpeg.mapping:{"graphs":[{"index":0,"graph":[{"src_name":"Parsed_null_0","src_filter":"null","dst_name":"format","dst_filter":"format","inpad":"default","outpad":"default","timebase": "1/90000","type":"video","format":"yuvj420p","width":1280,"height":720},{"src_name":"graph 0 input from stream 0:0","src_filter":"buffer","dst_name":"Parsed_null_0","dst_filter":"null","inpad":"default","outpad":"default","timebase": "1/90000","type":"video","format":"yuvj420p","width":1280,"height":720},{"src_name":"format","src_filter":"format","dst_name":"out_0_0","dst_filter":"buffersink","inpad":"default","outpad":"default","timebase": "1/90000","type":"video","format":"yuvj420p","width":1280,"height":720}]},{"index":1,"graph":[{"src_name":"Parsed_anull_0","src_filter":"anull","dst_name":"auto_aresample_0","dst_filter":"aresample","inpad":"default","outpad":"default","timebase": "1/44100","type":"audio","format":"u8","sampling_hz":44100,"layout":"mono"},{"src_name":"graph_1_in_2_0","src_filter":"abuffer","dst_name":"Parsed_anull_0","dst_filter":"anull","inpad":"default","outpad":"default","timebase": "1/44100","type":"audio","format":"u8","sampling_hz":44100,"layout":"mono"},{"src_name":"format_out_0_2","src_filter":"aformat","dst_name":"out_0_2","dst_filter":"abuffersink","inpad":"default","outpad":"default","timebase": "1/44100","type":"audio","format":"fltp","sampling_hz":44100,"layout":"mono"},{"src_name":"auto_aresample_0","src_filter":"aresample","dst_name":"format_out_0_2","dst_filter":"aformat","inpad":"default","outpad":"default","timebase": "1/44100","type":"audio","format":"fltp","sampling_hz":44100,"layout":"mono"}]},{"index":2,"graph":[{"src_name":"Parsed_anull_0","src_filter":"anull","dst_name":"auto_aresample_0","dst_filter":"aresample","inpad":"default","outpad":"default","timebase": "1/44100","type":"audio","format":"u8","sampling_hz":44100,"layout":"mono"},{"src_name":"graph_2_in_2_0","src_filter":"abuffer","dst_name":"Parsed_anull_0","dst_filter":"anull","inpad":"default","outpad":"default","timebase": "1/44100","type":"audio","format":"u8","sampling_hz":44100,"layout":"mono"},{"src_name":"format_out_0_3","src_filter":"aformat","dst_name":"out_0_3","dst_filter":"abuffersink","inpad":"default","outpad":"default","timebase": "1/44100","type":"audio","format":"fltp","sampling_hz":44100,"layout":"mono"},{"src_name":"auto_aresample_0","src_filter":"aresample","dst_name":"format_out_0_3","dst_filter":"aformat","inpad":"default","outpad":"default","timebase": "1/44100","type":"audio","format":"fltp","sampling_hz":44100,"layout":"mono"}]}],"mapping":[{"input":{"index":0,"stream":0},"graph":{"index":0,"name":"graph 0 input from stream 0:0"},"output":null},{"input":{"index":2,"stream":0},"graph":{"index":1,"name":"graph_1_in_2_0"},"output":null},{"input":{"index":2,"stream":0},"graph":{"index":2,"name":"graph_2_in_2_0"},"output":null},{"input":null,"graph":{"index":0,"name":"out_0_0"},"output":{"index":0,"stream":0}},{"input":{"index":1,"stream":0},"output":{"index":0,"stream":1},"copy":true},{"input":null,"graph":{"index":1,"name":"out_0_2"},"output":{"index":0,"stream":2}},{"input":null,"graph":{"index":2,"name":"out_0_3"},"output":{"index":0,"stream":3}}]}`)
parser.Parse(`ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"framerate":{"min":24.975,"max":24.975,"avg":24.975},"frame":149,"keyframe":3,"packet":149,"size_kb":1467,"size_bytes":1501854},{"index":1,"stream":0,"framerate":{"min":24.975,"max":24.975,"avg":24.975},"frame":149,"keyframe":3,"packet":149,"size_kb":4428,"size_bytes":4534541},{"index":2,"stream":0,"framerate":{"min":43.066,"max":43.068,"avg":43.066},"frame":257,"keyframe":257,"packet":257,"size_kb":257,"size_bytes":263168}],"outputs":[{"index":0,"stream":0,"frame":149,"keyframe":3,"packet":149,"q":-1.0,"size_kb":1467,"size_bytes":1501923,"extradata_size_bytes":69},{"index":0,"stream":1,"frame":149,"keyframe":3,"packet":149,"q":-1.0,"size_kb":4428,"size_bytes":4534612,"extradata_size_bytes":71},{"index":0,"stream":2,"frame":257,"keyframe":256,"packet":256,"size_kb":1,"size_bytes":1046,"extradata_size_bytes":5},{"index":0,"stream":3,"frame":257,"keyframe":256,"packet":256,"size_kb":1,"size_bytes":1046,"extradata_size_bytes":5}],"frame":149,"packet":149,"q":-1.0,"size_kb":5897,"size_bytes":6038627,"time":"0h0m5.96s","speed":4.79,"dup":0,"drop":0}`)
progress := parser.Progress()
require.Equal(t, 3, len(progress.Input))
require.Equal(t, 4, len(progress.Output))
require.Equal(t, StreamMapping{
Graphs: []Graph{
{
Index: 0,
Graph: []GraphElement{
{SrcName: "Parsed_null_0", SrcFilter: "null", DstName: "format", DstFilter: "format", Inpad: "default", Outpad: "default", Timebase: "1/90000", Type: "video", Format: "yuvj420p", Sampling: 0, Layout: "", Width: 1280, Height: 720},
{SrcName: "graph 0 input from stream 0:0", SrcFilter: "buffer", DstName: "Parsed_null_0", DstFilter: "null", Inpad: "default", Outpad: "default", Timebase: "1/90000", Type: "video", Format: "yuvj420p", Sampling: 0, Layout: "", Width: 1280, Height: 720},
{SrcName: "format", SrcFilter: "format", DstName: "out_0_0", DstFilter: "buffersink", Inpad: "default", Outpad: "default", Timebase: "1/90000", Type: "video", Format: "yuvj420p", Sampling: 0, Layout: "", Width: 1280, Height: 720},
},
}, {
Index: 1,
Graph: []GraphElement{
{SrcName: "Parsed_anull_0", SrcFilter: "anull", DstName: "auto_aresample_0", DstFilter: "aresample", Inpad: "default", Outpad: "default", Timebase: "1/44100", Type: "audio", Format: "u8", Sampling: 44100, Layout: "mono", Width: 0, Height: 0},
{SrcName: "graph_1_in_2_0", SrcFilter: "abuffer", DstName: "Parsed_anull_0", DstFilter: "anull", Inpad: "default", Outpad: "default", Timebase: "1/44100", Type: "audio", Format: "u8", Sampling: 44100, Layout: "mono", Width: 0, Height: 0},
{SrcName: "format_out_0_2", SrcFilter: "aformat", DstName: "out_0_2", DstFilter: "abuffersink", Inpad: "default", Outpad: "default", Timebase: "1/44100", Type: "audio", Format: "fltp", Sampling: 44100, Layout: "mono", Width: 0, Height: 0},
{SrcName: "auto_aresample_0", SrcFilter: "aresample", DstName: "format_out_0_2", DstFilter: "aformat", Inpad: "default", Outpad: "default", Timebase: "1/44100", Type: "audio", Format: "fltp", Sampling: 44100, Layout: "mono", Width: 0, Height: 0},
},
}, {
Index: 2,
Graph: []GraphElement{
{SrcName: "Parsed_anull_0", SrcFilter: "anull", DstName: "auto_aresample_0", DstFilter: "aresample", Inpad: "default", Outpad: "default", Timebase: "1/44100", Type: "audio", Format: "u8", Sampling: 44100, Layout: "mono", Width: 0, Height: 0},
{SrcName: "graph_2_in_2_0", SrcFilter: "abuffer", DstName: "Parsed_anull_0", DstFilter: "anull", Inpad: "default", Outpad: "default", Timebase: "1/44100", Type: "audio", Format: "u8", Sampling: 44100, Layout: "mono", Width: 0, Height: 0},
{SrcName: "format_out_0_3", SrcFilter: "aformat", DstName: "out_0_3", DstFilter: "abuffersink", Inpad: "default", Outpad: "default", Timebase: "1/44100", Type: "audio", Format: "fltp", Sampling: 44100, Layout: "mono", Width: 0, Height: 0},
{SrcName: "auto_aresample_0", SrcFilter: "aresample", DstName: "format_out_0_3", DstFilter: "aformat", Inpad: "default", Outpad: "default", Timebase: "1/44100", Type: "audio", Format: "fltp", Sampling: 44100, Layout: "mono", Width: 0, Height: 0},
},
},
},
Mapping: []Mapping{
{
Input: 0,
Output: -1,
Graph: MappingGraph{
Index: 0,
Name: "graph 0 input from stream 0:0",
},
Copy: false,
},
{
Input: 2,
Output: -1,
Graph: MappingGraph{
Index: 1,
Name: "graph_1_in_2_0",
},
Copy: false,
},
{
Input: 2,
Output: -1,
Graph: MappingGraph{
Index: 2,
Name: "graph_2_in_2_0",
},
Copy: false,
},
{
Input: -1,
Output: 0,
Graph: MappingGraph{
Index: 0,
Name: "out_0_0",
},
Copy: false,
},
{
Input: 1,
Output: 1,
Graph: MappingGraph{
Index: -1,
Name: "",
},
Copy: true,
},
{
Input: -1,
Output: 2,
Graph: MappingGraph{
Index: 1,
Name: "out_0_2",
},
Copy: false,
},
{
Input: -1,
Output: 3,
Graph: MappingGraph{
Index: 2,
Name: "out_0_3",
},
Copy: false,
},
},
}, progress.Mapping)
require.Equal(t, "http://127.0.0.1:8080/memfs/live/0.m3u8", progress.Output[0].Address)
require.Equal(t, uint64(0), progress.Output[0].Index)
require.Equal(t, uint64(0), progress.Output[0].Stream)
require.Equal(t, "http://127.0.0.1:8080/memfs/live/1.m3u8", progress.Output[1].Address)
require.Equal(t, uint64(1), progress.Output[1].Index)
require.Equal(t, uint64(0), progress.Output[1].Stream)
require.Equal(t, "http://127.0.0.1:8080/memfs/live/0.m3u8", progress.Output[2].Address)
require.Equal(t, uint64(0), progress.Output[2].Index)
require.Equal(t, uint64(1), progress.Output[2].Stream)
require.Equal(t, "http://127.0.0.1:8080/memfs/live/1.m3u8", progress.Output[3].Address)
require.Equal(t, uint64(1), progress.Output[3].Index)
require.Equal(t, uint64(1), progress.Output[3].Stream)
}
func TestParserHLSMapping(t *testing.T) {
parser := New(Config{
LogLines: 20,
}).(*parser)
parser.Parse(`ffmpeg.inputs:[{"url":"https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8","format":"hls","index":0,"stream":0,"type":"video","codec":"h264","coder":"h264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":25.000000,"pix_fmt":"yuvj420p","width":1280,"height":720},{"url":"https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk_1440.m3u8","format":"hls","index":1,"stream":0,"type":"video","codec":"h264","coder":"h264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":25.000000,"pix_fmt":"yuvj420p","width":2560,"height":1440},{"url":"anullsrc=r=44100:cl=mono","format":"lavfi","index":2,"stream":0,"type":"audio","codec":"pcm_u8","coder":"pcm_u8","bitrate_kbps":352,"duration_sec":0.000000,"language":"und","sampling_hz":44100,"layout":"mono","channels":1}]`)
parser.Parse(`hls.streammap:{"address":"http://127.0.0.1:8080/memfs/live/%v.m3u8","variants":[{"variant":0,"address":"http://127.0.0.1:8080/memfs/live/0.m3u8","streams":[0,2]},{"variant":1,"address":"http://127.0.0.1:8080/memfs/live/1.m3u8","streams":[1,3]}]}`)
parser.Parse(`ffmpeg.outputs:[{"url":"http://127.0.0.1:8080/memfs/live/%v.m3u8","format":"hls","index":0,"stream":0,"type":"video","codec":"h264","coder":"copy","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":25.000000,"pix_fmt":"yuvj420p","width":1280,"height":720},{"url":"http://127.0.0.1:8080/memfs/live/%v.m3u8","format":"hls","index":0,"stream":1,"type":"video","codec":"h264","coder":"copy","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":25.000000,"pix_fmt":"yuvj420p","width":2560,"height":1440},{"url":"http://127.0.0.1:8080/memfs/live/%v.m3u8","format":"hls","index":0,"stream":2,"type":"audio","codec":"aac","coder":"aac","bitrate_kbps":69,"duration_sec":0.000000,"language":"und","sampling_hz":44100,"layout":"mono","channels":1},{"url":"http://127.0.0.1:8080/memfs/live/%v.m3u8","format":"hls","index":0,"stream":3,"type":"audio","codec":"aac","coder":"aac","bitrate_kbps":69,"duration_sec":0.000000,"language":"und","sampling_hz":44100,"layout":"mono","channels":1}]`)
parser.Parse(`ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"framerate":{"min":24.975,"max":24.975,"avg":24.975},"frame":149,"keyframe":3,"packet":149,"size_kb":1467,"size_bytes":1501854},{"index":1,"stream":0,"framerate":{"min":24.975,"max":24.975,"avg":24.975},"frame":149,"keyframe":3,"packet":149,"size_kb":4428,"size_bytes":4534541},{"index":2,"stream":0,"framerate":{"min":43.066,"max":43.068,"avg":43.066},"frame":257,"keyframe":257,"packet":257,"size_kb":257,"size_bytes":263168}],"outputs":[{"index":0,"stream":0,"frame":149,"keyframe":3,"packet":149,"q":-1.0,"size_kb":1467,"size_bytes":1501923,"extradata_size_bytes":69},{"index":0,"stream":1,"frame":149,"keyframe":3,"packet":149,"q":-1.0,"size_kb":4428,"size_bytes":4534612,"extradata_size_bytes":71},{"index":0,"stream":2,"frame":257,"keyframe":256,"packet":256,"size_kb":1,"size_bytes":1046,"extradata_size_bytes":5},{"index":0,"stream":3,"frame":257,"keyframe":256,"packet":256,"size_kb":1,"size_bytes":1046,"extradata_size_bytes":5}],"frame":149,"packet":149,"q":-1.0,"size_kb":5897,"size_bytes":6038627,"time":"0h0m5.96s","speed":4.79,"dup":0,"drop":0}`)
progress := parser.Progress()
require.Equal(t, 3, len(progress.Input))
require.Equal(t, 4, len(progress.Output))
require.Equal(t, "http://127.0.0.1:8080/memfs/live/0.m3u8", progress.Output[0].Address)
require.Equal(t, uint64(0), progress.Output[0].Index)
require.Equal(t, uint64(0), progress.Output[0].Stream)
require.Equal(t, "http://127.0.0.1:8080/memfs/live/1.m3u8", progress.Output[1].Address)
require.Equal(t, uint64(1), progress.Output[1].Index)
require.Equal(t, uint64(0), progress.Output[1].Stream)
require.Equal(t, "http://127.0.0.1:8080/memfs/live/0.m3u8", progress.Output[2].Address)
require.Equal(t, uint64(0), progress.Output[2].Index)
require.Equal(t, uint64(1), progress.Output[2].Stream)
require.Equal(t, "http://127.0.0.1:8080/memfs/live/1.m3u8", progress.Output[3].Address)
require.Equal(t, uint64(1), progress.Output[3].Index)
require.Equal(t, uint64(1), progress.Output[3].Stream)
}
func TestParserPatterns(t *testing.T) { func TestParserPatterns(t *testing.T) {
p := New(Config{ p := New(Config{
LogHistory: 3, LogHistory: 3,

View File

@@ -115,8 +115,6 @@ type ffmpegProgressIO struct {
} }
func (io *ffmpegProgressIO) exportTo(progress *ProgressIO) { func (io *ffmpegProgressIO) exportTo(progress *ProgressIO) {
progress.Index = io.Index
progress.Stream = io.Stream
progress.Frame = io.Frame progress.Frame = io.Frame
progress.Keyframe = io.Keyframe progress.Keyframe = io.Keyframe
progress.Framerate.Min = io.Framerate.Min progress.Framerate.Min = io.Framerate.Min
@@ -228,9 +226,132 @@ func (io *ffmpegProcessIO) export() ProgressIO {
} }
} }
type ffmpegGraphElement struct {
SrcName string `json:"src_name"`
SrcFilter string `json:"src_filter"`
DstName string `json:"dst_name"`
DstFilter string `json:"dst_filter"`
Inpad string `json:"inpad"`
Outpad string `json:"outpad"`
Timebase string `json:"timebase"`
Type string `json:"type"`
Format string `json:"format"`
Sampling uint64 `json:"sampling_hz"`
Layout string `json:"layout"`
Width uint64 `json:"width"`
Height uint64 `json:"height"`
}
func (f *ffmpegGraphElement) Export() GraphElement {
return GraphElement{
SrcName: f.SrcName,
SrcFilter: f.SrcFilter,
DstName: f.DstName,
DstFilter: f.DstFilter,
Inpad: f.Inpad,
Outpad: f.Outpad,
Timebase: f.Timebase,
Type: f.Type,
Format: f.Format,
Sampling: f.Sampling,
Layout: f.Layout,
Width: f.Width,
Height: f.Height,
}
}
type ffmpegGraph struct {
Index uint64 `json:"index"`
Graph []ffmpegGraphElement `json:"graph"`
}
func (f *ffmpegGraph) Export() Graph {
g := Graph{
Index: f.Index,
}
for _, e := range f.Graph {
g.Graph = append(g.Graph, e.Export())
}
return g
}
type ffmpegMapping struct {
Input *ffmpegMappingIO `json:"input"`
Output *ffmpegMappingIO `json:"output"`
Graph struct {
Index uint64 `json:"index"`
Name string `json:"name"`
} `json:"graph"`
Copy bool `json:"copy"`
}
type ffmpegMappingIO struct {
Index uint64 `json:"index"`
Stream uint64 `json:"stream"`
}
type ffmpegStreamMapping struct {
Graphs []ffmpegGraph `json:"graphs"`
Mapping []ffmpegMapping `json:"mapping"`
}
type ffmpegProcess struct { type ffmpegProcess struct {
input []ffmpegProcessIO input []ffmpegProcessIO
output []ffmpegProcessIO output []ffmpegProcessIO
mapping ffmpegStreamMapping
hlsMapping *ffmpegHLSStreamMap
}
func (f *ffmpegProcess) ExportMapping() StreamMapping {
sm := StreamMapping{}
for _, g := range f.mapping.Graphs {
sm.Graphs = append(sm.Graphs, g.Export())
}
for _, fm := range f.mapping.Mapping {
m := Mapping{
Input: -1,
Output: -1,
Graph: MappingGraph{
Index: int(fm.Graph.Index),
Name: fm.Graph.Name,
},
Copy: fm.Copy,
}
if len(m.Graph.Name) == 0 {
m.Graph.Index = -1
}
if fm.Input != nil {
for i, in := range f.input {
if in.Index != fm.Input.Index || in.Stream != fm.Input.Stream {
continue
}
m.Input = i
break
}
}
if fm.Output != nil {
for i, out := range f.output {
if out.Index != fm.Output.Index || out.Stream != fm.Output.Stream {
continue
}
m.Output = i
break
}
}
sm.Mapping = append(sm.Mapping, m)
}
return sm
} }
func (p *ffmpegProcess) export() Progress { func (p *ffmpegProcess) export() Progress {
@@ -248,9 +369,40 @@ func (p *ffmpegProcess) export() Progress {
progress.Output = append(progress.Output, aio) progress.Output = append(progress.Output, aio)
} }
progress.Mapping = p.ExportMapping()
if p.hlsMapping != nil {
for _, variant := range p.hlsMapping.Variants {
for s, stream := range variant.Streams {
if stream >= len(progress.Output) {
continue
}
output := progress.Output[stream]
output.Address = variant.Address
output.Index = variant.Variant
output.Stream = uint64(s)
progress.Output[stream] = output
}
}
}
return progress return progress
} }
type ffmpegHLSStreamMap struct {
Address string `json:"address"`
Variants []ffmpegHLSVariant `json:"variants"`
}
type ffmpegHLSVariant struct {
Variant uint64 `json:"variant"`
Address string `json:"address"`
Streams []int `json:"streams"`
}
type ProgressIO struct { type ProgressIO struct {
Address string Address string
@@ -293,6 +445,7 @@ type ProgressIO struct {
type Progress struct { type Progress struct {
Input []ProgressIO Input []ProgressIO
Output []ProgressIO Output []ProgressIO
Mapping StreamMapping
Frame uint64 Frame uint64
Packet uint64 Packet uint64
FPS float64 FPS float64
@@ -341,3 +494,41 @@ type Usage struct {
Limit uint64 Limit uint64
} }
} }
type GraphElement struct {
SrcName string
SrcFilter string
DstName string
DstFilter string
Inpad string
Outpad string
Timebase string
Type string // audio or video
Format string
Sampling uint64 // Hz
Layout string
Width uint64
Height uint64
}
type Graph struct {
Index uint64
Graph []GraphElement
}
type MappingGraph struct {
Index int
Name string
}
type Mapping struct {
Input int
Output int
Graph MappingGraph
Copy bool
}
type StreamMapping struct {
Graphs []Graph
Mapping []Mapping
}