Allow films to be loaded when content is missing.
authorCarl Hetherington <cth@carlh.net>
Tue, 22 Oct 2013 20:06:41 +0000 (21:06 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 22 Oct 2013 20:06:41 +0000 (21:06 +0100)
14 files changed:
ChangeLog
src/lib/content.cc
src/lib/content.h
src/lib/content_factory.cc
src/lib/content_factory.h
src/lib/film.cc
src/lib/film.h
src/lib/player.cc
src/lib/playlist.cc
src/lib/playlist.h
src/wx/content_menu.cc
src/wx/content_menu.h
src/wx/film_editor.cc
src/wx/film_viewer.cc

index 8a0a7d1de77d5fcb9baaada6dc5dd0fb6337eafe..d49a63f98fafac2e0bf0101bf0378bb6d53e59b2 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2013-10-22  Carl Hetherington  <cth@carlh.net>
+
+       * Allow films to be loaded when content is missing,
+       and then that content can be re-found.
+
 2013-10-21  Carl Hetherington  <cth@carlh.net>
 
        * Version 1.19 released.
index dbb84120034f441ed2028e2eb63a2836aed8ea48..e3ad42560edf82ef0e1870003f46eaa04c5cbfad 100644 (file)
@@ -31,10 +31,11 @@ using std::set;
 using boost::shared_ptr;
 using boost::lexical_cast;
 
-int const ContentProperty::POSITION = 400;
-int const ContentProperty::LENGTH = 401;
-int const ContentProperty::TRIM_START = 402;
-int const ContentProperty::TRIM_END = 403;
+int const ContentProperty::PATH = 400;
+int const ContentProperty::POSITION = 401;
+int const ContentProperty::LENGTH = 402;
+int const ContentProperty::TRIM_START = 403;
+int const ContentProperty::TRIM_END = 404;
 
 Content::Content (shared_ptr<const Film> f, Time p)
        : _film (f)
@@ -191,3 +192,18 @@ Content::identifier () const
 
        return s.str ();
 }
+
+bool
+Content::path_valid () const
+{
+       return boost::filesystem::exists (_path);
+}
+
+void
+Content::set_path (boost::filesystem::path path)
+{
+       _path = path;
+       signal_changed (ContentProperty::PATH);
+}
+
+       
index 9c7ad2fc2984df5ff021c2f623634ba96310f6a3..c066c61e036a047813ecde76a71c2a9d1c9552b6 100644 (file)
@@ -38,6 +38,7 @@ class Film;
 class ContentProperty
 {
 public:
+       static int const PATH;
        static int const POSITION;
        static int const LENGTH;
        static int const TRIM_START;
@@ -61,12 +62,16 @@ public:
        virtual std::string identifier () const;
 
        boost::shared_ptr<Content> clone () const;
+
+       void set_path (boost::filesystem::path);
        
        boost::filesystem::path path () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _path;
        }
 
+       bool path_valid () const;
+
        /** @return MD5 digest of the content's file(s) */
        std::string digest () const {
                boost::mutex::scoped_lock lm (_mutex);
index 6ed01f174acb6e0068bdf9ca88d368a2de06455c..d42491f7f58c16f40b4542a11699edbf041bc107 100644 (file)
@@ -22,6 +22,7 @@
 #include "still_image_content.h"
 #include "moving_image_content.h"
 #include "sndfile_content.h"
+#include "util.h"
 
 using std::string;
 using boost::shared_ptr;
@@ -45,3 +46,19 @@ content_factory (shared_ptr<const Film> film, shared_ptr<cxml::Node> node)
 
        return content;
 }
+
+shared_ptr<Content>
+content_factory (shared_ptr<const Film> film, boost::filesystem::path path)
+{
+       shared_ptr<Content> content;
+               
+       if (valid_image_file (path)) {
+               content.reset (new StillImageContent (film, path));
+       } else if (SndfileContent::valid_file (path)) {
+               content.reset (new SndfileContent (film, path));
+       } else {
+               content.reset (new FFmpegContent (film, path));
+       }
+
+       return content;
+}
index 27cd36024c9046c6297b7c48c2f5434e12cf5222..93fd98d8346b3902c63e867eff1dfe5b50de0d39 100644 (file)
@@ -20,3 +20,4 @@
 class Film;
 
 extern boost::shared_ptr<Content> content_factory (boost::shared_ptr<const Film>, boost::shared_ptr<cxml::Node>);
+extern boost::shared_ptr<Content> content_factory (boost::shared_ptr<const Film>, boost::filesystem::path);
index f869289d5700fbab7fca1de7e5785d58a3775d29..650163efebe174b1e810f960869bfdb27ad46d6c 100644 (file)
@@ -844,6 +844,12 @@ Film::best_video_frame_rate () const
        return _playlist->best_dcp_frame_rate ();
 }
 
+bool
+Film::content_paths_valid () const
+{
+       return _playlist->content_paths_valid ();
+}
+
 void
 Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
 {
index 8cfc490886629ea99c3cc5e0cddd07b7932ceb7a..6bd04572b4f771979dfee6cf568652a4d22da2fd 100644 (file)
@@ -111,10 +111,10 @@ public:
        /* Proxies for some Playlist methods */
 
        ContentList content () const;
-
        Time length () const;
        bool has_subtitles () const;
        OutputVideoFrame best_video_frame_rate () const;
+       bool content_paths_valid () const;
 
        libdcp::KDM
        make_kdm (
index f792655586362cac6b637e91358be4f2dd0ebfd6..310e91b6c1fd814b59a3acc0dcfc77cfa4c8d9e9 100644 (file)
@@ -479,6 +479,10 @@ Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
                property == VideoContentProperty::VIDEO_RATIO
                ) {
                
+               Changed (frequent);
+
+       } else if (property == ContentProperty::PATH) {
+
                Changed (frequent);
        }
 }
index 1712dc8ff54dee392f5e865830aa91cea76c2cf1..621b99dd742ed6aa914beabb8f60d6042c622ebf 100644 (file)
@@ -390,3 +390,15 @@ Playlist::move_later (shared_ptr<Content> c)
        
        Changed ();
 }
+
+bool
+Playlist::content_paths_valid () const
+{
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+               if (!(*i)->path_valid ()) {
+                       return false;
+               }
+       }
+
+       return true;
+}
index 05928ee57c13e48aa6755a6b04e06acbc8299708..a1ae9b151bfad4647fc6b9e4cd67158bbe0f8f53 100644 (file)
@@ -80,6 +80,8 @@ public:
 
        void repeat (ContentList, int);
 
+       bool content_paths_valid () const;
+
        mutable boost::signals2::signal<void ()> Changed;
        /** Third parameter is true if signals are currently being emitted frequently */
        mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> ContentChanged;
index 60a95a9f26ed8c5166849f114ee7cb3c87220ba1..6183e344470fc496871746c80a8aa83d84891808 100644 (file)
 */
 
 #include <wx/wx.h>
+#include <wx/dirdlg.h>
 #include "lib/playlist.h"
 #include "lib/film.h"
+#include "lib/moving_image_content.h"
+#include "lib/content_factory.h"
+#include "lib/examine_content_job.h"
+#include "lib/job_manager.h"
 #include "content_menu.h"
 #include "repeat_dialog.h"
+#include "wx_util.h"
 
 using std::cout;
 using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
 
 enum {
        ID_repeat = 1,
+       ID_find_missing,
        ID_remove
 };
 
@@ -36,12 +45,14 @@ ContentMenu::ContentMenu (shared_ptr<Film> f, wxWindow* p)
        , _film (f)
        , _parent (p)
 {
-       _menu->Append (ID_repeat, _("Repeat..."));
+       _repeat = _menu->Append (ID_repeat, _("Repeat..."));
+       _find_missing = _menu->Append (ID_find_missing, _("Find missing..."));
        _menu->AppendSeparator ();
-       _menu->Append (ID_remove, _("Remove"));
+       _remove = _menu->Append (ID_remove, _("Remove"));
 
-       _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, &ContentMenu::repeat, this, ID_repeat);
-       _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, &ContentMenu::remove, this, ID_remove);
+       _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::repeat, this), ID_repeat);
+       _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::find_missing, this), ID_find_missing);
+       _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::remove, this), ID_remove);
 }
 
 ContentMenu::~ContentMenu ()
@@ -53,32 +64,38 @@ void
 ContentMenu::popup (ContentList c, wxPoint p)
 {
        _content = c;
+       _repeat->Enable (!_content.empty ());
+       _find_missing->Enable (_content.size() == 1 && !_content.front()->path_valid ());
+       _remove->Enable (!_content.empty ());
        _parent->PopupMenu (_menu, p);
 }
 
 void
-ContentMenu::repeat (wxCommandEvent &)
+ContentMenu::repeat ()
 {
        if (_content.empty ()) {
                return;
        }
                
-       RepeatDialog d (_parent);
-       d.ShowModal ();
+       RepeatDialog* d = new RepeatDialog (_parent);
+       if (d->ShowModal() != wxID_OK) {
+               d->Destroy ();
+               return;
+       }
 
        shared_ptr<const Film> film = _film.lock ();
        if (!film) {
                return;
        }
 
-       film->playlist()->repeat (_content, d.number ());
-       d.Destroy ();
+       film->playlist()->repeat (_content, d->number ());
+       d->Destroy ();
 
        _content.clear ();
 }
 
 void
-ContentMenu::remove (wxCommandEvent &)
+ContentMenu::remove ()
 {
        if (_content.empty ()) {
                return;
@@ -94,3 +111,73 @@ ContentMenu::remove (wxCommandEvent &)
        _content.clear ();
 }
 
+void
+ContentMenu::find_missing ()
+{
+       if (_content.size() != 1) {
+               return;
+       }
+
+       shared_ptr<const Film> film = _film.lock ();
+       if (!film) {
+               return;
+       }
+       
+       shared_ptr<Content> content;
+
+       /* XXX: a bit nasty */
+       if (dynamic_pointer_cast<MovingImageContent> (_content.front ())) {
+               wxDirDialog* d = new wxDirDialog (0, _("Choose a folder"), wxT (""), wxDD_DIR_MUST_EXIST);
+               int const r = d->ShowModal ();
+               if (r == wxID_OK) {
+                       content.reset (new MovingImageContent (film, boost::filesystem::path (wx_to_std (d->GetPath ()))));
+               }
+               d->Destroy ();
+       } else {
+               wxFileDialog* d = new wxFileDialog (0, _("Choose a file"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE);
+               int const r = d->ShowModal ();
+               if (r == wxID_OK) {
+                       content = content_factory (film, wx_to_std (d->GetPath ()));
+               }
+               d->Destroy ();
+       }
+
+       if (!content) {
+               return;
+       }
+
+       shared_ptr<Job> j (new ExamineContentJob (film, content));
+       
+       j->Finished.connect (
+               bind (
+                       &ContentMenu::maybe_found_missing,
+                       this,
+                       boost::weak_ptr<Job> (j),
+                       boost::weak_ptr<Content> (_content.front ()),
+                       boost::weak_ptr<Content> (content)
+                       )
+               );
+       
+       JobManager::instance()->add (j);
+}
+
+void
+ContentMenu::maybe_found_missing (weak_ptr<Job> j, weak_ptr<Content> oc, weak_ptr<Content> nc)
+{
+       shared_ptr<Job> job = j.lock ();
+       if (!job || !job->finished_ok ()) {
+               return;
+       }
+
+       shared_ptr<Content> old_content = oc.lock ();
+       shared_ptr<Content> new_content = nc.lock ();
+       assert (old_content);
+       assert (new_content);
+
+       if (new_content->digest() != old_content->digest()) {
+               error_dialog (0, _("The content file(s) you specified are not the same as those that are missing.  Either try again with the correct content file or remove the missing content."));
+               return;
+       }
+
+       old_content->set_path (new_content->path ());
+}
index 127fbea1a2ee05b4e9508772d5425d48f1ab3ccf..2a672532001ca0ddaebcec1f41c04d4d03a599b8 100644 (file)
@@ -36,13 +36,18 @@ public:
        void popup (ContentList, wxPoint);
 
 private:
-       void repeat (wxCommandEvent &);
-       void remove (wxCommandEvent &);
+       void repeat ();
+       void find_missing ();
+       void remove ();
+       void maybe_found_missing (boost::weak_ptr<Job>, boost::weak_ptr<Content>, boost::weak_ptr<Content>);
        
        wxMenu* _menu;
        boost::weak_ptr<Film> _film;
        wxWindow* _parent;
        ContentList _content;
+       wxMenuItem* _repeat;
+       wxMenuItem* _find_missing;
+       wxMenuItem* _remove;
 };
 
 #endif
index f2514da510358bab694432a43fb5e35ba8128ac2..1472c8b89571b1c4f9bb35361919c9c3e2f6ed99 100644 (file)
@@ -44,6 +44,8 @@
 #include "lib/sound_processor.h"
 #include "lib/scaler.h"
 #include "lib/playlist.h"
+#include "lib/content.h"
+#include "lib/content_factory.h"
 #include "timecode.h"
 #include "wx_util.h"
 #include "film_editor.h"
@@ -490,6 +492,8 @@ FilmEditor::film_content_changed (weak_ptr<Content> weak_content, int property)
 
        if (property == FFmpegContentProperty::AUDIO_STREAM) {
                setup_dcp_name ();
+       } else if (property == ContentProperty::PATH) {
+               setup_content ();
        }
 }
 
@@ -705,10 +709,22 @@ FilmEditor::setup_content ()
        ContentList content = _film->content ();
        for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
                int const t = _content->GetItemCount ();
-               _content->InsertItem (t, std_to_wx ((*i)->summary ()));
+               bool const valid = (*i)->path_valid ();
+
+               string s = (*i)->summary ();
+               if (!valid) {
+                       s = _("MISSING: ") + s;
+               }
+                       
+               _content->InsertItem (t, std_to_wx (s));
+
                if ((*i)->summary() == selected_summary) {
                        _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
                }
+
+               if (!valid) {
+                       _content->SetItemTextColour (t, *wxRED);
+               }
        }
 
        if (selected_summary.empty () && !content.empty ()) {
@@ -734,19 +750,7 @@ FilmEditor::content_add_file_clicked ()
        /* XXX: check for lots of files here and do something */
 
        for (unsigned int i = 0; i < paths.GetCount(); ++i) {
-               boost::filesystem::path p (wx_to_std (paths[i]));
-
-               shared_ptr<Content> c;
-
-               if (valid_image_file (p)) {
-                       c.reset (new StillImageContent (_film, p));
-               } else if (SndfileContent::valid_file (p)) {
-                       c.reset (new SndfileContent (_film, p));
-               } else {
-                       c.reset (new FFmpegContent (_film, p));
-               }
-
-               _film->examine_and_add_content (c);
+               _film->examine_and_add_content (content_factory (_film, wx_to_std (paths[i])));
        }
 }
 
@@ -916,7 +920,9 @@ void
 FilmEditor::content_right_click (wxListEvent& ev)
 {
        ContentList cl;
-       cl.push_back (selected_content ());
+       if (selected_content ()) {
+               cl.push_back (selected_content ());
+       }
        _menu.popup (cl, ev.GetPoint ());
 }
 
index 945644fb1ede92c74131d721a283237d5897bba4..00aa6bef3848e156034300d1d092d17789c24d17 100644 (file)
@@ -345,6 +345,10 @@ FilmViewer::fetch_next_frame ()
                        _play_button->SetValue (false);
                        check_play_state ();
                        error_dialog (this, wxString::Format (_("Could not decode video for view (%s)"), std_to_wx(e.what()).data()));
+               } catch (OpenFileError& e) {
+                       /* There was a problem opening a content file; we'll let this slide as it
+                          probably means a missing content file, which we're already taking care of.
+                       */
                }
        }