From 12d27ce67aaf21652acc84ccd5a037c900735c81 Mon Sep 17 00:00:00 2001 From: Elite Encoder Date: Thu, 11 Jul 2024 13:52:20 -0400 Subject: [PATCH] Add media duration to lpms_get_codec_info for GetCodecInfo (#407) * add fps and duration to GetCodecInfo --- ffmpeg/decoder.c | 2 +- ffmpeg/extras.c | 4 ++++ ffmpeg/extras.h | 2 ++ ffmpeg/ffmpeg.go | 6 +++++- ffmpeg/ffmpeg_test.go | 47 ++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 58 insertions(+), 3 deletions(-) diff --git a/ffmpeg/decoder.c b/ffmpeg/decoder.c index 907751c1bb..6b2019efcb 100755 --- a/ffmpeg/decoder.c +++ b/ffmpeg/decoder.c @@ -337,7 +337,7 @@ int open_input(input_params *params, struct input_ctx *ctx) ctx->transmuxing = params->transmuxing; - // open demuxer + // open demuxer/ open demuxer AVDictionary **demuxer_opts = NULL; if (params->demuxer.opts) demuxer_opts = ¶ms->demuxer.opts; ret = avformat_open_input(&ic, inp, NULL, demuxer_opts); diff --git a/ffmpeg/extras.c b/ffmpeg/extras.c index 471dd48d31..82a7609e1e 100644 --- a/ffmpeg/extras.c +++ b/ffmpeg/extras.c @@ -164,6 +164,9 @@ int lpms_get_codec_info(char *fname, pcodec_info out) // instead of returning -1 ret = GET_CODEC_STREAMS_MISSING; } + if (ic->duration != AV_NOPTS_VALUE) { + out->dur = ic->duration / AV_TIME_BASE; + } // Return if (video_present && vc->name) { strncpy(out->video_codec, vc->name, MIN(strlen(out->video_codec), strlen(vc->name))+1); @@ -176,6 +179,7 @@ int lpms_get_codec_info(char *fname, pcodec_info out) } out->width = ic->streams[vstream]->codecpar->width; out->height = ic->streams[vstream]->codecpar->height; + out->fps = av_q2d(ic->streams[vstream]->r_frame_rate); } else { // Indicate failure to extract video codec from given container out->video_codec[0] = 0; diff --git a/ffmpeg/extras.h b/ffmpeg/extras.h index 96f172a1e6..b06b0903ac 100644 --- a/ffmpeg/extras.h +++ b/ffmpeg/extras.h @@ -7,6 +7,8 @@ typedef struct s_codec_info { int pixel_format; int width; int height; + double fps; + double dur; } codec_info, *pcodec_info; int lpms_rtmp2hls(char *listen, char *outf, char *ts_tmpl, char *seg_time, char *seg_start); diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go index 072bd4a9c6..94d9ff33d6 100755 --- a/ffmpeg/ffmpeg.go +++ b/ffmpeg/ffmpeg.go @@ -245,6 +245,8 @@ type MediaFormatInfo struct { Acodec, Vcodec string PixFormat PixelFormat Width, Height int + FPS float32 + DurSecs int64 } func (f *MediaFormatInfo) ScaledHeight(width int) int { @@ -277,6 +279,8 @@ func GetCodecInfo(fname string) (CodecStatus, MediaFormatInfo, error) { format.PixFormat = PixelFormat{int(params_c.pixel_format)} format.Width = int(params_c.width) format.Height = int(params_c.height) + format.FPS = float32(params_c.fps) + format.DurSecs = int64(params_c.dur) return status, format, nil } @@ -979,7 +983,7 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions) input.Profile.FramerateDen = 1 } - // Do not try tofree in this function because in the C code avformat_open_input() + // Do not try to free in this function because in the C code avformat_open_input() // will destroy this demuxerOpts.opts = newAVOpts(map[string]string{ "framerate": fmt.Sprintf("%d/%d", input.Profile.Framerate, input.Profile.FramerateDen), diff --git a/ffmpeg/ffmpeg_test.go b/ffmpeg/ffmpeg_test.go index 5c59128dfe..fa2f822523 100644 --- a/ffmpeg/ffmpeg_test.go +++ b/ffmpeg/ffmpeg_test.go @@ -1873,7 +1873,6 @@ func TestTranscoder_VFR(t *testing.T) { run, dir := setupTest(t) defer os.RemoveAll(dir) - // prepare the input by generating a vfr video and verify its properties cmd := ` ffmpeg -hide_banner -i "$1/../transcoder/test.ts" -an -vf "setpts='\ @@ -1967,3 +1966,49 @@ PTS_EOF ` run(cmd) } + +func TestDurationFPS_GetCodecInfo(t *testing.T) { + run, dir := setupTest(t) + defer os.RemoveAll(dir) + + //Generate test files + cmd := ` + cp "$1/../data/duplicate-audio-dts.ts" test.ts + ffprobe -loglevel warning -show_format test.ts | grep duration=2.008555 + ffprobe -loglevel warning -show_streams -select_streams v test.ts | grep r_frame_rate=30/1 + cp "$1/../data/bunny.mp4" test.mp4 + ffmpeg -loglevel warning -i test.mp4 -c:v copy -c:a copy -t 2 test-short.mp4 + ffprobe -loglevel warning -show_format test-short.mp4 | grep duration=2.043356 + ffprobe -loglevel warning -show_streams -select_streams v test-short.mp4 | grep r_frame_rate=24/1 + ffmpeg -loglevel warning -i test-short.mp4 -c:v libvpx -c:a vorbis -strict -2 -t 2 test.webm + ffprobe -loglevel warning -show_format test.webm | grep duration=2.049000 + ffprobe -loglevel warning -show_streams -select_streams v test.webm | grep r_frame_rate=24/1 + ffmpeg -loglevel warning -i test-short.mp4 -vn -c:a aac -b:a 128k test.m4a + ffprobe -loglevel warning -show_format test.m4a | grep duration=2.042993 + ffmpeg -loglevel warning -i test-short.mp4 -vn -c:a flac test.flac + ffprobe -loglevel warning -show_format test.flac | grep duration=2.043356 + ` + run(cmd) + + files := []struct { + Filename string + Duration int64 + FPS float32 + }{ + {Filename: "test-short.mp4", Duration: 2, FPS: 24}, + {Filename: "test.ts", Duration: 2, FPS: 30.0}, + {Filename: "test.flac", Duration: 2, FPS: 0.0}, + {Filename: "test.webm", Duration: 2, FPS: 24}, + {Filename: "test.m4a", Duration: 2, FPS: 0.0}, + } + for _, file := range files { + t.Run(file.Filename, func(t *testing.T) { + assert := assert.New(t) + status, format, err := GetCodecInfo(path.Join(dir, file.Filename)) + assert.Nil(err, "getcodecinfo error") + assert.Equal(CodecStatusOk, status, "status not ok") + assert.Equal(file.Duration, format.DurSecs, "duration mismatch") + assert.Equal(file.FPS, format.FPS, "fps mismatch") + }) + } +}