From b58ea0495d72d654161958e515dc6c5ba9992b80 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Sun, 2 Aug 2020 22:24:05 +0200 Subject: [PATCH] Detect soft 2:3 pulldown (telecine) files and decode them at 23.976. DVD rips from NTSC DVDs are sometimes (always?) encoded using soft 2:3 pulldown. The video frames are actually 23.976 but FFmpeg detects them as 29.97. With the current approach of the video decoder ignoring most PTSs and assuming a constant frame rate it is vital that the file contains the number of frames per second that the detected frame rate predicts. This fixes large sync errors with NTSC DVD rips (#1790). Cherry-picked from af680761cf7c3e97660e8e55c68f42e90b026bf9 in v2.15.x. --- src/lib/ffmpeg_content.cc | 12 ++++++++++ src/lib/ffmpeg_examiner.cc | 46 ++++++++++++++++++++++++++++++++++---- src/lib/ffmpeg_examiner.h | 13 ++++++++++- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index a3a1cfb0f..6383fe58b 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -324,6 +324,18 @@ FFmpegContent::examine (shared_ptr film, shared_ptr job) if (examiner->has_video ()) { set_default_colour_conversion (); } + + if (examiner->has_video() && examiner->pulldown() && video_frame_rate() && fabs(*video_frame_rate() - 29.97) < 0.001) { + /* FFmpeg has detected this file as 29.97 and the examiner thinks it is using "soft" 2:3 pulldown (telecine). + * This means we can treat it as a 23.976fps file. + */ + set_video_frame_rate (24000.0 / 1001); + video->set_length (video->length() * 24.0 / 30); + } + +#ifdef DCPOMATIC_VARIANT_SWAROOP + _id = examiner->id (); +#endif } string diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc index b31280cd5..1a2c49f1c 100644 --- a/src/lib/ffmpeg_examiner.cc +++ b/src/lib/ffmpeg_examiner.cc @@ -18,6 +18,7 @@ */ +#include "dcpomatic_log.h" extern "C" { #include #include @@ -40,14 +41,22 @@ extern "C" { using std::string; using std::cout; using std::max; +using std::vector; using boost::shared_ptr; using boost::optional; +/* This is how many frames from the start of any video that we will examine to see if we + * can spot soft 2:3 pull-down ("telecine"). + */ +static const int PULLDOWN_CHECK_FRAMES = 16; + + /** @param job job that the examiner is operating in, or 0 */ FFmpegExaminer::FFmpegExaminer (shared_ptr c, shared_ptr job) : FFmpeg (c) , _video_length (0) , _need_video_length (false) + , _pulldown (false) { /* Find audio and subtitle streams */ @@ -100,9 +109,15 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr c, shared_ptr here but we want to search the array for a pattern later, + * and a string seems a reasonably neat way to do that. + */ + string temporal_reference; while (true) { int r = av_read_frame (_format_context, &_packet); if (r < 0) { @@ -120,7 +135,7 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr c, shared_ptrstreams[_packet.stream_index]->codec; if (_video_stream && _packet.stream_index == _video_stream.get()) { - video_packet (context); + video_packet (context, temporal_reference); } bool got_all_audio = true; @@ -136,7 +151,7 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr c, shared_ptr= PULLDOWN_CHECK_FRAMES) { /* All done */ break; } @@ -165,14 +180,33 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr c, shared_ptrmetadata, SWAROOP_ID_TAG, 0, 0); + if (e) { + _id = e->value; + } +#endif } + +/** @param temporal_reference A string to which we should add two characters per frame; + * the first is T or B depending on whether it's top- or bottom-field first, + * ths seconds is 3 or 2 depending on whether "repeat_pict" is true or not. + */ void -FFmpegExaminer::video_packet (AVCodecContext* context) +FFmpegExaminer::video_packet (AVCodecContext* context, string& temporal_reference) { DCPOMATIC_ASSERT (_video_stream); - if (_first_video && !_need_video_length) { + if (_first_video && !_need_video_length && temporal_reference.size() >= PULLDOWN_CHECK_FRAMES) { return; } @@ -186,6 +220,10 @@ FFmpegExaminer::video_packet (AVCodecContext* context) _format_context->streams[_video_stream.get()] ).get_value_or (ContentTime ()).frames_round (video_frame_rate().get ()); } + if (temporal_reference.size() < PULLDOWN_CHECK_FRAMES) { + temporal_reference += (_frame->top_field_first ? "T" : "B"); + temporal_reference += (_frame->repeat_pict ? "3" : "2"); + } } } diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h index 67fdcfae0..9ff5a79e7 100644 --- a/src/lib/ffmpeg_examiner.h +++ b/src/lib/ffmpeg_examiner.h @@ -75,8 +75,18 @@ public: return _rotation; } + bool pulldown () const { + return _pulldown; + } + +#ifdef DCPOMATIC_VARIANT_SWAROOP + boost::optional id () const { + return _id; + } +#endif + private: - void video_packet (AVCodecContext *); + void video_packet (AVCodecContext *, std::string& temporal_reference); void audio_packet (AVCodecContext *, boost::shared_ptr); std::string stream_name (AVStream* s) const; @@ -93,6 +103,7 @@ private: bool _need_video_length; boost::optional _rotation; + bool _pulldown; struct SubtitleStart { -- 2.30.2