X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Ffilm.cc;h=426c7f81ddeb30bb0b90b4a26db8e6edacf8c5d6;hb=1e77753ef4119b6d7df7d2255b1a1d8d6af951de;hp=dd31388b6fd7adf58210cd49fa72e3432822830e;hpb=1460bda6f80b6529e31a1a63029dc0ec5f7d0ae8;p=dcpomatic.git diff --git a/src/lib/film.cc b/src/lib/film.cc index dd31388b6..426c7f81d 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2017 Carl Hetherington + Copyright (C) 2012-2018 Carl Hetherington This file is part of DCP-o-matic. @@ -19,7 +19,7 @@ */ /** @file src/film.cc - * @brief A representation of some audio and video content, and details of + * @brief A representation of some audio, video and subtitle content, and details of * how they should be presented in a DCP. */ @@ -27,6 +27,7 @@ #include "job.h" #include "util.h" #include "job_manager.h" +#include "dcp_encoder.h" #include "transcode_job.h" #include "upload_job.h" #include "null_log.h" @@ -45,11 +46,13 @@ #include "screen.h" #include "audio_content.h" #include "video_content.h" -#include "subtitle_content.h" +#include "text_content.h" #include "ffmpeg_content.h" #include "dcp_content.h" #include "screen_kdm.h" #include "cinema.h" +#include "change_signaller.h" +#include "check_content_change_job.h" #include #include #include @@ -98,6 +101,8 @@ 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); +string const Film::metadata_file = "metadata.xml"; + /* 5 -> 6 * AudioMapping XML changed. * 6 -> 7 @@ -121,8 +126,11 @@ using dcp::raw_convert; * VideoFrameType in VideoContent is a string rather than an integer. * 35 -> 36 * EffectColour rather than OutlineColour in Subtitle. + * 36 -> 37 + * TextContent can be in a Caption tag, and some of the tag names + * have had Subtitle prefixes or suffixes removed. */ -int const Film::current_state_version = 36; +int const Film::current_state_version = 37; /** Construct a Film object in a given directory. * @@ -148,15 +156,16 @@ Film::Film (optional dir) , _audio_processor (0) , _reel_type (REELTYPE_SINGLE) , _reel_length (2000000000) - , _upload_after_make_dcp (false) + , _upload_after_make_dcp (Config::instance()->default_upload_after_make_dcp()) + , _reencode_j2k (false) , _state_version (current_state_version) , _dirty (false) { set_isdcf_date_today (); - _playlist_changed_connection = _playlist->Changed.connect (bind (&Film::playlist_changed, this)); + _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_content_changed_connection = _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2, _3)); + _playlist_content_change_connection = _playlist->ContentChange.connect (bind (&Film::playlist_content_change, this, _1, _2, _3, _4)); if (dir) { /* Make state.directory a complete path without ..s (where possible) @@ -290,7 +299,7 @@ void Film::make_dcp () { if (dcp_name().find ("/") != string::npos) { - throw BadSettingError (_("name"), _("cannot contain slashes")); + throw BadSettingError (_("name"), _("Cannot contain slashes")); } if (container() == 0) { @@ -298,7 +307,7 @@ Film::make_dcp () } if (content().empty()) { - throw runtime_error (_("you must add some content to the DCP before creating it")); + throw runtime_error (_("You must add some content to the DCP before creating it")); } if (dcp_content_type() == 0) { @@ -306,7 +315,7 @@ Film::make_dcp () } if (name().empty()) { - throw MissingSettingError (_("name")); + set_name ("DCP"); } BOOST_FOREACH (shared_ptr i, content ()) { @@ -315,10 +324,10 @@ Film::make_dcp () } shared_ptr dcp = dynamic_pointer_cast (i); if (dcp && dcp->needs_kdm()) { - throw runtime_error (_("some of your content needs a KDM")); + throw runtime_error (_("Some of your content needs a KDM")); } if (dcp && dcp->needs_assets()) { - throw runtime_error (_("some of your content needs an OV")); + throw runtime_error (_("Some of your content needs an OV")); } } @@ -335,11 +344,14 @@ Film::make_dcp () if (Config::instance()->only_servers_encode ()) { LOG_GENERAL_NC ("0 threads: ONLY SERVERS SET TO ENCODE"); } else { - LOG_GENERAL ("%1 threads", Config::instance()->num_local_encoding_threads()); + LOG_GENERAL ("%1 threads", Config::instance()->master_encoding_threads()); } LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth()); - JobManager::instance()->add (shared_ptr (new TranscodeJob (shared_from_this()))); + shared_ptr tj (new TranscodeJob (shared_from_this())); + tj->set_encoder (shared_ptr (new DCPEncoder (shared_from_this(), tj))); + shared_ptr cc (new CheckContentChangeJob (shared_from_this(), tj)); + JobManager::instance()->add (cc); } /** Start a job to send our DCP to the configured TMS */ @@ -387,11 +399,19 @@ 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"); _playlist->as_xml (root->add_child ("Playlist"), with_content_paths); return doc; } +void +Film::write_metadata (boost::filesystem::path path) const +{ + shared_ptr doc = metadata (); + doc->write_to_file_formatted (path.string()); +} + /** Write state to our `metadata' file */ void Film::write_metadata () const @@ -399,7 +419,7 @@ Film::write_metadata () const DCPOMATIC_ASSERT (directory()); boost::filesystem::create_directories (directory().get()); shared_ptr doc = metadata (); - doc->write_to_file_formatted (file("metadata.xml").string ()); + doc->write_to_file_formatted (file(metadata_file).string ()); _dirty = false; } @@ -419,11 +439,11 @@ list Film::read_metadata (optional path) { if (!path) { - if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) { + if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file (metadata_file))) { throw runtime_error (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version. You will need to create a new Film, re-add your content and set it up again. Sorry!")); } - path = file ("metadata.xml"); + path = file (metadata_file); } cxml::Document f ("Metadata"); @@ -494,6 +514,7 @@ Film::read_metadata (optional path) _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); list notes; /* This method is the only one that can return notes (so far) */ @@ -509,10 +530,11 @@ Film::read_metadata (optional path) } /** Given a directory name, return its full path within the Film's directory. - * The directory (and its parents) will be created if they do not exist. + * @param d directory name within the Film's directory. + * @param create true to create the directory (and its parents) if they do not exist. */ boost::filesystem::path -Film::dir (boost::filesystem::path d) const +Film::dir (boost::filesystem::path d, bool create) const { DCPOMATIC_ASSERT (_directory); @@ -520,7 +542,9 @@ Film::dir (boost::filesystem::path d) const p /= _directory.get(); p /= d; - boost::filesystem::create_directories (p); + if (create) { + boost::filesystem::create_directories (p); + } return p; } @@ -661,7 +685,7 @@ Film::isdcf_name (bool if_created_now) const /* XXX: this uses the first bit of content only */ - /* The standard says we don't do this for trailers, for some strange reason */ + /* Interior aspect ratio. The standard says we don't do this for trailers, for some strange reason */ if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) { Ratio const * content_ratio = 0; BOOST_FOREACH (shared_ptr i, content ()) { @@ -677,7 +701,8 @@ Film::isdcf_name (bool if_created_now) const } if (content_ratio && content_ratio != container()) { - d += "-" + content_ratio->isdcf_name(); + /* This needs to be the numeric version of the ratio, and ::id() is close enough */ + d += "-" + content_ratio->id(); } } @@ -685,14 +710,19 @@ Film::isdcf_name (bool if_created_now) const d += "_" + dm.audio_language; if (!dm.subtitle_language.empty()) { - bool burnt_in = true; - BOOST_FOREACH (shared_ptr i, content ()) { - if (!i->subtitle) { - continue; - } + /* I'm not clear on the precise details of the convention for CCAP labelling; + for now I'm just appending -CCAP if we have any closed captions. + */ - if (i->subtitle->use() && !i->subtitle->burn()) { - burnt_in = false; + 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() && !j->burn()) { + burnt_in = false; + } else if (j->type() == TEXT_CLOSED_CAPTION) { + ccap = true; + } } } @@ -704,6 +734,9 @@ Film::isdcf_name (bool if_created_now) const } d += "-" + language; + if (ccap) { + d += "-CCAP"; + } } else { d += "-XX"; } @@ -720,24 +753,11 @@ Film::isdcf_name (bool if_created_now) const /* Count mapped audio channels */ - int non_lfe = 0; - int lfe = 0; - - BOOST_FOREACH (int i, mapped_audio_channels ()) { - 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 (non_lfe) { - d += String::compose("_%1%2", non_lfe, lfe); + pair ch = audio_channel_types (mapped_audio_channels(), 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 */ @@ -771,7 +791,17 @@ Film::isdcf_name (bool if_created_now) const bool vf = false; BOOST_FOREACH (shared_ptr i, content ()) { shared_ptr dc = dynamic_pointer_cast (i); - if (dc && (dc->reference_video() || dc->reference_audio() || dc->reference_subtitle())) { + if (!dc) { + continue; + } + + bool any_text = false; + for (int i = 0; i < TEXT_COUNT; ++i) { + if (dc->reference_text(static_cast(i))) { + any_text = true; + } + } + if (dc->reference_video() || dc->reference_audio() || any_text) { vf = true; } } @@ -807,133 +837,149 @@ Film::set_directory (boost::filesystem::path d) void Film::set_name (string n) { + ChangeSignaller ch (this, NAME); _name = n; - signal_changed (NAME); } void Film::set_use_isdcf_name (bool u) { + ChangeSignaller ch (this, USE_ISDCF_NAME); _use_isdcf_name = u; - signal_changed (USE_ISDCF_NAME); } void Film::set_dcp_content_type (DCPContentType const * t) { + ChangeSignaller ch (this, DCP_CONTENT_TYPE); _dcp_content_type = t; - signal_changed (DCP_CONTENT_TYPE); } void Film::set_container (Ratio const * c) { + ChangeSignaller ch (this, CONTAINER); _container = c; - signal_changed (CONTAINER); } void Film::set_resolution (Resolution r) { + ChangeSignaller ch (this, RESOLUTION); _resolution = r; - signal_changed (RESOLUTION); } void Film::set_j2k_bandwidth (int b) { + ChangeSignaller ch (this, J2K_BANDWIDTH); _j2k_bandwidth = b; - signal_changed (J2K_BANDWIDTH); } void Film::set_isdcf_metadata (ISDCFMetadata m) { + ChangeSignaller ch (this, ISDCF_METADATA); _isdcf_metadata = m; - signal_changed (ISDCF_METADATA); } void Film::set_video_frame_rate (int f) { + ChangeSignaller ch (this, VIDEO_FRAME_RATE); _video_frame_rate = f; - signal_changed (VIDEO_FRAME_RATE); } void Film::set_audio_channels (int c) { + ChangeSignaller ch (this, AUDIO_CHANNELS); _audio_channels = c; - signal_changed (AUDIO_CHANNELS); } void Film::set_three_d (bool t) { + ChangeSignaller ch (this, THREE_D); _three_d = t; - signal_changed (THREE_D); if (_three_d && _isdcf_metadata.two_d_version_of_three_d) { + ChangeSignaller ch (this, ISDCF_METADATA); _isdcf_metadata.two_d_version_of_three_d = false; - signal_changed (ISDCF_METADATA); } } void Film::set_interop (bool i) { + ChangeSignaller ch (this, INTEROP); _interop = i; - signal_changed (INTEROP); } void Film::set_audio_processor (AudioProcessor const * processor) { + ChangeSignaller ch1 (this, AUDIO_PROCESSOR); + ChangeSignaller ch2 (this, AUDIO_CHANNELS); _audio_processor = processor; - signal_changed (AUDIO_PROCESSOR); - signal_changed (AUDIO_CHANNELS); } void Film::set_reel_type (ReelType t) { + ChangeSignaller ch (this, REEL_TYPE); _reel_type = t; - signal_changed (REEL_TYPE); } /** @param r Desired reel length in bytes */ void Film::set_reel_length (int64_t r) { + ChangeSignaller ch (this, REEL_LENGTH); _reel_length = r; - signal_changed (REEL_LENGTH); } void Film::set_upload_after_make_dcp (bool u) { + ChangeSignaller ch (this, UPLOAD_AFTER_MAKE_DCP); _upload_after_make_dcp = u; - signal_changed (UPLOAD_AFTER_MAKE_DCP); } void -Film::signal_changed (Property p) +Film::set_reencode_j2k (bool r) { - _dirty = true; + ChangeSignaller ch (this, REENCODE_J2K); + _reencode_j2k = r; +} - switch (p) { - case Film::CONTENT: - set_video_frame_rate (_playlist->best_video_frame_rate ()); - break; - case Film::VIDEO_FRAME_RATE: - case Film::SEQUENCE: - _playlist->maybe_sequence (); - break; - default: - break; - } +void +Film::signal_change (ChangeType type, int p) +{ + signal_change (type, static_cast(p)); +} + +void +Film::signal_change (ChangeType type, Property p) +{ + if (type == CHANGE_TYPE_DONE) { + _dirty = true; + + if (p == Film::CONTENT) { + set_video_frame_rate (_playlist->best_video_frame_rate ()); + } - emit (boost::bind (boost::ref (Changed), p)); + emit (boost::bind (boost::ref (Change), type, p)); + + if (p == Film::VIDEO_FRAME_RATE || p == Film::SEQUENCE) { + /* We want to call Playlist::maybe_sequence but this must happen after the + main signal emission (since the butler will see that emission and un-suspend itself). + */ + emit (boost::bind(&Playlist::maybe_sequence, _playlist.get())); + } + } else { + Change (type, p); + } } void @@ -1010,22 +1056,22 @@ Film::cpls () const void Film::set_signed (bool s) { + ChangeSignaller ch (this, SIGNED); _signed = s; - signal_changed (SIGNED); } void Film::set_encrypted (bool e) { + ChangeSignaller ch (this, ENCRYPTED); _encrypted = e; - signal_changed (ENCRYPTED); } void Film::set_key (dcp::Key key) { + ChangeSignaller ch (this, KEY); _key = key; - signal_changed (KEY); } ContentList @@ -1034,24 +1080,27 @@ Film::content () const return _playlist->content (); } +/** @param content Content to add. + * @param disable_audio_analysis true to never do automatic audio analysis, even if it is enabled in configuration. + */ void -Film::examine_and_add_content (shared_ptr c) +Film::examine_and_add_content (shared_ptr content, bool disable_audio_analysis) { - if (dynamic_pointer_cast (c) && _directory) { - run_ffprobe (c->path(0), file ("ffprobe.log"), _log); + if (dynamic_pointer_cast (content) && _directory) { + run_ffprobe (content->path(0), file ("ffprobe.log"), _log); } - shared_ptr j (new ExamineContentJob (shared_from_this(), c)); + shared_ptr j (new ExamineContentJob (shared_from_this(), content)); _job_connections.push_back ( - j->Finished.connect (bind (&Film::maybe_add_content, this, weak_ptr (j), weak_ptr (c))) + j->Finished.connect (bind (&Film::maybe_add_content, this, weak_ptr(j), weak_ptr(content), disable_audio_analysis)) ); JobManager::instance()->add (j); } void -Film::maybe_add_content (weak_ptr j, weak_ptr c) +Film::maybe_add_content (weak_ptr j, weak_ptr c, bool disable_audio_analysis) { shared_ptr job = j.lock (); if (!job || !job->finished_ok ()) { @@ -1065,12 +1114,12 @@ Film::maybe_add_content (weak_ptr j, weak_ptr c) add_content (content); - if (Config::instance()->automatic_audio_analysis() && content->audio) { + if (Config::instance()->automatic_audio_analysis() && content->audio && !disable_audio_analysis) { shared_ptr playlist (new Playlist); playlist->add (content); boost::signals2::connection c; JobManager::instance()->analyse_audio ( - shared_from_this (), playlist, c, bind (&Film::audio_analysis_finished, this) + shared_from_this(), playlist, false, c, bind (&Film::audio_analysis_finished, this) ); _audio_analysis_connections.push_back (c); } @@ -1081,17 +1130,15 @@ Film::add_content (shared_ptr 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 ()); + c->set_position (_playlist->video_end()); + } else if (!c->text.empty()) { + c->set_position (_playlist->text_end()); } if (_template_film) { /* Take settings from the first piece of content of c's type in _template */ BOOST_FOREACH (shared_ptr i, _template_film->content()) { - if (typeid(i.get()) == typeid(c.get())) { - c->use_template (i); - } + c->take_settings_from (i); } } @@ -1120,7 +1167,7 @@ Film::move_content_later (shared_ptr c) DCPTime Film::length () const { - return _playlist->length (); + return _playlist->length().ceil(video_frame_rate()); } int @@ -1136,30 +1183,33 @@ Film::active_frame_rate_change (DCPTime t) const } void -Film::playlist_content_changed (weak_ptr c, int p, bool frequent) +Film::playlist_content_change (ChangeType type, weak_ptr c, int p, bool frequent) { - _dirty = true; - if (p == ContentProperty::VIDEO_FRAME_RATE) { - set_video_frame_rate (_playlist->best_video_frame_rate ()); + signal_change (type, Film::CONTENT); } else if (p == AudioContentProperty::STREAMS) { - signal_changed (NAME); + signal_change (type, Film::NAME); } - emit (boost::bind (boost::ref (ContentChanged), c, p, frequent)); + if (type == CHANGE_TYPE_DONE) { + emit (boost::bind (boost::ref (ContentChange), type, c, p, frequent)); + } else { + ContentChange (type, c, p, frequent); + } } void -Film::playlist_changed () +Film::playlist_change (ChangeType type) { - signal_changed (CONTENT); - signal_changed (NAME); + signal_change (type, CONTENT); + signal_change (type, NAME); } void Film::playlist_order_changed () { - signal_changed (CONTENT_ORDER); + /* XXX: missing PENDING */ + signal_change (CHANGE_TYPE_DONE, CONTENT_ORDER); } int @@ -1177,9 +1227,13 @@ Film::audio_frame_rate () const void Film::set_sequence (bool s) { + if (s == _sequence) { + return; + } + + ChangeSignaller cc (this, SEQUENCE); _sequence = s; _playlist->set_sequence (s); - signal_changed (SEQUENCE); } /** @return Size of the largest possible image in whatever resolution we are using */ @@ -1204,19 +1258,32 @@ 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 +/** @param recipient KDM recipient certificate. + * @param trusted_devices Certificate thumbprints of other trusted devices (can be empty). + * @param cpl_file CPL filename. + * @param from KDM from time expressed as a local time with an offset from UTC. + * @param until KDM to time expressed as a local time with an offset from UTC. + * @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. */ dcp::EncryptedKDM Film::make_kdm ( dcp::Certificate recipient, - vector trusted_devices, + vector trusted_devices, boost::filesystem::path cpl_file, dcp::LocalTime from, dcp::LocalTime until, - dcp::Formulation formulation + dcp::Formulation formulation, + bool disable_forensic_marking_picture, + optional disable_forensic_marking_audio ) const { + if (!_encrypted) { + throw runtime_error (_("Cannot make a KDM as this project is not encrypted.")); + } + shared_ptr cpl (new dcp::CPL (cpl_file)); shared_ptr signer = Config::instance()->signer_chain (); if (!signer->valid ()) { @@ -1261,19 +1328,27 @@ Film::make_kdm ( return dcp::DecryptedKDM ( cpl->id(), keys, from, until, cpl->content_title_text(), cpl->content_title_text(), dcp::LocalTime().as_string() - ).encrypt (signer, recipient, trusted_devices, formulation); + ).encrypt (signer, recipient, trusted_devices, formulation, disable_forensic_marking_picture, disable_forensic_marking_audio); } -/** @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. +/** @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 dcp, + boost::filesystem::path cpl_file, boost::posix_time::ptime from, boost::posix_time::ptime until, - dcp::Formulation formulation + dcp::Formulation formulation, + bool disable_forensic_marking_picture, + optional disable_forensic_marking_audio ) const { list kdms; @@ -1282,11 +1357,13 @@ Film::make_kdms ( if (i->recipient) { dcp::EncryptedKDM const kdm = make_kdm ( i->recipient.get(), - i->trusted_devices, - dcp, + i->trusted_device_thumbprints(), + cpl_file, 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 + formulation, + disable_forensic_marking_picture, + disable_forensic_marking_audio ); kdms.push_back (ScreenKDM (i, kdm)); @@ -1345,10 +1422,9 @@ Film::subtitle_language () const { set languages; - ContentList cl = content (); - BOOST_FOREACH (shared_ptr& c, cl) { - if (c->subtitle) { - languages.insert (c->subtitle->language ()); + BOOST_FOREACH (shared_ptr i, content()) { + BOOST_FOREACH (shared_ptr j, i->text) { + languages.insert (j->language ()); } } @@ -1456,7 +1532,7 @@ list Film::reels () const { list p; - DCPTime const len = length().ceil (video_frame_rate ()); + DCPTime const len = length(); switch (reel_type ()) { case REELTYPE_SINGLE: @@ -1507,48 +1583,15 @@ Film::reels () const return p; } +/** @param period A period within the DCP + * @return Name of the content which most contributes to the given period. + */ string Film::content_summary (DCPTimePeriod period) const { return _playlist->content_summary (period); } -list -Film::fix_conflicting_settings () -{ - list notes; - - list was_referencing; - BOOST_FOREACH (shared_ptr i, content()) { - shared_ptr d = dynamic_pointer_cast (i); - if (d) { - list reasons; - bool was = false; - if (!d->can_reference_video(reasons) && d->reference_video()) { - d->set_reference_video (false); - was = true; - } - if (!d->can_reference_audio(reasons) && d->reference_audio()) { - d->set_reference_audio (false); - was = true; - } - if (!d->can_reference_subtitle(reasons) && d->reference_subtitle()) { - d->set_reference_subtitle (false); - was = true; - } - if (was) { - was_referencing.push_back (d->path(0).parent_path().filename()); - } - } - } - - BOOST_FOREACH (boost::filesystem::path d, was_referencing) { - notes.push_back (String::compose (_("The DCP %1 was being referred to by this film. This not now possible because the reel sizes in the film no longer agree with those in the imported DCP.\n\nSetting the 'Reel type' to 'split by video content' will probably help.\n\nAfter doing that you would need to re-tick the appropriate 'refer to existing DCP' checkboxes."), d.string())); - } - - return notes; -} - void Film::use_template (string name) { @@ -1570,4 +1613,60 @@ Film::use_template (string name) _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; +} + +pair +Film::speed_up_range (int dcp_frame_rate) const +{ + return _playlist->speed_up_range (dcp_frame_rate); +} + +void +Film::copy_from (shared_ptr film) +{ + read_metadata (film->file (metadata_file)); +} + +bool +Film::references_dcp_video () const +{ + BOOST_FOREACH (shared_ptr i, _playlist->content()) { + shared_ptr d = dynamic_pointer_cast(i); + if (d && d->reference_video()) { + return true; + } + } + + return false; +} + +bool +Film::references_dcp_audio () const +{ + BOOST_FOREACH (shared_ptr i, _playlist->content()) { + shared_ptr d = dynamic_pointer_cast(i); + if (d && d->reference_audio()) { + return true; + } + } + + return false; +} + +list +Film::closed_caption_tracks () const +{ + list tt; + BOOST_FOREACH (shared_ptr i, content()) { + BOOST_FOREACH (shared_ptr j, i->text) { + /* XXX: Empty DCPTextTrack ends up being a magic value here */ + DCPTextTrack dtt = j->dcp_track().get_value_or(DCPTextTrack()); + if (j->type() == TEXT_CLOSED_CAPTION && find(tt.begin(), tt.end(), dtt) == tt.end()) { + tt.push_back (dtt); + } + } + } + + return tt; }