X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=blobdiff_plain;f=src%2Flib%2Ffilm.cc;h=9943711f55cfdc45b3f8a6536a83ffe32447addb;hp=4eed5e3f0d0bd412dd554850bbbec8788eff49f1;hb=e519feaca11005d330e7b8403b948d4ad9cc3ae8;hpb=95ddf2cc72b800967b9917f75917cfdde325d2e5 diff --git a/src/lib/film.cc b/src/lib/film.cc index 4eed5e3f0..9943711f5 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2019 Carl Hetherington + Copyright (C) 2012-2020 Carl Hetherington This file is part of DCP-o-matic. @@ -23,6 +23,7 @@ * how they should be presented in a DCP. */ +#include "atmos_content.h" #include "film.h" #include "job.h" #include "util.h" @@ -50,10 +51,12 @@ #include "text_content.h" #include "ffmpeg_content.h" #include "dcp_content.h" -#include "screen_kdm.h" +#include "kdm_with_metadata.h" #include "cinema.h" #include "change_signaller.h" #include "check_content_change_job.h" +#include "ffmpeg_subtitle_stream.h" +#include "font.h" #include #include #include @@ -93,11 +96,15 @@ using std::copy; using std::back_inserter; using std::map; using std::exception; +using std::find; using boost::shared_ptr; using boost::weak_ptr; using boost::dynamic_pointer_cast; using boost::optional; using boost::is_any_of; +#if BOOST_VERSION >= 106100 +using namespace boost::placeholders; +#endif using dcp::raw_convert; using namespace dcpomatic; @@ -129,8 +136,10 @@ string const Film::metadata_file = "metadata.xml"; * 36 -> 37 * TextContent can be in a Caption tag, and some of the tag names * have had Subtitle prefixes or suffixes removed. + * 37 -> 38 + * VideoContent scale expressed just as "guess" or "custom" */ -int const Film::current_state_version = 37; +int const Film::current_state_version = 38; /** Construct a Film object in a given directory. * @@ -143,7 +152,6 @@ Film::Film (optional dir) , _dcp_content_type (Config::instance()->default_dcp_content_type ()) , _container (Config::instance()->default_container ()) , _resolution (RESOLUTION_2K) - , _signed (true) , _encrypted (false) , _context_id (dcp::make_uuid ()) , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ()) @@ -156,17 +164,26 @@ Film::Film (optional dir) , _audio_processor (0) , _reel_type (REELTYPE_SINGLE) , _reel_length (2000000000) - , _upload_after_make_dcp (Config::instance()->default_upload_after_make_dcp()) , _reencode_j2k (false) , _user_explicit_video_frame_rate (false) + , _user_explicit_container (false) + , _user_explicit_resolution (false) + , _name_language (dcp::LanguageTag("en-US")) + , _audio_language (dcp::LanguageTag("en-US")) + , _release_territory (dcp::LanguageTag::RegionSubtag("US")) + , _version_number (1) + , _status (dcp::FINAL) + , _luminance (dcp::Luminance(4.5, dcp::Luminance::FOOT_LAMBERT)) , _state_version (current_state_version) , _dirty (false) + , _tolerant (false) { set_isdcf_date_today (); _playlist_change_connection = _playlist->Change.connect (bind (&Film::playlist_change, this, _1)); - _playlist_order_changed_connection = _playlist->OrderChanged.connect (bind (&Film::playlist_order_changed, this)); + _playlist_order_changed_connection = _playlist->OrderChange.connect (bind (&Film::playlist_order_changed, this)); _playlist_content_change_connection = _playlist->ContentChange.connect (bind (&Film::playlist_content_change, this, _1, _2, _3, _4)); + _playlist_length_change_connection = _playlist->LengthChange.connect (bind(&Film::playlist_length_change, this)); if (dir) { /* Make state.directory a complete path without ..s (where possible) @@ -178,7 +195,8 @@ Film::Film (optional dir) boost::filesystem::path result; for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) { if (*i == "..") { - if (boost::filesystem::is_symlink (result) || result.filename() == "..") { + boost::system::error_code ec; + if (boost::filesystem::is_symlink(result, ec) || result.filename() == "..") { result /= *i; } else { result = result.parent_path (); @@ -223,7 +241,8 @@ Film::video_identifier () const + "_" + raw_convert(j2k_bandwidth()); if (encrypted ()) { - s += "_E"; + /* This is insecure but hey, the key is in plaintext in metadata.xml */ + s += "_E" + _key.hex(); } else { s += "_P"; } @@ -297,6 +316,39 @@ Film::audio_analysis_path (shared_ptr playlist) const return p; } + +boost::filesystem::path +Film::subtitle_analysis_path (shared_ptr content) const +{ + boost::filesystem::path p = dir ("analysis"); + + Digester digester; + digester.add (content->digest()); + + if (!content->text.empty()) { + shared_ptr tc = content->text.front(); + digester.add (tc->x_scale()); + digester.add (tc->y_scale()); + BOOST_FOREACH (shared_ptr i, tc->fonts()) { + digester.add (i->id()); + } + if (tc->effect()) { + digester.add (tc->effect().get()); + } + digester.add (tc->line_spacing()); + digester.add (tc->outline_width()); + } + + shared_ptr fc = dynamic_pointer_cast(content); + if (fc) { + digester.add (fc->subtitle_stream()->identifier()); + } + + p /= digester.get (); + return p; +} + + /** Add suitable Jobs to the JobManager to create a DCP for this Film. * @param gui true if this is being called from a GUI tool. * @param check true to check the content in the project for changes before making the DCP. @@ -316,6 +368,10 @@ Film::make_dcp (bool gui, bool check) throw runtime_error (_("You must add some content to the DCP before creating it")); } + if (length() == DCPTime()) { + throw runtime_error (_("The DCP is empty, perhaps because all the content has zero length.")); + } + if (dcp_content_type() == 0) { throw MissingSettingError (_("content type")); } @@ -399,7 +455,6 @@ Film::metadata (bool with_content_paths) const root->add_child("ThreeD")->add_child_text (_three_d ? "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"); root->add_child("Key")->add_child_text (_key.hex ()); root->add_child("ContextID")->add_child_text (_context_id); @@ -408,7 +463,6 @@ Film::metadata (bool with_content_paths) const } 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"); root->add_child("ReencodeJ2K")->add_child_text (_reencode_j2k ? "1" : "0"); root->add_child("UserExplicitVideoFrameRate")->add_child_text(_user_explicit_video_frame_rate ? "1" : "0"); for (map::const_iterator i = _markers.begin(); i != _markers.end(); ++i) { @@ -419,6 +473,24 @@ Film::metadata (bool with_content_paths) const BOOST_FOREACH (dcp::Rating i, _ratings) { i.as_xml (root->add_child("Rating")); } + BOOST_FOREACH (string i, _content_versions) { + root->add_child("ContentVersion")->add_child_text(i); + } + root->add_child("NameLanguage")->add_child_text(_name_language.to_string()); + root->add_child("AudioLanguage")->add_child_text(_audio_language.to_string()); + root->add_child("ReleaseTerritory")->add_child_text(_release_territory.subtag()); + root->add_child("VersionNumber")->add_child_text(raw_convert(_version_number)); + root->add_child("Status")->add_child_text(dcp::status_to_string(_status)); + root->add_child("Chain")->add_child_text(_chain); + root->add_child("Distributor")->add_child_text(_distributor); + root->add_child("Facility")->add_child_text(_facility); + root->add_child("LuminanceValue")->add_child_text(raw_convert(_luminance.value())); + root->add_child("LuminanceUnit")->add_child_text(dcp::Luminance::unit_to_string(_luminance.unit())); + root->add_child("UserExplicitContainer")->add_child_text(_user_explicit_container ? "1" : "0"); + root->add_child("UserExplicitResolution")->add_child_text(_user_explicit_resolution ? "1" : "0"); + BOOST_FOREACH (dcp::LanguageTag i, _subtitle_languages) { + root->add_child("SubtitleLanguage")->add_child_text(i.to_string()); + } _playlist->as_xml (root->add_child ("Playlist"), with_content_paths); return doc; @@ -465,6 +537,10 @@ Film::read_metadata (optional path) path = file (metadata_file); } + if (!boost::filesystem::exists(*path)) { + throw FileNotFoundError(*path); + } + cxml::Document f ("Metadata"); f.read_file (path.get ()); @@ -512,7 +588,6 @@ Film::read_metadata (optional path) _resolution = string_to_resolution (f.string_child ("Resolution")); _j2k_bandwidth = f.number_child ("J2KBandwidth"); _video_frame_rate = f.number_child ("VideoFrameRate"); - _signed = f.optional_bool_child("Signed").get_value_or (true); _encrypted = f.bool_child ("Encrypted"); _audio_channels = f.number_child ("AudioChannels"); /* We used to allow odd numbers (and zero) channels, but it's just not worth @@ -541,9 +616,15 @@ Film::read_metadata (optional path) _audio_processor = 0; } + if (_audio_processor && !Config::instance()->show_experimental_audio_processors()) { + list ap = AudioProcessor::visible(); + if (find(ap.begin(), ap.end(), _audio_processor) == ap.end()) { + Config::instance()->set_show_experimental_audio_processors(true); + } + } + _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); _reencode_j2k = f.optional_bool_child("ReencodeJ2K").get_value_or(false); _user_explicit_video_frame_rate = f.optional_bool_child("UserExplicitVideoFrameRate").get_value_or(false); @@ -555,6 +636,48 @@ Film::read_metadata (optional path) _ratings.push_back (dcp::Rating(i)); } + BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children("ContentVersion")) { + _content_versions.push_back (i->content()); + } + + optional name_language = f.optional_string_child("NameLanguage"); + if (name_language) { + _name_language = dcp::LanguageTag (*name_language); + } + optional audio_language = f.optional_string_child("AudioLanguage"); + if (audio_language) { + _audio_language = dcp::LanguageTag (*audio_language); + } + optional release_territory = f.optional_string_child("ReleaseTerritory"); + if (release_territory) { + _release_territory = dcp::LanguageTag::RegionSubtag (*release_territory); + } + + _version_number = f.optional_number_child("VersionNumber").get_value_or(0); + + optional status = f.optional_string_child("Status"); + if (status) { + _status = dcp::string_to_status (*status); + } + + _chain = f.optional_string_child("Chain").get_value_or(""); + _distributor = f.optional_string_child("Distributor").get_value_or(""); + _facility = f.optional_string_child("Facility").get_value_or(""); + + float value = f.optional_number_child("LuminanceValue").get_value_or(4.5); + optional unit = f.optional_string_child("LuminanceUnit"); + if (unit) { + _luminance = dcp::Luminance (value, dcp::Luminance::string_to_unit(*unit)); + } + + /* Disable guessing for files made in previous DCP-o-matic versions */ + _user_explicit_container = f.optional_bool_child("UserExplicitContainer").get_value_or(true); + _user_explicit_resolution = f.optional_bool_child("UserExplicitResolution").get_value_or(true); + + BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children("SubtitleLanguage")) { + _subtitle_languages.push_back (dcp::LanguageTag(i->content())); + } + list notes; _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version, notes); @@ -729,11 +852,7 @@ Film::isdcf_name (bool if_created_now) const BOOST_FOREACH (shared_ptr i, content ()) { if (i->video) { /* Here's the first piece of video content */ - if (i->video->scale().ratio ()) { - content_ratio = i->video->scale().ratio (); - } else { - content_ratio = Ratio::from_ratio (i->video->size().ratio ()); - } + content_ratio = Ratio::nearest_from_ratio(i->video->scaled_size(frame_size()).ratio()); break; } } @@ -751,39 +870,27 @@ Film::isdcf_name (bool if_created_now) const for now I'm just appending -CCAP if we have any closed captions. */ - optional subtitle_language; bool burnt_in = true; bool ccap = false; BOOST_FOREACH (shared_ptr i, content()) { BOOST_FOREACH (shared_ptr j, i->text) { - if (j->type() == TEXT_OPEN_SUBTITLE && j->use()) { - subtitle_language = j->language (); - if (!j->burn()) { - burnt_in = false; - } + if (j->type() == TEXT_OPEN_SUBTITLE && j->use() && !j->burn()) { + burnt_in = false; } else if (j->type() == TEXT_CLOSED_CAPTION && j->use()) { ccap = true; } } } - if (dm.subtitle_language) { - /* Subtitle language is overridden in ISDCF metadata, primarily to handle - content with pre-burnt subtitles. - */ - d += "-" + *dm.subtitle_language; - if (ccap) { - d += "-CCAP"; - } - } else if (subtitle_language) { - /* Language is worked out from the content */ - if (burnt_in && *subtitle_language != "XX") { - transform (subtitle_language->begin(), subtitle_language->end(), subtitle_language->begin(), ::tolower); + if (!_subtitle_languages.empty()) { + string lang = _subtitle_languages.front().language().get_value_or("en").subtag(); + if (burnt_in) { + transform (lang.begin(), lang.end(), lang.begin(), ::tolower); } else { - transform (subtitle_language->begin(), subtitle_language->end(), subtitle_language->begin(), ::toupper); + transform (lang.begin(), lang.end(), lang.begin(), ::toupper); } - d += "-" + *subtitle_language; + d += "-" + lang; if (ccap) { d += "-CCAP"; } @@ -804,14 +911,21 @@ Film::isdcf_name (bool if_created_now) const /* Count mapped audio channels */ - pair ch = audio_channel_types (mapped_audio_channels(), audio_channels()); + list mapped = mapped_audio_channels (); + + pair ch = audio_channel_types (mapped, audio_channels()); if (!ch.first && !ch.second) { d += "_MOS"; } else if (ch.first) { d += String::compose("_%1%2", ch.first, ch.second); } - /* XXX: HI/VI */ + if (audio_channels() > static_cast(dcp::HI) && find(mapped.begin(), mapped.end(), dcp::HI) != mapped.end()) { + d += "-HI"; + } + if (audio_channels() > static_cast(dcp::VI) && find(mapped.begin(), mapped.end(), dcp::VI) != mapped.end()) { + d += "-VI"; + } d += "_" + resolution_to_string (_resolution); @@ -906,20 +1020,39 @@ Film::set_dcp_content_type (DCPContentType const * t) _dcp_content_type = t; } + +/** @param explicit_user true if this is being set because of + * a direct user request, false if it is being done because + * DCP-o-matic is guessing the best container to use. + */ void -Film::set_container (Ratio const * c) +Film::set_container (Ratio const * c, bool explicit_user) { ChangeSignaller ch (this, CONTAINER); _container = c; + + if (explicit_user) { + _user_explicit_container = true; + } } + +/** @param explicit_user true if this is being set because of + * a direct user request, false if it is being done because + * DCP-o-matic is guessing the best resolution to use. + */ void -Film::set_resolution (Resolution r) +Film::set_resolution (Resolution r, bool explicit_user) { ChangeSignaller ch (this, RESOLUTION); _resolution = r; + + if (explicit_user) { + _user_explicit_resolution = true; + } } + void Film::set_j2k_bandwidth (int b) { @@ -997,13 +1130,6 @@ Film::set_reel_length (int64_t r) _reel_length = r; } -void -Film::set_upload_after_make_dcp (bool u) -{ - ChangeSignaller ch (this, UPLOAD_AFTER_MAKE_DCP); - _upload_after_make_dcp = u; -} - void Film::set_reencode_j2k (bool r) { @@ -1114,13 +1240,6 @@ Film::cpls () const return out; } -void -Film::set_signed (bool s) -{ - ChangeSignaller ch (this, SIGNED); - _signed = s; -} - void Film::set_encrypted (bool e) { @@ -1128,13 +1247,6 @@ Film::set_encrypted (bool e) _encrypted = e; } -void -Film::set_key (dcp::Key key) -{ - ChangeSignaller ch (this, KEY); - _key = key; -} - ContentList Film::content () const { @@ -1204,12 +1316,58 @@ Film::add_content (shared_ptr c) } _playlist->add (shared_from_this(), c); + + maybe_set_container_and_resolution (); + if (c->atmos) { + set_audio_channels (14); + set_interop (false); + } +} + + +void +Film::maybe_set_container_and_resolution () +{ + /* Get the only piece of video content, if there is only one */ + shared_ptr video; + BOOST_FOREACH (shared_ptr i, _playlist->content()) { + if (i->video) { + if (!video) { + video = i->video; + } else { + video.reset (); + } + } + } + + if (video) { + /* This is the only piece of video content in this Film. Use it to make a guess for + * DCP container size and resolution, unless the user has already explicitly set these + * things. + */ + if (!_user_explicit_container) { + if (video->size().ratio() > 2.3) { + set_container (Ratio::from_id("239"), false); + } else { + set_container (Ratio::from_id("185"), false); + } + } + + if (!_user_explicit_resolution) { + if (video->size_after_crop().width > 2048 || video->size_after_crop().height > 1080) { + set_resolution (RESOLUTION_4K, false); + } else { + set_resolution (RESOLUTION_2K, false); + } + } + } } void Film::remove_content (shared_ptr c) { _playlist->remove (c); + maybe_set_container_and_resolution (); } void @@ -1224,11 +1382,13 @@ Film::move_content_later (shared_ptr c) _playlist->move_later (shared_from_this(), c); } -/** @return length of the film from time 0 to the last thing on the playlist */ +/** @return length of the film from time 0 to the last thing on the playlist, + * with a minimum length of 1 second. + */ DCPTime Film::length () const { - return _playlist->length(shared_from_this()).ceil(video_frame_rate()); + return max(DCPTime::from_seconds(1), _playlist->length(shared_from_this()).ceil(video_frame_rate())); } int @@ -1259,9 +1419,20 @@ Film::playlist_content_change (ChangeType type, weak_ptr c, int p, bool if (type == CHANGE_TYPE_DONE) { emit (boost::bind (boost::ref (ContentChange), type, c, p, frequent)); + if (!frequent) { + check_settings_consistency (); + } } else { ContentChange (type, c, p, frequent); } + + _dirty = true; +} + +void +Film::playlist_length_change () +{ + LengthChange (); } void @@ -1271,28 +1442,53 @@ Film::playlist_change (ChangeType type) signal_change (type, NAME); if (type == CHANGE_TYPE_DONE) { - /* Check that this change hasn't made our settings inconsistent */ - bool change_made = false; - BOOST_FOREACH (shared_ptr i, content()) { - shared_ptr d = dynamic_pointer_cast(i); - if (!d) { - continue; - } + check_settings_consistency (); + } - string why_not; - if (d->reference_video() && !d->can_reference_video(shared_from_this(), why_not)) { - d->set_reference_video(false); - change_made = true; - } - if (d->reference_audio() && !d->can_reference_audio(shared_from_this(), why_not)) { - d->set_reference_audio(false); - change_made = true; + _dirty = true; +} + +/** Check for (and if necessary fix) impossible settings combinations, like + * video set to being referenced when it can't be. + */ +void +Film::check_settings_consistency () +{ + optional atmos_rate; + BOOST_FOREACH (shared_ptr i, content()) { + + if (i->atmos) { + int rate = lrintf (i->atmos->edit_rate().as_float()); + if (atmos_rate && *atmos_rate != rate) { + Message (_("You have more than one piece of Atmos content, and they do not have the same frame rate. You must remove some Atmos content.")); + } else if (!atmos_rate && rate != video_frame_rate()) { + atmos_rate = rate; + set_video_frame_rate (rate, false); + Message (_("DCP-o-matic had to change your settings so that the film's frame rate is the same as that of your Atmos content.")); } } + } - if (change_made) { - Message (_("DCP-o-matic had to change your settings for referring to DCPs as OV. Please review those settings to make sure they are what you want.")); + bool change_made = false; + BOOST_FOREACH (shared_ptr i, content()) { + shared_ptr d = dynamic_pointer_cast(i); + if (!d) { + continue; + } + + string why_not; + if (d->reference_video() && !d->can_reference_video(shared_from_this(), why_not)) { + d->set_reference_video(false); + change_made = true; } + if (d->reference_audio() && !d->can_reference_audio(shared_from_this(), why_not)) { + d->set_reference_audio(false); + change_made = true; + } + } + + if (change_made) { + Message (_("DCP-o-matic had to change your settings for referring to DCPs as OV. Please review those settings to make sure they are what you want.")); } } @@ -1346,6 +1542,26 @@ Film::frame_size () const return fit_ratio_within (container()->ratio(), full_frame ()); } + +/** @return Area of Film::frame_size() that contains picture rather than pillar/letterboxing */ +dcp::Size +Film::active_area () const +{ + dcp::Size const frame = frame_size (); + dcp::Size active; + + BOOST_FOREACH (shared_ptr i, content()) { + if (i->video) { + dcp::Size s = i->video->scaled_size (frame); + active.width = max(active.width, s.width); + active.height = max(active.height, s.height); + } + } + + return active; +} + + /** @param recipient KDM recipient certificate. * @param trusted_devices Certificate thumbprints of other trusted devices (can be empty). * @param cpl_file CPL filename. @@ -1418,47 +1634,6 @@ Film::make_kdm ( ).encrypt (signer, recipient, trusted_devices, formulation, disable_forensic_marking_picture, disable_forensic_marking_audio); } -/** @param screens Screens to make KDMs for. - * @param cpl_file Path to CPL to make KDMs for. - * @param from KDM from time expressed as a local time in the time zone of the Screen's Cinema. - * @param until KDM to time expressed as a local time in the time zone of the Screen's Cinema. - * @param formulation KDM formulation to use. - * @param disable_forensic_marking_picture true to disable forensic marking of picture. - * @param disable_forensic_marking_audio if not set, don't disable forensic marking of audio. If set to 0, - * disable all forensic marking; if set above 0, disable forensic marking above that channel. - */ -list > -Film::make_kdms ( - list > screens, - boost::filesystem::path cpl_file, - boost::posix_time::ptime from, - boost::posix_time::ptime until, - dcp::Formulation formulation, - bool disable_forensic_marking_picture, - optional disable_forensic_marking_audio - ) const -{ - list > kdms; - - BOOST_FOREACH (shared_ptr i, screens) { - if (i->recipient) { - dcp::EncryptedKDM const kdm = make_kdm ( - i->recipient.get(), - i->trusted_device_thumbprints(), - cpl_file, - dcp::LocalTime (from, i->cinema ? i->cinema->utc_offset_hour() : 0, i->cinema ? i->cinema->utc_offset_minute() : 0), - dcp::LocalTime (until, i->cinema ? i->cinema->utc_offset_hour() : 0, i->cinema ? i->cinema->utc_offset_minute() : 0), - formulation, - disable_forensic_marking_picture, - disable_forensic_marking_audio - ); - - kdms.push_back (shared_ptr(new DCPScreenKDM(i, kdm))); - } - } - - return kdms; -} /** @return The approximate disk space required to encode a DCP of this film with the * current settings, in bytes. @@ -1504,33 +1679,10 @@ Film::should_be_enough_disk_space (double& required, double& available, bool& ca return (available - required) > 1; } -string -Film::subtitle_language () const -{ - set languages; - - BOOST_FOREACH (shared_ptr i, content()) { - BOOST_FOREACH (shared_ptr j, i->text) { - languages.insert (j->language ()); - } - } - - string all; - BOOST_FOREACH (string s, languages) { - if (!all.empty ()) { - all += "/" + s; - } else { - all += s; - } - } - - return all; -} - /** @return The names of the channels that audio contents' outputs are passed into; * this is either the DCP or a AudioProcessor. */ -vector +vector Film::audio_output_names () const { if (audio_processor ()) { @@ -1539,10 +1691,12 @@ Film::audio_output_names () const DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16); - vector n; + vector n; for (int i = 0; i < audio_channels(); ++i) { - n.push_back (short_audio_channel_name (i)); + if (i != 8 && i != 9 && i != 15) { + n.push_back (NamedChannel(short_audio_channel_name(i), i)); + } } return n; @@ -1578,37 +1732,43 @@ Film::reels () const break; case REELTYPE_BY_VIDEO_CONTENT: { - optional last_split; - shared_ptr last_video; - BOOST_FOREACH (shared_ptr c, content ()) { + /* Collect all reel boundaries */ + list split_points; + split_points.push_back (DCPTime()); + split_points.push_back (len); + BOOST_FOREACH (shared_ptr c, content()) { if (c->video) { BOOST_FOREACH (DCPTime t, c->reel_split_points(shared_from_this())) { - if (last_split) { - p.push_back (DCPTimePeriod (last_split.get(), t)); - } - last_split = t; + split_points.push_back (t); } - last_video = c; + split_points.push_back (c->end(shared_from_this())); } } - DCPTime video_end = last_video ? last_video->end(shared_from_this()) : DCPTime(0); - if (last_split) { - /* Definitely go from the last split to the end of the video content */ - p.push_back (DCPTimePeriod (last_split.get(), video_end)); - } - - if (video_end < len) { - /* And maybe go after that as well if there is any non-video hanging over the end */ - p.push_back (DCPTimePeriod (video_end, len)); + split_points.sort (); + split_points.unique (); + + /* Make them into periods, coalescing any that are less than 1 second long */ + optional last; + BOOST_FOREACH (DCPTime t, split_points) { + if (last && (t - *last) >= DCPTime::from_seconds(1)) { + /* Period from *last to t is long enough; use it and start a new one */ + p.push_back (DCPTimePeriod(*last, t)); + last = t; + } else if (!last) { + /* That was the first time, so start a new period */ + last = t; + } } break; } case REELTYPE_BY_LENGTH: { DCPTime current; - /* Integer-divide reel length by the size of one frame to give the number of frames per reel */ - Frame const reel_in_frames = _reel_length / ((j2k_bandwidth() / video_frame_rate()) / 8); + /* Integer-divide reel length by the size of one frame to give the number of frames per reel, + * making sure we don't go less than 1s long. + */ + Frame const reel_in_frames = max(_reel_length / ((j2k_bandwidth() / video_frame_rate()) / 8), static_cast(video_frame_rate())); while (current < len) { DCPTime end = min (len, current + DCPTime::from_frames (reel_in_frames, video_frame_rate ())); p.push_back (DCPTimePeriod (current, end)); @@ -1641,7 +1801,6 @@ Film::use_template (string name) _resolution = _template_film->_resolution; _j2k_bandwidth = _template_film->_j2k_bandwidth; _video_frame_rate = _template_film->_video_frame_rate; - _signed = _template_film->_signed; _encrypted = _template_film->_encrypted; _audio_channels = _template_film->_audio_channels; _sequence = _template_film->_sequence; @@ -1650,7 +1809,6 @@ Film::use_template (string name) _audio_processor = _template_film->_audio_processor; _reel_type = _template_film->_reel_type; _reel_length = _template_film->_reel_length; - _upload_after_make_dcp = _template_film->_upload_after_make_dcp; _isdcf_metadata = _template_film->_isdcf_metadata; } @@ -1692,6 +1850,20 @@ Film::references_dcp_audio () const return false; } + +bool +Film::contains_atmos_content () const +{ + BOOST_FOREACH (shared_ptr i, _playlist->content()) { + if (i->atmos) { + return true; + } + } + + return false; +} + + list Film::closed_caption_tracks () const { @@ -1723,6 +1895,15 @@ Film::unset_marker (dcp::Marker type) _markers.erase (type); } + +void +Film::clear_markers () +{ + ChangeSignaller ch (this, MARKERS); + _markers.clear (); +} + + void Film::set_ratings (vector r) { @@ -1730,6 +1911,111 @@ Film::set_ratings (vector r) _ratings = r; } +void +Film::set_content_versions (vector v) +{ + ChangeSignaller ch (this, CONTENT_VERSIONS); + _content_versions = v; +} + + +void +Film::set_name_language (dcp::LanguageTag lang) +{ + ChangeSignaller ch (this, NAME_LANGUAGE); + _name_language = lang; +} + + +void +Film::set_audio_language (dcp::LanguageTag lang) +{ + ChangeSignaller ch (this, AUDIO_LANGUAGE); + _audio_language = lang; +} + + +void +Film::set_release_territory (dcp::LanguageTag::RegionSubtag region) +{ + ChangeSignaller ch (this, RELEASE_TERRITORY); + _release_territory = region; +} + + +void +Film::set_status (dcp::Status s) +{ + ChangeSignaller ch (this, STATUS); + _status = s; +} + + +void +Film::set_version_number (int v) +{ + ChangeSignaller ch (this, VERSION_NUMBER); + _version_number = v; +} + + +void +Film::set_chain (string c) +{ + ChangeSignaller ch (this, CHAIN); + _chain = c; +} + + +void +Film::set_distributor (string d) +{ + ChangeSignaller ch (this, DISTRIBUTOR); + _distributor = d; +} + + +void +Film::set_luminance (dcp::Luminance l) +{ + ChangeSignaller ch (this, LUMINANCE); + _luminance = l; +} + + +void +Film::set_subtitle_language (dcp::LanguageTag language) +{ + vector lang; + lang.push_back (language); + set_subtitle_languages (lang); +} + + +void +Film::unset_subtitle_language () +{ + ChangeSignaller ch (this, SUBTITLE_LANGUAGES); + _subtitle_languages.clear(); +} + + +void +Film::set_subtitle_languages (vector languages) +{ + ChangeSignaller ch (this, SUBTITLE_LANGUAGES); + _subtitle_languages = languages; +} + + +void +Film::set_facility (string f) +{ + ChangeSignaller ch (this, FACILITY); + _facility = f; +} + + optional Film::marker (dcp::Marker type) const { @@ -1739,3 +2025,51 @@ Film::marker (dcp::Marker type) const } return i->second; } + +shared_ptr +Film::info_file_handle (DCPTimePeriod period, bool read) const +{ + return shared_ptr (new InfoFileHandle(_info_file_mutex, info_file(period), read)); +} + +InfoFileHandle::InfoFileHandle (boost::mutex& mutex, boost::filesystem::path file, bool read) + : _lock (mutex) + , _file (file) +{ + if (read) { + _handle = fopen_boost (file, "rb"); + if (!_handle) { + throw OpenFileError (file, errno, OpenFileError::READ); + } + } else { + bool const exists = boost::filesystem::exists (file); + if (exists) { + _handle = fopen_boost (file, "r+b"); + } else { + _handle = fopen_boost (file, "wb"); + } + + if (!_handle) { + throw OpenFileError (file, errno, exists ? OpenFileError::READ_WRITE : OpenFileError::WRITE); + } + } +} + +InfoFileHandle::~InfoFileHandle () +{ + fclose (_handle); +} + + +/** Add FFOC and LFOC markers to a list if they are not already there */ +void +Film::add_ffoc_lfoc (Markers& markers) const +{ + if (markers.find(dcp::Marker::FFOC) == markers.end()) { + markers[dcp::Marker::FFOC] = dcpomatic::DCPTime(0); + } + + if (markers.find(dcp::Marker::LFOC) == markers.end()) { + markers[dcp::Marker::LFOC] = length() - DCPTime::from_frames(1, video_frame_rate()); + } +}