X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Ffilm.cc;h=a41ab3c95eb2c7722418ec814185a88c06e59701;hb=b81241ce69a689629307832f802ac4faa6ed885f;hp=9320cf5d2c8aaa9625cc4d4ca67695c80e07c64b;hpb=22873931f874a87fcf6a0077eddbec0f97eb3423;p=dcpomatic.git diff --git a/src/lib/film.cc b/src/lib/film.cc index 9320cf5d2..a41ab3c95 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -1,19 +1,20 @@ /* - Copyright (C) 2012-2015 Carl Hetherington + Copyright (C) 2012-2016 Carl Hetherington - This program is free software; you can redistribute it and/or modify + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - This program is distributed in the hope that it will be useful, + DCP-o-matic is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + along with DCP-o-matic. If not, see . */ @@ -37,11 +38,9 @@ #include "dcp_content_type.h" #include "ratio.h" #include "cross.h" -#include "safe_stringstream.h" #include "environment_info.h" -#include "raw_convert.h" #include "audio_processor.h" -#include "md5_digester.h" +#include "digester.h" #include "compose.hpp" #include "screen.h" #include "audio_content.h" @@ -50,16 +49,19 @@ #include "ffmpeg_content.h" #include "dcp_content.h" #include "screen_kdm.h" +#include "cinema.h" #include #include #include #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -86,6 +88,7 @@ using boost::weak_ptr; using boost::dynamic_pointer_cast; using boost::optional; using boost::is_any_of; +using dcp::raw_convert; #define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL); #define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, LogEntry::TYPE_GENERAL); @@ -106,8 +109,15 @@ using boost::is_any_of; * * 32 -> 33 * Changed to in FFmpegSubtitleStream + * 33 -> 34 + * Content only contains audio/subtitle-related tags if those things + * are present. + * 34 -> 35 + * VideoFrameType in VideoContent is a string rather than an integer. + * 35 -> 36 + * EffectColour rather than OutlineColour in Subtitle. */ -int const Film::current_state_version = 33; +int const Film::current_state_version = 36; /** Construct a Film object in a given directory. * @@ -125,13 +135,14 @@ Film::Film (boost::filesystem::path dir, bool log) , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ()) , _isdcf_metadata (Config::instance()->default_isdcf_metadata ()) , _video_frame_rate (24) - , _audio_channels (6) + , _audio_channels (Config::instance()->default_dcp_audio_channels ()) , _three_d (false) - , _sequence_video (true) + , _sequence (true) , _interop (Config::instance()->default_interop ()) , _audio_processor (0) , _reel_type (REELTYPE_SINGLE) , _reel_length (2000000000) + , _upload_after_make_dcp (false) , _state_version (current_state_version) , _dirty (false) { @@ -167,7 +178,7 @@ Film::Film (boost::filesystem::path dir, bool log) _log.reset (new NullLog); } - _playlist->set_sequence_video (_sequence_video); + _playlist->set_sequence (_sequence); } Film::~Film () @@ -186,32 +197,29 @@ Film::video_identifier () const { DCPOMATIC_ASSERT (container ()); - SafeStringStream s; - s.imbue (std::locale::classic ()); - - s << container()->id() - << "_" << resolution_to_string (_resolution) - << "_" << _playlist->video_identifier() - << "_" << _video_frame_rate - << "_" << j2k_bandwidth(); + string s = container()->id() + + "_" + resolution_to_string (_resolution) + + "_" + _playlist->video_identifier() + + "_" + raw_convert(_video_frame_rate) + + "_" + raw_convert(j2k_bandwidth()); if (encrypted ()) { - s << "_E"; + s += "_E"; } else { - s << "_P"; + s += "_P"; } if (_interop) { - s << "_I"; + s += "_I"; } else { - s << "_S"; + s += "_S"; } if (_three_d) { - s << "_3D"; + s += "_3D"; } - return s.str (); + return s; } /** @return The file to write video frame info to */ @@ -241,15 +249,14 @@ Film::audio_analysis_path (shared_ptr playlist) const { boost::filesystem::path p = dir ("analysis"); - MD5Digester digester; + Digester digester; BOOST_FOREACH (shared_ptr i, playlist->content ()) { - shared_ptr ac = dynamic_pointer_cast (i); - if (!ac) { + if (!i->audio) { continue; } - digester.add (ac->digest ()); - digester.add (ac->audio_mapping().digest ()); + digester.add (i->digest ()); + digester.add (i->audio->mapping().digest ()); if (playlist->content().size() != 1) { /* Analyses should be considered equal regardless of gain if they were made from just one piece of content. This @@ -257,7 +264,7 @@ Film::audio_analysis_path (shared_ptr playlist) const analysis at the plotting stage rather than having to recompute it. */ - digester.add (ac->audio_gain ()); + digester.add (i->audio->gain ()); } } @@ -346,7 +353,7 @@ Film::metadata () const root->add_child("ISDCFDate")->add_child_text (boost::gregorian::to_iso_string (_isdcf_date)); root->add_child("AudioChannels")->add_child_text (raw_convert (_audio_channels)); root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0"); - root->add_child("SequenceVideo")->add_child_text (_sequence_video ? "1" : "0"); + root->add_child("Sequence")->add_child_text (_sequence ? "1" : "0"); root->add_child("Interop")->add_child_text (_interop ? "1" : "0"); root->add_child("Signed")->add_child_text (_signed ? "1" : "0"); root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0"); @@ -354,8 +361,9 @@ Film::metadata () const if (_audio_processor) { root->add_child("AudioProcessor")->add_child_text (_audio_processor->id ()); } - root->add_child("ReelType")->add_child_text (raw_convert (_reel_type)); + root->add_child("ReelType")->add_child_text (raw_convert (static_cast (_reel_type))); root->add_child("ReelLength")->add_child_text (raw_convert (_reel_length)); + root->add_child("UploadAfterMakeDCP")->add_child_text (_upload_after_make_dcp ? "1" : "0"); _playlist->as_xml (root->add_child ("Playlist")); return doc; @@ -428,7 +436,13 @@ Film::read_metadata () } else if ((_audio_channels % 2) == 1) { _audio_channels++; } - _sequence_video = f.bool_child ("SequenceVideo"); + + if (f.optional_bool_child("SequenceVideo")) { + _sequence = f.bool_child("SequenceVideo"); + } else { + _sequence = f.bool_child("Sequence"); + } + _three_d = f.bool_child ("ThreeD"); _interop = f.bool_child ("Interop"); _key = dcp::Key (f.string_child ("Key")); @@ -441,6 +455,7 @@ Film::read_metadata () _reel_type = static_cast (f.optional_number_child("ReelType").get_value_or (static_cast(REELTYPE_SINGLE))); _reel_length = f.optional_number_child("ReelLength").get_value_or (2000000000); + _upload_after_make_dcp = f.optional_bool_child("UploadAfterMakeDCP").get_value_or (false); list notes; /* This method is the only one that can return notes (so far) */ @@ -483,11 +498,36 @@ Film::file (boost::filesystem::path f) const return p; } +list +Film::mapped_audio_channels () const +{ + list mapped; + + if (audio_processor ()) { + /* Processors are mapped 1:1 to DCP outputs so we can work out mappings from there */ + for (int i = 0; i < audio_processor()->out_channels(); ++i) { + mapped.push_back (i); + } + } else { + BOOST_FOREACH (shared_ptr i, content ()) { + if (i->audio) { + list c = i->audio->mapping().mapped_output_channels (); + copy (c.begin(), c.end(), back_inserter (mapped)); + } + } + + mapped.sort (); + mapped.unique (); + } + + return mapped; +} + /** @return a ISDCF-compliant name for a DCP of this film */ string Film::isdcf_name (bool if_created_now) const { - SafeStringStream d; + string d; string raw_name = name (); @@ -530,49 +570,49 @@ Film::isdcf_name (bool if_created_now) const fixed_name = fixed_name.substr (0, 14); } - d << fixed_name; + d += fixed_name; if (dcp_content_type()) { - d << "_" << dcp_content_type()->isdcf_name(); - d << "-" << isdcf_metadata().content_version; + d += "_" + dcp_content_type()->isdcf_name(); + d += "-" + raw_convert(isdcf_metadata().content_version); } ISDCFMetadata const dm = isdcf_metadata (); if (dm.temp_version) { - d << "-Temp"; + d += "-Temp"; } if (dm.pre_release) { - d << "-Pre"; + d += "-Pre"; } if (dm.red_band) { - d << "-RedBand"; + d += "-RedBand"; } if (!dm.chain.empty ()) { - d << "-" << dm.chain; + d += "-" + dm.chain; } if (three_d ()) { - d << "-3D"; + d += "-3D"; } if (dm.two_d_version_of_three_d) { - d << "-2D"; + d += "-2D"; } if (!dm.mastered_luminance.empty ()) { - d << "-" << dm.mastered_luminance; + d += "-" + dm.mastered_luminance; } if (video_frame_rate() != 24) { - d << "-" << video_frame_rate(); + d += "-" + raw_convert(video_frame_rate()); } if (container()) { - d << "_" << container()->isdcf_name(); + d += "_" + container()->isdcf_name(); } /* XXX: this uses the first bit of content only */ @@ -581,134 +621,107 @@ Film::isdcf_name (bool if_created_now) const if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) { Ratio const * content_ratio = 0; BOOST_FOREACH (shared_ptr i, content ()) { - shared_ptr vc = dynamic_pointer_cast (i); - if (vc) { + if (i->video) { /* Here's the first piece of video content */ - if (vc->scale().ratio ()) { - content_ratio = vc->scale().ratio (); + if (i->video->scale().ratio ()) { + content_ratio = i->video->scale().ratio (); } else { - content_ratio = Ratio::from_ratio (vc->video_size().ratio ()); + content_ratio = Ratio::from_ratio (i->video->size().ratio ()); } break; } } if (content_ratio && content_ratio != container()) { - d << "-" << content_ratio->isdcf_name(); + d += "-" + content_ratio->isdcf_name(); } } if (!dm.audio_language.empty ()) { - d << "_" << dm.audio_language; + d += "_" + dm.audio_language; if (!dm.subtitle_language.empty()) { - bool burnt_in = false; + bool burnt_in = true; BOOST_FOREACH (shared_ptr i, content ()) { - shared_ptr sc = dynamic_pointer_cast (i); - if (!sc) { + if (!i->subtitle) { continue; } - if (sc->use_subtitles() && sc->burn_subtitles()) { - burnt_in = true; + if (i->subtitle->use() && !i->subtitle->burn()) { + burnt_in = false; } } string language = dm.subtitle_language; - if (burnt_in) { + if (burnt_in && language != "XX") { transform (language.begin(), language.end(), language.begin(), ::tolower); } else { transform (language.begin(), language.end(), language.begin(), ::toupper); } - d << "-" << language; + d += "-" + language; } else { - d << "-XX"; + d += "-XX"; } } if (!dm.territory.empty ()) { - d << "_" << dm.territory; + d += "_" + dm.territory; if (dm.rating.empty ()) { - d << "-NR"; + d += "-NR"; } else { - d << "-" << dm.rating; + d += "-" + dm.rating; } } - /* Find all mapped channels */ + /* Count mapped audio channels */ int non_lfe = 0; int lfe = 0; - if (audio_processor ()) { - /* Processors are mapped 1:1 to DCP outputs so we can guess the number of LFE/ - non-LFE from the channel counts. - */ - non_lfe = audio_processor()->out_channels (); - if (non_lfe >= 4) { - --non_lfe; - ++lfe; - } - } else { - list mapped; - BOOST_FOREACH (shared_ptr i, content ()) { - shared_ptr ac = dynamic_pointer_cast (i); - if (ac) { - list c = ac->audio_mapping().mapped_output_channels (); - copy (c.begin(), c.end(), back_inserter (mapped)); - } + BOOST_FOREACH (int i, mapped_audio_channels ()) { + if (i >= audio_channels()) { + /* This channel is mapped but is not included in the DCP */ + continue; } - mapped.sort (); - mapped.unique (); - - /* Count them */ - - for (list::const_iterator i = mapped.begin(); i != mapped.end(); ++i) { - if (*i >= audio_channels()) { - /* This channel is mapped but is not included in the DCP */ - continue; - } - - if (static_cast (*i) == dcp::LFE) { - ++lfe; - } else { - ++non_lfe; - } + if (static_cast (i) == dcp::LFE) { + ++lfe; + } else { + ++non_lfe; } } if (non_lfe) { - d << "_" << non_lfe << lfe; + d += String::compose("_%1%2", non_lfe, lfe); } /* XXX: HI/VI */ - d << "_" << resolution_to_string (_resolution); + d += "_" + resolution_to_string (_resolution); if (!dm.studio.empty ()) { - d << "_" << dm.studio; + d += "_" + dm.studio; } if (if_created_now) { - d << "_" << boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ()); + d += "_" + boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ()); } else { - d << "_" << boost::gregorian::to_iso_string (_isdcf_date); + d += "_" + boost::gregorian::to_iso_string (_isdcf_date); } if (!dm.facility.empty ()) { - d << "_" << dm.facility; + d += "_" + dm.facility; } if (_interop) { - d << "_IOP"; + d += "_IOP"; } else { - d << "_SMPTE"; + d += "_SMPTE"; } if (three_d ()) { - d << "-3D"; + d += "-3D"; } bool vf = false; @@ -720,12 +733,12 @@ Film::isdcf_name (bool if_created_now) const } if (vf) { - d << "_VF"; + d += "_VF"; } else { - d << "_OV"; + d += "_OV"; } - return d.str (); + return d; } /** @return name to give the DCP */ @@ -866,6 +879,13 @@ Film::set_reel_length (int64_t r) signal_changed (REEL_LENGTH); } +void +Film::set_upload_after_make_dcp (bool u) +{ + _upload_after_make_dcp = u; + signal_changed (UPLOAD_AFTER_MAKE_DCP); +} + void Film::signal_changed (Property p) { @@ -873,11 +893,11 @@ Film::signal_changed (Property p) switch (p) { case Film::CONTENT: - set_video_frame_rate (_playlist->best_dcp_frame_rate ()); + set_video_frame_rate (_playlist->best_video_frame_rate ()); break; case Film::VIDEO_FRAME_RATE: - case Film::SEQUENCE_VIDEO: - _playlist->maybe_sequence_video (); + case Film::SEQUENCE: + _playlist->maybe_sequence (); break; default: break; @@ -899,23 +919,23 @@ Film::j2c_path (int reel, Frame frame, Eyes eyes, bool tmp) const p /= "j2c"; p /= video_identifier (); - SafeStringStream s; - s.width (8); - s << setfill('0') << reel << "_" << frame; + char buffer[256]; + snprintf(buffer, sizeof(buffer), "%08d_%08" PRId64, reel, frame); + string s (buffer); if (eyes == EYES_LEFT) { - s << ".L"; + s += ".L"; } else if (eyes == EYES_RIGHT) { - s << ".R"; + s += ".R"; } - s << ".j2c"; + s += ".j2c"; if (tmp) { - s << ".tmp"; + s += ".tmp"; } - p /= s.str(); + p /= s; return file (p); } @@ -1016,7 +1036,7 @@ Film::maybe_add_content (weak_ptr j, weak_ptr c) } add_content (content); - if (Config::instance()->automatic_audio_analysis ()) { + if (Config::instance()->automatic_audio_analysis() && content->audio) { shared_ptr playlist (new Playlist); playlist->add (content); boost::signals2::connection c; @@ -1030,9 +1050,11 @@ Film::maybe_add_content (weak_ptr j, weak_ptr c) void Film::add_content (shared_ptr c) { - /* Add video content after any existing content */ - if (dynamic_pointer_cast (c)) { + /* Add {video,subtitle} content after any existing {video,subtitle} content */ + if (c->video) { c->set_position (_playlist->video_end ()); + } else if (c->subtitle) { + c->set_position (_playlist->subtitle_end ()); } _playlist->add (c); @@ -1066,7 +1088,7 @@ Film::length () const int Film::best_video_frame_rate () const { - return _playlist->best_dcp_frame_rate (); + return _playlist->best_video_frame_rate (); } FrameRateChange @@ -1080,9 +1102,9 @@ Film::playlist_content_changed (weak_ptr c, int p, bool frequent) { _dirty = true; - if (p == VideoContentProperty::VIDEO_FRAME_RATE) { - set_video_frame_rate (_playlist->best_dcp_frame_rate ()); - } else if (p == AudioContentProperty::AUDIO_STREAMS) { + if (p == ContentProperty::VIDEO_FRAME_RATE) { + set_video_frame_rate (_playlist->best_video_frame_rate ()); + } else if (p == AudioContentProperty::STREAMS) { signal_changed (NAME); } @@ -1106,8 +1128,7 @@ int Film::audio_frame_rate () const { BOOST_FOREACH (shared_ptr i, content ()) { - shared_ptr a = dynamic_pointer_cast (i); - if (a && a->has_rate_above_48k ()) { + if (i->audio && i->audio->has_rate_above_48k ()) { return 96000; } } @@ -1116,11 +1137,11 @@ Film::audio_frame_rate () const } void -Film::set_sequence_video (bool s) +Film::set_sequence (bool s) { - _sequence_video = s; - _playlist->set_sequence_video (s); - signal_changed (SEQUENCE_VIDEO); + _sequence = s; + _playlist->set_sequence (s); + signal_changed (SEQUENCE); } /** @return Size of the largest possible image in whatever resolution we are using */ @@ -1145,6 +1166,9 @@ Film::frame_size () const return fit_ratio_within (container()->ratio(), full_frame ()); } +/** @param from KDM from time expressed as a local time with an offset from UTC + * @param to KDM to time expressed as a local time with an offset from UTC + */ dcp::EncryptedKDM Film::make_kdm ( dcp::Certificate recipient, @@ -1162,16 +1186,19 @@ Film::make_kdm ( } return dcp::DecryptedKDM ( - cpl, key(), from, until, "DCP-o-matic", cpl->content_title_text(), dcp::LocalTime().as_string() + cpl, key(), from, until, cpl->content_title_text(), cpl->content_title_text(), dcp::LocalTime().as_string() ).encrypt (signer, recipient, trusted_devices, formulation); } +/** @param from KDM from time expressed as a local time in the time zone of the Screen's Cinema. + * @param to KDM to time expressed as a local time in the time zone of the Screen's Cinema. + */ list Film::make_kdms ( list > screens, boost::filesystem::path dcp, - dcp::LocalTime from, - dcp::LocalTime until, + boost::posix_time::ptime from, + boost::posix_time::ptime until, dcp::Formulation formulation ) const { @@ -1179,7 +1206,16 @@ Film::make_kdms ( BOOST_FOREACH (shared_ptr i, screens) { if (i->recipient) { - kdms.push_back (ScreenKDM (i, make_kdm (i->recipient.get(), i->trusted_devices, dcp, from, until, formulation))); + dcp::EncryptedKDM const kdm = make_kdm ( + i->recipient.get(), + i->trusted_devices, + dcp, + dcp::LocalTime (from, i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()), + dcp::LocalTime (until, i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()), + formulation + ); + + kdms.push_back (ScreenKDM (i, kdm)); } } @@ -1237,9 +1273,8 @@ Film::subtitle_language () const ContentList cl = content (); BOOST_FOREACH (shared_ptr& c, cl) { - shared_ptr sc = dynamic_pointer_cast (c); - if (sc) { - languages.insert (sc->subtitle_language ()); + if (c->subtitle) { + languages.insert (c->subtitle->language ()); } } @@ -1257,18 +1292,44 @@ Film::subtitle_language () const /** Change the gains of the supplied AudioMapping to make it a default * for this film. The defaults are guessed based on what processor (if any) - * is in use and the number of input channels. + * is in use, the number of input channels and any filename supplied. */ void -Film::make_audio_mapping_default (AudioMapping& mapping) const +Film::make_audio_mapping_default (AudioMapping& mapping, optional filename) const { + static string const regex[] = { + ".*[\\._-]L[\\._-].*", + ".*[\\._-]R[\\._-].*", + ".*[\\._-]C[\\._-].*", + ".*[\\._-]Lfe[\\._-].*", + ".*[\\._-]Ls[\\._-].*", + ".*[\\._-]Rs[\\._-].*" + }; + + static int const regexes = sizeof(regex) / sizeof(*regex); + if (audio_processor ()) { audio_processor()->make_audio_mapping_default (mapping); } else { mapping.make_zero (); if (mapping.input_channels() == 1) { - /* Mono -> Centre */ - mapping.set (0, static_cast (dcp::CENTRE), 1); + bool guessed = false; + + /* See if we can guess where this stream should go */ + if (filename) { + for (int i = 0; i < regexes; ++i) { + boost::regex e (regex[i], boost::regex::icase); + if (boost::regex_match (filename->string(), e) && i < mapping.output_channels()) { + mapping.set (0, i, 1); + guessed = true; + } + } + } + + if (!guessed) { + /* If we have no idea, just put it on centre */ + mapping.set (0, static_cast (dcp::CENTRE), 1); + } } else { /* 1:1 mapping */ for (int i = 0; i < min (mapping.input_channels(), mapping.output_channels()); ++i) { @@ -1342,18 +1403,16 @@ Film::reels () const case REELTYPE_BY_VIDEO_CONTENT: { optional last_split; - shared_ptr last_video; - ContentList cl = content (); + shared_ptr last_video; BOOST_FOREACH (shared_ptr c, content ()) { - shared_ptr v = dynamic_pointer_cast (c); - if (v) { - BOOST_FOREACH (DCPTime t, v->reel_split_points()) { + if (c->video) { + BOOST_FOREACH (DCPTime t, c->reel_split_points()) { if (last_split) { p.push_back (DCPTimePeriod (last_split.get(), t)); } last_split = t; } - last_video = v; + last_video = c; } } @@ -1385,3 +1444,9 @@ Film::reels () const return p; } + +string +Film::content_summary (DCPTimePeriod period) const +{ + return _playlist->content_summary (period); +}