mirror of
https://github.com/datarhei/core.git
synced 2025-10-06 16:37:04 +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=")
|
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{}
|
||||||
|
|
||||||
|
@@ -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,
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user