mirror of
https://github.com/datarhei/core.git
synced 2025-10-06 08:27:08 +08:00
Add parser for ffmpeg and HLS stream mapping
This commit is contained in:
@@ -204,7 +204,9 @@ func (p *parser) Parse(line string) uint64 {
|
||||
isDefaultProgress := strings.HasPrefix(line, "frame=")
|
||||
isFFmpegInputs := strings.HasPrefix(line, "ffmpeg.inputs:")
|
||||
isFFmpegOutputs := strings.HasPrefix(line, "ffmpeg.outputs:")
|
||||
isFFmpegMapping := strings.HasPrefix(line, "ffmpeg.mapping:")
|
||||
isFFmpegProgress := strings.HasPrefix(line, "ffmpeg.progress:")
|
||||
isHLSStreamMap := strings.HasPrefix(line, "hls.streammap:")
|
||||
isAVstreamProgress := strings.HasPrefix(line, "avstream.progress:")
|
||||
|
||||
p.lock.log.Lock()
|
||||
@@ -233,7 +235,7 @@ func (p *parser) Parse(line string) uint64 {
|
||||
}
|
||||
|
||||
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{
|
||||
"line": line,
|
||||
"error": err,
|
||||
@@ -243,8 +245,17 @@ func (p *parser) Parse(line string) uint64 {
|
||||
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 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{
|
||||
"line": line,
|
||||
"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 {
|
||||
// Write the current non-progress line to the log
|
||||
p.addLog(line)
|
||||
@@ -491,7 +513,7 @@ func (p *parser) parseDefaultProgress(line string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) parseIO(kind, line string) error {
|
||||
func (p *parser) parseFFmpegIO(kind, line string) error {
|
||||
processIO := []ffmpegProcessIO{}
|
||||
|
||||
err := json.Unmarshal([]byte(line), &processIO)
|
||||
@@ -518,6 +540,32 @@ func (p *parser) parseIO(kind, line string) error {
|
||||
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 {
|
||||
progress := ffmpegProgress{}
|
||||
|
||||
|
@@ -922,6 +922,165 @@ func TestParserProgressPlayout(t *testing.T) {
|
||||
}, 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) {
|
||||
p := New(Config{
|
||||
LogHistory: 3,
|
||||
|
@@ -115,8 +115,6 @@ type ffmpegProgressIO struct {
|
||||
}
|
||||
|
||||
func (io *ffmpegProgressIO) exportTo(progress *ProgressIO) {
|
||||
progress.Index = io.Index
|
||||
progress.Stream = io.Stream
|
||||
progress.Frame = io.Frame
|
||||
progress.Keyframe = io.Keyframe
|
||||
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 {
|
||||
input []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 {
|
||||
@@ -248,9 +369,40 @@ func (p *ffmpegProcess) export() Progress {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
Address string
|
||||
|
||||
@@ -293,6 +445,7 @@ type ProgressIO struct {
|
||||
type Progress struct {
|
||||
Input []ProgressIO
|
||||
Output []ProgressIO
|
||||
Mapping StreamMapping
|
||||
Frame uint64
|
||||
Packet uint64
|
||||
FPS float64
|
||||
@@ -341,3 +494,41 @@ type Usage struct {
|
||||
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
|
||||
}
|
||||
|
Reference in New Issue
Block a user