Allow import of OV/VF DCPs (#906).
authorCarl Hetherington <cth@carlh.net>
Mon, 22 Aug 2016 23:27:12 +0000 (00:27 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 24 Aug 2016 09:01:52 +0000 (10:01 +0100)
14 files changed:
ChangeLog
src/lib/dcp.cc [new file with mode: 0644]
src/lib/dcp.h [new file with mode: 0644]
src/lib/dcp_content.cc
src/lib/dcp_content.h
src/lib/dcp_decoder.cc
src/lib/dcp_decoder.h
src/lib/dcp_examiner.cc
src/lib/dcp_examiner.h
src/lib/wscript
src/wx/audio_mapping_view.cc
src/wx/content_menu.cc
src/wx/content_menu.h
src/wx/content_panel.cc

index 2dca7ef13205cab19fd06d6fc5f17d6bee3c0a77..dfa2cde5bd635df0ee8dc58435efedf3b099cd9a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+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.
diff --git a/src/lib/dcp.cc b/src/lib/dcp.cc
new file mode 100644 (file)
index 0000000..6d5c0b2
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+    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;
+}
diff --git a/src/lib/dcp.h b/src/lib/dcp.h
new file mode 100644 (file)
index 0000000..d83f95f
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+    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
index a14dbaf5f0dd2629dd0975ccead07a18c0b71965..03e6f1aaa5415e64f85cfdb5cbfc93eca72046bc 100644 (file)
@@ -61,6 +61,7 @@ int const DCPContentProperty::REFERENCE_SUBTITLE = 603;
 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)
@@ -95,8 +96,8 @@ DCPContent::DCPContent (shared_ptr<const Film> 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<const Film> 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> 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<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
index cb92797069b28a1aaedc18df702b14240eb481b5..f3a8236a2d4c0c77194640d4ec463c67a9bb2bc1 100644 (file)
@@ -68,7 +68,7 @@ public:
        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);
@@ -76,12 +76,15 @@ public:
        }
 
        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);
 
@@ -110,6 +113,11 @@ public:
 
        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;
 
@@ -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<dcp::EncryptedKDM> _kdm;
        /** true if _kdm successfully decrypts the first frame of our DCP */
        bool _kdm_valid;
@@ -142,6 +152,10 @@ private:
 
        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
index a4207f14441c513f3700bcc0b8f5a77df8140de8..2e3ed374aaa8c07ef9dffe276ca40ab297aa78cd 100644 (file)
@@ -51,7 +51,7 @@ using boost::shared_ptr;
 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));
@@ -66,13 +66,15 @@ DCPDecoder::DCPDecoder (shared_ptr<const DCPContent> c, shared_ptr<Log> 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;
@@ -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 ();
index 12e37104e599b76f7e272d212bef9fdfb1683b7d..6e1f95356a06f674a4562f3a385fd4d67f89b1f3 100644 (file)
@@ -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<const DCPContent>, boost::shared_ptr<Log> log);
@@ -57,7 +58,6 @@ private:
        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;
index 06bef4c3aa29ac2ed1680e2cd2fc041541883c02..b2034890b04eab8e58cc52824fd359143bce09e6 100644 (file)
@@ -35,7 +35,9 @@
 #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"
@@ -47,33 +49,67 @@ using boost::shared_ptr;
 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;
@@ -81,18 +117,24 @@ DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content)
                                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 ();
@@ -106,21 +148,27 @@ DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> 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<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);
 
@@ -139,7 +187,10 @@ DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content)
                }
        }
 
-       _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 ();
 }
index 6fc041cc6a9713bcf1bc0a3926a6dffda1e1c9e0..0558bfcfce1bc688853f6f13cb7c5ef63f3c3757 100644 (file)
 
 #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>);
@@ -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<double> _video_frame_rate;
        boost::optional<dcp::Size> _video_size;
@@ -94,7 +103,9 @@ private:
        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;
 };
index 911ee0af43ac8c12cfe51906fc17a2f4684db09e..b43443bd13d5f87d1ed66e1154619706160a8419 100644 (file)
@@ -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
index f0ec44e6b2761f8ab5665128c6d86c6b15e2d793..cb3cc786ff7d00d679ac0e834d60c41c72d198ee 100644 (file)
@@ -487,6 +487,10 @@ AudioMappingView::paint_top_labels ()
 void
 AudioMappingView::set_input_groups (vector<Group> const & groups)
 {
+       if (_grid->GetNumberRows() == 0) {
+               return;
+       }
+
        _input_groups = groups;
        _input_group_positions.clear ();
 
index 70fc766ac9c14135590b9b9b15225eda8031dfe1..3b1fd31897dc32054408c837bcd280f71bdfe699 100644 (file)
@@ -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<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 ()
 {
index b62f066624df46b17966dc93747eac06fd6401dd..9aaf9d59fc075beb7eb476ac2aa50cd19f393618 100644 (file)
@@ -45,6 +45,7 @@ private:
        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>);
 
@@ -60,6 +61,7 @@ private:
        wxMenuItem* _properties;
        wxMenuItem* _re_examine;
        wxMenuItem* _kdm;
+       wxMenuItem* _ov;
        wxMenuItem* _remove;
 
        boost::signals2::scoped_connection _job_connection;
index 73866fa59798a5ccf4e87b198d723e69465858b7..1d9f010bfda2d5808b55c60862cbf5a14fa8768b 100644 (file)
@@ -498,7 +498,8 @@ ContentPanel::setup ()
                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 ();
 
@@ -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);
                }
        }