X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=blobdiff_plain;f=src%2Flib%2Fffmpeg_content.cc;h=f3c0d01cbafc792eb9356ae6b484a6e4e420a1ea;hp=64da0b1af0b71d2f8e8d6909655a8ed08a896e41;hb=5eb8b5c3a1566aef638e9d9df03b88d320735092;hpb=249ae25148213a2ab5d76980133182e7f2521524 diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index 64da0b1af..f3c0d01cb 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-2019 Carl Hetherington This file is part of DCP-o-matic. @@ -30,9 +30,11 @@ #include "filter.h" #include "film.h" #include "log.h" +#include "config.h" #include "exceptions.h" #include "frame_rate_change.h" -#include "subtitle_content.h" +#include "text_content.h" +#include "decrypted_ecinema_kdm.h" #include #include extern "C" { @@ -45,8 +47,6 @@ extern "C" { #include "i18n.h" -#define LOG_GENERAL(...) film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL); - using std::string; using std::vector; using std::list; @@ -58,23 +58,39 @@ using boost::shared_ptr; using boost::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 (shared_ptr film, boost::filesystem::path p) - : Content (film, p) +FFmpegContent::FFmpegContent (boost::filesystem::path p) + : Content (p) +#ifdef DCPOMATIC_VARIANT_SWAROOP + , _encrypted (false) +#endif { } -FFmpegContent::FFmpegContent (shared_ptr film, cxml::ConstNodePtr node, int version, list& notes) - : Content (film, node) +template +optional +get_optional_enum (cxml::ConstNodePtr node, string name) +{ + optional 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) { video = VideoContent::from_xml (this, node, version); audio = AudioContent::from_xml (this, node, version); - subtitle = SubtitleContent::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) { @@ -109,29 +125,29 @@ FFmpegContent::FFmpegContent (shared_ptr film, cxml::ConstNodePtr no _first_video = ContentTime (f.get ()); } - _color_range = static_cast (node->optional_number_child("ColorRange").get_value_or (AVCOL_RANGE_UNSPECIFIED)); - _color_primaries = static_cast (node->optional_number_child("ColorPrimaries").get_value_or (AVCOL_PRI_UNSPECIFIED)); - _color_trc = static_cast ( - node->optional_number_child("ColorTransferCharacteristic").get_value_or (AVCOL_TRC_UNSPECIFIED) - ); - _colorspace = static_cast (node->optional_number_child("Colorspace").get_value_or (AVCOL_SPC_UNSPECIFIED)); + _color_range = get_optional_enum(node, "ColorRange"); + _color_primaries = get_optional_enum(node, "ColorPrimaries"); + _color_trc = get_optional_enum(node, "ColorTransferCharacteristic"); + _colorspace = get_optional_enum(node, "Colorspace"); _bits_per_pixel = node->optional_number_child ("BitsPerPixel"); - +#ifdef DCPOMATIC_VARIANT_SWAROOP + _encrypted = node->optional_bool_child("Encrypted").get_value_or(false); +#endif } -FFmpegContent::FFmpegContent (shared_ptr film, vector > c) - : Content (film, c) +FFmpegContent::FFmpegContent (vector > c) + : Content (c) { vector >::const_iterator i = c.begin (); bool need_video = false; bool need_audio = false; - bool need_subtitle = false; + bool need_text = false; if (i != c.end ()) { need_video = static_cast ((*i)->video); need_audio = static_cast ((*i)->audio); - need_subtitle = static_cast ((*i)->subtitle); + need_text = !(*i)->text.empty(); } while (i != c.end ()) { @@ -141,8 +157,8 @@ FFmpegContent::FFmpegContent (shared_ptr film, vector ((*i)->audio)) { throw JoinError (_("Content to be joined must all have or not have audio")); } - if (need_subtitle != static_cast ((*i)->subtitle)) { - throw JoinError (_("Content to be joined must all have or not have subtitles")); + if (need_text != !(*i)->text.empty()) { + throw JoinError (_("Content to be joined must all have or not have subtitles or captions")); } ++i; } @@ -153,8 +169,8 @@ FFmpegContent::FFmpegContent (shared_ptr film, vector (new TextContent (this, c))); } shared_ptr ref = dynamic_pointer_cast (c[0]); @@ -162,7 +178,7 @@ FFmpegContent::FFmpegContent (shared_ptr film, vector fc = dynamic_pointer_cast (c[i]); - if (fc->subtitle && fc->subtitle->use() && *(fc->_subtitle_stream.get()) != *(ref->_subtitle_stream.get())) { + 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.")); } } @@ -178,6 +194,9 @@ FFmpegContent::FFmpegContent (shared_ptr film, vector_color_trc; _colorspace = ref->_colorspace; _bits_per_pixel = ref->_bits_per_pixel; +#ifdef DCPOMATIC_VARIANT_SWAROOP + _encrypted = ref->_encrypted; +#endif } void @@ -200,8 +219,8 @@ FFmpegContent::as_xml (xmlpp::Node* node, bool with_paths) const } } - if (subtitle) { - subtitle->as_xml (node); + if (only_text()) { + only_text()->as_xml (node); } boost::mutex::scoped_lock lm (_mutex); @@ -222,21 +241,39 @@ FFmpegContent::as_xml (xmlpp::Node* node, bool with_paths) const node->add_child("FirstVideo")->add_child_text (raw_convert (_first_video.get().get())); } - node->add_child("ColorRange")->add_child_text (raw_convert (static_cast (_color_range))); - node->add_child("ColorPrimaries")->add_child_text (raw_convert (static_cast (_color_primaries))); - node->add_child("ColorTransferCharacteristic")->add_child_text (raw_convert (static_cast (_color_trc))); - node->add_child("Colorspace")->add_child_text (raw_convert (static_cast (_colorspace))); + if (_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))); + } + if (_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))); + } if (_bits_per_pixel) { - node->add_child("BitsPerPixel")->add_child_text (raw_convert (_bits_per_pixel.get ())); + node->add_child("BitsPerPixel")->add_child_text (raw_convert (*_bits_per_pixel)); } +#ifdef DCPOMATIC_VARIANT_SWAROOP + if (_encrypted) { + node->add_child("Encypted")->add_child_text ("1"); + } +#endif } void -FFmpegContent::examine (shared_ptr job) +FFmpegContent::examine (shared_ptr film, shared_ptr job) { - job->set_progress_unknown (); + ChangeSignaller cc1 (this, FFmpegContentProperty::SUBTITLE_STREAMS); + ChangeSignaller cc2 (this, FFmpegContentProperty::SUBTITLE_STREAM); + + if (job) { + job->set_progress_unknown (); + } - Content::examine (job); + Content::examine (film, job); shared_ptr examiner (new FFmpegExaminer (shared_from_this (), job)); @@ -257,6 +294,18 @@ FFmpegContent::examine (shared_ptr job) _color_trc = examiner->color_trc (); _colorspace = examiner->colorspace (); _bits_per_pixel = examiner->bits_per_pixel (); + + if (examiner->rotation()) { + double rot = *examiner->rotation (); + if (fabs (rot - 180) < 1.0) { + _filters.push_back (Filter::from_id ("vflip")); + _filters.push_back (Filter::from_id ("hflip")); + } else if (fabs (rot - 90) < 1.0) { + _filters.push_back (Filter::from_id ("90clock")); + } else if (fabs (rot - 270) < 1.0) { + _filters.push_back (Filter::from_id ("90anticlock")); + } + } } if (!examiner->audio_streams().empty ()) { @@ -268,24 +317,29 @@ FFmpegContent::examine (shared_ptr job) AudioStreamPtr as = audio->streams().front(); AudioMapping m = as->mapping (); - film()->make_audio_mapping_default (m, first_path); + m.make_default (film ? film->audio_processor() : 0, first_path); as->set_mapping (m); } _subtitle_streams = examiner->subtitle_streams (); if (!_subtitle_streams.empty ()) { - subtitle.reset (new SubtitleContent (this)); + text.clear (); + text.push_back (shared_ptr (new TextContent (this, TEXT_OPEN_SUBTITLE, TEXT_UNKNOWN))); _subtitle_stream = _subtitle_streams.front (); } +#ifdef DCPOMATIC_VARIANT_SWAROOP + _encrypted = first_path.extension() == ".ecinema"; +#endif } if (examiner->has_video ()) { set_default_colour_conversion (); } - signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS); - signal_changed (FFmpegContentProperty::SUBTITLE_STREAM); +#ifdef DCPOMATIC_VARIANT_SWAROOP + _id = examiner->id (); +#endif } string @@ -339,12 +393,12 @@ FFmpegContent::technical_summary () const void FFmpegContent::set_subtitle_stream (shared_ptr s) { + ChangeSignaller cc (this, FFmpegContentProperty::SUBTITLE_STREAM); + { boost::mutex::scoped_lock lm (_mutex); _subtitle_stream = s; } - - signal_changed (FFmpegContentProperty::SUBTITLE_STREAM); } bool @@ -360,32 +414,52 @@ operator!= (FFmpegStream const & a, FFmpegStream const & b) } DCPTime -FFmpegContent::full_length () const +FFmpegContent::full_length (shared_ptr film) const { - FrameRateChange const frc (active_video_frame_rate (), film()->video_frame_rate ()); + FrameRateChange const frc (film, shared_from_this()); if (video) { - return DCPTime::from_frames (llrint (video->length_after_3d_combine() * frc.factor()), film()->video_frame_rate()); + return DCPTime::from_frames (llrint (video->length_after_3d_combine() * frc.factor()), film->video_frame_rate()); + } + + if (audio) { + DCPTime longest; + BOOST_FOREACH (AudioStreamPtr 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(); +} + +DCPTime +FFmpegContent::approximate_length () const +{ + if (video) { + return DCPTime::from_frames (video->length_after_3d_combine(), 24); } DCPOMATIC_ASSERT (audio); - DCPTime longest; + Frame longest = 0; BOOST_FOREACH (AudioStreamPtr i, audio->streams ()) { - longest = max (longest, DCPTime::from_frames (llrint (i->length() / frc.speed_up), i->frame_rate())); + 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); + { boost::mutex::scoped_lock lm (_mutex); _filters = filters; } - - signal_changed (FFmpegContentProperty::FILTERS); } string @@ -397,8 +471,8 @@ FFmpegContent::identifier () const s += "_" + video->identifier(); } - if (subtitle && subtitle->use() && subtitle->burn()) { - s += "_" + subtitle->identifier(); + if (only_text() && only_text()->use() && only_text()->burn()) { + s += "_" + only_text()->identifier(); } boost::mutex::scoped_lock lm (_mutex); @@ -423,7 +497,7 @@ FFmpegContent::set_default_colour_conversion () boost::mutex::scoped_lock lm (_mutex); - switch (_colorspace) { + switch (_colorspace.get_value_or(AVCOL_SPC_UNSPECIFIED)) { case AVCOL_SPC_RGB: video->set_colour_conversion (PresetColourConversion::from_id ("srgb").conversion); break; @@ -450,9 +524,9 @@ FFmpegContent::set_default_colour_conversion () } void -FFmpegContent::add_properties (list& p) const +FFmpegContent::add_properties (shared_ptr film, list& p) const { - Content::add_properties (p); + Content::add_properties (film, p); if (video) { video->add_properties (p); @@ -461,7 +535,7 @@ FFmpegContent::add_properties (list& p) const int const sub = 219 * pow (2, _bits_per_pixel.get() - 8); int const total = pow (2, _bits_per_pixel.get()); - switch (_color_range) { + switch (_color_range.get_value_or(AVCOL_RANGE_UNSPECIFIED)) { case AVCOL_RANGE_UNSPECIFIED: /// TRANSLATORS: this means that the range of pixel values used in this /// file is unknown (not specified in the file). @@ -485,7 +559,7 @@ FFmpegContent::add_properties (list& p) const DCPOMATIC_ASSERT (false); } } else { - switch (_color_range) { + switch (_color_range.get_value_or(AVCOL_RANGE_UNSPECIFIED)) { case AVCOL_RANGE_UNSPECIFIED: /// TRANSLATORS: this means that the range of pixel values used in this /// file is unknown (not specified in the file). @@ -519,11 +593,21 @@ FFmpegContent::add_properties (list& p) const _("BT2020"), _("SMPTE ST 428-1 (CIE 1931 XYZ)"), _("SMPTE ST 431-2 (2011)"), - _("SMPTE ST 432-1 D65 (2010)") + _("SMPTE ST 432-1 D65 (2010)"), // 12 + "", // 13 + "", // 14 + "", // 15 + "", // 16 + "", // 17 + "", // 18 + "", // 19 + "", // 20 + "", // 21 + _("JEDEC P22") }; - DCPOMATIC_ASSERT (AVCOL_PRI_NB <= 13); - p.push_back (UserProperty (UserProperty::VIDEO, _("Colour primaries"), primaries[_color_primaries])); + DCPOMATIC_ASSERT (AVCOL_PRI_NB <= 23); + p.push_back (UserProperty (UserProperty::VIDEO, _("Colour primaries"), primaries[_color_primaries.get_value_or(AVCOL_PRI_UNSPECIFIED)])); char const * transfers[] = { _("Unspecified"), @@ -548,7 +632,7 @@ FFmpegContent::add_properties (list& p) const }; DCPOMATIC_ASSERT (AVCOL_TRC_NB <= 19); - p.push_back (UserProperty (UserProperty::VIDEO, _("Colour transfer characteristic"), transfers[_color_trc])); + p.push_back (UserProperty (UserProperty::VIDEO, _("Colour transfer characteristic"), transfers[_color_trc.get_value_or(AVCOL_TRC_UNSPECIFIED)])); char const * spaces[] = { _("RGB / sRGB (IEC61966-2-1)"), @@ -563,18 +647,21 @@ FFmpegContent::add_properties (list& p) const _("BT2020 non-constant luminance"), _("BT2020 constant luminance"), _("SMPTE 2085, Y'D'zD'x"), + _("Chroma-derived non-constant luminance"), + _("Chroma-derived constant luminance"), + _("BT2100") }; - DCPOMATIC_ASSERT (AVCOL_SPC_NB == 12); - p.push_back (UserProperty (UserProperty::VIDEO, _("Colourspace"), spaces[_colorspace])); + DCPOMATIC_ASSERT (AVCOL_SPC_NB == 15); + p.push_back (UserProperty (UserProperty::VIDEO, _("Colourspace"), spaces[_colorspace.get_value_or(AVCOL_SPC_UNSPECIFIED)])); if (_bits_per_pixel) { - p.push_back (UserProperty (UserProperty::VIDEO, _("Bits per pixel"), _bits_per_pixel.get ())); + p.push_back (UserProperty (UserProperty::VIDEO, _("Bits per pixel"), *_bits_per_pixel)); } } if (audio) { - audio->add_properties (p); + audio->add_properties (film, p); } } @@ -586,7 +673,8 @@ FFmpegContent::add_properties (list& p) const void FFmpegContent::signal_subtitle_stream_changed () { - signal_changed (FFmpegContentProperty::SUBTITLE_STREAM); + /* XXX: this is too late; really it should be before the change */ + ChangeSignaller cc (this, FFmpegContentProperty::SUBTITLE_STREAM); } vector > @@ -606,8 +694,39 @@ FFmpegContent::ffmpeg_audio_streams () const void FFmpegContent::take_settings_from (shared_ptr c) { - Content::take_settings_from (c); - shared_ptr fc = dynamic_pointer_cast (c); + if (!fc) { + return; + } + + Content::take_settings_from (c); _filters = fc->_filters; } + +#ifdef DCPOMATIC_VARIANT_SWAROOP +void +FFmpegContent::add_kdm (EncryptedECinemaKDM kdm) +{ + ChangeSignaller cc (this, FFmpegContentProperty::KDM); + boost::mutex::scoped_lock lm (_mutex); + _kdm = kdm; + +} + +bool +FFmpegContent::kdm_timing_window_valid () const +{ + if (!_kdm) { + return true; + } + + DCPOMATIC_ASSERT (Config::instance()->decryption_chain()->key()); + + DecryptedECinemaKDM decrypted (*_kdm, *Config::instance()->decryption_chain()->key()); + + dcp::LocalTime now; + return (!decrypted.not_valid_before() || *decrypted.not_valid_before() < now) && + (!decrypted.not_valid_after() || now < *decrypted.not_valid_after()); +} + +#endif