From: Carl Hetherington Date: Mon, 22 Aug 2016 23:27:12 +0000 (+0100) Subject: Allow import of OV/VF DCPs (#906). X-Git-Tag: v2.9.16~20 X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=df28b0e939bd0f12ae31e6f7ba94fa954496b3b8 Allow import of OV/VF DCPs (#906). --- diff --git a/ChangeLog b/ChangeLog index 2dca7ef13..dfa2cde5b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2016-08-24 c.hetherington + + * Allow import of OV/VF DCPs (#906). + 2016-08-24 Carl Hetherington * Keep timeline above main window. diff --git a/src/lib/dcp.cc b/src/lib/dcp.cc new file mode 100644 index 000000000..6d5c0b291 --- /dev/null +++ b/src/lib/dcp.cc @@ -0,0 +1,59 @@ +/* + Copyright (C) 2014-2016 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + +#include "dcp.h" +#include "config.h" +#include "dcp_content.h" +#include +#include +#include + +using std::list; +using boost::shared_ptr; + +/** Find all the CPLs in our directories, cross-add assets and return the CPLs */ +list > +DCP::cpls () const +{ + list > dcps; + list > cpls; + + BOOST_FOREACH (boost::filesystem::path i, _dcp_content->directories()) { + shared_ptr dcp (new dcp::DCP (i)); + dcp->read (false, 0, true); + if (_dcp_content->kdm ()) { + dcp->add (dcp::DecryptedKDM (_dcp_content->kdm().get(), Config::instance()->decryption_chain()->key().get ())); + } + dcps.push_back (dcp); + BOOST_FOREACH (shared_ptr i, dcp->cpls()) { + cpls.push_back (i); + } + } + + BOOST_FOREACH (shared_ptr i, dcps) { + BOOST_FOREACH (shared_ptr j, dcps) { + if (i != j) { + i->resolve_refs (j->assets ()); + } + } + } + + return cpls; +} diff --git a/src/lib/dcp.h b/src/lib/dcp.h new file mode 100644 index 000000000..d83f95f37 --- /dev/null +++ b/src/lib/dcp.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2014-2016 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + +#ifndef DCPOMATIC_DCP_H +#define DCPOMATIC_DCP_H + +#include +#include +#include + +class DCPContent; + +class DCP +{ +protected: + DCP (boost::shared_ptr content) + : _dcp_content (content) + {} + + std::list > cpls () const; + boost::shared_ptr _dcp_content; +}; + +#endif diff --git a/src/lib/dcp_content.cc b/src/lib/dcp_content.cc index a14dbaf5f..03e6f1aaa 100644 --- a/src/lib/dcp_content.cc +++ b/src/lib/dcp_content.cc @@ -61,6 +61,7 @@ int const DCPContentProperty::REFERENCE_SUBTITLE = 603; DCPContent::DCPContent (shared_ptr film, boost::filesystem::path p) : Content (film) , _encrypted (false) + , _needs_assets (false) , _kdm_valid (false) , _reference_video (false) , _reference_audio (false) @@ -95,8 +96,8 @@ DCPContent::DCPContent (shared_ptr film, cxml::ConstNodePtr node, in ); _name = node->string_child ("Name"); - _encrypted = node->bool_child ("Encrypted"); + _needs_assets = node->bool_child ("NeedsAssets"); if (node->optional_node_child ("KDM")) { _kdm = dcp::EncryptedKDM (node->string_child ("KDM")); } @@ -115,6 +116,7 @@ DCPContent::DCPContent (shared_ptr film, cxml::ConstNodePtr node, in } } _three_d = node->optional_bool_child("ThreeD").get_value_or (false); + _cpl = node->optional_string_child("CPL"); } void @@ -160,9 +162,11 @@ DCPContent::examine (shared_ptr job) subtitle.reset (new SubtitleContent (this)); } _encrypted = examiner->encrypted (); + _needs_assets = examiner->needs_assets (); _kdm_valid = examiner->kdm_valid (); _standard = examiner->standard (); _three_d = examiner->three_d (); + _cpl = examiner->cpl (); } if (could_be_played != can_be_played ()) { @@ -212,6 +216,7 @@ DCPContent::as_xml (xmlpp::Node* node) const boost::mutex::scoped_lock lm (_mutex); node->add_child("Name")->add_child_text (_name); node->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0"); + node->add_child("NeedsAssets")->add_child_text (_needs_assets ? "1" : "0"); if (_kdm) { node->add_child("KDM")->add_child_text (_kdm->as_xml ()); } @@ -232,6 +237,9 @@ DCPContent::as_xml (xmlpp::Node* node) const } } node->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0"); + if (_cpl) { + node->add_child("CPL")->add_child_text (_cpl.get ()); + } } DCPTime @@ -259,27 +267,36 @@ DCPContent::add_kdm (dcp::EncryptedKDM k) _kdm = k; } +void +DCPContent::add_ov (boost::filesystem::path ov) +{ + read_directory (ov); +} + bool DCPContent::can_be_played () const +{ + return !needs_kdm() && !needs_assets(); +} + +bool +DCPContent::needs_kdm () const { boost::mutex::scoped_lock lm (_mutex); - return !_encrypted || _kdm_valid; + return _encrypted && !_kdm_valid; } -boost::filesystem::path -DCPContent::directory () const +bool +DCPContent::needs_assets () const { - optional smallest; - boost::filesystem::path dir; - for (size_t i = 0; i < number_of_paths(); ++i) { - boost::filesystem::path const p = path (i).parent_path (); - size_t const d = distance (p.begin(), p.end()); - if (!smallest || d < smallest.get ()) { - dir = p; - } - } + boost::mutex::scoped_lock lm (_mutex); + return _needs_assets; +} - return dir; +vector +DCPContent::directories () const +{ + return dcp::DCP::directories_from_files (paths ()); } void diff --git a/src/lib/dcp_content.h b/src/lib/dcp_content.h index cb9279706..f3a8236a2 100644 --- a/src/lib/dcp_content.h +++ b/src/lib/dcp_content.h @@ -68,7 +68,7 @@ public: void set_default_colour_conversion (); std::list reel_split_points () const; - boost::filesystem::path directory () const; + std::vector directories () const; bool encrypted () const { boost::mutex::scoped_lock lm (_mutex); @@ -76,12 +76,15 @@ public: } void add_kdm (dcp::EncryptedKDM); + void add_ov (boost::filesystem::path ov); boost::optional kdm () const { return _kdm; } bool can_be_played () const; + bool needs_kdm () const; + bool needs_assets () const; void set_reference_video (bool r); @@ -110,6 +113,11 @@ public: bool can_reference_subtitle (std::list &) const; + boost::optional cpl () const { + boost::mutex::scoped_lock lm (_mutex); + return _cpl; + } + private: void add_properties (std::list& p) const; @@ -124,6 +132,8 @@ private: std::string _name; /** true if our DCP is encrypted */ bool _encrypted; + /** true if this DCP needs more assets before it can be played */ + bool _needs_assets; boost::optional _kdm; /** true if _kdm successfully decrypts the first frame of our DCP */ bool _kdm_valid; @@ -142,6 +152,10 @@ private: boost::optional _standard; bool _three_d; + /** ID of the CPL to use; older metadata might not specify this: in that case + * just use the only CPL. + */ + boost::optional _cpl; }; #endif diff --git a/src/lib/dcp_decoder.cc b/src/lib/dcp_decoder.cc index a4207f144..2e3ed374a 100644 --- a/src/lib/dcp_decoder.cc +++ b/src/lib/dcp_decoder.cc @@ -51,7 +51,7 @@ using boost::shared_ptr; using boost::dynamic_pointer_cast; DCPDecoder::DCPDecoder (shared_ptr c, shared_ptr log) - : _dcp_content (c) + : DCP (c) , _decode_referenced (false) { video.reset (new VideoDecoder (this, c, log)); @@ -66,13 +66,15 @@ DCPDecoder::DCPDecoder (shared_ptr c, shared_ptr log) ) ); - dcp::DCP dcp (c->directory ()); - dcp.read (false, 0, true); - if (c->kdm ()) { - dcp.add (dcp::DecryptedKDM (c->kdm().get (), Config::instance()->decryption_chain()->key().get ())); + shared_ptr cpl; + BOOST_FOREACH (shared_ptr i, cpls ()) { + if (_dcp_content->cpl() && i->id() == _dcp_content->cpl().get()) { + cpl = i; + } } - DCPOMATIC_ASSERT (dcp.cpls().size() == 1); - _reels = dcp.cpls().front()->reels (); + + DCPOMATIC_ASSERT (cpl); + _reels = cpl->reels (); _reel = _reels.begin (); _offset = 0; @@ -178,7 +180,7 @@ DCPDecoder::next_reel () void DCPDecoder::get_readers () { - if (_reel == _reels.end()) { + if (_reel == _reels.end() || !_dcp_content->can_be_played ()) { _mono_reader.reset (); _stereo_reader.reset (); _sound_reader.reset (); diff --git a/src/lib/dcp_decoder.h b/src/lib/dcp_decoder.h index 12e37104e..6e1f95356 100644 --- a/src/lib/dcp_decoder.h +++ b/src/lib/dcp_decoder.h @@ -23,6 +23,7 @@ */ #include "decoder.h" +#include "dcp.h" namespace dcp { class Reel; @@ -35,7 +36,7 @@ class DCPContent; class Log; struct dcp_subtitle_within_dcp_test; -class DCPDecoder : public Decoder +class DCPDecoder : public DCP, public Decoder { public: DCPDecoder (boost::shared_ptr, boost::shared_ptr log); @@ -57,7 +58,6 @@ private: std::list image_subtitles_during (ContentTimePeriod, bool starting) const; std::list text_subtitles_during (ContentTimePeriod, bool starting) const; - boost::shared_ptr _dcp_content; /** Time of next thing to return from pass relative to the start of _reel */ ContentTime _next; std::list > _reels; diff --git a/src/lib/dcp_examiner.cc b/src/lib/dcp_examiner.cc index 06bef4c3a..b2034890b 100644 --- a/src/lib/dcp_examiner.cc +++ b/src/lib/dcp_examiner.cc @@ -35,7 +35,9 @@ #include #include #include +#include #include +#include #include #include "i18n.h" @@ -47,33 +49,67 @@ using boost::shared_ptr; using boost::dynamic_pointer_cast; DCPExaminer::DCPExaminer (shared_ptr content) - : _video_length (0) + : DCP (content) + , _video_length (0) , _audio_length (0) , _has_subtitles (false) , _encrypted (false) + , _needs_assets (false) , _kdm_valid (false) , _three_d (false) { - dcp::DCP dcp (content->directory ()); - dcp.read (false, 0, true); + shared_ptr cpl; - if (content->kdm ()) { - dcp.add (dcp::DecryptedKDM (content->kdm().get(), Config::instance()->decryption_chain()->key().get ())); + if (content->cpl ()) { + /* Use the CPL that the content was using before */ + BOOST_FOREACH (shared_ptr i, cpls()) { + if (i->id() == content->cpl().get()) { + cpl = i; + } + } + } else { + /* Choose the CPL with the fewest unsatisfied references */ + + int least_unsatisfied = INT_MAX; + + BOOST_FOREACH (shared_ptr i, cpls()) { + int unsatisfied = 0; + BOOST_FOREACH (shared_ptr j, i->reels()) { + if (j->main_picture() && !j->main_picture()->asset_ref().resolved()) { + ++unsatisfied; + } + if (j->main_sound() && !j->main_sound()->asset_ref().resolved()) { + ++unsatisfied; + } + if (j->main_subtitle() && !j->main_subtitle()->asset_ref().resolved()) { + ++unsatisfied; + } + } + + if (unsatisfied < least_unsatisfied) { + least_unsatisfied = unsatisfied; + cpl = i; + } + } } - if (dcp.cpls().size() == 0) { + if (!cpl) { throw DCPError ("No CPLs found in DCP"); - } else if (dcp.cpls().size() > 1) { - throw DCPError ("Multiple CPLs found in DCP"); } - _name = dcp.cpls().front()->content_title_text (); + _cpl = cpl->id (); + _name = cpl->content_title_text (); - list > reels = dcp.cpls().front()->reels (); - for (list >::const_iterator i = reels.begin(); i != reels.end(); ++i) { + BOOST_FOREACH (shared_ptr i, cpl->reels()) { - if ((*i)->main_picture ()) { - dcp::Fraction const frac = (*i)->main_picture()->edit_rate (); + if (i->main_picture ()) { + if (!i->main_picture()->asset_ref().resolved()) { + /* We are missing this asset so we can't continue; examination will be repeated later */ + _needs_assets = true; + return; + } + + dcp::Fraction const frac = i->main_picture()->edit_rate (); float const fr = float(frac.numerator) / frac.denominator; if (!_video_frame_rate) { _video_frame_rate = fr; @@ -81,18 +117,24 @@ DCPExaminer::DCPExaminer (shared_ptr content) throw DCPError (_("Mismatched frame rates in DCP")); } - shared_ptr asset = (*i)->main_picture()->asset (); + shared_ptr asset = i->main_picture()->asset (); if (!_video_size) { _video_size = asset->size (); } else if (_video_size.get() != asset->size ()) { throw DCPError (_("Mismatched video sizes in DCP")); } - _video_length += (*i)->main_picture()->duration(); + _video_length += i->main_picture()->duration(); } - if ((*i)->main_sound ()) { - shared_ptr asset = (*i)->main_sound()->asset (); + if (i->main_sound ()) { + if (!i->main_sound()->asset_ref().resolved()) { + /* We are missing this asset so we can't continue; examination will be repeated later */ + _needs_assets = true; + return; + } + + shared_ptr asset = i->main_sound()->asset (); if (!_audio_channels) { _audio_channels = asset->channels (); @@ -106,21 +148,27 @@ DCPExaminer::DCPExaminer (shared_ptr content) throw DCPError (_("Mismatched audio sample rates in DCP")); } - _audio_length += (*i)->main_sound()->duration(); + _audio_length += i->main_sound()->duration(); } - if ((*i)->main_subtitle ()) { + if (i->main_subtitle ()) { + if (!i->main_subtitle()->asset_ref().resolved()) { + /* We are missing this asset so we can't continue; examination will be repeated later */ + _needs_assets = true; + return; + } + _has_subtitles = true; } } - _encrypted = dcp.encrypted (); + _encrypted = cpl->encrypted (); _kdm_valid = true; /* Check that we can read the first picture frame */ try { - if (!dcp.cpls().empty () && !dcp.cpls().front()->reels().empty ()) { - shared_ptr asset = dcp.cpls().front()->reels().front()->main_picture()->asset (); + if (!cpl->reels().empty ()) { + shared_ptr asset = cpl->reels().front()->main_picture()->asset (); shared_ptr mono = dynamic_pointer_cast (asset); shared_ptr stereo = dynamic_pointer_cast (asset); @@ -139,7 +187,10 @@ DCPExaminer::DCPExaminer (shared_ptr content) } } - _standard = dcp.standard (); - _three_d = !reels.empty() && reels.front()->main_picture() && - dynamic_pointer_cast (reels.front()->main_picture()->asset()); + DCPOMATIC_ASSERT (cpl->standard ()); + _standard = cpl->standard().get(); + _three_d = !cpl->reels().empty() && cpl->reels().front()->main_picture() && + dynamic_pointer_cast (cpl->reels().front()->main_picture()->asset()); + + _cpl = cpl->id (); } diff --git a/src/lib/dcp_examiner.h b/src/lib/dcp_examiner.h index 6fc041cc6..0558bfcfc 100644 --- a/src/lib/dcp_examiner.h +++ b/src/lib/dcp_examiner.h @@ -24,10 +24,11 @@ #include "video_examiner.h" #include "audio_examiner.h" +#include "dcp.h" class DCPContent; -class DCPExaminer : public VideoExaminer, public AudioExaminer +class DCPExaminer : public DCP, public VideoExaminer, public AudioExaminer { public: DCPExaminer (boost::shared_ptr); @@ -60,6 +61,10 @@ public: return _encrypted; } + bool needs_assets () const { + return _needs_assets; + } + int audio_channels () const { return _audio_channels.get_value_or (0); } @@ -84,6 +89,10 @@ public: return _three_d; } + std::string cpl () const { + return _cpl; + } + private: boost::optional _video_frame_rate; boost::optional _video_size; @@ -94,7 +103,9 @@ private: std::string _name; bool _has_subtitles; bool _encrypted; + bool _needs_assets; bool _kdm_valid; boost::optional _standard; bool _three_d; + std::string _cpl; }; diff --git a/src/lib/wscript b/src/lib/wscript index 911ee0af4..b43443bd1 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -45,6 +45,7 @@ sources = """ content_factory.cc cross.cc curl_uploader.cc + dcp.cc dcp_content.cc dcp_content_type.cc dcp_decoder.cc diff --git a/src/wx/audio_mapping_view.cc b/src/wx/audio_mapping_view.cc index f0ec44e6b..cb3cc786f 100644 --- a/src/wx/audio_mapping_view.cc +++ b/src/wx/audio_mapping_view.cc @@ -487,6 +487,10 @@ AudioMappingView::paint_top_labels () void AudioMappingView::set_input_groups (vector const & groups) { + if (_grid->GetNumberRows() == 0) { + return; + } + _input_groups = groups; _input_group_positions.clear (); diff --git a/src/wx/content_menu.cc b/src/wx/content_menu.cc index 70fc766ac..3b1fd3189 100644 --- a/src/wx/content_menu.cc +++ b/src/wx/content_menu.cc @@ -53,6 +53,7 @@ enum { ID_properties, ID_re_examine, ID_kdm, + ID_ov, ID_remove }; @@ -65,7 +66,9 @@ ContentMenu::ContentMenu (wxWindow* p) _find_missing = _menu->Append (ID_find_missing, _("Find missing...")); _properties = _menu->Append (ID_properties, _("Properties...")); _re_examine = _menu->Append (ID_re_examine, _("Re-examine...")); + _menu->AppendSeparator (); _kdm = _menu->Append (ID_kdm, _("Add KDM...")); + _ov = _menu->Append (ID_ov, _("Add OV...")); _menu->AppendSeparator (); _remove = _menu->Append (ID_remove, _("Remove")); @@ -75,6 +78,7 @@ ContentMenu::ContentMenu (wxWindow* p) _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::properties, this), ID_properties); _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::re_examine, this), ID_re_examine); _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::kdm, this), ID_kdm); + _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::ov, this), ID_ov); _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::remove, this), ID_remove); } @@ -337,6 +341,25 @@ ContentMenu::kdm () d->Destroy (); } +void +ContentMenu::ov () +{ + DCPOMATIC_ASSERT (!_content.empty ()); + shared_ptr dcp = dynamic_pointer_cast (_content.front ()); + DCPOMATIC_ASSERT (dcp); + + wxDirDialog* d = new wxDirDialog (_parent, _("Select OV")); + + if (d->ShowModal() == wxID_OK) { + dcp->add_ov (wx_to_std (d->GetPath ())); + shared_ptr film = _film.lock (); + DCPOMATIC_ASSERT (film); + film->examine_content (dcp); + } + + d->Destroy (); +} + void ContentMenu::properties () { diff --git a/src/wx/content_menu.h b/src/wx/content_menu.h index b62f06662..9aaf9d59f 100644 --- a/src/wx/content_menu.h +++ b/src/wx/content_menu.h @@ -45,6 +45,7 @@ private: void properties (); void re_examine (); void kdm (); + void ov (); void remove (); void maybe_found_missing (boost::weak_ptr, boost::weak_ptr, boost::weak_ptr); @@ -60,6 +61,7 @@ private: wxMenuItem* _properties; wxMenuItem* _re_examine; wxMenuItem* _kdm; + wxMenuItem* _ov; wxMenuItem* _remove; boost::signals2::scoped_connection _job_connection; diff --git a/src/wx/content_panel.cc b/src/wx/content_panel.cc index 73866fa59..1d9f010bf 100644 --- a/src/wx/content_panel.cc +++ b/src/wx/content_panel.cc @@ -498,7 +498,8 @@ ContentPanel::setup () int const t = _content->GetItemCount (); bool const valid = i->paths_valid (); shared_ptr dcp = dynamic_pointer_cast (i); - bool const needs_kdm = dcp && !dcp->can_be_played (); + bool const needs_kdm = dcp && dcp->needs_kdm (); + bool const needs_assets = dcp && dcp->needs_assets (); string s = i->summary (); @@ -510,6 +511,10 @@ ContentPanel::setup () s = _("NEEDS KDM: ") + s; } + if (needs_assets) { + s = _("NEEDS OV: ") + s; + } + wxListItem item; item.SetId (t); item.SetText (std_to_wx (s)); @@ -520,7 +525,7 @@ ContentPanel::setup () _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } - if (!valid || needs_kdm) { + if (!valid || needs_kdm || needs_assets) { _content->SetItemTextColour (t, *wxRED); } }