X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Ffilm.cc;h=f61d5106f14e42c5a60e71d015ed1221d015c152;hb=74873babf8ed3ffb3dd79858328d5b3f142479a3;hp=086d12e634188138590807aa9fab6c13f70f9b50;hpb=5aa057f73c9b5d0f2a4a1479bd75dee849250265;p=dcpomatic.git diff --git a/src/lib/film.cc b/src/lib/film.cc index 086d12e63..f61d5106f 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2018 Carl Hetherington + Copyright (C) 2012-2019 Carl Hetherington This file is part of DCP-o-matic. @@ -32,6 +32,7 @@ #include "upload_job.h" #include "null_log.h" #include "file_log.h" +#include "dcpomatic_log.h" #include "exceptions.h" #include "examine_content_job.h" #include "config.h" @@ -91,6 +92,7 @@ using std::runtime_error; using std::copy; using std::back_inserter; using std::map; +using std::exception; using boost::shared_ptr; using boost::weak_ptr; using boost::dynamic_pointer_cast; @@ -98,9 +100,6 @@ 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); - string const Film::metadata_file = "metadata.xml"; /* 5 -> 6 @@ -157,6 +156,8 @@ Film::Film (optional dir) , _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) , _state_version (current_state_version) , _dirty (false) { @@ -289,6 +290,8 @@ Film::audio_analysis_path (shared_ptr playlist) const digester.add (audio_processor()->id ()); } + digester.add (audio_channels()); + p /= digester.get (); return p; } @@ -398,11 +401,20 @@ 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"); _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 @@ -443,6 +455,16 @@ Film::read_metadata (optional path) _state_version = f.number_child ("Version"); if (_state_version > current_state_version) { throw runtime_error (_("This film was created with a newer version of DCP-o-matic, and it cannot be loaded into this version. Sorry!")); + } else if (_state_version < current_state_version) { + /* This is an older version; save a copy (if we haven't already) */ + boost::filesystem::path const older = path->parent_path() / String::compose("metadata.%1.xml", _state_version); + if (!boost::filesystem::is_regular_file(older)) { + try { + boost::filesystem::copy_file(*path, older); + } catch (...) { + /* Never mind; at least we tried */ + } + } } _name = f.string_child ("Name"); @@ -505,6 +527,8 @@ 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); + _user_explicit_video_frame_rate = f.optional_bool_child("UserExplicitVideoFrameRate").get_value_or(false); list notes; /* This method is the only one that can return notes (so far) */ @@ -873,11 +897,18 @@ Film::set_isdcf_metadata (ISDCFMetadata m) _isdcf_metadata = m; } +/** @param f New frame rate. + * @param user_explicit true if this comes from a direct user instruction, false if it is from + * DCP-o-matic being helpful. + */ void -Film::set_video_frame_rate (int f) +Film::set_video_frame_rate (int f, bool user_explicit) { ChangeSignaller ch (this, VIDEO_FRAME_RATE); _video_frame_rate = f; + if (user_explicit) { + _user_explicit_video_frame_rate = true; + } } void @@ -936,6 +967,13 @@ Film::set_upload_after_make_dcp (bool u) _upload_after_make_dcp = u; } +void +Film::set_reencode_j2k (bool r) +{ + ChangeSignaller ch (this, REENCODE_J2K); + _reencode_j2k = r; +} + void Film::signal_change (ChangeType type, int p) { @@ -948,19 +986,20 @@ Film::signal_change (ChangeType type, Property p) if (type == CHANGE_TYPE_DONE) { _dirty = true; - 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; + if (p == Film::CONTENT) { + if (!_user_explicit_video_frame_rate) { + set_video_frame_rate (best_video_frame_rate()); + } } 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(), shared_from_this())); + } } else { Change (type, p); } @@ -999,7 +1038,16 @@ Film::j2c_path (int reel, Frame frame, Eyes eyes, bool tmp) const return file (p); } -/** Find all the DCPs in our directory that can be dcp::DCP::read() and return details of their CPLs */ +static +bool +cpl_summary_compare (CPLSummary const & a, CPLSummary const & b) +{ + return a.last_write_time > b.last_write_time; +} + +/** Find all the DCPs in our directory that can be dcp::DCP::read() and return details of their CPLs. + * The list will be returned in reverse order of timestamp (i.e. most recent first). + */ vector Film::cpls () const { @@ -1017,23 +1065,15 @@ Film::cpls () const ) { try { - dcp::DCP dcp (*i); - dcp.read (); - DCPOMATIC_ASSERT (dcp.cpls().front()->file()); - out.push_back ( - CPLSummary ( - i->path().leaf().string(), - dcp.cpls().front()->id(), - dcp.cpls().front()->annotation_text(), - dcp.cpls().front()->file().get() - ) - ); + out.push_back (CPLSummary(*i)); } catch (...) { } } } + sort (out.begin(), out.end(), cpl_summary_compare); + return out; } @@ -1071,7 +1111,7 @@ void Film::examine_and_add_content (shared_ptr content, bool disable_audio_analysis) { if (dynamic_pointer_cast (content) && _directory) { - run_ffprobe (content->path(0), file ("ffprobe.log"), _log); + run_ffprobe (content->path(0), file("ffprobe.log")); } shared_ptr j (new ExamineContentJob (shared_from_this(), content)); @@ -1100,7 +1140,7 @@ Film::maybe_add_content (weak_ptr j, weak_ptr c, bool disable_audi if (Config::instance()->automatic_audio_analysis() && content->audio && !disable_audio_analysis) { shared_ptr playlist (new Playlist); - playlist->add (content); + playlist->add (shared_from_this(), content); boost::signals2::connection c; JobManager::instance()->analyse_audio ( shared_from_this(), playlist, false, c, bind (&Film::audio_analysis_finished, this) @@ -1114,9 +1154,9 @@ 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()); + c->set_position (shared_from_this(), _playlist->video_end(shared_from_this())); } else if (!c->text.empty()) { - c->set_position (_playlist->text_end()); + c->set_position (shared_from_this(), _playlist->text_end(shared_from_this())); } if (_template_film) { @@ -1126,7 +1166,7 @@ Film::add_content (shared_ptr c) } } - _playlist->add (c); + _playlist->add (shared_from_this(), c); } void @@ -1138,26 +1178,31 @@ Film::remove_content (shared_ptr c) void Film::move_content_earlier (shared_ptr c) { - _playlist->move_earlier (c); + _playlist->move_earlier (shared_from_this(), c); } void Film::move_content_later (shared_ptr c) { - _playlist->move_later (c); + _playlist->move_later (shared_from_this(), c); } /** @return length of the film from time 0 to the last thing on the playlist */ DCPTime Film::length () const { - return _playlist->length().ceil(video_frame_rate()); + return _playlist->length(shared_from_this()).ceil(video_frame_rate()); } int Film::best_video_frame_rate () const { - return _playlist->best_video_frame_rate (); + /* Don't default to anything above 30fps (make the user select that explicitly) */ + int best = _playlist->best_video_frame_rate (); + if (best > 30) { + best /= 2; + } + return best; } FrameRateChange @@ -1187,6 +1232,31 @@ Film::playlist_change (ChangeType type) { signal_change (type, CONTENT); 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; + } + + 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.")); + } + } } void @@ -1199,12 +1269,9 @@ Film::playlist_order_changed () int Film::audio_frame_rate () const { - BOOST_FOREACH (shared_ptr i, content ()) { - if (i->audio && i->audio->has_rate_above_48k ()) { - return 96000; - } - } - + /* It seems that nobody makes 96kHz DCPs at the moment, so let's avoid them. + See #1436. + */ return 48000; } @@ -1243,7 +1310,7 @@ Film::frame_size () const } /** @param recipient KDM recipient certificate. - * @param trusted_devices Certificates of other trusted devices (can be empty). + * @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. @@ -1255,7 +1322,7 @@ Film::frame_size () const 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, @@ -1341,10 +1408,10 @@ Film::make_kdms ( if (i->recipient) { dcp::EncryptedKDM const kdm = make_kdm ( i->recipient.get(), - i->trusted_devices, + 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()), + 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 @@ -1363,13 +1430,13 @@ Film::make_kdms ( uint64_t Film::required_disk_space () const { - return _playlist->required_disk_space (j2k_bandwidth(), audio_channels(), audio_frame_rate()); + return _playlist->required_disk_space (shared_from_this(), j2k_bandwidth(), audio_channels(), audio_frame_rate()); } /** This method checks the disk that the Film is on and tries to decide whether or not * there will be enough space to make a DCP for it. If so, true is returned; if not, * false is returned and required and available are filled in with the amount of disk space - * required and available respectively (in Gb). + * required and available respectively (in GB). * * Note: the decision made by this method isn't, of course, 100% reliable. */ @@ -1424,55 +1491,6 @@ Film::subtitle_language () const return all; } -/** 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, the number of input channels and any filename supplied. - */ -void -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) { - 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) { - mapping.set (i, i, 1); - } - } - } -} - /** @return The names of the channels that audio contents' outputs are passed into; * this is either the DCP or a AudioProcessor. */ @@ -1497,7 +1515,7 @@ Film::audio_output_names () const void Film::repeat_content (ContentList c, int n) { - _playlist->repeat (c, n); + _playlist->repeat (shared_from_this(), c, n); } void @@ -1528,7 +1546,7 @@ Film::reels () const shared_ptr last_video; BOOST_FOREACH (shared_ptr c, content ()) { if (c->video) { - BOOST_FOREACH (DCPTime t, c->reel_split_points()) { + BOOST_FOREACH (DCPTime t, c->reel_split_points(shared_from_this())) { if (last_split) { p.push_back (DCPTimePeriod (last_split.get(), t)); } @@ -1538,7 +1556,7 @@ Film::reels () const } } - DCPTime video_end = last_video ? last_video->end() : DCPTime(0); + 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)); @@ -1573,7 +1591,7 @@ Film::reels () const string Film::content_summary (DCPTimePeriod period) const { - return _playlist->content_summary (period); + return _playlist->content_summary (shared_from_this(), period); } void @@ -1644,8 +1662,10 @@ Film::closed_caption_tracks () const list tt; BOOST_FOREACH (shared_ptr i, content()) { BOOST_FOREACH (shared_ptr j, i->text) { - if (j->type() == TEXT_CLOSED_CAPTION && j->dcp_track() && find(tt.begin(), tt.end(), j->dcp_track().get()) == tt.end()) { - tt.push_back (j->dcp_track().get()); + /* 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); } } }