X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=blobdiff_plain;f=src%2Flib%2Ffilm.cc;h=d311c76cb4b519f78e22c5b1791be4f5387eb77d;hp=cbf08aa498075986b7670bf03081888c2b0cf00e;hb=eefc143f08e4b4dc26ebbd2136488f5c95e6cc2a;hpb=b6a2b6d23998bc3acd2a91eafcfda75778e3d6bc diff --git a/src/lib/film.cc b/src/lib/film.cc index cbf08aa49..d311c76cb 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. @@ -92,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; @@ -156,6 +157,7 @@ Film::Film (optional dir) , _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) { @@ -175,7 +177,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 (); @@ -288,6 +291,8 @@ Film::audio_analysis_path (shared_ptr playlist) const digester.add (audio_processor()->id ()); } + digester.add (audio_channels()); + p /= digester.get (); return p; } @@ -398,6 +403,7 @@ Film::metadata (bool with_content_paths) const 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; @@ -450,6 +456,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"); @@ -463,6 +479,7 @@ Film::read_metadata (optional path) _isdcf_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate")); } + { optional c = f.optional_string_child ("DCPContentType"); if (c) { @@ -513,9 +530,9 @@ Film::read_metadata (optional path) _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) */ _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version, notes); /* Write backtraces to this film's directory, until another film is loaded */ @@ -706,36 +723,49 @@ Film::isdcf_name (bool if_created_now) const if (!dm.audio_language.empty ()) { d += "_" + dm.audio_language; - if (!dm.subtitle_language.empty()) { - /* 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. - */ + /* 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. + */ - 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()) { + 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; - } else if (j->type() == TEXT_CLOSED_CAPTION) { - ccap = true; } + } else if (j->type() == TEXT_CLOSED_CAPTION && j->use()) { + ccap = true; } } + } - string language = dm.subtitle_language; - if (burnt_in && language != "XX") { - transform (language.begin(), language.end(), language.begin(), ::tolower); + 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); } else { - transform (language.begin(), language.end(), language.begin(), ::toupper); + transform (subtitle_language->begin(), subtitle_language->end(), subtitle_language->begin(), ::toupper); } - d += "-" + language; + d += "-" + *subtitle_language; if (ccap) { d += "-CCAP"; } } else { + /* No subtitles */ d += "-XX"; } } @@ -751,14 +781,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); @@ -881,11 +918,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 @@ -964,7 +1008,9 @@ Film::signal_change (ChangeType type, Property p) _dirty = true; if (p == Film::CONTENT) { - set_video_frame_rate (_playlist->best_video_frame_rate ()); + if (!_user_explicit_video_frame_rate) { + set_video_frame_rate (best_video_frame_rate()); + } } emit (boost::bind (boost::ref (Change), type, p)); @@ -1013,7 +1059,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 { @@ -1031,23 +1086,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; } @@ -1171,7 +1218,12 @@ Film::length () const 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 @@ -1201,6 +1253,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 @@ -1213,12 +1290,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; } @@ -1383,7 +1457,7 @@ Film::required_disk_space () const /** 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. */ @@ -1489,24 +1563,24 @@ Film::reels () const break; case REELTYPE_BY_VIDEO_CONTENT: { - optional last_split; + DCPTime last_split; shared_ptr last_video; 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)); + if (last_split != t) { + p.push_back (DCPTimePeriod(last_split, t)); + last_split = t; } - last_split = t; } last_video = c; } } DCPTime video_end = last_video ? last_video->end(shared_from_this()) : DCPTime(0); - if (last_split) { + if (last_split != video_end) { /* Definitely go from the last split to the end of the video content */ - p.push_back (DCPTimePeriod (last_split.get(), video_end)); + p.push_back (DCPTimePeriod(last_split, video_end)); } if (video_end < len) { @@ -1609,7 +1683,7 @@ 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 */ + /* XXX: Empty DCPTextTrack ends up being a magic value here - the "unknown" or "not specified" track */ 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); @@ -1619,3 +1693,37 @@ Film::closed_caption_tracks () const return tt; } + +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); +}