Merge 1.0
authorCarl Hetherington <cth@carlh.net>
Tue, 31 Dec 2013 15:44:51 +0000 (15:44 +0000)
committerCarl Hetherington <cth@carlh.net>
Tue, 31 Dec 2013 15:44:51 +0000 (15:44 +0000)
16 files changed:
ChangeLog
run/dcpomatic_create [new file with mode: 0755]
run/dcpomatic_server [new file with mode: 0755]
run/dcpomatic_server_gui [deleted file]
src/lib/encoder.cc
src/lib/encoder.h
src/lib/exceptions.h
src/lib/player.cc
src/lib/server_finder.cc
src/lib/server_finder.h
src/lib/writer.cc
src/tools/dcpomatic.cc
src/tools/dcpomatic_create.cc [new file with mode: 0644]
src/tools/dcpomatic_server.cc
src/tools/wscript
src/wx/wx_util.cc

index a8b8630b7c17a505dc78a3316963c7fc7b63dbf2..5aa58f5ea77149870f510ff1290cb613cc96d618 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2013-12-30  Carl Hetherington  <cth@carlh.net>
+
+       * Put catches around a few threads which could have uncaught exceptions.
+
+       * Add nascent dcpomatic_create command-line program to create films.
+
 2013-12-29  Carl Hetherington  <cth@carlh.net>
 
        * Version 1.53 released.
diff --git a/run/dcpomatic_create b/run/dcpomatic_create
new file mode 100755 (executable)
index 0000000..bf3c3c4
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+export LD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:$LD_LIBRARY_PATH
+if [ "$1" == "--debug" ]; then
+    shift
+    gdb --args build/src/tools/dcpomatic_create $*
+elif [ "$1" == "--valgrind" ]; then
+    shift
+    valgrind --tool="memcheck" build/src/tools/dcpomatic_create $*
+elif [ "$1" == "--callgrind" ]; then
+    shift
+    valgrind --tool="callgrind" build/src/tools/dcpomatic_create $*
+elif [ "$1" == "--perf" ]; then
+    shift
+    perf record build/src/tools/dcpomatic_create $*
+else
+    build/src/tools/dcpomatic_create $*
+fi
diff --git a/run/dcpomatic_server b/run/dcpomatic_server
new file mode 100755 (executable)
index 0000000..5856221
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
+if [ "$1" == "--debug" ]; then
+    gdb --args build/src/tools/dcpomatic_server
+elif [ "$1" == "--valgrind" ]; then
+    valgrind --tool="memcheck" build/src/tools/dcpomatic_server
+else
+    build/src/tools/dcpomatic_server
+fi
diff --git a/run/dcpomatic_server_gui b/run/dcpomatic_server_gui
deleted file mode 100755 (executable)
index b7b122e..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
-if [ "$1" == "--debug" ]; then
-    gdb --args build/src/tools/dcpomatic_server_gui
-elif [ "$1" == "--valgrind" ]; then
-    valgrind --tool="memcheck" build/src/tools/dcpomatic_server_gui
-else
-    build/src/tools/dcpomatic_server_gui
-fi
index 475c230da5ac1f3ba4458afd6cb55d062810e365..ca9134c04d29e52400579330eb34d4fa75a5196e 100644 (file)
@@ -96,7 +96,6 @@ Encoder::process_begin ()
        ServerFinder::instance()->connect (boost::bind (&Encoder::server_found, this, _1));
 }
 
-
 void
 Encoder::process_end ()
 {
@@ -199,9 +198,12 @@ Encoder::process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversi
                return;
        }
 
-       if (_writer->thrown ()) {
-               _writer->rethrow ();
-       }
+       _writer->rethrow ();
+       /* Re-throw any exception raised by one of our threads.  If more
+          than one has thrown an exception, only one will be rethrown, I think;
+          but then, if that happens something has gone badly wrong.
+       */
+       rethrow ();
 
        if (_writer->can_fake_write (_video_frames_out)) {
                _writer->fake_write (_video_frames_out, eyes);
@@ -257,6 +259,7 @@ Encoder::terminate_threads ()
 
 void
 Encoder::encoder_thread (optional<ServerDescription> server)
+try
 {
        /* Number of seconds that we currently wait between attempts
           to connect to the server; not relevant for localhost
@@ -338,6 +341,10 @@ Encoder::encoder_thread (optional<ServerDescription> server)
                _condition.notify_all ();
        }
 }
+catch (...)
+{
+       store_current ();
+}
 
 void
 Encoder::server_found (ServerDescription s)
index d43e4e1d33c7d24833568813a840275894aeea29..079174f89c96be258109ea339d97076261b3bc7e 100644 (file)
@@ -56,7 +56,7 @@ class PlayerImage;
  *  is supplied as uncompressed PCM in blocks of various sizes.
  */
 
-class Encoder : public boost::noncopyable
+class Encoder : public boost::noncopyable, public ExceptionStore
 {
 public:
        Encoder (boost::shared_ptr<const Film> f, boost::weak_ptr<Job>);
index f4631c09b67310bcf75492daca3b2b58dfd5e835..c1240538f3de2e2a10b6dfb052e17d2bbbbffc9e 100644 (file)
@@ -230,17 +230,30 @@ public:
        PixelFormatError (std::string o, AVPixelFormat f);
 };
 
+/** A parent class for classes which have a need to catch and
+ *  re-throw exceptions.  This is intended for classes
+ *  which run their own thread; they should do something like
+ *
+ *  void my_thread ()
+ *  try {
+ *    // do things which might throw exceptions
+ *  } catch (...) {
+ *    store_current ();
+ *  }
+ *
+ *  and then in another thread call rethrow().  If any
+ *  exception was thrown by my_thread it will be stored by
+ *  store_current() and then rethrow() will re-throw it where
+ *  it can be handled.
+ */
 class ExceptionStore
 {
 public:
-       bool thrown () const {
-               boost::mutex::scoped_lock lm (_mutex);
-               return _exception;
-       }
-       
        void rethrow () {
                boost::mutex::scoped_lock lm (_mutex);
-               boost::rethrow_exception (_exception);
+               if (_exception) {
+                       boost::rethrow_exception (_exception);
+               }
        }
 
 protected:     
index 96d23a82b38c3244cec08e01f68016d43f52f60c..c9f9acd942179561184fb2c32bd70e2ee65a24b9 100644 (file)
@@ -343,16 +343,16 @@ void
 Player::flush ()
 {
        TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
-       if (tb.audio) {
+       if (_audio && tb.audio) {
                Audio (tb.audio, tb.time);
                _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
        }
 
-       while (_video_position < _audio_position) {
+       while (_video && _video_position < _audio_position) {
                emit_black ();
        }
 
-       while (_audio_position < _video_position) {
+       while (_audio && _audio_position < _video_position) {
                emit_silence (_video_position - _audio_position);
        }
        
index de90e0d5c1380ee85d6429bf8c591f94c9f95dc6..5b67d8048dc197a98d91213c4c0070de61f75d02 100644 (file)
@@ -47,6 +47,7 @@ ServerFinder::ServerFinder ()
 
 void
 ServerFinder::broadcast_thread ()
+try
 {
        boost::system::error_code error;
        boost::asio::io_service io_service;
@@ -88,9 +89,14 @@ ServerFinder::broadcast_thread ()
                dcpomatic_sleep (10);
        }
 }
+catch (...)
+{
+       store_current ();
+}
 
 void
 ServerFinder::listen_thread ()
+try
 {
        while (1) {
                shared_ptr<Socket> sock (new Socket (10));
@@ -117,6 +123,10 @@ ServerFinder::listen_thread ()
                }
        }
 }
+catch (...)
+{
+       store_current ();
+}
 
 bool
 ServerFinder::server_found (string ip) const
index 01e26f7dfee1aa3a7c432d8ba9c68c9d4e777057..202bee8f920d9610bd82c50b6951ea701199ed25 100644 (file)
@@ -20,7 +20,7 @@
 #include <boost/signals2.hpp>
 #include "server.h"
 
-class ServerFinder
+class ServerFinder : public ExceptionStore
 {
 public:
        void connect (boost::function<void (ServerDescription)>);
index 4129b7a824455ffc8138cf0a8a6e0ccc638920b9..414ea72ebb47425e55e9d4719e9819c453985b16 100644 (file)
@@ -335,9 +335,7 @@ Writer::finish ()
        lock.unlock ();
 
        _thread->join ();
-       if (thrown ()) {
-               rethrow ();
-       }
+       rethrow ();
        
        delete _thread;
        _thread = 0;
index 66f795ddf4a10de1d38edc6e771809e301650045..891c4623ca9b6e1ea1ac995d41108d119468e72f 100644 (file)
@@ -54,6 +54,7 @@
 #include "lib/cinema.h"
 #include "lib/kdm.h"
 #include "lib/send_kdm_email_job.h"
+#include "lib/server_finder.h"
 
 using std::cout;
 using std::string;
@@ -632,8 +633,12 @@ class App : public wxApp
                f->Show ();
 
                ui_signaller = new wxUISignaller (this);
-               this->Bind (wxEVT_IDLE, boost::bind (&App::idle, this));
+               Bind (wxEVT_IDLE, boost::bind (&App::idle, this));
 
+               Bind (wxEVT_TIMER, boost::bind (&App::check, this));
+               _timer.reset (new wxTimer (this));
+               _timer->Start (1000);
+               
                return true;
        }
        catch (exception& e)
@@ -670,6 +675,17 @@ class App : public wxApp
        {
                ui_signaller->ui_idle ();
        }
+
+       void check ()
+       {
+               try {
+                       ServerFinder::instance()->rethrow ();
+               } catch (exception& e) {
+                       error_dialog (0, std_to_wx (e.what ()));
+               }
+       }
+
+       shared_ptr<wxTimer> _timer;
 };
 
 IMPLEMENT_APP (App)
diff --git a/src/tools/dcpomatic_create.cc b/src/tools/dcpomatic_create.cc
new file mode 100644 (file)
index 0000000..8be468b
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program 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 this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <string>
+#include <iostream>
+#include <cstdlib>
+#include <getopt.h>
+#include <boost/filesystem.hpp>
+#include "lib/version.h"
+#include "lib/film.h"
+#include "lib/util.h"
+#include "lib/content_factory.h"
+#include "lib/job_manager.h"
+#include "lib/ui_signaller.h"
+#include "lib/job.h"
+
+using std::string;
+using std::cout;
+using std::cerr;
+using std::list;
+using boost::shared_ptr;
+
+static void
+help (string n)
+{
+       cerr << "Create a film directory (ready for making a DCP) from some content files.\n"
+            << "Syntax: " << n << " [OPTION] <CONTENT> [<CONTENT> ...]\n"
+            << "  -v, --version   show DCP-o-matic version\n"
+            << "  -h, --help      show this help\n"
+            << "  -n, --name      film name\n"
+            << "  -o, --output    output directory (required)\n";
+}
+
+int
+main (int argc, char* argv[])
+{
+       string name;
+       boost::filesystem::path output;
+       
+       int option_index = 0;
+       while (1) {
+               static struct option long_options[] = {
+                       { "version", no_argument, 0, 'v'},
+                       { "help", no_argument, 0, 'h'},
+                       { "name", required_argument, 0, 'n'},
+                       { "output", required_argument, 0, 'o'},
+                       { 0, 0, 0, 0}
+               };
+
+               int c = getopt_long (argc, argv, "vhn:o:", long_options, &option_index);
+               if (c == -1) {
+                       break;
+               }
+
+               switch (c) {
+               case 'v':
+                       cout << "dcpomatic version " << dcpomatic_version << " " << dcpomatic_git_commit << "\n";
+                       exit (EXIT_SUCCESS);
+               case 'h':
+                       help (argv[0]);
+                       exit (EXIT_SUCCESS);
+               case 'n':
+                       name = optarg;
+                       break;
+               case 'o':
+                       output = optarg;
+                       break;
+               }
+       }
+
+       if (optind > argc) {
+               help (argv[0]);
+               exit (EXIT_FAILURE);
+       }
+
+       if (output.empty ()) {
+               cerr << "Missing required option -o or --output.\n"
+                    << "Use " << argv[0] << " --help for help.\n";
+               exit (EXIT_FAILURE);
+       }
+
+       dcpomatic_setup ();
+       ui_signaller = new UISignaller ();
+
+       shared_ptr<Film> film (new Film (output));
+       if (!name.empty ()) {
+               film->set_name (name);
+       }
+
+       for (int i = optind; i < argc; ++i) {
+               film->examine_and_add_content (content_factory (film, argv[i]));
+       }
+
+       JobManager* jm = JobManager::instance ();
+       while (jm->work_to_do ()) {
+               ui_signaller->ui_idle ();
+       }
+
+       if (jm->errors ()) {
+               list<shared_ptr<Job> > jobs = jm->get ();
+               for (list<shared_ptr<Job> >::iterator i = jobs.begin(); i != jobs.end(); ++i) {
+                       if ((*i)->finished_in_error ()) {
+                               cerr << (*i)->error_summary () << "\n"
+                                    << (*i)->error_details () << "\n";
+                       }
+               }
+               exit (EXIT_FAILURE);
+       }
+
+       film->write_metadata ();
+       return 0;
+}
index 8c6a294619bed2230357fc4124bb6f317093025f..a82478dfdbd47fc3a9e27985bff669eddd8c678f 100644 (file)
@@ -27,6 +27,7 @@
 
 using std::cout;
 using std::string;
+using std::exception;
 using boost::shared_ptr;
 using boost::thread;
 using boost::bind;
@@ -133,7 +134,7 @@ private:
        }
 };
 
-class App : public wxApp
+class App : public wxApp, public ExceptionStore
 {
 public:
        App ()
@@ -154,6 +155,10 @@ private:
 
                _icon = new TaskBarIcon;
                _thread = new thread (bind (&App::main_thread, this));
+
+               Bind (wxEVT_TIMER, boost::bind (&App::check, this));
+               _timer.reset (new wxTimer (this));
+               _timer->Start (1000);
                
                return true;
        }
@@ -165,13 +170,29 @@ private:
        }
 
        void main_thread ()
-       {
+       try {
                Server server (memory_log, false);
                server.run (Config::instance()->num_local_encoding_threads ());
+       } catch (...) {
+               store_current ();
+       }
+
+       void check ()
+       {
+               try {
+                       rethrow ();
+               } catch (exception& e) {
+                       error_dialog (0, std_to_wx (e.what ()));
+                       wxTheApp->ExitMainLoop ();
+               } catch (...) {
+                       error_dialog (0, _("An unknown error has occurred with the DCP-o-matic server."));
+                       wxTheApp->ExitMainLoop ();
+               }
        }
 
        boost::thread* _thread;
        TaskBarIcon* _icon;
+       shared_ptr<wxTimer> _timer;
 };
 
 IMPLEMENT_APP (App)
index eafad44ec2cdb0fcb847965aba6b599bd7ff2a6a..0fd336676f217bde28aa410b7da95d7404e7d655 100644 (file)
@@ -9,7 +9,7 @@ def configure(conf):
         conf.env.append_value('LINKFLAGS', ['-mconsole'])
 
 def build(bld):
-    for t in ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test', 'dcpomatic_kdm']:
+    for t in ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test', 'dcpomatic_kdm', 'dcpomatic_create']:
         obj = bld(features = 'cxx cxxprogram')
         obj.uselib = 'BOOST_THREAD OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC WXWIDGETS QUICKMAIL'
         obj.includes = ['..']
index 103d36d006d1539043f8c357fae1ebdf98ceb0f7..367d1edbbf522d12634dbc2c967bea73732f83cd 100644 (file)
@@ -140,11 +140,18 @@ ThreadedStaticText::~ThreadedStaticText ()
 /** Run our thread and post the result to the GUI thread via AddPendingEvent */
 void
 ThreadedStaticText::run (function<string ()> fn)
+try
 {
        wxCommandEvent ev (wxEVT_COMMAND_TEXT_UPDATED, _update_event_id);
        ev.SetString (std_to_wx (fn ()));
        GetEventHandler()->AddPendingEvent (ev);
 }
+catch (...)
+{
+       /* Ignore exceptions; marginally better than the program quitting, but
+          only marginally.
+       */
+}
 
 /** Called in the GUI thread when our worker thread has finished */
 void