From 1677f9e95b1456c043482c82d96fd2eb6d8e0154 Mon Sep 17 00:00:00 2001 From: Darby Johnston Date: Sat, 31 Aug 2024 10:29:04 -0700 Subject: [PATCH] feat(ffmpeg): FFmpeg additional metadata (#4396) This change adds additional FFmpeg metadata from the video and data streams. These streams can hold the start timecode which is very useful in OpenTimelineIO applications. I also added `av_guess_frame_rate()` which might be more robust than just checking `stream->avg_frame_rate`, but I can also create a separate PR if it is better to break it up. It doesn't look like there are any FFmpeg tests in `testsuite`? I tested with the DPEL ALab trailer and some Netflix Open Content movies, and was able to see the timecode metadata in the OIIO image attributes. --------- Signed-off-by: Darby Johnston --- src/doc/builtinplugins.rst | 3 +++ src/ffmpeg.imageio/ffmpeginput.cpp | 34 +++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 5743de5890..905af01e7a 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -1368,6 +1368,9 @@ Some special attributes are used for movie files: * - ``FramesPerSecond`` - int[2] (rational) - Frames per second + * - ``ffmpeg:TimeCode`` + - string + - Start time timecode diff --git a/src/ffmpeg.imageio/ffmpeginput.cpp b/src/ffmpeg.imageio/ffmpeginput.cpp index a36027f1e7..1e78f77c72 100644 --- a/src/ffmpeg.imageio/ffmpeginput.cpp +++ b/src/ffmpeg.imageio/ffmpeginput.cpp @@ -122,6 +122,7 @@ class FFmpegInput final : public ImageInput { std::vector m_rgb_buffer; std::vector m_video_indexes; int m_video_stream; + int m_data_stream; int64_t m_frames; int m_last_search_pos; int m_last_decoded_pos; @@ -144,6 +145,7 @@ class FFmpegInput final : public ImageInput { m_rgb_buffer.clear(); m_video_indexes.clear(); m_video_stream = -1; + m_data_stream = -1; m_frames = 0; m_last_search_pos = 0; m_last_decoded_pos = 0; @@ -255,6 +257,14 @@ FFmpegInput::open(const std::string& name, ImageSpec& spec) errorfmt("\"{}\" could not find a valid videostream", file_name); return false; } + for (unsigned int i = 0; i < m_format_context->nb_streams; i++) { + if (stream_codec(i)->codec_type == AVMEDIA_TYPE_DATA) { + if (m_data_stream < 0) { + m_data_stream = i; + break; + } + } + } // codec context for videostream AVCodecParameters* par = stream_codec(m_video_stream); @@ -291,9 +301,7 @@ FFmpegInput::open(const std::string& name, ImageSpec& spec) & AV_CODEC_CAP_DELAY); AVStream* stream = m_format_context->streams[m_video_stream]; - if (stream->avg_frame_rate.num != 0 && stream->avg_frame_rate.den != 0) { - m_frame_rate = stream->avg_frame_rate; - } + m_frame_rate = av_guess_frame_rate(m_format_context, stream, NULL); m_frames = stream->nb_frames; m_start_time = stream->start_time; @@ -496,6 +504,26 @@ FFmpegInput::open(const std::string& name, ImageSpec& spec) AV_DICT_IGNORE_SUFFIX))) { m_spec.attribute(tag->key, tag->value); } + tag = NULL; + if (m_data_stream >= 0) { + while (( + tag = av_dict_get(m_format_context->streams[m_data_stream]->metadata, + "", tag, AV_DICT_IGNORE_SUFFIX))) { + if (strcmp(tag->key, "timecode") == 0) { + m_spec.attribute("ffmpeg:TimeCode", tag->value); + break; + } + } + } + tag = NULL; + while ( + (tag = av_dict_get(m_format_context->streams[m_video_stream]->metadata, + "", tag, AV_DICT_IGNORE_SUFFIX))) { + if (strcmp(tag->key, "timecode") == 0) { + m_spec.attribute("ffmpeg:TimeCode", tag->value); + break; + } + } int rat[2] = { m_frame_rate.num, m_frame_rate.den }; m_spec.attribute("FramesPerSecond", TypeRational, &rat); m_spec.attribute("oiio:Movie", true);