/*
- Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
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;
, _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)
{
digester.add (audio_processor()->id ());
}
+ digester.add (audio_channels());
+
p /= digester.get ();
return p;
}
root->add_child("ReelLength")->add_child_text (raw_convert<string> (_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<dcp::Marker, DCPTime>::const_iterator i = _markers.begin(); i != _markers.end(); ++i) {
+ xmlpp::Element* m = root->add_child("Marker");
+ m->set_attribute("Type", dcp::marker_to_string(i->first));
+ m->add_child_text(raw_convert<string>(i->second.get()));
+ }
_playlist->as_xml (root->add_child ("Playlist"), with_content_paths);
return doc;
_state_version = f.number_child<int> ("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");
_isdcf_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
}
+
{
optional<string> c = f.optional_string_child ("DCPContentType");
if (c) {
_reel_length = f.optional_number_child<int64_t>("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);
+
+ BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children("Marker")) {
+ _markers[dcp::marker_from_string(i->string_attribute("Type"))] = DCPTime(dcp::raw_convert<DCPTime::Type>(i->content()));
+ }
list<string> notes;
/* This method is the only one that can return notes (so far) */
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<Content> i, content()) {
- BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
- if (j->type() == TEXT_OPEN_SUBTITLE && j->use() && !j->burn()) {
+ optional<string> subtitle_language;
+ bool burnt_in = true;
+ bool ccap = false;
+ BOOST_FOREACH (shared_ptr<Content> i, content()) {
+ BOOST_FOREACH (shared_ptr<TextContent> 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";
}
}
_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<Film> ch (this, VIDEO_FRAME_RATE);
_video_frame_rate = f;
+ if (user_explicit) {
+ _user_explicit_video_frame_rate = true;
+ }
}
void
_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));
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<CPLSummary>
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;
}
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
{
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<Content> i, content()) {
+ shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent>(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
int
Film::audio_frame_rate () const
{
- BOOST_FOREACH (shared_ptr<Content> 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;
}
/** 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.
*/
list<DCPTextTrack> tt;
BOOST_FOREACH (shared_ptr<Content> i, content()) {
BOOST_FOREACH (shared_ptr<TextContent> 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);
return tt;
}
+
+void
+Film::set_marker (dcp::Marker type, DCPTime time)
+{
+ ChangeSignaller<Film> ch (this, MARKERS);
+ _markers[type] = time;
+}
+
+void
+Film::unset_marker (dcp::Marker type)
+{
+ ChangeSignaller<Film> ch (this, MARKERS);
+ _markers.erase (type);
+}
+
+optional<DCPTime>
+Film::marker (dcp::Marker type) const
+{
+ map<dcp::Marker, DCPTime>::const_iterator i = _markers.find (type);
+ if (i == _markers.end()) {
+ return optional<DCPTime>();
+ }
+ return i->second;
+}