Merge master; fix destruction of Server; some test cleanups.
authorCarl Hetherington <cth@carlh.net>
Thu, 19 Jun 2014 23:53:40 +0000 (00:53 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 19 Jun 2014 23:53:40 +0000 (00:53 +0100)
13 files changed:
1  2 
ChangeLog
src/lib/config.h
src/lib/encoder.h
src/lib/image_content.cc
src/lib/server.cc
src/lib/server.h
src/wx/about_dialog.cc
src/wx/audio_mapping_view.cc
src/wx/config_dialog.cc
test/client_server_test.cc
test/player_test.cc
test/recover_test.cc
test/test.cc

diff --combined ChangeLog
index 3194262ef06886fa797af33075cf0ad15cf0948f,22e314e4668fb1866dcf94ec00b90cc246a02db8..28cd9b332436f39c1d417a90c2b2a973fbd2fa1f
+++ b/ChangeLog
@@@ -1,7 -1,15 +1,19 @@@
 +2014-03-07  Carl Hetherington  <cth@carlh.net>
 +
 +      * Add subtitle view.
 +
+ 2014-06-18  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.29 released.
+ 2014-06-18  Carl Hetherington  <cth@carlh.net>
+       * Fix thinko causing incorrect audio sample rates in some cases.
+ 2014-06-15  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.28 released.
  2014-06-12  Carl Hetherington  <cth@carlh.net>
  
        * Version 1.69.27 released.
diff --combined src/lib/config.h
index f0d2630d0c893b22edf39c7a041d52e5d30ba2f4,671f53ef32dbcaf955f8109e6d46de6444ab8158..d82f520469c258f64b80b27d640a507f0503a962
  #include <boost/shared_ptr.hpp>
  #include <boost/signals2.hpp>
  #include <boost/filesystem.hpp>
 -#include <libdcp/metadata.h>
 +#include <dcp/metadata.h>
  #include "isdcf_metadata.h"
  #include "colour_conversion.h"
--#include "server.h"
  
  class ServerDescription;
  class Scaler;
@@@ -141,7 -141,7 +140,7 @@@ public
                return _default_dcp_content_type;
        }
  
 -      libdcp::XMLMetadata dcp_metadata () const {
 +      dcp::XMLMetadata dcp_metadata () const {
                return _dcp_metadata;
        }
  
                changed ();
        }
  
 -      void set_dcp_metadata (libdcp::XMLMetadata m) {
 +      void set_dcp_metadata (dcp::XMLMetadata m) {
                _dcp_metadata = m;
                changed ();
        }
@@@ -395,7 -395,7 +394,7 @@@ private
        int _default_still_length;
        Ratio const * _default_container;
        DCPContentType const * _default_dcp_content_type;
 -      libdcp::XMLMetadata _dcp_metadata;
 +      dcp::XMLMetadata _dcp_metadata;
        int _default_j2k_bandwidth;
        int _default_audio_delay;
        std::vector<PresetColourConversion> _colour_conversions;
diff --combined src/lib/encoder.h
index ac1d74c57b39af3aa449e43fd628651108701823,a8ee220aaac8cdea58ec3f0cd7c493dddeff93c0..678cdf04eae219bbcd8218765ff0d11facab3725
@@@ -38,6 -38,6 +38,7 @@@ extern "C" 
  #include "util.h"
  #include "config.h"
  #include "cross.h"
++#include "exceptions.h"
  
  class Image;
  class AudioBuffers;
@@@ -67,9 -67,10 +68,9 @@@ public
        void process_begin ();
  
        /** Call with a frame of video.
 -       *  @param pvf Video frame image.
 -       *  @param same true if pvf is the same as the last time we were called.
 +       *  @param f Video frame.
         */
 -      void process_video (boost::shared_ptr<PlayerVideoFrame> pvf, bool same);
 +      void process_video (boost::shared_ptr<PlayerVideoFrame> f);
  
        /** Call with some audio data */
        void process_audio (boost::shared_ptr<const AudioBuffers>);
@@@ -105,6 -106,7 +106,6 @@@ private
        /** Number of video frames written for the DCP so far */
        int _video_frames_out;
  
 -      bool _have_a_real_frame[EYES_COUNT];
        bool _terminate;
        std::list<boost::shared_ptr<DCPVideoFrame> > _queue;
        std::list<boost::thread *> _threads;
diff --combined src/lib/image_content.cc
index 8909240dc334e3d793e2ff64cc7d8b4922c8a1f1,6acf0bab924001eaa5b51e1d1349ce134e5e719c..acaedf0505d746b71661d89581192f7ef52d86ec
  #include <libcxml/cxml.h>
  #include "image_content.h"
  #include "image_examiner.h"
--#include "config.h"
  #include "compose.hpp"
  #include "film.h"
  #include "job.h"
  #include "frame_rate_change.h"
++#include "exceptions.h"
  
  #include "i18n.h"
  
@@@ -55,7 -55,7 +55,7 @@@ ImageContent::ImageContent (shared_ptr<
  }
  
  
 -ImageContent::ImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
 +ImageContent::ImageContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
        : Content (f, node)
        , VideoContent (f, node, version)
  {
@@@ -115,7 -115,7 +115,7 @@@ ImageContent::examine (shared_ptr<Job> 
  }
  
  void
 -ImageContent::set_video_length (VideoContent::Frame len)
 +ImageContent::set_video_length (ContentTime len)
  {
        {
                boost::mutex::scoped_lock lm (_mutex);
        signal_changed (ContentProperty::LENGTH);
  }
  
 -Time
 +DCPTime
  ImageContent::full_length () const
  {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
 -      
 -      FrameRateChange frc (video_frame_rate(), film->video_frame_rate ());
 -      return video_length_after_3d_combine() * frc.factor() * TIME_HZ / video_frame_rate();
 +      return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate(), film->video_frame_rate()));
  }
  
  string
@@@ -138,7 -140,7 +138,7 @@@ ImageContent::identifier () cons
  {
        stringstream s;
        s << VideoContent::identifier ();
 -      s << "_" << video_length();
 +      s << "_" << video_length().get();
        return s.str ();
  }
  
diff --combined src/lib/server.cc
index 59364fadd65aba787d1bfac5579c581eeba9a4c2,ed7fb6145c6a4b4498bbf6e3ff552a8fd3b8e01b..66ee2b0e3cf2ca93eeb7512a03763c2598836dcc
@@@ -29,7 -29,7 +29,7 @@@
  #include <boost/algorithm/string.hpp>
  #include <boost/scoped_array.hpp>
  #include <libcxml/cxml.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/raw_convert.h>
  #include "server.h"
  #include "util.h"
  #include "scaler.h"
@@@ -62,16 -62,16 +62,37 @@@ using boost::thread
  using boost::bind;
  using boost::scoped_array;
  using boost::optional;
 -using libdcp::Size;
 -using libdcp::raw_convert;
 +using dcp::Size;
 +using dcp::raw_convert;
  
  Server::Server (shared_ptr<Log> log, bool verbose)
--      : _log (log)
++      : _terminate (false)
++      , _log (log)
        , _verbose (verbose)
++      , _acceptor (_io_service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), Config::instance()->server_port_base()))
  {
  
  }
  
++Server::~Server ()
++{
++      {
++              boost::mutex::scoped_lock lm (_worker_mutex);
++              _terminate = true;
++              _worker_condition.notify_all ();
++      }
++
++      for (vector<boost::thread*>::iterator i = _worker_threads.begin(); i != _worker_threads.end(); ++i) {
++              (*i)->join ();
++              delete *i;
++      }
++
++      _io_service.stop ();
++
++      _broadcast.io_service.stop ();
++      _broadcast.thread->join ();
++}
++
  /** @param after_read Filled in with gettimeofday() after reading the input from the network.
   *  @param after_encode Filled in with gettimeofday() after encoding the image.
   */
@@@ -117,10 -117,10 +138,14 @@@ Server::worker_thread (
  {
        while (1) {
                boost::mutex::scoped_lock lock (_worker_mutex);
--              while (_queue.empty ()) {
++              while (_queue.empty () && !_terminate) {
                        _worker_condition.wait (lock);
                }
  
++              if (_terminate) {
++                      return;
++              }
++
                shared_ptr<Socket> socket = _queue.front ();
                _queue.pop_front ();
                
@@@ -187,39 -187,39 +212,18 @@@ Server::run (int num_threads
  
        _broadcast.thread = new thread (bind (&Server::broadcast_thread, this));
        
--      boost::asio::io_service io_service;
--
--      boost::asio::ip::tcp::acceptor acceptor (
--              io_service,
--              boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), Config::instance()->server_port_base ())
--              );
--      
--      while (1) {
--              shared_ptr<Socket> socket (new Socket);
--              acceptor.accept (socket->socket ());
--
--              boost::mutex::scoped_lock lock (_worker_mutex);
--              
--              /* Wait until the queue has gone down a bit */
--              while (int (_queue.size()) >= num_threads * 2) {
--                      _worker_condition.wait (lock);
--              }
--              
--              _queue.push_back (socket);
--              _worker_condition.notify_all ();
--      }
++      start_accept ();
++      _io_service.run ();
  }
  
  void
  Server::broadcast_thread ()
  try
  {
--      boost::asio::io_service io_service;
--
        boost::asio::ip::address address = boost::asio::ip::address_v4::any ();
        boost::asio::ip::udp::endpoint listen_endpoint (address, Config::instance()->server_port_base() + 1);
  
--      _broadcast.socket = new boost::asio::ip::udp::socket (io_service);
++      _broadcast.socket = new boost::asio::ip::udp::socket (_broadcast.io_service);
        _broadcast.socket->open (listen_endpoint.protocol ());
        _broadcast.socket->bind (listen_endpoint);
  
                boost::bind (&Server::broadcast_received, this)
                );
  
--      io_service.run ();
++      _broadcast.io_service.run ();
  }
  catch (...)
  {
@@@ -264,3 -264,3 +268,35 @@@ Server::broadcast_received (
                _broadcast.send_endpoint, boost::bind (&Server::broadcast_received, this)
                );
  }
++
++void
++Server::start_accept ()
++{
++      if (_terminate) {
++              return;
++      }
++
++      shared_ptr<Socket> socket (new Socket);
++      _acceptor.async_accept (socket->socket (), boost::bind (&Server::handle_accept, this, socket, boost::asio::placeholders::error));
++}
++
++void
++Server::handle_accept (shared_ptr<Socket> socket, boost::system::error_code const & error)
++{
++      if (error) {
++              return;
++      }
++
++      boost::mutex::scoped_lock lock (_worker_mutex);
++      
++      /* Wait until the queue has gone down a bit */
++      while (_queue.size() >= _worker_threads.size() * 2 && !_terminate) {
++              _worker_condition.wait (lock);
++      }
++      
++      _queue.push_back (socket);
++      _worker_condition.notify_all ();
++
++      start_accept ();
++}
++      
diff --combined src/lib/server.h
index a9b4b1c1c8982ed9d57c615c9cee6fcfd9df0324,a9b4b1c1c8982ed9d57c615c9cee6fcfd9df0324..9f3e99f9cbe577c20c7c07bedccbe0d7298f8c07
@@@ -90,6 -90,6 +90,7 @@@ class Server : public ExceptionStore, p
  {
  public:
        Server (boost::shared_ptr<Log> log, bool verbose);
++      ~Server ();
  
        void run (int num_threads);
  
@@@ -98,14 -98,14 +99,24 @@@ private
        int process (boost::shared_ptr<Socket> socket, struct timeval &, struct timeval &);
        void broadcast_thread ();
        void broadcast_received ();
++      void start_accept ();
++      void handle_accept (boost::shared_ptr<Socket>, boost::system::error_code const &);
++
++      bool _terminate;
  
        std::vector<boost::thread *> _worker_threads;
        std::list<boost::shared_ptr<Socket> > _queue;
        boost::mutex _worker_mutex;
        boost::condition _worker_condition;
++      
        boost::shared_ptr<Log> _log;
        bool _verbose;
  
++      boost::asio::io_service _io_service;
++      boost::asio::ip::tcp::acceptor _acceptor;
++
++      int _num_threads;
++
        struct Broadcast {
  
                Broadcast ()
                boost::asio::ip::udp::socket* socket;
                char buffer[64];
                boost::asio::ip::udp::endpoint send_endpoint;
++              boost::asio::io_service io_service;
                
        } _broadcast;
  };
diff --combined src/wx/about_dialog.cc
index bd4a39811e158e235d34a62a8f836efb28ed6ab9,63de451208f650813b0507d32d9b09289e68f62f..dd80e94cb68ff272f46177847fb18c37d8b5e246
@@@ -1,5 -1,5 +1,5 @@@
  /*
 -    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
 +    Copyright (C) 2013-2014 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
  
  */
  
 +/** @file  src/wx/about_dialog.cc
 + *  @brief The "about DCP-o-matic" dialogue box.
 + */
 +
  #include <wx/notebook.h>
  #include <wx/hyperlink.h>
  #include "lib/version.h"
@@@ -159,6 -155,7 +159,7 @@@ AboutDialog::AboutDialog (wxWindow* par
        tested_by.Add (wxT ("Mike Blakesley"));
        tested_by.Add (wxT ("David Booty"));
        tested_by.Add (wxT ("Roop Chand"));
+       tested_by.Add (wxT ("Daniel Chauvet"));
        tested_by.Add (wxT ("Adam Colt"));
        tested_by.Add (wxT ("John Convertino"));
        tested_by.Add (wxT ("Andreas Eli"));
        SetSizerAndFit (overall_sizer);
  }
  
 +/** Add a section of credits.
 + *  @param name Name of section.
 + *  @param credits List of names.
 + */
  void
  AboutDialog::add_section (wxString name, wxArrayString credits)
  {
index c65eadd5a0676c9122ee968b8f902fe774a399ff,6c1508aeebae25b197b4273ace9aec1b7c48395e..8e92400bdb956ffd7b58205dccf5626bf9015be7
@@@ -1,5 -1,5 +1,5 @@@
  /*
 -    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
 +    Copyright (C) 2013-2014 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
  
  */
  
 +/** @file  src/wx/audio_mapping_view.cc
 + *  @brief AudioMappingView class and helpers.
 + */
 +
  #include <wx/wx.h>
  #include <wx/renderer.h>
  #include <wx/grid.h>
 -#include <libdcp/types.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/types.h>
++#include <dcp/raw_convert.h>
  #include "lib/audio_mapping.h"
  #include "lib/util.h"
  #include "audio_mapping_view.h"
  #include "wx_util.h"
  #include "audio_gain_dialog.h"
 +#include <boost/lexical_cast.hpp>
  
  using std::cout;
  using std::list;
@@@ -57,9 -53,6 +58,9 @@@ public
        }
  };
  
 +/** @class ValueRenderer
 + *  @brief wxGridCellRenderer for a gain value.
 + */
  class ValueRenderer : public wxGridCellRenderer
  {
  public:
@@@ -162,7 -155,7 +163,7 @@@ AudioMappingView::left_click (wxGridEve
                return;
        }
  
 -      libdcp::Channel d = static_cast<libdcp::Channel> (ev.GetCol() - 1);
 +      dcp::Channel d = static_cast<dcp::Channel> (ev.GetCol() - 1);
        
        if (_map.get (ev.GetRow(), d) > 0) {
                _map.set (ev.GetRow(), d, 0);
@@@ -188,28 -181,28 +189,28 @@@ AudioMappingView::right_click (wxGridEv
  void
  AudioMappingView::off ()
  {
 -      _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 0);
 +      _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 0);
        map_changed ();
  }
  
  void
  AudioMappingView::full ()
  {
 -      _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 1);
 +      _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 1);
        map_changed ();
  }
  
  void
  AudioMappingView::minus6dB ()
  {
 -      _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), pow (10, -6.0 / 20));
 +      _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), pow (10, -6.0 / 20));
        map_changed ();
  }
  
  void
  AudioMappingView::edit ()
  {
 -      libdcp::Channel d = static_cast<libdcp::Channel> (_menu_column - 1);
 +      dcp::Channel d = static_cast<dcp::Channel> (_menu_column - 1);
        
        AudioGainDialog* dialog = new AudioGainDialog (this, _menu_row, _menu_column - 1, _map.get (_menu_row, d));
        if (dialog->ShowModal () == wxID_OK) {
@@@ -246,7 -239,7 +247,7 @@@ AudioMappingView::update_cells (
                _grid->SetCellValue (i, 0, wxString::Format (wxT("%d"), i + 1));
  
                for (int j = 1; j < _grid->GetNumberCols(); ++j) {
-                       _grid->SetCellValue (i, j, std_to_wx (lexical_cast<string> (_map.get (i, static_cast<dcp::Channel> (j - 1)))));
 -                      _grid->SetCellValue (i, j, std_to_wx (libdcp::raw_convert<string> (_map.get (i, static_cast<libdcp::Channel> (j - 1)))));
++                      _grid->SetCellValue (i, j, std_to_wx (dcp::raw_convert<string> (_map.get (i, static_cast<dcp::Channel> (j - 1)))));
                }
        }
  
@@@ -350,7 -343,7 +351,7 @@@ AudioMappingView::mouse_moved (wxMouseE
        if (row != _last_tooltip_row || column != _last_tooltip_column) {
  
                wxString s;
 -              float const gain = _map.get (row, static_cast<libdcp::Channel> (column - 1));
 +              float const gain = _map.get (row, static_cast<dcp::Channel> (column - 1));
                if (gain == 0) {
                        s = wxString::Format (_("No audio will be passed from content channel %d to DCP channel %d."), row + 1, column);
                } else if (gain == 1) {
diff --combined src/wx/config_dialog.cc
index 68fbc3f1b391b05a0bc1d97de2a42282254e817a,e65e931d023b2db5bda5381a096e03d269a1d3f0..b447e0eee9a57328f76541993b44c498ef785060
  #include <wx/preferences.h>
  #include <wx/filepicker.h>
  #include <wx/spinctrl.h>
 -#include <libdcp/colour_matrix.h>
 +#include <dcp/colour_matrix.h>
  #include "lib/config.h"
  #include "lib/ratio.h"
  #include "lib/scaler.h"
  #include "lib/filter.h"
  #include "lib/dcp_content_type.h"
  #include "lib/colour_conversion.h"
++#include "lib/log.h"
  #include "config_dialog.h"
  #include "wx_util.h"
  #include "editable_list.h"
@@@ -446,14 -446,14 +447,14 @@@ private
  
        void issuer_changed ()
        {
 -              libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
 +              dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
                m.issuer = wx_to_std (_issuer->GetValue ());
                Config::instance()->set_dcp_metadata (m);
        }
        
        void creator_changed ()
        {
 -              libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
 +              dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
                m.creator = wx_to_std (_creator->GetValue ());
                Config::instance()->set_dcp_metadata (m);
        }
index 1816de8e693df8a1a4fac0bf29e324a12efede53,07af1255c486ea42380f570ba6a643d566aedde0..51594a47acbd930347ded076aad887dadd9d8203
  
  */
  
 +/** @file  test/client_server_test.cc
 + *  @brief Test the server class.
 + *
 + *  Create a test image and then encode it using the standard mechanism
 + *  and also using a Server object running on localhost.  Compare the resulting
 + *  encoded data to check that they are the same.
 + */
 +
  #include <boost/test/unit_test.hpp>
  #include <boost/thread.hpp>
  #include "lib/server.h"
@@@ -47,12 -39,12 +47,12 @@@ do_remote_encode (shared_ptr<DCPVideoFr
        BOOST_CHECK (remotely_encoded);
        
        BOOST_CHECK_EQUAL (locally_encoded->size(), remotely_encoded->size());
 -      BOOST_CHECK (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()) == 0);
 +      BOOST_CHECK_EQUAL (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()), 0);
  }
  
  BOOST_AUTO_TEST_CASE (client_server_test_rgb)
  {
 -      shared_ptr<Image> image (new Image (PIX_FMT_RGB24, libdcp::Size (1998, 1080), true));
 +      shared_ptr<Image> image (new Image (PIX_FMT_RGB24, dcp::Size (1998, 1080), true));
        uint8_t* p = image->data()[0];
        
        for (int y = 0; y < 1080; ++y) {
@@@ -65,7 -57,7 +65,7 @@@
                p += image->stride()[0];
        }
  
 -      shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, libdcp::Size (100, 200), true));
 +      shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, dcp::Size (100, 200), true));
        p = sub_image->data()[0];
        for (int y = 0; y < 200; ++y) {
                uint8_t* q = p;
@@@ -84,8 -76,8 +84,8 @@@
                new PlayerVideoFrame (
                        shared_ptr<ImageProxy> (new RawImageProxy (image, log)),
                        Crop (),
 -                      libdcp::Size (1998, 1080),
 -                      libdcp::Size (1998, 1080),
 +                      dcp::Size (1998, 1080),
 +                      dcp::Size (1998, 1080),
                        Scaler::from_id ("bicubic"),
                        EYES_BOTH,
                        PART_WHOLE,
@@@ -93,7 -85,7 +93,7 @@@
                        )
                );
  
 -      pvf->set_subtitle (sub_image, Position<int> (50, 60));
 +      pvf->set_subtitle (PositionImage (sub_image, Position<int> (50, 60)));
  
        shared_ptr<DCPVideoFrame> frame (
                new DCPVideoFrame (
        for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
                delete *i;
        }
++
++      delete server;
  }
  
  BOOST_AUTO_TEST_CASE (client_server_test_yuv)
  {
 -      shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, libdcp::Size (1998, 1080), true));
 +      shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, dcp::Size (1998, 1080), true));
        uint8_t* p = image->data()[0];
  
        for (int i = 0; i < image->components(); ++i) {
                }
        }
  
 -      shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, libdcp::Size (100, 200), true));
 +      shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, dcp::Size (100, 200), true));
        p = sub_image->data()[0];
        for (int y = 0; y < 200; ++y) {
                uint8_t* q = p;
                new PlayerVideoFrame (
                        shared_ptr<ImageProxy> (new RawImageProxy (image, log)),
                        Crop (),
 -                      libdcp::Size (1998, 1080),
 -                      libdcp::Size (1998, 1080),
 +                      dcp::Size (1998, 1080),
 +                      dcp::Size (1998, 1080),
                        Scaler::from_id ("bicubic"),
                        EYES_BOTH,
                        PART_WHOLE,
                        )
                );
  
 -      pvf->set_subtitle (sub_image, Position<int> (50, 60));
 +      pvf->set_subtitle (PositionImage (sub_image, Position<int> (50, 60)));
  
        shared_ptr<DCPVideoFrame> frame (
                new DCPVideoFrame (
        for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
                delete *i;
        }
++
++      delete server;
  }
  
diff --combined test/player_test.cc
index 2412fc31299799411ed60608a33395886cad26f3,0000000000000000000000000000000000000000..d480602422b0ee1a810cfc74a1e38c0ad575eb1b
mode 100644,000000..100644
--- /dev/null
@@@ -1,104 -1,0 +1,104 @@@
-       BOOST_CHECK_EQUAL (A->full_length(), DCPTime::from_seconds (3));
 +/*
 +    Copyright (C) 2014 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.
 +
 +*/
 +
 +/** @file  test/player_test.cc
 + *  @brief Various tests of Player.
 + */
 +
 +#include <iostream>
 +#include <boost/test/unit_test.hpp>
 +#include "lib/film.h"
 +#include "lib/ffmpeg_content.h"
 +#include "lib/dcp_content_type.h"
 +#include "lib/ratio.h"
 +#include "lib/audio_buffers.h"
 +#include "lib/player.h"
 +#include "test.h"
 +
 +using std::cout;
 +using std::list;
 +using boost::shared_ptr;
 +
 +/** Player::overlaps */
 +BOOST_AUTO_TEST_CASE (player_overlaps_test)
 +{
 +      shared_ptr<Film> film = new_test_film ("player_overlaps_test");
 +      film->set_container (Ratio::from_id ("185"));
 +      shared_ptr<FFmpegContent> A (new FFmpegContent (film, "test/data/test.mp4"));
 +      shared_ptr<FFmpegContent> B (new FFmpegContent (film, "test/data/test.mp4"));
 +      shared_ptr<FFmpegContent> C (new FFmpegContent (film, "test/data/test.mp4"));
 +
 +      film->examine_and_add_content (A);
 +      film->examine_and_add_content (B);
 +      film->examine_and_add_content (C);
 +      wait_for_jobs ();
 +
++      BOOST_CHECK_EQUAL (A->full_length(), DCPTime (280000));
 +
 +      A->set_position (DCPTime::from_seconds (0));
 +      B->set_position (DCPTime::from_seconds (10));
 +      C->set_position (DCPTime::from_seconds (20));
 +
 +      shared_ptr<Player> player = film->make_player ();
 +
 +      list<shared_ptr<Piece> > o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (0), DCPTime::from_seconds (5));
 +      BOOST_CHECK_EQUAL (o.size(), 1);
 +      BOOST_CHECK_EQUAL (o.front()->content, A);
 +
 +      o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (5), DCPTime::from_seconds (8));
 +      BOOST_CHECK_EQUAL (o.size(), 0);
 +
 +      o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (8), DCPTime::from_seconds (12));
 +      BOOST_CHECK_EQUAL (o.size(), 1);
 +      BOOST_CHECK_EQUAL (o.front()->content, B);
 +
 +      o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (2), DCPTime::from_seconds (12));
 +      BOOST_CHECK_EQUAL (o.size(), 2);
 +      BOOST_CHECK_EQUAL (o.front()->content, A);
 +      BOOST_CHECK_EQUAL (o.back()->content, B);
 +
 +      o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (8), DCPTime::from_seconds (11));
 +      BOOST_CHECK_EQUAL (o.size(), 1);
 +      BOOST_CHECK_EQUAL (o.front()->content, B);
 +}
 +
 +/** Check that the Player correctly generates silence when used with a silent FFmpegContent */
 +BOOST_AUTO_TEST_CASE (player_silence_padding_test)
 +{
 +      shared_ptr<Film> film = new_test_film ("player_silence_padding_test");
 +      film->set_name ("player_silence_padding_test");
 +      shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/test.mp4"));
 +      film->set_container (Ratio::from_id ("185"));
 +      film->set_audio_channels (6);
 +      
 +      film->examine_and_add_content (c);
 +      wait_for_jobs ();
 +
 +      shared_ptr<Player> player = film->make_player ();
 +      shared_ptr<AudioBuffers> test = player->get_audio (DCPTime (0), DCPTime::from_seconds (1), true);
 +      BOOST_CHECK_EQUAL (test->frames(), 48000);
 +      BOOST_CHECK_EQUAL (test->channels(), film->audio_channels ());
 +
 +      for (int i = 0; i < test->frames(); ++i) {
 +              for (int c = 0; c < test->channels(); ++c) {
 +                      BOOST_CHECK_EQUAL (test->data()[c][i], 0);
 +              }
 +      }
 +}
 +
diff --combined test/recover_test.cc
index bf07811ddce917a3b73d4c9a76888bfe37ba062c,284895e0a75d3bcee73ae157e078fc1ad6b86925..c9a5932418252ab6246467fc8c2749483b43e8d9
  
  */
  
 +/** @file  test/recover_test.cc
 + *  @brief Test recovery of a DCP transcode after a crash.
 + */
 +
  #include <boost/test/unit_test.hpp>
 -#include <libdcp/stereo_picture_asset.h>
 +#include <dcp/stereo_picture_mxf.h>
  #include "lib/film.h"
  #include "lib/dcp_content_type.h"
  #include "lib/image_content.h"
@@@ -34,13 -30,12 +34,13 @@@ using std::string
  using boost::shared_ptr;
  
  static void
 -note (libdcp::NoteType, string n)
 +note (dcp::NoteType t, string n)
  {
 -      cout << n << "\n";
 +      if (t == dcp::DCP_ERROR) {
 +              cout << n << "\n";
 +      }
  }
  
 -/** Test recovery of a DCP transcode after a crash */
  BOOST_AUTO_TEST_CASE (recover_test)
  {
        shared_ptr<Film> film = new_test_film ("recover_test");
        film->make_dcp ();
        wait_for_jobs ();
  
-       boost::filesystem::path const video = "build/test/recover_test/video/185_2K_3651eded785682b85f4baca4b1d3b7a9_24_bicubic_200000000_P_S_3D.mxf";
++      boost::filesystem::path const video = "build/test/recover_test/video/185_2K_e8efb95857b62aa6ff94e3d669e75776_24_bicubic_100000000_P_S_3D.mxf";
 +
        boost::filesystem::copy_file (
 -              "build/test/recover_test/video/185_2K_58a090f8d70a2b410c534120d35e5256_24_bicubic_200000000_P_S_3D.mxf",
 +              video,
                "build/test/recover_test/original.mxf"
                );
        
 -      boost::filesystem::resize_file ("build/test/recover_test/video/185_2K_58a090f8d70a2b410c534120d35e5256_24_bicubic_200000000_P_S_3D.mxf", 2 * 1024 * 1024);
 +      boost::filesystem::resize_file (video, 2 * 1024 * 1024);
  
        film->make_dcp ();
        wait_for_jobs ();
  
 -      shared_ptr<libdcp::StereoPictureAsset> A (new libdcp::StereoPictureAsset ("build/test/recover_test", "original.mxf"));
 -      shared_ptr<libdcp::StereoPictureAsset> B (new libdcp::StereoPictureAsset ("build/test/recover_test/video", "185_2K_58a090f8d70a2b410c534120d35e5256_24_bicubic_200000000_P_S_3D.mxf"));
 +      shared_ptr<dcp::StereoPictureMXF> A (new dcp::StereoPictureMXF ("build/test/recover_test/original.mxf"));
 +      shared_ptr<dcp::StereoPictureMXF> B (new dcp::StereoPictureMXF (video));
  
 -      libdcp::EqualityOptions eq;
 +      dcp::EqualityOptions eq;
        eq.mxf_names_can_differ = true;
        BOOST_CHECK (A->equals (B, eq, boost::bind (&note, _1, _2)));
  }
diff --combined test/test.cc
index 1d8041656cf05e2acaf76081e327c00f7621661b,0b87b8062a67adc503bf076ba32624cf7678a2ff..32f74a7d2fb62b8243056d11c11b84eeb5eea0b1
@@@ -1,5 -1,5 +1,5 @@@
  /*
 -    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
 +    Copyright (C) 2012-2014 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
  
  */
  
 +/** @file  test/test.cc
 + *  @brief Overall test stuff and useful methods for tests.
 + */
 +
  #include <vector>
  #include <list>
 +#include <Magick++.h>
  #include <libxml++/libxml++.h>
 -#include <libdcp/dcp.h>
 +#include <dcp/dcp.h>
  #include "lib/config.h"
  #include "lib/util.h"
  #include "lib/ui_signaller.h"
@@@ -34,7 -29,6 +34,7 @@@
  #include "lib/job.h"
  #include "lib/cross.h"
  #include "lib/server_finder.h"
 +#include "lib/image.h"
  #define BOOST_TEST_DYN_LINK
  #define BOOST_TEST_MODULE dcpomatic_test
  #include <boost/test/unit_test.hpp>
@@@ -47,8 -41,6 +47,8 @@@ using std::cerr
  using std::list;
  using boost::shared_ptr;
  
 +boost::filesystem::path private_data = boost::filesystem::path ("test") / boost::filesystem::path ("private");
 +
  class TestUISignaller : public UISignaller
  {
  public:
  
  struct TestConfig
  {
 -      TestConfig()
 +      TestConfig ()
        {
 -              dcpomatic_setup();
 +              dcpomatic_setup ();
  
                Config::instance()->set_num_local_encoding_threads (1);
                Config::instance()->set_server_port_base (61920);
                Config::instance()->set_default_isdcf_metadata (ISDCFMetadata ());
                Config::instance()->set_default_container (static_cast<Ratio*> (0));
                Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
 +              Config::instance()->set_default_audio_delay (0);
  
                ServerFinder::instance()->disable ();
  
                ui_signaller = new TestUISignaller ();
        }
 +
 +      ~TestConfig ()
 +      {
 +              JobManager::drop ();
 +      }
  };
  
  BOOST_GLOBAL_FIXTURE (TestConfig);
@@@ -112,8 -98,8 +112,8 @@@ voi
  check_file (boost::filesystem::path ref, boost::filesystem::path check)
  {
        uintmax_t N = boost::filesystem::file_size (ref);
 -      BOOST_CHECK_EQUAL (N, boost::filesystem::file_size(check));
 -      FILE* ref_file = fopen_boost (ref, "rb");
 +      BOOST_CHECK_EQUAL (N, boost::filesystem::file_size (check));
 +      FILE* ref_file = fopen (ref.c_str(), "rb");
        BOOST_CHECK (ref_file);
        FILE* check_file = fopen_boost (check, "rb");
        BOOST_CHECK (check_file);
  }
  
  static void
 -note (libdcp::NoteType t, string n)
 +note (dcp::NoteType t, string n)
  {
-       if (t == dcp::ERROR) {
 -      if (t == libdcp::ERROR) {
++      if (t == dcp::DCP_ERROR) {
                cerr << n << "\n";
        }
  }
  
  void
 -check_dcp (string ref, string check)
 +check_dcp (boost::filesystem::path ref, boost::filesystem::path check)
  {
 -      libdcp::DCP ref_dcp (ref);
 +      dcp::DCP ref_dcp (ref);
        ref_dcp.read ();
 -      libdcp::DCP check_dcp (check);
 +      dcp::DCP check_dcp (check);
        check_dcp.read ();
  
 -      libdcp::EqualityOptions options;
 +      dcp::EqualityOptions options;
        options.max_mean_pixel_error = 5;
        options.max_std_dev_pixel_error = 5;
        options.max_audio_sample_error = 255;
 -      options.cpl_names_can_differ = true;
 +      options.cpl_annotation_texts_can_differ = true;
        options.mxf_names_can_differ = true;
        
        BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2)));
@@@ -232,19 -218,10 +232,19 @@@ wait_for_jobs (
                ui_signaller->ui_idle ();
        }
        if (jm->errors ()) {
 +              int N = 0;
                for (list<shared_ptr<Job> >::iterator i = jm->_jobs.begin(); i != jm->_jobs.end(); ++i) {
                        if ((*i)->finished_in_error ()) {
 -                              cerr << (*i)->error_summary () << "\n"
 -                                   << (*i)->error_details () << "\n";
 +                              ++N;
 +                      }
 +              }
 +              cerr << N << " errors.\n";
 +
 +              for (list<shared_ptr<Job> >::iterator i = jm->_jobs.begin(); i != jm->_jobs.end(); ++i) {
 +                      if ((*i)->finished_in_error ()) {
 +                              cerr << (*i)->name() << ":\n"
 +                                   << "\tsummary: " << (*i)->error_summary () << "\n"
 +                                   << "\tdetails: " << (*i)->error_details () << "\n";
                        }
                }
        }
  
        ui_signaller->ui_idle ();
  }
 +
 +void
 +write_image (shared_ptr<const Image> image, boost::filesystem::path file)
 +{
 +      using namespace MagickCore;
 +
 +      Magick::Image m (image->size().width, image->size().height, "ARGB", CharPixel, (void *) image->data()[0]);
 +      m.write (file.string ());
 +}