X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=blobdiff_plain;f=src%2Flib%2Fffmpeg_content.cc;h=516962936238824775970f5796dc97c10ca6f62e;hp=9192006796aefeda43d64a7a763f7beece3e98dc;hb=7bc2134d658778e04f1756c255e604b4ab5a5831;hpb=e13e5cd4cfda39b0a0b77ed8036e14e15f93ec2e diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index 919200679..516962936 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2016 Carl Hetherington + Copyright (C) 2013-2021 Carl Hetherington This file is part of DCP-o-matic. @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with DCP-o-matic. If not, see . + */ #include "ffmpeg_content.h" @@ -30,6 +31,7 @@ #include "filter.h" #include "film.h" #include "log.h" +#include "config.h" #include "exceptions.h" #include "frame_rate_change.h" #include "text_content.h" @@ -40,11 +42,11 @@ extern "C" { #include } #include -#include #include #include "i18n.h" + using std::string; using std::vector; using std::list; @@ -52,33 +54,39 @@ using std::cout; using std::pair; using std::make_pair; using std::max; -using boost::shared_ptr; -using boost::dynamic_pointer_cast; +using std::make_shared; +using std::shared_ptr; +using std::dynamic_pointer_cast; using boost::optional; using dcp::raw_convert; +using namespace dcpomatic; + int const FFmpegContentProperty::SUBTITLE_STREAMS = 100; int const FFmpegContentProperty::SUBTITLE_STREAM = 101; int const FFmpegContentProperty::FILTERS = 102; +int const FFmpegContentProperty::KDM = 103; + FFmpegContent::FFmpegContent (boost::filesystem::path p) : Content (p) - , _encrypted (false) { } + template optional get_optional_enum (cxml::ConstNodePtr node, string name) { - optional const v = node->optional_number_child(name); + auto const v = node->optional_number_child(name); if (!v) { return optional(); } return static_cast(*v); } + FFmpegContent::FFmpegContent (cxml::ConstNodePtr node, int version, list& notes) : Content (node) { @@ -86,35 +94,32 @@ FFmpegContent::FFmpegContent (cxml::ConstNodePtr node, int version, list audio = AudioContent::from_xml (this, node, version); text = TextContent::from_xml (this, node, version); - list c = node->node_children ("SubtitleStream"); - for (list::const_iterator i = c.begin(); i != c.end(); ++i) { - _subtitle_streams.push_back (shared_ptr (new FFmpegSubtitleStream (*i, version))); - if ((*i)->optional_number_child ("Selected")) { + for (auto i: node->node_children("SubtitleStream")) { + _subtitle_streams.push_back (make_shared(i, version)); + if (i->optional_number_child("Selected")) { _subtitle_stream = _subtitle_streams.back (); } } - c = node->node_children ("AudioStream"); - for (list::const_iterator i = c.begin(); i != c.end(); ++i) { - shared_ptr as (new FFmpegAudioStream (*i, version)); + for (auto i: node->node_children("AudioStream")) { + auto as = make_shared(i, version); audio->add_stream (as); - if (version < 11 && !(*i)->optional_node_child ("Selected")) { + if (version < 11 && !i->optional_node_child ("Selected")) { /* This is an old file and this stream is not selected, so un-map it */ as->set_mapping (AudioMapping (as->channels (), MAX_DCP_AUDIO_CHANNELS)); } } - c = node->node_children ("Filter"); - for (list::iterator i = c.begin(); i != c.end(); ++i) { - Filter const * f = Filter::from_id ((*i)->content ()); + for (auto i: node->node_children("Filter")) { + Filter const * f = Filter::from_id(i->content()); if (f) { _filters.push_back (f); } else { - notes.push_back (String::compose (_("DCP-o-matic no longer supports the `%1' filter, so it has been turned off."), (*i)->content())); + notes.push_back (String::compose (_("DCP-o-matic no longer supports the `%1' filter, so it has been turned off."), i->content())); } } - optional const f = node->optional_number_child ("FirstVideo"); + auto const f = node->optional_number_child ("FirstVideo"); if (f) { _first_video = ContentTime (f.get ()); } @@ -124,14 +129,13 @@ FFmpegContent::FFmpegContent (cxml::ConstNodePtr node, int version, list _color_trc = get_optional_enum(node, "ColorTransferCharacteristic"); _colorspace = get_optional_enum(node, "Colorspace"); _bits_per_pixel = node->optional_number_child ("BitsPerPixel"); - _decryption_key = node->optional_string_child ("DecryptionKey"); - _encrypted = node->optional_bool_child("Encrypted").get_value_or(false); } -FFmpegContent::FFmpegContent (vector > c) + +FFmpegContent::FFmpegContent (vector> c) : Content (c) { - vector >::const_iterator i = c.begin (); + auto i = c.begin (); bool need_video = false; bool need_audio = false; @@ -157,20 +161,20 @@ FFmpegContent::FFmpegContent (vector > c) } if (need_video) { - video.reset (new VideoContent (this, c)); + video = make_shared(this, c); } if (need_audio) { - audio.reset (new AudioContent (this, c)); + audio = make_shared(this, c); } if (need_text) { - text.push_back (shared_ptr (new TextContent (this, c))); + text.push_back (make_shared(this, c)); } - shared_ptr ref = dynamic_pointer_cast (c[0]); + auto ref = dynamic_pointer_cast (c[0]); DCPOMATIC_ASSERT (ref); for (size_t i = 0; i < c.size(); ++i) { - shared_ptr fc = dynamic_pointer_cast (c[i]); + auto fc = dynamic_pointer_cast(c[i]); if (fc->only_text() && fc->only_text()->use() && *(fc->_subtitle_stream.get()) != *(ref->_subtitle_stream.get())) { throw JoinError (_("Content to be joined must use the same subtitle stream.")); } @@ -187,13 +191,13 @@ FFmpegContent::FFmpegContent (vector > c) _color_trc = ref->_color_trc; _colorspace = ref->_colorspace; _bits_per_pixel = ref->_bits_per_pixel; - _encrypted = ref->_encrypted; } + void FFmpegContent::as_xml (xmlpp::Node* node, bool with_paths) const { - node->add_child("Type")->add_child_text ("FFmpeg"); + node->add_child("Type")->add_child_text("FFmpeg"); Content::as_xml (node, with_paths); if (video) { @@ -203,8 +207,8 @@ FFmpegContent::as_xml (xmlpp::Node* node, bool with_paths) const if (audio) { audio->as_xml (node); - BOOST_FOREACH (AudioStreamPtr i, audio->streams ()) { - shared_ptr f = dynamic_pointer_cast (i); + for (auto i: audio->streams()) { + auto f = dynamic_pointer_cast (i); DCPOMATIC_ASSERT (f); f->as_xml (node->add_child("AudioStream")); } @@ -216,63 +220,60 @@ FFmpegContent::as_xml (xmlpp::Node* node, bool with_paths) const boost::mutex::scoped_lock lm (_mutex); - for (vector >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) { - xmlpp::Node* t = node->add_child("SubtitleStream"); - if (_subtitle_stream && *i == _subtitle_stream) { + for (auto i: _subtitle_streams) { + auto t = node->add_child("SubtitleStream"); + if (_subtitle_stream && i == _subtitle_stream) { t->add_child("Selected")->add_child_text("1"); } - (*i)->as_xml (t); + i->as_xml (t); } - for (vector::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { - node->add_child("Filter")->add_child_text ((*i)->id ()); + for (auto i: _filters) { + node->add_child("Filter")->add_child_text(i->id()); } if (_first_video) { - node->add_child("FirstVideo")->add_child_text (raw_convert (_first_video.get().get())); + node->add_child("FirstVideo")->add_child_text(raw_convert(_first_video.get().get())); } if (_color_range) { - node->add_child("ColorRange")->add_child_text (raw_convert (static_cast (*_color_range))); + node->add_child("ColorRange")->add_child_text(raw_convert(static_cast(*_color_range))); } if (_color_primaries) { - node->add_child("ColorPrimaries")->add_child_text (raw_convert (static_cast (*_color_primaries))); + node->add_child("ColorPrimaries")->add_child_text(raw_convert(static_cast(*_color_primaries))); } if (_color_trc) { - node->add_child("ColorTransferCharacteristic")->add_child_text (raw_convert (static_cast (*_color_trc))); + node->add_child("ColorTransferCharacteristic")->add_child_text(raw_convert(static_cast(*_color_trc))); } if (_colorspace) { - node->add_child("Colorspace")->add_child_text (raw_convert (static_cast (*_colorspace))); + node->add_child("Colorspace")->add_child_text(raw_convert(static_cast(*_colorspace))); } if (_bits_per_pixel) { - node->add_child("BitsPerPixel")->add_child_text (raw_convert (*_bits_per_pixel)); - } - if (_decryption_key) { - node->add_child("DecryptionKey")->add_child_text (_decryption_key.get()); - } - if (_encrypted) { - node->add_child("Encypted")->add_child_text ("1"); + node->add_child("BitsPerPixel")->add_child_text(raw_convert(*_bits_per_pixel)); } } + void FFmpegContent::examine (shared_ptr film, shared_ptr job) { - ChangeSignaller cc1 (this, FFmpegContentProperty::SUBTITLE_STREAMS); - ChangeSignaller cc2 (this, FFmpegContentProperty::SUBTITLE_STREAM); + ContentChangeSignaller cc1 (this, FFmpegContentProperty::SUBTITLE_STREAMS); + ContentChangeSignaller cc2 (this, FFmpegContentProperty::SUBTITLE_STREAM); - job->set_progress_unknown (); + if (job) { + job->set_progress_unknown (); + } Content::examine (film, job); - shared_ptr examiner (new FFmpegExaminer (shared_from_this (), job)); + auto examiner = make_shared(shared_from_this (), job); if (examiner->has_video ()) { video.reset (new VideoContent (this)); video->take_from_examiner (examiner); } - boost::filesystem::path first_path = path (0); + auto first_path = path (0); { boost::mutex::scoped_lock lm (_mutex); @@ -286,7 +287,7 @@ FFmpegContent::examine (shared_ptr film, shared_ptr job) _bits_per_pixel = examiner->bits_per_pixel (); if (examiner->rotation()) { - double rot = *examiner->rotation (); + auto rot = *examiner->rotation (); if (fabs (rot - 180) < 1.0) { _filters.push_back (Filter::from_id ("vflip")); _filters.push_back (Filter::from_id ("hflip")); @@ -298,53 +299,61 @@ FFmpegContent::examine (shared_ptr film, shared_ptr job) } } - if (!examiner->audio_streams().empty ()) { - audio.reset (new AudioContent (this)); + if (!examiner->audio_streams().empty()) { + audio = make_shared(this); - BOOST_FOREACH (shared_ptr i, examiner->audio_streams ()) { + for (auto i: examiner->audio_streams()) { audio->add_stream (i); } - AudioStreamPtr as = audio->streams().front(); - AudioMapping m = as->mapping (); - m.make_default (film->audio_processor(), first_path); + auto as = audio->streams().front(); + auto m = as->mapping (); + m.make_default (film ? film->audio_processor() : 0, first_path); as->set_mapping (m); } _subtitle_streams = examiner->subtitle_streams (); if (!_subtitle_streams.empty ()) { text.clear (); - text.push_back (shared_ptr (new TextContent (this, TEXT_OPEN_SUBTITLE, TEXT_UNKNOWN))); + text.push_back (make_shared(this, TextType::OPEN_SUBTITLE, TextType::UNKNOWN)); _subtitle_stream = _subtitle_streams.front (); } - - _encrypted = first_path.extension() == ".ecinema"; } 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); + } } + string FFmpegContent::summary () const { if (video && audio) { - return String::compose (_("%1 [movie]"), path_summary ()); + return String::compose (_("%1 [movie]"), path_summary()); } else if (video) { - return String::compose (_("%1 [video]"), path_summary ()); + return String::compose (_("%1 [video]"), path_summary()); } else if (audio) { - return String::compose (_("%1 [audio]"), path_summary ()); + return String::compose (_("%1 [audio]"), path_summary()); } return path_summary (); } + string FFmpegContent::technical_summary () const { string as = ""; - BOOST_FOREACH (shared_ptr i, ffmpeg_audio_streams ()) { + for (auto i: ffmpeg_audio_streams ()) { as += i->technical_summary () + " " ; } @@ -357,9 +366,9 @@ FFmpegContent::technical_summary () const ss = _subtitle_stream->technical_summary (); } - string filt = Filter::ffmpeg_string (_filters); + auto filt = Filter::ffmpeg_string (_filters); - string s = Content::technical_summary (); + auto s = Content::technical_summary (); if (video) { s += " - " + video->technical_summary (); @@ -374,10 +383,11 @@ FFmpegContent::technical_summary () const ); } + void FFmpegContent::set_subtitle_stream (shared_ptr s) { - ChangeSignaller cc (this, FFmpegContentProperty::SUBTITLE_STREAM); + ContentChangeSignaller cc (this, FFmpegContentProperty::SUBTITLE_STREAM); { boost::mutex::scoped_lock lm (_mutex); @@ -385,18 +395,21 @@ FFmpegContent::set_subtitle_stream (shared_ptr s) } } + bool operator== (FFmpegStream const & a, FFmpegStream const & b) { return a._id == b._id; } + bool operator!= (FFmpegStream const & a, FFmpegStream const & b) { return a._id != b._id; } + DCPTime FFmpegContent::full_length (shared_ptr film) const { @@ -405,20 +418,42 @@ FFmpegContent::full_length (shared_ptr film) const return DCPTime::from_frames (llrint (video->length_after_3d_combine() * frc.factor()), film->video_frame_rate()); } + if (audio) { + DCPTime longest; + for (auto i: audio->streams()) { + longest = max (longest, DCPTime::from_frames(llrint(i->length() / frc.speed_up), i->frame_rate())); + } + return longest; + } + + /* XXX: subtitle content? */ + + return {}; +} + + +DCPTime +FFmpegContent::approximate_length () const +{ + if (video) { + return DCPTime::from_frames (video->length_after_3d_combine(), 24); + } + DCPOMATIC_ASSERT (audio); - DCPTime longest; - BOOST_FOREACH (AudioStreamPtr i, audio->streams ()) { - longest = max (longest, DCPTime::from_frames (llrint (i->length() / frc.speed_up), i->frame_rate())); + Frame longest = 0; + for (auto i: audio->streams()) { + longest = max (longest, Frame(llrint(i->length()))); } - return longest; + return DCPTime::from_frames (longest, 24); } + void FFmpegContent::set_filters (vector const & filters) { - ChangeSignaller cc (this, FFmpegContentProperty::FILTERS); + ContentChangeSignaller cc (this, FFmpegContentProperty::FILTERS); { boost::mutex::scoped_lock lm (_mutex); @@ -426,6 +461,7 @@ FFmpegContent::set_filters (vector const & filters) } } + string FFmpegContent::identifier () const { @@ -445,19 +481,20 @@ FFmpegContent::identifier () const s += "_" + _subtitle_stream->identifier (); } - for (vector::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { - s += "_" + (*i)->id (); + for (auto i: _filters) { + s += "_" + i->id(); } return s; } + void FFmpegContent::set_default_colour_conversion () { DCPOMATIC_ASSERT (video); - dcp::Size const s = video->size (); + auto const s = video->size (); boost::mutex::scoped_lock lm (_mutex); @@ -487,17 +524,20 @@ FFmpegContent::set_default_colour_conversion () } } + void FFmpegContent::add_properties (shared_ptr film, list& p) const { - Content::add_properties (p); + Content::add_properties (film, p); if (video) { video->add_properties (p); if (_bits_per_pixel) { - int const sub = 219 * pow (2, _bits_per_pixel.get() - 8); - int const total = pow (2, _bits_per_pixel.get()); + /* Assuming there's three components, so bits per pixel component is _bits_per_pixel / 3 */ + int const lim_start = pow(2, _bits_per_pixel.get() / 3 - 4); + int const lim_end = 235 * pow(2, _bits_per_pixel.get() / 3 - 8); + int const total = pow(2, _bits_per_pixel.get() / 3); switch (_color_range.get_value_or(AVCOL_RANGE_UNSPECIFIED)) { case AVCOL_RANGE_UNSPECIFIED: @@ -510,7 +550,7 @@ FFmpegContent::add_properties (shared_ptr film, list& /// file is limited, so that not all possible values are valid. p.push_back ( UserProperty ( - UserProperty::VIDEO, _("Colour range"), String::compose (_("Limited (%1-%2)"), (total - sub) / 2, (total + sub) / 2) + UserProperty::VIDEO, _("Colour range"), String::compose(_("Limited (%1-%2)"), lim_start, lim_end) ) ); break; @@ -629,6 +669,7 @@ FFmpegContent::add_properties (shared_ptr film, list& } } + /** Our subtitle streams have colour maps, which can be changed, but * they have no way of signalling that change. As a hack, we have this * method which callers can use when they've modified one of our subtitle @@ -638,27 +679,29 @@ void FFmpegContent::signal_subtitle_stream_changed () { /* XXX: this is too late; really it should be before the change */ - ChangeSignaller cc (this, FFmpegContentProperty::SUBTITLE_STREAM); + ContentChangeSignaller cc (this, FFmpegContentProperty::SUBTITLE_STREAM); } -vector > + +vector> FFmpegContent::ffmpeg_audio_streams () const { - vector > fa; + vector> fa; if (audio) { - BOOST_FOREACH (AudioStreamPtr i, audio->streams()) { - fa.push_back (dynamic_pointer_cast (i)); + for (auto i: audio->streams()) { + fa.push_back (dynamic_pointer_cast(i)); } } return fa; } + void FFmpegContent::take_settings_from (shared_ptr c) { - shared_ptr fc = dynamic_pointer_cast (c); + auto fc = dynamic_pointer_cast (c); if (!fc) { return; } @@ -666,3 +709,4 @@ FFmpegContent::take_settings_from (shared_ptr c) Content::take_settings_from (c); _filters = fc->_filters; } +