X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Fffmpeg_content.cc;h=c7a15619dd0e12d6c4eed919af2567d03932467d;hb=75538d19688ad8c29c5949de9bff9f044c713936;hp=cc6a703baf60b8882467a687cbe5b8d1870097b0;hpb=3d6eaf43f7c75fe4d47c1ed1c63366f24cde51fe;p=dcpomatic.git diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index cc6a703ba..c7a15619d 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -32,9 +32,8 @@ #include "log.h" #include "exceptions.h" #include "frame_rate_change.h" -#include "safe_stringstream.h" -#include "raw_convert.h" -#include "subtitle_content.h" +#include "text_content.h" +#include #include extern "C" { #include @@ -58,6 +57,7 @@ using std::max; using boost::shared_ptr; using boost::dynamic_pointer_cast; using boost::optional; +using dcp::raw_convert; int const FFmpegContentProperty::SUBTITLE_STREAMS = 100; int const FFmpegContentProperty::SUBTITLE_STREAM = 101; @@ -65,16 +65,28 @@ int const FFmpegContentProperty::FILTERS = 102; FFmpegContent::FFmpegContent (shared_ptr film, boost::filesystem::path p) : Content (film, p) + , _encrypted (false) { } +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 (shared_ptr film, cxml::ConstNodePtr node, int version, list& notes) : Content (film, node) { video = VideoContent::from_xml (this, node, version); - audio = AudioContent::from_xml (this, node); - subtitle = SubtitleContent::from_xml (this, node, version); + 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) { @@ -109,29 +121,59 @@ 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"); - + _decryption_key = node->optional_string_child ("DecryptionKey"); + _encrypted = node->optional_bool_child("Encrypted").get_value_or(false); } -FFmpegContent::FFmpegContent (shared_ptr film, vector > c) +FFmpegContent::FFmpegContent (shared_ptr film, vector > c) : Content (film, c) { - video.reset (new VideoContent (this, c)); - audio.reset (new AudioContent (this, c)); - subtitle.reset (new SubtitleContent (this, c)); + vector >::const_iterator i = c.begin (); + + bool need_video = false; + bool need_audio = false; + bool need_text = false; + + if (i != c.end ()) { + need_video = static_cast ((*i)->video); + need_audio = static_cast ((*i)->audio); + need_text = !(*i)->text.empty(); + } + + while (i != c.end ()) { + if (need_video != static_cast ((*i)->video)) { + throw JoinError (_("Content to be joined must all have or not have video")); + } + if (need_audio != static_cast ((*i)->audio)) { + throw JoinError (_("Content to be joined must all have or not have audio")); + } + if (need_text != !(*i)->text.empty()) { + throw JoinError (_("Content to be joined must all have or not have subtitles or captions")); + } + ++i; + } + + if (need_video) { + video.reset (new VideoContent (this, c)); + } + if (need_audio) { + audio.reset (new AudioContent (this, c)); + } + if (need_text) { + text.push_back (shared_ptr (new TextContent (this, c))); + } shared_ptr 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]); - if (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.")); } } @@ -147,13 +189,14 @@ FFmpegContent::FFmpegContent (shared_ptr film, vector_color_trc; _colorspace = ref->_colorspace; _bits_per_pixel = ref->_bits_per_pixel; + _encrypted = ref->_encrypted; } void -FFmpegContent::as_xml (xmlpp::Node* node) const +FFmpegContent::as_xml (xmlpp::Node* node, bool with_paths) const { node->add_child("Type")->add_child_text ("FFmpeg"); - Content::as_xml (node); + Content::as_xml (node, with_paths); if (video) { video->as_xml (node); @@ -169,8 +212,8 @@ FFmpegContent::as_xml (xmlpp::Node* node) const } } - if (subtitle) { - subtitle->as_xml (node); + if (only_text()) { + only_text()->as_xml (node); } boost::mutex::scoped_lock lm (_mutex); @@ -191,18 +234,35 @@ FFmpegContent::as_xml (xmlpp::Node* node) const node->add_child("FirstVideo")->add_child_text (raw_convert (_first_video.get().get())); } - node->add_child("ColorRange")->add_child_text (raw_convert (_color_range)); - node->add_child("ColorPrimaries")->add_child_text (raw_convert (_color_primaries)); - node->add_child("ColorTransferCharacteristic")->add_child_text (raw_convert (_color_trc)); - node->add_child("Colorspace")->add_child_text (raw_convert (_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)); + } + if (_decryption_key) { + node->add_child("DecryptionKey")->add_child_text (_decryption_key.get()); + } + if (_encrypted) { + node->add_child("Encypted")->add_child_text ("1"); } } void FFmpegContent::examine (shared_ptr job) { + ChangeSignaller cc1 (this, FFmpegContentProperty::SUBTITLE_STREAMS); + ChangeSignaller cc2 (this, FFmpegContentProperty::SUBTITLE_STREAM); + job->set_progress_unknown (); Content::examine (job); @@ -212,9 +272,10 @@ FFmpegContent::examine (shared_ptr job) if (examiner->has_video ()) { video.reset (new VideoContent (this)); video->take_from_examiner (examiner); - set_default_colour_conversion (); } + boost::filesystem::path first_path = path (0); + { boost::mutex::scoped_lock lm (_mutex); @@ -225,6 +286,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 ()) { @@ -236,20 +309,23 @@ FFmpegContent::examine (shared_ptr job) AudioStreamPtr as = audio->streams().front(); AudioMapping m = as->mapping (); - film()->make_audio_mapping_default (m); + film()->make_audio_mapping_default (m, 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 (); } + _encrypted = first_path.extension() == ".ecinema"; } - signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS); - signal_changed (FFmpegContentProperty::SUBTITLE_STREAM); + if (examiner->has_video ()) { + set_default_colour_conversion (); + } } string @@ -303,12 +379,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 @@ -344,62 +420,38 @@ FFmpegContent::full_length () const 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 FFmpegContent::identifier () const { - SafeStringStream s; - - s << Content::identifier(); + string s = Content::identifier(); if (video) { - s << "_" << video->identifier(); + s += "_" + video->identifier(); } - if (subtitle) { - s << "_" << subtitle->identifier(); + if (only_text() && only_text()->use() && only_text()->burn()) { + s += "_" + only_text()->identifier(); } boost::mutex::scoped_lock lm (_mutex); if (_subtitle_stream) { - s << "_" << _subtitle_stream->identifier (); + s += "_" + _subtitle_stream->identifier (); } for (vector::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { - s << "_" << (*i)->id (); - } - - return s.str (); -} - -list -FFmpegContent::image_subtitles_during (ContentTimePeriod period, bool starting) const -{ - shared_ptr stream = subtitle_stream (); - if (!stream) { - return list (); - } - - return stream->image_subtitles_during (period, starting); -} - -list -FFmpegContent::text_subtitles_during (ContentTimePeriod period, bool starting) const -{ - shared_ptr stream = subtitle_stream (); - if (!stream) { - return list (); + s += "_" + (*i)->id (); } - return stream->text_subtitles_during (period, starting); + return s; } void @@ -411,10 +463,29 @@ FFmpegContent::set_default_colour_conversion () boost::mutex::scoped_lock lm (_mutex); - if (s.width < 1080) { - video->set_colour_conversion (PresetColourConversion::from_id ("rec601").conversion); - } else { + switch (_colorspace.get_value_or(AVCOL_SPC_UNSPECIFIED)) { + case AVCOL_SPC_RGB: + video->set_colour_conversion (PresetColourConversion::from_id ("srgb").conversion); + break; + case AVCOL_SPC_BT709: video->set_colour_conversion (PresetColourConversion::from_id ("rec709").conversion); + break; + case AVCOL_SPC_BT470BG: + case AVCOL_SPC_SMPTE170M: + case AVCOL_SPC_SMPTE240M: + video->set_colour_conversion (PresetColourConversion::from_id ("rec601").conversion); + break; + case AVCOL_SPC_BT2020_CL: + case AVCOL_SPC_BT2020_NCL: + video->set_colour_conversion (PresetColourConversion::from_id ("rec2020").conversion); + break; + default: + if (s.width < 1080) { + video->set_colour_conversion (PresetColourConversion::from_id ("rec601").conversion); + } else { + video->set_colour_conversion (PresetColourConversion::from_id ("rec709").conversion); + } + break; } } @@ -430,45 +501,45 @@ 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). - p.push_back (UserProperty (_("Video"), _("Colour range"), _("Unspecified"))); + p.push_back (UserProperty (UserProperty::VIDEO, _("Colour range"), _("Unspecified"))); break; case AVCOL_RANGE_MPEG: /// TRANSLATORS: this means that the range of pixel values used in this /// file is limited, so that not all possible values are valid. p.push_back ( UserProperty ( - _("Video"), _("Colour range"), String::compose (_("Limited (%1-%2)"), (total - sub) / 2, (total + sub) / 2) + UserProperty::VIDEO, _("Colour range"), String::compose (_("Limited (%1-%2)"), (total - sub) / 2, (total + sub) / 2) ) ); break; case AVCOL_RANGE_JPEG: /// TRANSLATORS: this means that the range of pixel values used in this /// file is full, so that all possible pixel values are valid. - p.push_back (UserProperty (_("Video"), _("Colour range"), String::compose (_("Full (0-%1)"), total))); + p.push_back (UserProperty (UserProperty::VIDEO, _("Colour range"), String::compose (_("Full (0-%1)"), total))); break; default: 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). - p.push_back (UserProperty (_("Video"), _("Colour range"), _("Unspecified"))); + p.push_back (UserProperty (UserProperty::VIDEO, _("Colour range"), _("Unspecified"))); break; case AVCOL_RANGE_MPEG: /// TRANSLATORS: this means that the range of pixel values used in this /// file is limited, so that not all possible values are valid. - p.push_back (UserProperty (_("Video"), _("Colour range"), _("Limited"))); + p.push_back (UserProperty (UserProperty::VIDEO, _("Colour range"), _("Limited"))); break; case AVCOL_RANGE_JPEG: /// TRANSLATORS: this means that the range of pixel values used in this /// file is full, so that all possible pixel values are valid. - p.push_back (UserProperty (_("Video"), _("Colour range"), _("Full"))); + p.push_back (UserProperty (UserProperty::VIDEO, _("Colour range"), _("Full"))); break; default: DCPOMATIC_ASSERT (false); @@ -486,11 +557,23 @@ FFmpegContent::add_properties (list& p) const _("SMPTE 240M"), _("Film"), _("BT2020"), - _("SMPTE ST 428-1 (CIE 1931 XYZ)") + _("SMPTE ST 428-1 (CIE 1931 XYZ)"), + _("SMPTE ST 431-2 (2011)"), + _("SMPTE ST 432-1 D65 (2010)"), // 12 + "", // 13 + "", // 14 + "", // 15 + "", // 16 + "", // 17 + "", // 18 + "", // 19 + "", // 20 + "", // 21 + _("JEDEC P22") }; - DCPOMATIC_ASSERT (AVCOL_PRI_NB <= 11); - p.push_back (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"), @@ -510,11 +593,12 @@ FFmpegContent::add_properties (list& p) const _("BT2020 for a 10-bit system"), _("BT2020 for a 12-bit system"), _("SMPTE ST 2084 for 10, 12, 14 and 16 bit systems"), - _("SMPTE ST 428-1") + _("SMPTE ST 428-1"), + _("ARIB STD-B67 ('Hybrid log-gamma')") }; - DCPOMATIC_ASSERT (AVCOL_TRC_NB <= 18); - p.push_back (UserProperty (_("Video"), _("Colour transfer characteristic"), transfers[_color_trc])); + DCPOMATIC_ASSERT (AVCOL_TRC_NB <= 19); + 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)"), @@ -528,13 +612,17 @@ FFmpegContent::add_properties (list& p) const _("YCOCG"), _("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 == 11); - p.push_back (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 (_("Video"), _("Bits per pixel"), raw_convert (_bits_per_pixel.get ()))); + p.push_back (UserProperty (UserProperty::VIDEO, _("Bits per pixel"), *_bits_per_pixel)); } } @@ -551,7 +639,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 > @@ -567,3 +656,15 @@ FFmpegContent::ffmpeg_audio_streams () const return fa; } + +void +FFmpegContent::take_settings_from (shared_ptr c) +{ + shared_ptr fc = dynamic_pointer_cast (c); + if (!fc) { + return; + } + + Content::take_settings_from (c); + _filters = fc->_filters; +}