+2016-08-24 c.hetherington <cth@carlh.net>
+
+ * Allow import of OV/VF DCPs (#906).
+
2016-08-24 Carl Hetherington <cth@carlh.net>
* Keep timeline above main window.
--- /dev/null
+/*
+ Copyright (C) 2014-2016 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "dcp.h"
+#include "config.h"
+#include "dcp_content.h"
+#include <dcp/dcp.h>
+#include <dcp/decrypted_kdm.h>
+#include <boost/foreach.hpp>
+
+using std::list;
+using boost::shared_ptr;
+
+/** Find all the CPLs in our directories, cross-add assets and return the CPLs */
+list<shared_ptr<dcp::CPL> >
+DCP::cpls () const
+{
+ list<shared_ptr<dcp::DCP> > dcps;
+ list<shared_ptr<dcp::CPL> > cpls;
+
+ BOOST_FOREACH (boost::filesystem::path i, _dcp_content->directories()) {
+ shared_ptr<dcp::DCP> 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<dcp::CPL> i, dcp->cpls()) {
+ cpls.push_back (i);
+ }
+ }
+
+ BOOST_FOREACH (shared_ptr<dcp::DCP> i, dcps) {
+ BOOST_FOREACH (shared_ptr<dcp::DCP> j, dcps) {
+ if (i != j) {
+ i->resolve_refs (j->assets ());
+ }
+ }
+ }
+
+ return cpls;
+}
--- /dev/null
+/*
+ Copyright (C) 2014-2016 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef DCPOMATIC_DCP_H
+#define DCPOMATIC_DCP_H
+
+#include <dcp/cpl.h>
+#include <boost/shared_ptr.hpp>
+#include <list>
+
+class DCPContent;
+
+class DCP
+{
+protected:
+ DCP (boost::shared_ptr<const DCPContent> content)
+ : _dcp_content (content)
+ {}
+
+ std::list<boost::shared_ptr<dcp::CPL> > cpls () const;
+ boost::shared_ptr<const DCPContent> _dcp_content;
+};
+
+#endif
DCPContent::DCPContent (shared_ptr<const Film> film, boost::filesystem::path p)
: Content (film)
, _encrypted (false)
+ , _needs_assets (false)
, _kdm_valid (false)
, _reference_video (false)
, _reference_audio (false)
);
_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"));
}
}
}
_three_d = node->optional_bool_child("ThreeD").get_value_or (false);
+ _cpl = node->optional_string_child("CPL");
}
void
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 ()) {
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 ());
}
}
}
node->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0");
+ if (_cpl) {
+ node->add_child("CPL")->add_child_text (_cpl.get ());
+ }
}
DCPTime
_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<size_t> 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<boost::filesystem::path>
+DCPContent::directories () const
+{
+ return dcp::DCP::directories_from_files (paths ());
}
void
void set_default_colour_conversion ();
std::list<DCPTime> reel_split_points () const;
- boost::filesystem::path directory () const;
+ std::vector<boost::filesystem::path> directories () const;
bool encrypted () const {
boost::mutex::scoped_lock lm (_mutex);
}
void add_kdm (dcp::EncryptedKDM);
+ void add_ov (boost::filesystem::path ov);
boost::optional<dcp::EncryptedKDM> kdm () const {
return _kdm;
}
bool can_be_played () const;
+ bool needs_kdm () const;
+ bool needs_assets () const;
void set_reference_video (bool r);
bool can_reference_subtitle (std::list<std::string> &) const;
+ boost::optional<std::string> cpl () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _cpl;
+ }
+
private:
void add_properties (std::list<UserProperty>& p) const;
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<dcp::EncryptedKDM> _kdm;
/** true if _kdm successfully decrypts the first frame of our DCP */
bool _kdm_valid;
boost::optional<dcp::Standard> _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<std::string> _cpl;
};
#endif
using boost::dynamic_pointer_cast;
DCPDecoder::DCPDecoder (shared_ptr<const DCPContent> c, shared_ptr<Log> log)
- : _dcp_content (c)
+ : DCP (c)
, _decode_referenced (false)
{
video.reset (new VideoDecoder (this, c, 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<dcp::CPL> cpl;
+ BOOST_FOREACH (shared_ptr<dcp::CPL> 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;
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 ();
*/
#include "decoder.h"
+#include "dcp.h"
namespace dcp {
class Reel;
class Log;
struct dcp_subtitle_within_dcp_test;
-class DCPDecoder : public Decoder
+class DCPDecoder : public DCP, public Decoder
{
public:
DCPDecoder (boost::shared_ptr<const DCPContent>, boost::shared_ptr<Log> log);
std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod, bool starting) const;
std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod, bool starting) const;
- boost::shared_ptr<const DCPContent> _dcp_content;
/** Time of next thing to return from pass relative to the start of _reel */
ContentTime _next;
std::list<boost::shared_ptr<dcp::Reel> > _reels;
#include <dcp/stereo_picture_asset.h>
#include <dcp/stereo_picture_asset_reader.h>
#include <dcp/stereo_picture_frame.h>
+#include <dcp/reel_subtitle_asset.h>
#include <dcp/sound_asset.h>
+#include <boost/foreach.hpp>
#include <iostream>
#include "i18n.h"
using boost::dynamic_pointer_cast;
DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> 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<dcp::CPL> 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<dcp::CPL> 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<dcp::CPL> i, cpls()) {
+ int unsatisfied = 0;
+ BOOST_FOREACH (shared_ptr<dcp::Reel> 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<shared_ptr<dcp::Reel> > reels = dcp.cpls().front()->reels ();
- for (list<shared_ptr<dcp::Reel> >::const_iterator i = reels.begin(); i != reels.end(); ++i) {
+ BOOST_FOREACH (shared_ptr<dcp::Reel> 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;
throw DCPError (_("Mismatched frame rates in DCP"));
}
- shared_ptr<dcp::PictureAsset> asset = (*i)->main_picture()->asset ();
+ shared_ptr<dcp::PictureAsset> 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<dcp::SoundAsset> 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<dcp::SoundAsset> asset = i->main_sound()->asset ();
if (!_audio_channels) {
_audio_channels = asset->channels ();
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<dcp::PictureAsset> asset = dcp.cpls().front()->reels().front()->main_picture()->asset ();
+ if (!cpl->reels().empty ()) {
+ shared_ptr<dcp::PictureAsset> asset = cpl->reels().front()->main_picture()->asset ();
shared_ptr<dcp::MonoPictureAsset> mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (asset);
shared_ptr<dcp::StereoPictureAsset> stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (asset);
}
}
- _standard = dcp.standard ();
- _three_d = !reels.empty() && reels.front()->main_picture() &&
- dynamic_pointer_cast<dcp::StereoPictureAsset> (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<dcp::StereoPictureAsset> (cpl->reels().front()->main_picture()->asset());
+
+ _cpl = cpl->id ();
}
#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<const DCPContent>);
return _encrypted;
}
+ bool needs_assets () const {
+ return _needs_assets;
+ }
+
int audio_channels () const {
return _audio_channels.get_value_or (0);
}
return _three_d;
}
+ std::string cpl () const {
+ return _cpl;
+ }
+
private:
boost::optional<double> _video_frame_rate;
boost::optional<dcp::Size> _video_size;
std::string _name;
bool _has_subtitles;
bool _encrypted;
+ bool _needs_assets;
bool _kdm_valid;
boost::optional<dcp::Standard> _standard;
bool _three_d;
+ std::string _cpl;
};
content_factory.cc
cross.cc
curl_uploader.cc
+ dcp.cc
dcp_content.cc
dcp_content_type.cc
dcp_decoder.cc
void
AudioMappingView::set_input_groups (vector<Group> const & groups)
{
+ if (_grid->GetNumberRows() == 0) {
+ return;
+ }
+
_input_groups = groups;
_input_group_positions.clear ();
ID_properties,
ID_re_examine,
ID_kdm,
+ ID_ov,
ID_remove
};
_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"));
_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);
}
d->Destroy ();
}
+void
+ContentMenu::ov ()
+{
+ DCPOMATIC_ASSERT (!_content.empty ());
+ shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (_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 = _film.lock ();
+ DCPOMATIC_ASSERT (film);
+ film->examine_content (dcp);
+ }
+
+ d->Destroy ();
+}
+
void
ContentMenu::properties ()
{
void properties ();
void re_examine ();
void kdm ();
+ void ov ();
void remove ();
void maybe_found_missing (boost::weak_ptr<Job>, boost::weak_ptr<Content>, boost::weak_ptr<Content>);
wxMenuItem* _properties;
wxMenuItem* _re_examine;
wxMenuItem* _kdm;
+ wxMenuItem* _ov;
wxMenuItem* _remove;
boost::signals2::scoped_connection _job_connection;
int const t = _content->GetItemCount ();
bool const valid = i->paths_valid ();
shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (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 ();
s = _("NEEDS KDM: ") + s;
}
+ if (needs_assets) {
+ s = _("NEEDS OV: ") + s;
+ }
+
wxListItem item;
item.SetId (t);
item.SetText (std_to_wx (s));
_content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
}
- if (!valid || needs_kdm) {
+ if (!valid || needs_kdm || needs_assets) {
_content->SetItemTextColour (t, *wxRED);
}
}