Merge master.
authorCarl Hetherington <cth@carlh.net>
Thu, 12 Jun 2014 21:27:11 +0000 (22:27 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 12 Jun 2014 21:27:11 +0000 (22:27 +0100)
50 files changed:
1  2 
ChangeLog
cscript
src/lib/audio_content.cc
src/lib/audio_content.h
src/lib/config.cc
src/lib/config.h
src/lib/content.cc
src/lib/dcp_content_type.cc
src/lib/dcp_content_type.h
src/lib/ffmpeg_content.cc
src/lib/film.cc
src/lib/film.h
src/lib/frame_rate_change.h
src/lib/image_content.cc
src/lib/image_proxy.cc
src/lib/isdcf_metadata.cc
src/lib/isdcf_metadata.h
src/lib/player.cc
src/lib/playlist.h
src/lib/ratio.cc
src/lib/ratio.h
src/lib/server.cc
src/lib/sndfile_content.cc
src/lib/util.cc
src/lib/video_content.cc
src/lib/wscript
src/tools/dcpomatic_create.cc
src/wx/about_dialog.cc
src/wx/audio_panel.cc
src/wx/config_dialog.cc
src/wx/content_widget.h
src/wx/film_editor.cc
src/wx/timeline.cc
src/wx/timeline.h
src/wx/timing_panel.cc
src/wx/video_panel.cc
src/wx/wscript
src/wx/wx_util.cc
test/4k_test.cc
test/audio_delay_test.cc
test/black_fill_test.cc
test/client_server_test.cc
test/colour_conversion_test.cc
test/film_metadata_test.cc
test/frame_rate_test.cc
test/recover_test.cc
test/scaling_test.cc
test/silence_padding_test.cc
test/test.cc
test/wscript

diff --combined ChangeLog
index 78954c82da4b1fe42918bf5e7af6ddcc255d87ee,8ad710af238db6fc673ac89c1c0e442ed7f2406d..126730e27f8e232e481b470e7fda00549606f49e
+++ b/ChangeLog
@@@ -1,7 -1,55 +1,59 @@@
 +2014-03-07  Carl Hetherington  <cth@carlh.net>
 +
 +      * Add subtitle view.
 +
+ 2014-06-12  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.26 released.
+ 2014-06-12  Carl Hetherington  <cth@carlh.net>
+       * Fix bug where DCP-o-matic does not recreate video after
+       subtitles are turned on or off.
+ 2014-06-10  Carl Hetherington  <cth@carlh.net>
+       * Support ISDCF naming convention version 9 (#257).
+       * Rename DCI to ISDCF when talking about the digital cinema
+       naming convention (#362).
+       * Fix crash when opening the timeline with no content (#369).
+ 2014-06-09  Carl Hetherington  <cth@carlh.net>
+       * Fix server/client with non-RGB24 sources.
+       * Version 1.69.25 released.
+ 2014-06-09  Carl Hetherington  <cth@carlh.net>
+       * Make audio gain a floating-point value in the UI (#367).
+       * Work-around out-of-memory crashes with large start trims (#252).
+       * Version 1.69.24 released.
+ 2014-06-06  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.23 released.
+ 2014-06-05  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.22 released.
+ 2014-06-05  Carl Hetherington  <cth@carlh.net>
+       * Large speed-up to multi-image source file decoding.
+       * Back-port changes from v2 which work out how separate
+       audio files should be resampled by looking at the video
+       files which are present at the same time.
+ 2014-06-03  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.21 released.
  2014-06-03  Carl Hetherington  <cth@carlh.net>
  
        * Fix bad resampling of separate sound file sources that
diff --combined cscript
index e1a987fdec0f73b13aa222b682ee0178d0ad5412,b4e211e664d9384c8991179bf85e8e93886364a0..adc4da639a2be9ad4e3ca8e7e093f5b9b38604ba
+++ b/cscript
@@@ -7,8 -7,6 +7,6 @@@ deb_build_depends = {'debhelper': '8.0.
                       'g++': '4:4.6.3',
                       'pkg-config': '0.26',
                       'libssh-dev': '0.5.2',
-                      'libboost-filesystem-dev': '1.46.0',
-                      'libboost-thread-dev': '1.46.0',
                       'libsndfile1-dev': '1.0.25',
                       'libmagick++-dev': '8:6.6.9.7',
                       'libgtk2.0-dev': '2.24.10'}
@@@ -17,15 -15,15 +15,15 @@@ deb_depends = dict(
  
  deb_depends['12.04'] = {'libc6': '2.15',
                          'libssh-4': '0.5.2',
-                         'libboost-filesystem1.46.1': '1.46.1',
-                         'libboost-thread1.46.1': '1.46.1',
+                         'libboost-filesystem1.48.0': '1.48.0-3',
+                         'libboost-thread1.48.0': '1.48.0-3',
                          'libsndfile1': '1.0.25',
                          'libmagick++4': '8:6.6.9.7',
                          'libxml++2.6-2': '2.34.1',
                          'libgtk2.0-0': '2.24.10',
                          'libxmlsec1': '1.2.14-1.2build1',
                          'libxmlsec1-openssl': '1.2.14-1.2build1',
-                         'libboost-date-time1.46.1': '1.46.1',
+                         'libboost-date-time1.48.0': '1.48.0-3',
                          'libcurl3': '7.22.0-3ubuntu4',
                          'libzip2': '0.10-1ubuntu1'}
  
@@@ -144,7 -142,7 +142,7 @@@ def make_control(debian_version, bits, 
  
  def dependencies(target):
      return (('ffmpeg-cdist', 'bba68a5'),
 -            ('libdcp', 'v0.95.0'))
 +            ('libdcp', '1.0'))
  
  def build(target, options):
      cmd = './waf configure --prefix=%s' % target.work_dir_cscript()
diff --combined src/lib/audio_content.cc
index 03bfe9630a9cb304de9d23c54456c7cf432c1fcc,29d159a29a9603f330eb6100ed6c42743a33c630..9f0d26573453c347873237e21e195df9b53aab36
@@@ -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
  */
  
  #include <libcxml/cxml.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/raw_convert.h>
  #include "audio_content.h"
  #include "analyse_audio_job.h"
  #include "job_manager.h"
  #include "film.h"
  #include "exceptions.h"
  #include "config.h"
+ #include "frame_rate_change.h"
  
  #include "i18n.h"
  
@@@ -33,7 -34,7 +34,7 @@@ using std::cout
  using std::vector;
  using boost::shared_ptr;
  using boost::dynamic_pointer_cast;
 -using libdcp::raw_convert;
 +using dcp::raw_convert;
  
  int const AudioContentProperty::AUDIO_CHANNELS = 200;
  int const AudioContentProperty::AUDIO_LENGTH = 201;
@@@ -42,7 -43,7 +43,7 @@@ int const AudioContentProperty::AUDIO_G
  int const AudioContentProperty::AUDIO_DELAY = 204;
  int const AudioContentProperty::AUDIO_MAPPING = 205;
  
 -AudioContent::AudioContent (shared_ptr<const Film> f, Time s)
 +AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s)
        : Content (f, s)
        , _audio_gain (0)
        , _audio_delay (Config::instance()->default_audio_delay ())
@@@ -58,7 -59,7 +59,7 @@@ AudioContent::AudioContent (shared_ptr<
  
  }
  
 -AudioContent::AudioContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
 +AudioContent::AudioContent (shared_ptr<const Film> f, cxml::ConstNodePtr node)
        : Content (f, node)
  {
        _audio_gain = node->number_child<float> ("AudioGain");
@@@ -97,7 -98,7 +98,7 @@@ AudioContent::as_xml (xmlpp::Node* node
  
  
  void
- AudioContent::set_audio_gain (float g)
+ AudioContent::set_audio_gain (double g)
  {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@@ -147,38 -148,24 +148,38 @@@ AudioContent::audio_analysis_path () co
  string
  AudioContent::technical_summary () const
  {
 -      return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate());
 +      return String::compose (
 +              "audio: channels %1, length %2, content rate %3, resampled rate %4",
 +              audio_channels(),
 +              audio_length().seconds(),
 +              audio_frame_rate(),
 +              resampled_audio_frame_rate()
 +              );
  }
  
 +void
 +AudioContent::set_audio_mapping (AudioMapping)
 +{
 +      signal_changed (AudioContentProperty::AUDIO_MAPPING);
 +}
 +
 +/** @return the frame rate that this content should be resampled to in order
 + *  that it is in sync with the active video content at its start time.
 + */
  int
 -AudioContent::output_audio_frame_rate () const
 +AudioContent::resampled_audio_frame_rate () const
  {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
        
        /* Resample to a DCI-approved sample rate */
 -      double t = dcp_audio_frame_rate (content_audio_frame_rate ());
 +      double t = dcp_audio_frame_rate (audio_frame_rate ());
  
        FrameRateChange frc = film->active_frame_rate_change (position ());
  
        /* Compensate if the DCP is being run at a different frame rate
           to the source; that is, if the video is run such that it will
           look different in the DCP compared to the source (slower or faster).
 -         skip/repeat doesn't come into effect here.
        */
  
        if (frc.change_speed) {
  
        return rint (t);
  }
 -
diff --combined src/lib/audio_content.h
index 1ceb01f780edd20cb91fc4849527e9d27df64d5a,2c324a3a4966e0dad899c10f24f3f99115317420..336a98629bae47b103cdb0d3d88e15ced581cfaa
@@@ -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/lib/audio_content.h
 + *  @brief AudioContent and AudioContentProperty classes.
 + */
 +
  #ifndef DCPOMATIC_AUDIO_CONTENT_H
  #define DCPOMATIC_AUDIO_CONTENT_H
  
@@@ -42,40 -38,34 +42,40 @@@ public
        static int const AUDIO_MAPPING;
  };
  
 +/** @class AudioContent
 + *  @brief Parent class for content which may contain audio data.
 + */
  class AudioContent : public virtual Content
  {
  public:
        typedef int64_t Frame;
        
 -      AudioContent (boost::shared_ptr<const Film>, Time);
 +      AudioContent (boost::shared_ptr<const Film>, DCPTime);
        AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
 -      AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
 +      AudioContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr);
        AudioContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
  
        void as_xml (xmlpp::Node *) const;
        std::string technical_summary () const;
  
 +      /** @return number of audio channels in the content */
        virtual int audio_channels () const = 0;
 -      virtual AudioContent::Frame audio_length () const = 0;
 -      virtual int content_audio_frame_rate () const = 0;
 +      /** @return the length of the audio in the content */
 +      virtual ContentTime audio_length () const = 0;
 +      /** @return the frame rate of the content */
 +      virtual int audio_frame_rate () const = 0;
        virtual AudioMapping audio_mapping () const = 0;
 -      virtual void set_audio_mapping (AudioMapping) = 0;
 +      virtual void set_audio_mapping (AudioMapping);
        virtual boost::filesystem::path audio_analysis_path () const;
  
 -      int output_audio_frame_rate () const;
 -      
 +      int resampled_audio_frame_rate () const;
 +
        boost::signals2::connection analyse_audio (boost::function<void()>);
  
-       void set_audio_gain (float);
+       void set_audio_gain (double);
        void set_audio_delay (int);
        
-       float audio_gain () const {
+       double audio_gain () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _audio_gain;
        }
@@@ -87,7 -77,7 +87,7 @@@
  
  private:
        /** Gain to apply to audio in dB */
-       float _audio_gain;
+       double _audio_gain;
        /** Delay to apply to audio (positive moves audio later) in milliseconds */
        int _audio_delay;
  };
diff --combined src/lib/config.cc
index 8be31a329645151bd424c4b77f7f52b13d74cc60,0c3dd023d165b1d285ba98ec1019a4d918432bf5..a0211386baf224d248543f13848f9fb3aaac58a4
@@@ -23,8 -23,8 +23,8 @@@
  #include <glib.h>
  #include <boost/filesystem.hpp>
  #include <boost/algorithm/string.hpp>
 -#include <libdcp/colour_matrix.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/colour_matrix.h>
 +#include <dcp/raw_convert.h>
  #include <libcxml/cxml.h>
  #include "config.h"
  #include "server.h"
@@@ -50,7 -50,7 +50,7 @@@ using boost::shared_ptr
  using boost::optional;
  using boost::algorithm::is_any_of;
  using boost::algorithm::split;
 -using libdcp::raw_convert;
 +using dcp::raw_convert;
  
  Config* Config::_instance = 0;
  
@@@ -64,7 -64,7 +64,7 @@@ Config::Config (
        , _allow_any_dcp_frame_rate (false)
        , _default_still_length (10)
        , _default_container (Ratio::from_id ("185"))
-       , _default_dcp_content_type (DCPContentType::from_dci_name ("TST"))
+       , _default_dcp_content_type (DCPContentType::from_isdcf_name ("TST"))
        , _default_j2k_bandwidth (100000000)
        , _default_audio_delay (0)
        , _kdm_email (
@@@ -82,9 -82,9 +82,9 @@@
        _allowed_dcp_frame_rates.push_back (50);
        _allowed_dcp_frame_rates.push_back (60);
  
 -      _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6));
 -      _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6));
 -      _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
 +      _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6));
 +      _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, dcp::colour_matrix::srgb_to_xyz, 2.6));
 +      _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
  }
  
  void
@@@ -141,13 -141,18 +141,18 @@@ Config::read (
  
        c = f.optional_string_child ("DefaultDCPContentType");
        if (c) {
-               _default_dcp_content_type = DCPContentType::from_dci_name (c.get ());
+               _default_dcp_content_type = DCPContentType::from_isdcf_name (c.get ());
        }
  
        _dcp_metadata.issuer = f.optional_string_child ("DCPMetadataIssuer").get_value_or ("");
        _dcp_metadata.creator = f.optional_string_child ("DCPMetadataCreator").get_value_or ("");
  
-       _default_dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
+       if (version && version.get() >= 2) {
+               _default_isdcf_metadata = ISDCFMetadata (f.node_child ("ISDCFMetadata"));
+       } else {
+               _default_isdcf_metadata = ISDCFMetadata (f.node_child ("DCIMetadata"));
+       }
+       
        _default_still_length = f.optional_number_child<int>("DefaultStillLength").get_value_or (10);
        _default_j2k_bandwidth = f.optional_number_child<int>("DefaultJ2KBandwidth").get_value_or (200000000);
        _default_audio_delay = f.optional_number_child<int>("DefaultAudioDelay").get_value_or (0);
                /* Loading version 0 (before Rec. 709 was added as a preset).
                   Add it in.
                */
 -              _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
 +              _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
        }
  
        list<cxml::NodePtr> cin = f.node_children ("Cinema");
@@@ -245,7 -250,7 +250,7 @@@ Config::read_old_metadata (
                } else if (k == "default_container") {
                        _default_container = Ratio::from_id (v);
                } else if (k == "default_dcp_content_type") {
-                       _default_dcp_content_type = DCPContentType::from_dci_name (v);
+                       _default_dcp_content_type = DCPContentType::from_isdcf_name (v);
                } else if (k == "dcp_metadata_issuer") {
                        _dcp_metadata.issuer = v;
                } else if (k == "dcp_metadata_creator") {
                        _dcp_metadata.issue_date = v;
                }
  
-               _default_dci_metadata.read_old_metadata (k, v);
+               _default_isdcf_metadata.read_old_metadata (k, v);
        }
  }
  
@@@ -315,7 -320,7 +320,7 @@@ Config::write () cons
        xmlpp::Document doc;
        xmlpp::Element* root = doc.create_root_node ("Config");
  
-       root->add_child("Version")->add_child_text ("1");
+       root->add_child("Version")->add_child_text ("2");
        root->add_child("NumLocalEncodingThreads")->add_child_text (raw_convert<string> (_num_local_encoding_threads));
        root->add_child("DefaultDirectory")->add_child_text (_default_directory.string ());
        root->add_child("ServerPortBase")->add_child_text (raw_convert<string> (_server_port_base));
                root->add_child("DefaultContainer")->add_child_text (_default_container->id ());
        }
        if (_default_dcp_content_type) {
-               root->add_child("DefaultDCPContentType")->add_child_text (_default_dcp_content_type->dci_name ());
+               root->add_child("DefaultDCPContentType")->add_child_text (_default_dcp_content_type->isdcf_name ());
        }
        root->add_child("DCPMetadataIssuer")->add_child_text (_dcp_metadata.issuer);
        root->add_child("DCPMetadataCreator")->add_child_text (_dcp_metadata.creator);
  
-       _default_dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+       _default_isdcf_metadata.as_xml (root->add_child ("ISDCFMetadata"));
  
        root->add_child("DefaultStillLength")->add_child_text (raw_convert<string> (_default_still_length));
        root->add_child("DefaultJ2KBandwidth")->add_child_text (raw_convert<string> (_default_j2k_bandwidth));
diff --combined src/lib/config.h
index ccd37ec1e22a2bc2d133b82138bf0a6268428196,671f53ef32dbcaf955f8109e6d46de6444ab8158..f0d2630d0c893b22edf39c7a041d52e5d30ba2f4
@@@ -28,8 -28,8 +28,8 @@@
  #include <boost/shared_ptr.hpp>
  #include <boost/signals2.hpp>
  #include <boost/filesystem.hpp>
 -#include <libdcp/metadata.h>
 +#include <dcp/metadata.h>
- #include "dci_metadata.h"
+ #include "isdcf_metadata.h"
  #include "colour_conversion.h"
  #include "server.h"
  
@@@ -121,8 -121,8 +121,8 @@@ public
                return _allow_any_dcp_frame_rate;
        }
        
-       DCIMetadata default_dci_metadata () const {
-               return _default_dci_metadata;
+       ISDCFMetadata default_isdcf_metadata () const {
+               return _default_isdcf_metadata;
        }
  
        boost::optional<std::string> language () const {
                return _default_dcp_content_type;
        }
  
 -      libdcp::XMLMetadata dcp_metadata () const {
 +      dcp::XMLMetadata dcp_metadata () const {
                return _dcp_metadata;
        }
  
                changed ();
        }
  
-       void set_default_dci_metadata (DCIMetadata d) {
-               _default_dci_metadata = d;
+       void set_default_isdcf_metadata (ISDCFMetadata d) {
+               _default_isdcf_metadata = d;
                changed ();
        }
  
                changed ();
        }
  
 -      void set_dcp_metadata (libdcp::XMLMetadata m) {
 +      void set_dcp_metadata (dcp::XMLMetadata m) {
                _dcp_metadata = m;
                changed ();
        }
@@@ -389,13 -389,13 +389,13 @@@ private
        std::list<int> _allowed_dcp_frame_rates;
        /** Allow any video frame rate for the DCP; if true, overrides _allowed_dcp_frame_rates */
        bool _allow_any_dcp_frame_rate;
-       /** Default DCI metadata for newly-created Films */
-       DCIMetadata _default_dci_metadata;
+       /** Default ISDCF metadata for newly-created Films */
+       ISDCFMetadata _default_isdcf_metadata;
        boost::optional<std::string> _language;
        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/content.cc
index b6678cb4d9e7e9555773beb8746754755f0673e9,7966219ff60a4bf186a3af6ce58a6b52aa0a97e5..bbbe9b6ce4bdd4dd5ff6b8dcf231c8d08126e253
@@@ -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/lib/content.cc
 + *  @brief Content class.
 + */
 +
  #include <boost/thread/mutex.hpp>
  #include <libxml++/libxml++.h>
  #include <libcxml/cxml.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/raw_convert.h>
  #include "content.h"
  #include "util.h"
  #include "content_factory.h"
@@@ -40,8 -36,9 +40,9 @@@ using std::set
  using std::list;
  using std::cout;
  using std::vector;
+ using std::max;
  using boost::shared_ptr;
 -using libdcp::raw_convert;
 +using dcp::raw_convert;
  
  int const ContentProperty::PATH = 400;
  int const ContentProperty::POSITION = 401;
@@@ -59,7 -56,7 +60,7 @@@ Content::Content (shared_ptr<const Film
  
  }
  
 -Content::Content (shared_ptr<const Film> f, Time p)
 +Content::Content (shared_ptr<const Film> f, DCPTime p)
        : _film (f)
        , _position (p)
        , _trim_start (0)
@@@ -79,7 -76,7 +80,7 @@@ Content::Content (shared_ptr<const Film
        _paths.push_back (p);
  }
  
 -Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
 +Content::Content (shared_ptr<const Film> f, cxml::ConstNodePtr node)
        : _film (f)
        , _change_signals_frequent (false)
  {
@@@ -88,9 -85,9 +89,9 @@@
                _paths.push_back ((*i)->content ());
        }
        _digest = node->string_child ("Digest");
 -      _position = node->number_child<Time> ("Position");
 -      _trim_start = node->number_child<Time> ("TrimStart");
 -      _trim_end = node->number_child<Time> ("TrimEnd");
 +      _position = DCPTime (node->number_child<double> ("Position"));
 +      _trim_start = DCPTime (node->number_child<double> ("TrimStart"));
 +      _trim_end = DCPTime (node->number_child<double> ("TrimEnd"));
  }
  
  Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
        , _change_signals_frequent (false)
  {
        for (size_t i = 0; i < c.size(); ++i) {
 -              if (i > 0 && c[i]->trim_start ()) {
 +              if (i > 0 && c[i]->trim_start() > DCPTime()) {
                        throw JoinError (_("Only the first piece of content to be joined can have a start trim."));
                }
  
 -              if (i < (c.size() - 1) && c[i]->trim_end ()) {
 +              if (i < (c.size() - 1) && c[i]->trim_end () > DCPTime()) {
                        throw JoinError (_("Only the last piece of content to be joined can have an end trim."));
                }
  
@@@ -124,9 -121,9 +125,9 @@@ Content::as_xml (xmlpp::Node* node) con
                node->add_child("Path")->add_child_text (i->string ());
        }
        node->add_child("Digest")->add_child_text (_digest);
 -      node->add_child("Position")->add_child_text (raw_convert<string> (_position));
 -      node->add_child("TrimStart")->add_child_text (raw_convert<string> (_trim_start));
 -      node->add_child("TrimEnd")->add_child_text (raw_convert<string> (_trim_end));
 +      node->add_child("Position")->add_child_text (raw_convert<string> (_position.get ()));
 +      node->add_child("TrimStart")->add_child_text (raw_convert<string> (_trim_start.get ()));
 +      node->add_child("TrimEnd")->add_child_text (raw_convert<string> (_trim_end.get ()));
  }
  
  void
@@@ -151,7 -148,7 +152,7 @@@ Content::signal_changed (int p
  }
  
  void
 -Content::set_position (Time p)
 +Content::set_position (DCPTime p)
  {
        {
                boost::mutex::scoped_lock lm (_mutex);
  }
  
  void
 -Content::set_trim_start (Time t)
 +Content::set_trim_start (DCPTime t)
  {
        {
                boost::mutex::scoped_lock lm (_mutex);
  }
  
  void
 -Content::set_trim_end (Time t)
 +Content::set_trim_end (DCPTime t)
  {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@@ -209,13 -206,22 +210,13 @@@ Content::clone () cons
  string
  Content::technical_summary () const
  {
 -      return String::compose ("%1 %2 %3", path_summary(), digest(), position());
 +      return String::compose ("%1 %2 %3", path_summary(), digest(), position().seconds());
  }
  
 -Time
 +DCPTime
  Content::length_after_trim () const
  {
-       return full_length() - trim_start() - trim_end();
 -      return max (int64_t (0), full_length() - trim_start() - trim_end());
 -}
 -
 -/** @param t A time relative to the start of this content (not the position).
 - *  @return true if this time is trimmed by our trim settings.
 - */
 -bool
 -Content::trimmed (Time t) const
 -{
 -      return (t < trim_start() || t > (full_length() - trim_end ()));
++      return max (DCPTime (), full_length() - trim_start() - trim_end());
  }
  
  /** @return string which includes everything about how this content affects
@@@ -227,9 -233,9 +228,9 @@@ Content::identifier () cons
        stringstream s;
        
        s << Content::digest()
 -        << "_" << position()
 -        << "_" << trim_start()
 -        << "_" << trim_end();
 +        << "_" << position().get()
 +        << "_" << trim_start().get()
 +        << "_" << trim_end().get();
  
        return s.str ();
  }
index b3a45e40e410e156d5e4b94ff6032ee2e0011d77,f24ed95ea24de85c74dd4d6b646fe744cc526f36..e5466e1398936c1e4917efe11e95fa1cf5b12472
@@@ -30,10 -30,10 +30,10 @@@ using namespace std
  
  vector<DCPContentType const *> DCPContentType::_dcp_content_types;
  
 -DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d)
 +DCPContentType::DCPContentType (string p, dcp::ContentKind k, string d)
        : _pretty_name (p)
        , _libdcp_kind (k)
-       , _dci_name (d)
+       , _isdcf_name (d)
  {
  
  }
  void
  DCPContentType::setup_dcp_content_types ()
  {
 -      _dcp_content_types.push_back (new DCPContentType (_("Feature"), libdcp::FEATURE, N_("FTR")));
 -      _dcp_content_types.push_back (new DCPContentType (_("Short"), libdcp::SHORT, N_("SHR")));
 -      _dcp_content_types.push_back (new DCPContentType (_("Trailer"), libdcp::TRAILER, N_("TLR")));
 -      _dcp_content_types.push_back (new DCPContentType (_("Test"), libdcp::TEST, N_("TST")));
 -      _dcp_content_types.push_back (new DCPContentType (_("Transitional"), libdcp::TRANSITIONAL, N_("XSN")));
 -      _dcp_content_types.push_back (new DCPContentType (_("Rating"), libdcp::RATING, N_("RTG")));
 -      _dcp_content_types.push_back (new DCPContentType (_("Teaser"), libdcp::TEASER, N_("TSR")));
 -      _dcp_content_types.push_back (new DCPContentType (_("Policy"), libdcp::POLICY, N_("POL")));
 -      _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), libdcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA")));
 -      _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), libdcp::ADVERTISEMENT, N_("ADV")));
 +      _dcp_content_types.push_back (new DCPContentType (_("Feature"), dcp::FEATURE, N_("FTR")));
 +      _dcp_content_types.push_back (new DCPContentType (_("Short"), dcp::SHORT, N_("SHR")));
 +      _dcp_content_types.push_back (new DCPContentType (_("Trailer"), dcp::TRAILER, N_("TLR")));
 +      _dcp_content_types.push_back (new DCPContentType (_("Test"), dcp::TEST, N_("TST")));
 +      _dcp_content_types.push_back (new DCPContentType (_("Transitional"), dcp::TRANSITIONAL, N_("XSN")));
 +      _dcp_content_types.push_back (new DCPContentType (_("Rating"), dcp::RATING, N_("RTG")));
 +      _dcp_content_types.push_back (new DCPContentType (_("Teaser"), dcp::TEASER, N_("TSR")));
 +      _dcp_content_types.push_back (new DCPContentType (_("Policy"), dcp::POLICY, N_("POL")));
 +      _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), dcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA")));
 +      _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), dcp::ADVERTISEMENT, N_("ADV")));
  }
  
  DCPContentType const *
@@@ -66,10 -66,10 +66,10 @@@ DCPContentType::from_pretty_name (strin
  }
  
  DCPContentType const *
- DCPContentType::from_dci_name (string n)
+ DCPContentType::from_isdcf_name (string n)
  {
        for (vector<DCPContentType const *>::const_iterator i = _dcp_content_types.begin(); i != _dcp_content_types.end(); ++i) {
-               if ((*i)->dci_name() == n) {
+               if ((*i)->isdcf_name() == n) {
                        return *i;
                }
        }
index 05f30af5515d5c4ad1cd5713fb59d6200164a562,88f3c4a857e18d8e1574846906118ec1921c344f..ebfe09518a5577189e745d05d0a37bfd6d1ce04c
@@@ -26,7 -26,7 +26,7 @@@
  
  #include <string>
  #include <vector>
 -#include <libdcp/dcp.h>
 +#include <dcp/dcp.h>
  
  /** @class DCPContentType
   *  @brief A description of the type of content for a DCP (e.g. feature, trailer etc.)
  class DCPContentType : public boost::noncopyable
  {
  public:
 -      DCPContentType (std::string, libdcp::ContentKind, std::string);
 +      DCPContentType (std::string, dcp::ContentKind, std::string);
  
        /** @return user-visible `pretty' name */
        std::string pretty_name () const {
                return _pretty_name;
        }
  
 -      libdcp::ContentKind libdcp_kind () const {
 +      dcp::ContentKind libdcp_kind () const {
                return _libdcp_kind;
        }
  
-       std::string dci_name () const {
-               return _dci_name;
+       std::string isdcf_name () const {
+               return _isdcf_name;
        }
  
        static DCPContentType const * from_pretty_name (std::string);
-       static DCPContentType const * from_dci_name (std::string);
+       static DCPContentType const * from_isdcf_name (std::string);
        static DCPContentType const * from_index (int);
        static int as_index (DCPContentType const *);
        static std::vector<DCPContentType const *> all ();
@@@ -58,8 -58,8 +58,8 @@@
  
  private:
        std::string _pretty_name;
 -      libdcp::ContentKind _libdcp_kind;
 +      dcp::ContentKind _libdcp_kind;
-       std::string _dci_name;
+       std::string _isdcf_name;
  
        /** All available DCP content types */
        static std::vector<DCPContentType const *> _dcp_content_types;
index 9889d511cd45016c97b08746bce15856a953e265,4d886a6ddf224ceabc38d15369d9da1bb4261806..d3e0fa7b25687010a18cc2797db9aa9a76df3483
@@@ -21,11 -21,9 +21,11 @@@ extern "C" 
  #include <libavformat/avformat.h>
  }
  #include <libcxml/cxml.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/raw_convert.h>
  #include "ffmpeg_content.h"
  #include "ffmpeg_examiner.h"
 +#include "ffmpeg_subtitle_stream.h"
 +#include "ffmpeg_audio_stream.h"
  #include "compose.hpp"
  #include "job.h"
  #include "util.h"
@@@ -33,6 -31,7 +33,7 @@@
  #include "film.h"
  #include "log.h"
  #include "exceptions.h"
+ #include "frame_rate_change.h"
  
  #include "i18n.h"
  
@@@ -46,7 -45,7 +47,7 @@@ using std::cout
  using std::pair;
  using boost::shared_ptr;
  using boost::dynamic_pointer_cast;
 -using libdcp::raw_convert;
 +using dcp::raw_convert;
  
  int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
  int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
@@@ -63,7 -62,7 +64,7 @@@ FFmpegContent::FFmpegContent (shared_pt
  
  }
  
 -FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version, list<string>& notes)
 +FFmpegContent::FFmpegContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version, list<string>& notes)
        : Content (f, node)
        , VideoContent (f, node, version)
        , AudioContent (f, node)
@@@ -157,7 -156,7 +158,7 @@@ FFmpegContent::as_xml (xmlpp::Node* nod
        }
  
        if (_first_video) {
 -              node->add_child("FirstVideo")->add_child_text (raw_convert<string> (_first_video.get ()));
 +              node->add_child("FirstVideo")->add_child_text (raw_convert<string> (_first_video.get().get()));
        }
  }
  
@@@ -168,14 -167,14 +169,14 @@@ FFmpegContent::examine (shared_ptr<Job
  
        Content::examine (job);
  
 -      shared_ptr<const Film> film = _film.lock ();
 -      assert (film);
 -
        shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
 +      take_from_video_examiner (examiner);
  
 -      VideoContent::Frame video_length = 0;
 -      video_length = examiner->video_length ();
 -      LOG_GENERAL ("Video length obtained from header as %1 frames", video_length);
 +      ContentTime video_length = examiner->video_length ();
 +
 +      shared_ptr<const Film> film = _film.lock ();
 +      assert (film);
 +      LOG_GENERAL ("Video length obtained from header as %1 frames", video_length.frames (video_frame_rate ()));
  
        {
                boost::mutex::scoped_lock lm (_mutex);
                _first_video = examiner->first_video ();
        }
  
 -      take_from_video_examiner (examiner);
 -
        signal_changed (ContentProperty::LENGTH);
        signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
@@@ -236,13 -237,13 +237,13 @@@ FFmpegContent::technical_summary () con
  string
  FFmpegContent::information () const
  {
 -      if (video_length() == 0 || video_frame_rate() == 0) {
 +      if (video_length() == ContentTime (0) || video_frame_rate() == 0) {
                return "";
        }
        
        stringstream s;
        
 -      s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine(), video_frame_rate()) << "\n";
 +      s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine().frames (video_frame_rate()), video_frame_rate()) << "\n";
        s << VideoContent::information ();
  
        return s.str ();
@@@ -270,14 -271,19 +271,14 @@@ FFmpegContent::set_audio_stream (shared
        signal_changed (FFmpegContentProperty::AUDIO_STREAM);
  }
  
 -AudioContent::Frame
 +ContentTime
  FFmpegContent::audio_length () const
  {
 -      int const cafr = content_audio_frame_rate ();
 -      float const vfr = video_frame_rate ();
 -      VideoContent::Frame const vl = video_length_after_3d_combine ();
 -
 -      boost::mutex::scoped_lock lm (_mutex);
 -      if (!_audio_stream) {
 -              return 0;
 +      if (!audio_stream ()) {
 +              return ContentTime ();
        }
 -      
 -      return video_frames_to_audio_frames (vl, cafr, vfr);
 +
 +      return video_length ();
  }
  
  int
@@@ -293,7 -299,7 +294,7 @@@ FFmpegContent::audio_channels () cons
  }
  
  int
 -FFmpegContent::content_audio_frame_rate () const
 +FFmpegContent::audio_frame_rate () const
  {
        boost::mutex::scoped_lock lm (_mutex);
  
@@@ -316,12 -322,94 +317,12 @@@ operator!= (FFmpegStream const & a, FFm
        return a._id != b._id;
  }
  
 -FFmpegStream::FFmpegStream (shared_ptr<const cxml::Node> node)
 -      : name (node->string_child ("Name"))
 -      , _id (node->number_child<int> ("Id"))
 -{
 -
 -}
 -
 -void
 -FFmpegStream::as_xml (xmlpp::Node* root) const
 -{
 -      root->add_child("Name")->add_child_text (name);
 -      root->add_child("Id")->add_child_text (raw_convert<string> (_id));
 -}
 -
 -FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node, int version)
 -      : FFmpegStream (node)
 -      , mapping (node->node_child ("Mapping"), version)
 -{
 -      frame_rate = node->number_child<int> ("FrameRate");
 -      channels = node->number_child<int64_t> ("Channels");
 -      first_audio = node->optional_number_child<double> ("FirstAudio");
 -}
 -
 -void
 -FFmpegAudioStream::as_xml (xmlpp::Node* root) const
 -{
 -      FFmpegStream::as_xml (root);
 -      root->add_child("FrameRate")->add_child_text (raw_convert<string> (frame_rate));
 -      root->add_child("Channels")->add_child_text (raw_convert<string> (channels));
 -      if (first_audio) {
 -              root->add_child("FirstAudio")->add_child_text (raw_convert<string> (first_audio.get ()));
 -      }
 -      mapping.as_xml (root->add_child("Mapping"));
 -}
 -
 -bool
 -FFmpegStream::uses_index (AVFormatContext const * fc, int index) const
 -{
 -      size_t i = 0;
 -      while (i < fc->nb_streams) {
 -              if (fc->streams[i]->id == _id) {
 -                      return int (i) == index;
 -              }
 -              ++i;
 -      }
 -
 -      return false;
 -}
 -
 -AVStream *
 -FFmpegStream::stream (AVFormatContext const * fc) const
 -{
 -      size_t i = 0;
 -      while (i < fc->nb_streams) {
 -              if (fc->streams[i]->id == _id) {
 -                      return fc->streams[i];
 -              }
 -              ++i;
 -      }
 -
 -      assert (false);
 -      return 0;
 -}
 -
 -/** Construct a SubtitleStream from a value returned from to_string().
 - *  @param t String returned from to_string().
 - *  @param v State file version.
 - */
 -FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
 -      : FFmpegStream (node)
 -{
 -      
 -}
 -
 -void
 -FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
 -{
 -      FFmpegStream::as_xml (root);
 -}
 -
 -Time
 +DCPTime
  FFmpegContent::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 / film->video_frame_rate ();
 +      return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate (), film->video_frame_rate ()));
  }
  
  AudioMapping
@@@ -351,7 -439,7 +352,7 @@@ voi
  FFmpegContent::set_audio_mapping (AudioMapping m)
  {
        audio_stream()->mapping = m;
 -      signal_changed (AudioContentProperty::AUDIO_MAPPING);
 +      AudioContent::set_audio_mapping (m);
  }
  
  string
@@@ -394,18 -482,3 +395,21 @@@ FFmpegContent::audio_analysis_path () c
        p /= name;
        return p;
  }
 +
 +bool
 +FFmpegContent::has_subtitle_during (ContentTimePeriod period) const
 +{
 +      shared_ptr<FFmpegSubtitleStream> stream = subtitle_stream ();
++      if (!stream) {
++              return false;
++      }
 +
 +      /* XXX: inefficient */
 +      for (vector<ContentTimePeriod>::const_iterator i = stream->periods.begin(); i != stream->periods.end(); ++i) {
 +              if (i->from <= period.to && i->to >= period.from) {
 +                      return true;
 +              }
 +      }
 +
 +      return false;
 +}
diff --combined src/lib/film.cc
index 25730ae1cbe8bff7e07da16c6eced81e099c7c79,609003bbae7f2f632c1d190da567b70ce6133e18..14735d1b1048be4fa5c507e1fcf2f968eb4f6800
  #include <unistd.h>
  #include <boost/filesystem.hpp>
  #include <boost/algorithm/string.hpp>
 -#include <boost/date_time.hpp>
 +#include <boost/lexical_cast.hpp>
  #include <libxml++/libxml++.h>
  #include <libcxml/cxml.h>
 -#include <libdcp/signer_chain.h>
 -#include <libdcp/cpl.h>
 -#include <libdcp/signer.h>
 -#include <libdcp/util.h>
 -#include <libdcp/kdm.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/signer_chain.h>
 +#include <dcp/cpl.h>
 +#include <dcp/signer.h>
 +#include <dcp/util.h>
 +#include <dcp/local_time.h>
 +#include <dcp/raw_convert.h>
  #include "film.h"
  #include "job.h"
  #include "util.h"
@@@ -77,9 -77,9 +77,9 @@@ using boost::to_upper_copy
  using boost::ends_with;
  using boost::starts_with;
  using boost::optional;
 -using libdcp::Size;
 -using libdcp::Signer;
 -using libdcp::raw_convert;
 +using dcp::Size;
 +using dcp::Signer;
 +using dcp::raw_convert;
  
  #define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
  #define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, Log::TYPE_GENERAL);
   * Subtitle offset changed to subtitle y offset, and subtitle x offset added.
   * 7 -> 8
   * Use <Scale> tag in <VideoContent> rather than <Ratio>.
+  * 8 -> 9
+  * DCI -> ISDCF
 + *
 + * Bumped to 32 for 2.0 branch; some times are expressed in Times rather
 + * than frames now.
   */
 -int const Film::current_state_version = 9;
 +int const Film::current_state_version = 32;
  
  /** Construct a Film object in a given directory.
   *
  
  Film::Film (boost::filesystem::path dir, bool log)
        : _playlist (new Playlist)
-       , _use_dci_name (true)
+       , _use_isdcf_name (true)
        , _dcp_content_type (Config::instance()->default_dcp_content_type ())
        , _container (Config::instance()->default_container ())
        , _resolution (RESOLUTION_2K)
        , _signed (true)
        , _encrypted (false)
        , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ())
-       , _dci_metadata (Config::instance()->default_dci_metadata ())
+       , _isdcf_metadata (Config::instance()->default_isdcf_metadata ())
        , _video_frame_rate (24)
        , _audio_channels (6)
        , _three_d (false)
        , _state_version (current_state_version)
        , _dirty (false)
  {
-       set_dci_date_today ();
+       set_isdcf_date_today ();
  
        _playlist->Changed.connect (bind (&Film::playlist_changed, this));
        _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
@@@ -185,6 -184,10 +187,10 @@@ Film::video_identifier () cons
                s << "_3D";
        }
  
+       if (_with_subtitles) {
+               s << "_WS";
+       }
        return s.str ();
  }
          
@@@ -248,7 -251,7 +254,7 @@@ Film::audio_analysis_dir () cons
  void
  Film::make_dcp ()
  {
-       set_dci_date_today ();
+       set_isdcf_date_today ();
        
        if (dcp_name().find ("/") != string::npos) {
                throw BadSettingError (_("name"), _("cannot contain slashes"));
@@@ -352,10 -355,10 +358,10 @@@ Film::metadata () cons
  
        root->add_child("Version")->add_child_text (raw_convert<string> (current_state_version));
        root->add_child("Name")->add_child_text (_name);
-       root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0");
+       root->add_child("UseISDCFName")->add_child_text (_use_isdcf_name ? "1" : "0");
  
        if (_dcp_content_type) {
-               root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
+               root->add_child("DCPContentType")->add_child_text (_dcp_content_type->isdcf_name ());
        }
  
        if (_container) {
        root->add_child("Scaler")->add_child_text (_scaler->id ());
        root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
        root->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth));
-       _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+       _isdcf_metadata.as_xml (root->add_child ("ISDCFMetadata"));
        root->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate));
-       root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
+       root->add_child("ISDCFDate")->add_child_text (boost::gregorian::to_iso_string (_isdcf_date));
        root->add_child("AudioChannels")->add_child_text (raw_convert<string> (_audio_channels));
        root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0");
        root->add_child("SequenceVideo")->add_child_text (_sequence_video ? "1" : "0");
@@@ -410,12 -413,20 +416,20 @@@ Film::read_metadata (
        }
        
        _name = f.string_child ("Name");
-       _use_dci_name = f.bool_child ("UseDCIName");
+       if (_state_version >= 9) {
+               _use_isdcf_name = f.bool_child ("UseISDCFName");
+               _isdcf_metadata = ISDCFMetadata (f.node_child ("ISDCFMetadata"));
+               _isdcf_date = boost::gregorian::from_undelimited_string (f.string_child ("ISDCFDate"));
+       } else {
+               _use_isdcf_name = f.bool_child ("UseDCIName");
+               _isdcf_metadata = ISDCFMetadata (f.node_child ("DCIMetadata"));
+               _isdcf_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
+       }
  
        {
                optional<string> c = f.optional_string_child ("DCPContentType");
                if (c) {
-                       _dcp_content_type = DCPContentType::from_dci_name (c.get ());
+                       _dcp_content_type = DCPContentType::from_isdcf_name (c.get ());
                }
        }
  
        _scaler = Scaler::from_id (f.string_child ("Scaler"));
        _with_subtitles = f.bool_child ("WithSubtitles");
        _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
-       _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
        _video_frame_rate = f.number_child<int> ("VideoFrameRate");
-       _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
        _signed = f.optional_bool_child("Signed").get_value_or (true);
        _encrypted = f.bool_child ("Encrypted");
        _audio_channels = f.number_child<int> ("AudioChannels");
        _sequence_video = f.bool_child ("SequenceVideo");
        _three_d = f.bool_child ("ThreeD");
        _interop = f.bool_child ("Interop");
 -      _key = libdcp::Key (f.string_child ("Key"));
 +      _key = dcp::Key (f.string_child ("Key"));
  
        list<string> notes;
        /* This method is the only one that can return notes (so far) */
@@@ -479,20 -488,26 +491,26 @@@ Film::file (boost::filesystem::path f) 
        return p;
  }
  
- /** @return a DCI-compliant name for a DCP of this film */
+ /** @return a ISDCF-compliant name for a DCP of this film */
  string
- Film::dci_name (bool if_created_now) const
+ Film::isdcf_name (bool if_created_now) const
  {
        stringstream d;
  
-       string fixed_name = to_upper_copy (name());
-       for (size_t i = 0; i < fixed_name.length(); ++i) {
-               if (fixed_name[i] == ' ') {
-                       fixed_name[i] = '-';
+       string raw_name = name ();
+       string fixed_name;
+       bool cap_next = true;
+       for (size_t i = 0; i < raw_name.length(); ++i) {
+               if (raw_name[i] == ' ') {
+                       cap_next = true;
+               } else if (cap_next) {
+                       fixed_name += toupper (raw_name[i]);
+                       cap_next = false;
+               } else {
+                       fixed_name += tolower (raw_name[i]);
                }
        }
  
-       /* Spec is that the name part should be maximum 14 characters, as I understand it */
        if (fixed_name.length() > 14) {
                fixed_name = fixed_name.substr (0, 14);
        }
        d << fixed_name;
  
        if (dcp_content_type()) {
-               d << "_" << dcp_content_type()->dci_name();
-               d << "-" << dci_metadata().content_version;
+               d << "_" << dcp_content_type()->isdcf_name();
+               d << "-" << isdcf_metadata().content_version;
+       }
+       ISDCFMetadata const dm = isdcf_metadata ();
+       if (dm.temp_version) {
+               d << "-Temp";
+       }
+       
+       if (dm.pre_release) {
+               d << "-Pre";
+       }
+       
+       if (dm.red_band) {
+               d << "-RedBand";
+       }
+       
+       if (!dm.chain.empty ()) {
+               d << "-" << dm.chain;
        }
  
        if (three_d ()) {
                d << "-3D";
        }
  
+       if (dm.two_d_version_of_three_d) {
+               d << "-2D";
+       }
+       if (!dm.mastered_luminance.empty ()) {
+               d << "-" << dm.mastered_luminance;
+       }
        if (video_frame_rate() != 24) {
                d << "-" << video_frame_rate();
        }
+       
        if (container()) {
-               d << "_" << container()->dci_name();
+               d << "_" << container()->isdcf_name();
        }
  
-       DCIMetadata const dm = dci_metadata ();
+       /* XXX: this only works for content which has been scaled to a given ratio,
+          and uses the first bit of content only.
+       */
+       /* The standard says we don't do this for trailers, for some strange reason */
 -      if (dcp_content_type() && dcp_content_type()->libdcp_kind() != libdcp::TRAILER) {
++      if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) {
+               ContentList cl = content ();
+               Ratio const * content_ratio = 0;
+               for (ContentList::const_iterator i = cl.begin(); i != cl.end(); ++i) {
+                       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
+                       if (vc && (content_ratio == 0 || vc->scale().ratio() != content_ratio)) {
+                               content_ratio = vc->scale().ratio();
+                       }
+               }
+               
+               if (content_ratio && content_ratio != container()) {
+                       d << "-" << content_ratio->isdcf_name();
+               }
+       }
  
        if (!dm.audio_language.empty ()) {
                d << "_" << dm.audio_language;
                break;
        }
  
-       d << "_" << resolution_to_string (_resolution);
+       /* XXX: HI/VI */
  
+       d << "_" << resolution_to_string (_resolution);
+       
        if (!dm.studio.empty ()) {
                d << "_" << dm.studio;
        }
        if (if_created_now) {
                d << "_" << boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
        } else {
-               d << "_" << boost::gregorian::to_iso_string (_dci_date);
+               d << "_" << boost::gregorian::to_iso_string (_isdcf_date);
        }
  
        if (!dm.facility.empty ()) {
                d << "_" << dm.facility;
        }
  
+       if (_interop) {
+               d << "_IOP";
+       } else {
+               d << "_SMPTE";
+       }
+       
+       if (three_d ()) {
+               d << "-3D";
+       }
        if (!dm.package_type.empty ()) {
                d << "_" << dm.package_type;
        }
  string
  Film::dcp_name (bool if_created_now) const
  {
-       if (use_dci_name()) {
-               return dci_name (if_created_now);
+       if (use_isdcf_name()) {
+               return isdcf_name (if_created_now);
        }
  
        return name();
@@@ -605,10 -676,10 +679,10 @@@ Film::set_name (string n
  }
  
  void
- Film::set_use_dci_name (bool u)
+ Film::set_use_isdcf_name (bool u)
  {
-       _use_dci_name = u;
-       signal_changed (USE_DCI_NAME);
+       _use_isdcf_name = u;
+       signal_changed (USE_ISDCF_NAME);
  }
  
  void
@@@ -654,10 -725,10 +728,10 @@@ Film::set_j2k_bandwidth (int b
  }
  
  void
- Film::set_dci_metadata (DCIMetadata m)
+ Film::set_isdcf_metadata (ISDCFMetadata m)
  {
-       _dci_metadata = m;
-       signal_changed (DCI_METADATA);
+       _isdcf_metadata = m;
+       signal_changed (ISDCF_METADATA);
  }
  
  void
@@@ -711,9 -782,9 +785,9 @@@ Film::signal_changed (Property p
  }
  
  void
- Film::set_dci_date_today ()
+ Film::set_isdcf_date_today ()
  {
-       _dci_date = boost::gregorian::day_clock::local_day ();
+       _isdcf_date = boost::gregorian::day_clock::local_day ();
  }
  
  boost::filesystem::path
@@@ -783,14 -854,11 +857,14 @@@ Film::cpls () cons
                        ) {
  
                        try {
 -                              libdcp::DCP dcp (*i);
 +                              dcp::DCP dcp (*i);
                                dcp.read ();
                                out.push_back (
                                        CPLSummary (
 -                                              i->path().leaf().string(), dcp.cpls().front()->id(), dcp.cpls().front()->name(), dcp.cpls().front()->filename()
 +                                              i->path().leaf().string(),
 +                                              dcp.cpls().front()->id(),
 +                                              dcp.cpls().front()->annotation_text(),
 +                                              dcp.cpls().front()->file()
                                                )
                                        );
                        } catch (...) {
@@@ -889,7 -957,7 +963,7 @@@ Film::move_content_later (shared_ptr<Co
        _playlist->move_later (c);
  }
  
 -Time
 +DCPTime
  Film::length () const
  {
        return _playlist->length ();
@@@ -901,18 -969,12 +975,18 @@@ Film::has_subtitles () cons
        return _playlist->has_subtitles ();
  }
  
 -OutputVideoFrame
 +int
  Film::best_video_frame_rate () const
  {
        return _playlist->best_dcp_frame_rate ();
  }
  
 +FrameRateChange
 +Film::active_frame_rate_change (DCPTime t) const
 +{
 +      return _playlist->active_frame_rate_change (t, video_frame_rate ());
 +}
 +
  void
  Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
  {
@@@ -931,7 -993,31 +1005,7 @@@ Film::playlist_changed (
        signal_changed (CONTENT);
  }     
  
 -OutputAudioFrame
 -Film::time_to_audio_frames (Time t) const
 -{
 -      return divide_with_round (t * audio_frame_rate (), TIME_HZ);
 -}
 -
 -OutputVideoFrame
 -Film::time_to_video_frames (Time t) const
 -{
 -      return divide_with_round (t * video_frame_rate (), TIME_HZ);
 -}
 -
 -Time
 -Film::audio_frames_to_time (OutputAudioFrame f) const
 -{
 -      return divide_with_round (f * TIME_HZ, audio_frame_rate ());
 -}
 -
 -Time
 -Film::video_frames_to_time (OutputVideoFrame f) const
 -{
 -      return divide_with_round (f * TIME_HZ, video_frame_rate ());
 -}
 -
 -OutputAudioFrame
 +int
  Film::audio_frame_rate () const
  {
        /* XXX */
@@@ -947,50 -1033,56 +1021,50 @@@ Film::set_sequence_video (bool s
  }
  
  /** @return Size of the largest possible image in whatever resolution we are using */
 -libdcp::Size
 +dcp::Size
  Film::full_frame () const
  {
        switch (_resolution) {
        case RESOLUTION_2K:
 -              return libdcp::Size (2048, 1080);
 +              return dcp::Size (2048, 1080);
        case RESOLUTION_4K:
 -              return libdcp::Size (4096, 2160);
 +              return dcp::Size (4096, 2160);
        }
  
        assert (false);
 -      return libdcp::Size ();
 +      return dcp::Size ();
  }
  
  /** @return Size of the frame */
 -libdcp::Size
 +dcp::Size
  Film::frame_size () const
  {
        return fit_ratio_within (container()->ratio(), full_frame ());
  }
  
 -/** @param from KDM from time in local time.
 - *  @param to KDM to time in local time.
 - */
 -libdcp::KDM
 +dcp::EncryptedKDM
  Film::make_kdm (
 -      shared_ptr<libdcp::Certificate> target,
 +      shared_ptr<dcp::Certificate> target,
        boost::filesystem::path cpl_file,
 -      boost::posix_time::ptime from,
 -      boost::posix_time::ptime until
 +      dcp::LocalTime from,
 +      dcp::LocalTime until
        ) const
  {
 -      shared_ptr<const Signer> signer = make_signer ();
 -
 -      time_t now = time (0);
 -      struct tm* tm = localtime (&now);
 -      string const issue_date = libdcp::tm_to_string (tm);
 -      
 -      return libdcp::KDM (cpl_file, signer, target, key (), from, until, "DCP-o-matic", issue_date);
 +      shared_ptr<const dcp::CPL> cpl (new dcp::CPL (cpl_file));
 +      return dcp::DecryptedKDM (
 +              cpl, from, until, "DCP-o-matic", cpl->content_title_text(), dcp::LocalTime().as_string()
 +              ).encrypt (make_signer(), target);
  }
  
 -list<libdcp::KDM>
 +list<dcp::EncryptedKDM>
  Film::make_kdms (
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
 -      boost::posix_time::ptime from,
 -      boost::posix_time::ptime until
 +      dcp::LocalTime from,
 +      dcp::LocalTime until
        ) const
  {
 -      list<libdcp::KDM> kdms;
 +      list<dcp::EncryptedKDM> kdms;
  
        for (list<shared_ptr<Screen> >::iterator i = screens.begin(); i != screens.end(); ++i) {
                kdms.push_back (make_kdm ((*i)->certificate, dcp, from, until));
  uint64_t
  Film::required_disk_space () const
  {
 -      return uint64_t (j2k_bandwidth() / 8) * length() / TIME_HZ;
 +      return uint64_t (j2k_bandwidth() / 8) * length().seconds();
  }
  
  /** This method checks the disk that the Film is on and tries to decide whether or not
@@@ -1023,3 -1115,10 +1097,3 @@@ Film::should_be_enough_disk_space (doub
        available = double (s.available) / 1073741824.0f;
        return (available - required) > 1;
  }
 -
 -FrameRateChange
 -Film::active_frame_rate_change (Time t) const
 -{
 -      return _playlist->active_frame_rate_change (t, video_frame_rate ());
 -}
 -
diff --combined src/lib/film.h
index d9d7e82fd2140a4cbf9fae20c69ea9e6badd4db6,96ea930ccd10a493e7c24099ebc487a77cdb8991..067588fa3f42a52d7988aa930840f6d0a7b31863
  #include <boost/signals2.hpp>
  #include <boost/enable_shared_from_this.hpp>
  #include <boost/filesystem.hpp>
 -#include <libdcp/key.h>
 -#include <libdcp/kdm.h>
 +#include <dcp/key.h>
 +#include <dcp/decrypted_kdm.h>
 +#include <dcp/encrypted_kdm.h>
  #include "util.h"
  #include "types.h"
- #include "dci_metadata.h"
+ #include "isdcf_metadata.h"
+ #include "frame_rate_change.h"
  
  class DCPContentType;
  class Log;
@@@ -46,6 -46,7 +47,7 @@@ class Playlist
  class AudioContent;
  class Scaler;
  class Screen;
+ class isdcf_name_test;
  
  /** @class Film
   *
@@@ -88,7 -89,7 +90,7 @@@ public
        void write_metadata () const;
        boost::shared_ptr<xmlpp::Document> metadata () const;
  
-       std::string dci_name (bool if_created_now) const;
+       std::string isdcf_name (bool if_created_now) const;
        std::string dcp_name (bool if_created_now = false) const;
  
        /** @return true if our state has changed since we last saved it */
                return _dirty;
        }
  
 -      libdcp::Size full_frame () const;
 -      libdcp::Size frame_size () const;
 +      dcp::Size full_frame () const;
 +      dcp::Size frame_size () const;
  
        std::vector<CPLSummary> cpls () const;
  
        boost::shared_ptr<Player> make_player () const;
        boost::shared_ptr<Playlist> playlist () const;
  
 -      OutputAudioFrame audio_frame_rate () const;
 -
 -      OutputAudioFrame time_to_audio_frames (Time) const;
 -      OutputVideoFrame time_to_video_frames (Time) const;
 -      Time video_frames_to_time (OutputVideoFrame) const;
 -      Time audio_frames_to_time (OutputAudioFrame) const;
 +      int audio_frame_rate () const;
  
        uint64_t required_disk_space () const;
        bool should_be_enough_disk_space (double &, double &) const;
        /* Proxies for some Playlist methods */
  
        ContentList content () const;
 -      Time length () const;
 +      DCPTime length () const;
        bool has_subtitles () const;
 -      OutputVideoFrame best_video_frame_rate () const;
 -      FrameRateChange active_frame_rate_change (Time) const;
 +      int best_video_frame_rate () const;
 +      FrameRateChange active_frame_rate_change (DCPTime) const;
  
 -      libdcp::KDM
 +      dcp::EncryptedKDM
        make_kdm (
 -              boost::shared_ptr<libdcp::Certificate> target,
 +              boost::shared_ptr<dcp::Certificate> target,
                boost::filesystem::path cpl_file,
 -              boost::posix_time::ptime from,
 -              boost::posix_time::ptime until
 +              dcp::LocalTime from,
 +              dcp::LocalTime until
                ) const;
        
 -      std::list<libdcp::KDM> make_kdms (
 +      std::list<dcp::EncryptedKDM> make_kdms (
                std::list<boost::shared_ptr<Screen> >,
                boost::filesystem::path cpl_file,
 -              boost::posix_time::ptime from,
 -              boost::posix_time::ptime until
 +              dcp::LocalTime from,
 +              dcp::LocalTime until
                ) const;
  
 -      libdcp::Key key () const {
 +      dcp::Key key () const {
                return _key;
        }
  
        enum Property {
                NONE,
                NAME,
-               USE_DCI_NAME,
+               USE_ISDCF_NAME,
                /** The playlist's content list has changed (i.e. content has been added, moved around or removed) */
                CONTENT,
                DCP_CONTENT_TYPE,
                SIGNED,
                ENCRYPTED,
                J2K_BANDWIDTH,
-               DCI_METADATA,
+               ISDCF_METADATA,
                VIDEO_FRAME_RATE,
                AUDIO_CHANNELS,
                /** The setting of _three_d has been changed */
                return _name;
        }
  
-       bool use_dci_name () const {
-               return _use_dci_name;
+       bool use_isdcf_name () const {
+               return _use_isdcf_name;
        }
  
        DCPContentType const * dcp_content_type () const {
                return _j2k_bandwidth;
        }
  
-       DCIMetadata dci_metadata () const {
-               return _dci_metadata;
+       ISDCFMetadata isdcf_metadata () const {
+               return _isdcf_metadata;
        }
  
        /** @return The frame rate of the DCP */
  
        void set_directory (boost::filesystem::path);
        void set_name (std::string);
-       void set_use_dci_name (bool);
+       void set_use_isdcf_name (bool);
        void examine_and_add_content (boost::shared_ptr<Content>);
        void add_content (boost::shared_ptr<Content>);
        void remove_content (boost::shared_ptr<Content>);
        void set_signed (bool);
        void set_encrypted (bool);
        void set_j2k_bandwidth (int);
-       void set_dci_metadata (DCIMetadata);
+       void set_isdcf_metadata (ISDCFMetadata);
        void set_video_frame_rate (int);
        void set_audio_channels (int);
        void set_three_d (bool);
-       void set_dci_date_today ();
+       void set_isdcf_date_today ();
        void set_sequence_video (bool);
        void set_interop (bool);
  
  
  private:
  
+       friend class ::isdcf_name_test;
        void signal_changed (Property);
        std::string video_identifier () const;
        void playlist_changed ();
        
        /** Name for DCP-o-matic */
        std::string _name;
-       /** True if a auto-generated DCI-compliant name should be used for our DCP */
-       bool _use_dci_name;
+       /** True if a auto-generated ISDCF-compliant name should be used for our DCP */
+       bool _use_isdcf_name;
        /** The type of content that this Film represents (feature, trailer etc.) */
        DCPContentType const * _dcp_content_type;
        /** The container to put this Film in (flat, scope, etc.) */
        bool _encrypted;
        /** bandwidth for J2K files in bits per second */
        int _j2k_bandwidth;
-       /** DCI naming stuff */
-       DCIMetadata _dci_metadata;
+       /** ISDCF naming stuff */
+       ISDCFMetadata _isdcf_metadata;
        /** Frames per second to run our DCP at */
        int _video_frame_rate;
-       /** The date that we should use in a DCI name */
-       boost::gregorian::date _dci_date;
+       /** The date that we should use in a ISDCF name */
+       boost::gregorian::date _isdcf_date;
        /** Number of audio channels to put in the DCP */
        int _audio_channels;
        /** If true, the DCP will be written in 3D mode; otherwise in 2D.
        bool _three_d;
        bool _sequence_video;
        bool _interop;
 -      libdcp::Key _key;
 +      dcp::Key _key;
  
        int _state_version;
  
index 6165f684010473e59f458d61ac656eb222ec23e5,f53adc05925c1dea9fa461a491c54f2c111b6f23..92af0ec0174635586161726426ed0295a7e512e7
@@@ -17,6 -17,9 +17,9 @@@
  
  */
  
+ #ifndef DCPOMATIC_FRAME_RATE_CHANGE_H
+ #define DCPOMATIC_FRAME_RATE_CHANGE_H
  #include <string>
  
  struct FrameRateChange
@@@ -34,6 -37,9 +37,6 @@@
                return repeat;
        }
  
 -      float source;
 -      int dcp;
 -
        /** true to skip every other frame */
        bool skip;
        /** number of times to use each frame (e.g. 1 is normal, 2 means repeat each frame once, and so on) */
@@@ -56,3 -62,5 +59,5 @@@
  
        std::string description;
  };
+ #endif
diff --combined src/lib/image_content.cc
index 07f047ddd53bfc9c4651dca19d2b1b12fe8f753d,6acf0bab924001eaa5b51e1d1349ce134e5e719c..8909240dc334e3d793e2ff64cc7d8b4922c8a1f1
@@@ -24,6 -24,7 +24,7 @@@
  #include "compose.hpp"
  #include "film.h"
  #include "job.h"
+ #include "frame_rate_change.h"
  
  #include "i18n.h"
  
@@@ -54,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)
  {
@@@ -114,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
@@@ -137,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/image_proxy.cc
index 16bd92f6ece1ea0f933cc0b09ec8a168533c0ece,4e2f8a1354f9d8ccbc8b6f9cf1de5905935758cd..1eb9c169c5c3b375d4677b61f6b19bdc58af71db
@@@ -18,8 -18,8 +18,8 @@@
  */
  
  #include <Magick++.h>
 -#include <libdcp/util.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/util.h>
 +#include <dcp/raw_convert.h>
  #include "image_proxy.h"
  #include "image.h"
  #include "exceptions.h"
@@@ -51,11 -51,11 +51,11 @@@ RawImageProxy::RawImageProxy (shared_pt
  RawImageProxy::RawImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log)
        : ImageProxy (log)
  {
 -      libdcp::Size size (
 +      dcp::Size size (
                xml->number_child<int> ("Width"), xml->number_child<int> ("Height")
                );
  
-       _image.reset (new Image (PIX_FMT_RGB24, size, true));
+       _image.reset (new Image (static_cast<AVPixelFormat> (xml->number_child<int> ("PixelFormat")), size, true));
        _image->read_from_socket (socket);
  }
  
@@@ -69,8 -69,9 +69,9 @@@ voi
  RawImageProxy::add_metadata (xmlpp::Node* node) const
  {
        node->add_child("Type")->add_child_text (N_("Raw"));
 -      node->add_child("Width")->add_child_text (libdcp::raw_convert<string> (_image->size().width));
 -      node->add_child("Height")->add_child_text (libdcp::raw_convert<string> (_image->size().height));
 -      node->add_child("PixelFormat")->add_child_text (libdcp::raw_convert<string> (_image->pixel_format ()));
 +      node->add_child("Width")->add_child_text (dcp::raw_convert<string> (_image->size().width));
 +      node->add_child("Height")->add_child_text (dcp::raw_convert<string> (_image->size().height));
++      node->add_child("PixelFormat")->add_child_text (dcp::raw_convert<string> (_image->pixel_format ()));
  }
  
  void
@@@ -127,27 -128,20 +128,19 @@@ MagickImageProxy::image () cons
                throw DecodeError (_("Could not decode image file"));
        }
  
 +      dcp::Size size (magick_image->columns(), magick_image->rows());
        LOG_TIMING ("[%1] MagickImageProxy decode finished", boost::this_thread::get_id ());
  
 -      libdcp::Size size (magick_image->columns(), magick_image->rows());
 -
        _image.reset (new Image (PIX_FMT_RGB24, size, true));
  
-       using namespace MagickCore;
-       
+       /* Write line-by-line here as _image must be aligned, and write() cannot be told about strides */
        uint8_t* p = _image->data()[0];
-       for (int y = 0; y < size.height; ++y) {
-               uint8_t* q = p;
-               for (int x = 0; x < size.width; ++x) {
-                       Magick::Color c = magick_image->pixelColor (x, y);
-                       *q++ = c.redQuantum() * 255 / QuantumRange;
-                       *q++ = c.greenQuantum() * 255 / QuantumRange;
-                       *q++ = c.blueQuantum() * 255 / QuantumRange;
-               }
+       for (int i = 0; i < size.height; ++i) {
+               using namespace MagickCore;
+               magick_image->write (0, i, size.width, 1, "RGB", CharPixel, p);
                p += _image->stride()[0];
        }
  
-       delete magick_image;
        LOG_TIMING ("[%1] MagickImageProxy completes decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length());
  
        return _image;
index 0000000000000000000000000000000000000000,dfba50a9a2262ecf02c9e95a026cc9d2abab77a7..7d960b6ac758538d0728b68501a803e0368dcc7d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,88 +1,88 @@@
 -#include <libdcp/raw_convert.h>
+ /*
+     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
+     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 <iostream>
+ #include <libcxml/cxml.h>
 -using libdcp::raw_convert;
++#include <dcp/raw_convert.h>
+ #include "isdcf_metadata.h"
+ #include "i18n.h"
+ using std::string;
+ using boost::shared_ptr;
 -ISDCFMetadata::ISDCFMetadata (shared_ptr<const cxml::Node> node)
++using dcp::raw_convert;
++ISDCFMetadata::ISDCFMetadata (cxml::ConstNodePtr node)
+ {
+       content_version = node->number_child<int> ("ContentVersion");
+       audio_language = node->string_child ("AudioLanguage");
+       subtitle_language = node->string_child ("SubtitleLanguage");
+       territory = node->string_child ("Territory");
+       rating = node->string_child ("Rating");
+       studio = node->string_child ("Studio");
+       facility = node->string_child ("Facility");
+       package_type = node->string_child ("PackageType");
+       /* This stuff was added later */
+       temp_version = node->optional_bool_child ("TempVersion").get_value_or (false);
+       pre_release = node->optional_bool_child ("PreRelease").get_value_or (false);
+       red_band = node->optional_bool_child ("RedBand").get_value_or (false);
+       chain = node->optional_string_child ("Chain").get_value_or ("");
+       two_d_version_of_three_d = node->optional_bool_child ("TwoDVersionOfThreeD").get_value_or (false);
+       mastered_luminance = node->optional_string_child ("MasteredLuminance").get_value_or ("");
+ }
+ void
+ ISDCFMetadata::as_xml (xmlpp::Node* root) const
+ {
+       root->add_child("ContentVersion")->add_child_text (raw_convert<string> (content_version));
+       root->add_child("AudioLanguage")->add_child_text (audio_language);
+       root->add_child("SubtitleLanguage")->add_child_text (subtitle_language);
+       root->add_child("Territory")->add_child_text (territory);
+       root->add_child("Rating")->add_child_text (rating);
+       root->add_child("Studio")->add_child_text (studio);
+       root->add_child("Facility")->add_child_text (facility);
+       root->add_child("PackageType")->add_child_text (package_type);
+       root->add_child("TempVersion")->add_child_text (temp_version ? "1" : "0");
+       root->add_child("PreRelease")->add_child_text (pre_release ? "1" : "0");
+       root->add_child("RedBand")->add_child_text (red_band ? "1" : "0");
+       root->add_child("Chain")->add_child_text (chain);
+       root->add_child("TwoDVersionOfThreeD")->add_child_text (two_d_version_of_three_d ? "1" : "0");
+       root->add_child("MasteredLuminance")->add_child_text (mastered_luminance);
+ }
+ void
+ ISDCFMetadata::read_old_metadata (string k, string v)
+ {
+       if (k == N_("audio_language")) {
+               audio_language = v;
+       } else if (k == N_("subtitle_language")) {
+               subtitle_language = v;
+       } else if (k == N_("territory")) {
+               territory = v;
+       } else if (k == N_("rating")) {
+               rating = v;
+       } else if (k == N_("studio")) {
+               studio = v;
+       } else if (k == N_("facility")) {
+               facility = v;
+       } else if (k == N_("package_type")) {
+               package_type = v;
+       }
+ }     
diff --combined src/lib/isdcf_metadata.h
index 0000000000000000000000000000000000000000,0fb7e7baa0e1292468e0573d5d7424e7e7c165b2..e63f290e47de854ed9347433739eafc57564015d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,68 +1,65 @@@
 -
 -namespace cxml {
 -      class Node;
 -}
+ /*
+     Copyright (C) 2012 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.
+ */
+ #ifndef DCPOMATIC_ISDCF_METADATA_H
+ #define DCPOMATIC_ISDCF_METADATA_H
+ #include <string>
+ #include <libxml++/libxml++.h>
 -      ISDCFMetadata (boost::shared_ptr<const cxml::Node>);
++#include <libcxml/cxml.h>
+ class ISDCFMetadata
+ {
+ public:
+       ISDCFMetadata ()
+               : content_version (1)
+               , temp_version (false)
+               , pre_release (false)
+               , red_band (false)
+               , two_d_version_of_three_d (false)
+       {}
+       
++      ISDCFMetadata (cxml::ConstNodePtr);
+       void as_xml (xmlpp::Node *) const;
+       void read_old_metadata (std::string, std::string);
+       int content_version;
+       std::string audio_language;
+       std::string subtitle_language;
+       std::string territory;
+       std::string rating;
+       std::string studio;
+       std::string facility;
+       std::string package_type;
+       /** true if this is a temporary version (without final picture or sound) */
+       bool temp_version;
+       /** true if this is a pre-release version (final picture and sound, but without accessibility features) */
+       bool pre_release;
+       /** true if this has adult content */
+       bool red_band;
+       /** specific theatre chain or event */
+       std::string chain;
+       /** true if this is a 2D version of content that also exists in 3D */
+       bool two_d_version_of_three_d;
+       /** mastered luminance if there are multiple versions distributed (e.g. 35, 4fl, 6fl etc.) */
+       std::string mastered_luminance;
+ };
+ #endif
diff --combined src/lib/player.cc
index c3489b7e1f227d3c6645cce9ab65fcf06348f51c,7bf78c905248487e2e9d3723c38d267827e4a11b..d0eb27aa31cc4ab0eb404deec5ac47157dbf330f
  */
  
  #include <stdint.h>
 +#include <algorithm>
  #include "player.h"
  #include "film.h"
  #include "ffmpeg_decoder.h"
 +#include "audio_buffers.h"
  #include "ffmpeg_content.h"
  #include "image_decoder.h"
  #include "image_content.h"
  #include "sndfile_decoder.h"
  #include "sndfile_content.h"
  #include "subtitle_content.h"
 +#include "subrip_decoder.h"
 +#include "subrip_content.h"
  #include "playlist.h"
  #include "job.h"
  #include "image.h"
  #include "image_proxy.h"
  #include "ratio.h"
 -#include "resampler.h"
  #include "log.h"
  #include "scaler.h"
 +#include "render_subtitles.h"
 +#include "config.h"
 +#include "content_video.h"
  #include "player_video_frame.h"
+ #include "frame_rate_change.h"
  
  #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
  
@@@ -49,22 -44,23 +50,22 @@@ using std::list
  using std::cout;
  using std::min;
  using std::max;
 +using std::min;
  using std::vector;
  using std::pair;
  using std::map;
 +using std::make_pair;
  using boost::shared_ptr;
  using boost::weak_ptr;
  using boost::dynamic_pointer_cast;
 +using boost::optional;
  
  Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
        : _film (f)
        , _playlist (p)
 -      , _video (true)
 -      , _audio (true)
        , _have_valid_pieces (false)
 -      , _video_position (0)
 -      , _audio_position (0)
 -      , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
 -      , _last_emit_was_black (false)
 +      , _approximate_size (false)
 +      , _burn_subtitles (false)
  {
        _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
        _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
  }
  
  void
 -Player::disable_video ()
 -{
 -      _video = false;
 -}
 -
 -void
 -Player::disable_audio ()
 +Player::setup_pieces ()
  {
 -      _audio = false;
 -}
 +      list<shared_ptr<Piece> > old_pieces = _pieces;
 +      _pieces.clear ();
  
 -bool
 -Player::pass ()
 -{
 -      if (!_have_valid_pieces) {
 -              setup_pieces ();
 -      }
 +      ContentList content = _playlist->content ();
  
 -      Time earliest_t = TIME_MAX;
 -      shared_ptr<Piece> earliest;
 -      enum {
 -              VIDEO,
 -              AUDIO
 -      } type = VIDEO;
 +      for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
  
 -      for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
 -              if ((*i)->decoder->done () || (*i)->content->length_after_trim() == 0) {
 +              if (!(*i)->paths_valid ()) {
                        continue;
                }
 -
 -              shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
 -              shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
 -
 -              if (_video && vd) {
 -                      if ((*i)->video_position < earliest_t) {
 -                              earliest_t = (*i)->video_position;
 -                              earliest = *i;
 -                              type = VIDEO;
 +              
 +              shared_ptr<Decoder> decoder;
 +              optional<FrameRateChange> frc;
 +
 +              /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
 +              DCPTime best_overlap_t;
 +              shared_ptr<VideoContent> best_overlap;
 +              for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
 +                      shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
 +                      if (!vc) {
 +                              continue;
                        }
 -              }
 -
 -              if (_audio && ad && ad->has_audio ()) {
 -                      if ((*i)->audio_position < earliest_t) {
 -                              earliest_t = (*i)->audio_position;
 -                              earliest = *i;
 -                              type = AUDIO;
 +                      
 +                      DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end());
 +                      if (overlap > best_overlap_t) {
 +                              best_overlap = vc;
 +                              best_overlap_t = overlap;
                        }
                }
 -      }
 -
 -      if (!earliest) {
 -              flush ();
 -              return true;
 -      }
  
 -      switch (type) {
 -      case VIDEO:
 -              if (earliest_t > _video_position) {
 -                      emit_black ();
 +              optional<FrameRateChange> best_overlap_frc;
 +              if (best_overlap) {
 +                      best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
                } else {
 -                      if (earliest->repeating ()) {
 -                              earliest->repeat (this);
 -                      } else {
 -                              earliest->decoder->pass ();
 -                      }
 +                      /* No video overlap; e.g. if the DCP is just audio */
 +                      best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
                }
 -              break;
  
 -      case AUDIO:
 -              if (earliest_t > _audio_position) {
 -                      emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
 -              } else {
 -                      earliest->decoder->pass ();
 -
 -                      if (earliest->decoder->done()) {
 -                              shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
 -                              assert (ac);
 -                              shared_ptr<Resampler> re = resampler (ac, false);
 -                              if (re) {
 -                                      shared_ptr<const AudioBuffers> b = re->flush ();
 -                                      if (b->frames ()) {
 -                                              process_audio (earliest, b, ac->audio_length (), true);
 -                                      }
 -                              }
 -                      }
 +              /* FFmpeg */
 +              shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
 +              if (fc) {
 +                      decoder.reset (new FFmpegDecoder (fc, _film->log()));
 +                      frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
                }
 -              break;
 -      }
  
 -      if (_audio) {
 -              boost::optional<Time> audio_done_up_to;
 -              for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
 -                      if ((*i)->decoder->done ()) {
 -                              continue;
 +              /* ImageContent */
 +              shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
 +              if (ic) {
 +                      /* See if we can re-use an old ImageDecoder */
 +                      for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
 +                              shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
 +                              if (imd && imd->content() == ic) {
 +                                      decoder = imd;
 +                              }
                        }
  
 -                      shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
 -                      if (ad && ad->has_audio ()) {
 -                              audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
 +                      if (!decoder) {
 +                              decoder.reset (new ImageDecoder (ic));
                        }
 +
 +                      frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
 +              }
 +
 +              /* SndfileContent */
 +              shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
 +              if (sc) {
 +                      decoder.reset (new SndfileDecoder (sc));
 +                      frc = best_overlap_frc;
                }
  
 -              if (audio_done_up_to) {
 -                      TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ());
 -                      Audio (tb.audio, tb.time);
 -                      _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
 +              /* SubRipContent */
 +              shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
 +              if (rc) {
 +                      decoder.reset (new SubRipDecoder (rc));
 +                      frc = best_overlap_frc;
                }
 +
 +              _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
        }
 -              
 -      return false;
 +
 +      _have_valid_pieces = true;
  }
  
 -/** @param extra Amount of extra time to add to the content frame's time (for repeat) */
  void
 -Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const ImageProxy> image, Eyes eyes, Part part, bool same, VideoContent::Frame frame, Time extra)
 +Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
  {
 -      /* Keep a note of what came in so that we can repeat it if required */
 -      _last_incoming_video.weak_piece = weak_piece;
 -      _last_incoming_video.image = image;
 -      _last_incoming_video.eyes = eyes;
 -      _last_incoming_video.part = part;
 -      _last_incoming_video.same = same;
 -      _last_incoming_video.frame = frame;
 -      _last_incoming_video.extra = extra;
 -      
 -      shared_ptr<Piece> piece = weak_piece.lock ();
 -      if (!piece) {
 -              return;
 -      }
 -
 -      shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
 -      assert (content);
 -
 -      FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
 -      if (frc.skip && (frame % 2) == 1) {
 -              return;
 -      }
 -
 -      Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
 -      if (content->trimmed (relative_time)) {
 +      shared_ptr<Content> c = w.lock ();
 +      if (!c) {
                return;
        }
  
 -      Time const time = content->position() + relative_time + extra - content->trim_start ();
 -      libdcp::Size const image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
 -
 -      shared_ptr<PlayerVideoFrame> pi (
 -              new PlayerVideoFrame (
 -                      image,
 -                      content->crop(),
 -                      image_size,
 -                      _video_container_size,
 -                      _film->scaler(),
 -                      eyes,
 -                      part,
 -                      content->colour_conversion()
 -                      )
 -              );
 -      
 -      if (_film->with_subtitles ()) {
 -              for (list<Subtitle>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
 -                      if (i->covers (time)) {
 -                              /* This may be true for more than one of _subtitles, but the last (latest-starting)
 -                                 one is the one we want to use, so that's ok.
 -                              */
 -                              Position<int> const container_offset (
 -                                      (_video_container_size.width - image_size.width) / 2,
 -                                      (_video_container_size.height - image_size.width) / 2
 -                                      );
 -                              
 -                              pi->set_subtitle (i->out_image(), i->out_position() + container_offset);
 -                      }
 -              }
 -      }
 -
 -      /* Clear out old subtitles */
 -      for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ) {
 -              list<Subtitle>::iterator j = i;
 -              ++j;
 +      if (
 +              property == ContentProperty::POSITION ||
 +              property == ContentProperty::LENGTH ||
 +              property == ContentProperty::TRIM_START ||
 +              property == ContentProperty::TRIM_END ||
 +              property == ContentProperty::PATH ||
 +              property == VideoContentProperty::VIDEO_FRAME_TYPE
 +              ) {
                
 -              if (i->ends_before (time)) {
 -                      _subtitles.erase (i);
 -              }
 -
 -              i = j;
 -      }
 -
 -#ifdef DCPOMATIC_DEBUG
 -      _last_video = piece->content;
 -#endif
 -
 -      Video (pi, same, time);
 -
 -      _last_emit_was_black = false;
 -      _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
 +              _have_valid_pieces = false;
 +              Changed (frequent);
  
 -      if (frc.repeat > 1 && !piece->repeating ()) {
 -              piece->set_repeat (_last_incoming_video, frc.repeat - 1);
 +      } else if (
 +              property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
 +              property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
 +              property == SubtitleContentProperty::SUBTITLE_SCALE ||
 +              property == VideoContentProperty::VIDEO_CROP ||
 +              property == VideoContentProperty::VIDEO_SCALE ||
 +              property == VideoContentProperty::VIDEO_FRAME_RATE
 +              ) {
 +              
 +              Changed (frequent);
        }
  }
  
  /** @param already_resampled true if this data has already been through the chain up to the resampler */
  void
 -Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame, bool already_resampled)
 +Player::playlist_changed ()
  {
 -      shared_ptr<Piece> piece = weak_piece.lock ();
 -      if (!piece) {
 -              return;
 -      }
 +      _have_valid_pieces = false;
 +      Changed (false);
 +}
  
 -      shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
 -      assert (content);
 +void
 +Player::set_video_container_size (dcp::Size s)
 +{
 +      _video_container_size = s;
  
 -      if (!already_resampled) {
 -              /* Gain */
 -              if (content->audio_gain() != 0) {
 -                      shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
 -                      gain->apply_gain (content->audio_gain ());
 -                      audio = gain;
 -              }
 -              
 -              /* Resample */
 -              if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
 -                      shared_ptr<Resampler> r = resampler (content, true);
 -                      pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
 -                      audio = ro.first;
 -                      frame = ro.second;
 -              }
 -      }
 -      
 -      Time const relative_time = _film->audio_frames_to_time (frame);
 +      _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
 +      _black_image->make_black ();
 +}
  
 -      if (content->trimmed (relative_time)) {
 -              return;
 +void
 +Player::film_changed (Film::Property p)
 +{
 +      /* Here we should notice Film properties that affect our output, and
 +         alert listeners that our output now would be different to how it was
 +         last time we were run.
 +      */
 +
 +      if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
 +              Changed (false);
        }
 +}
  
 -      Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
 +list<PositionImage>
 +Player::process_content_image_subtitles (shared_ptr<SubtitleContent> content, list<shared_ptr<ContentImageSubtitle> > subs) const
 +{
 +      list<PositionImage> all;
        
 -      /* Remap channels */
 -      shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
 -      dcp_mapped->make_silent ();
 -
 -      AudioMapping map = content->audio_mapping ();
 -      for (int i = 0; i < map.content_channels(); ++i) {
 -              for (int j = 0; j < _film->audio_channels(); ++j) {
 -                      if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) {
 -                              dcp_mapped->accumulate_channel (
 -                                      audio.get(),
 -                                      i,
 -                                      static_cast<libdcp::Channel> (j),
 -                                      map.get (i, static_cast<libdcp::Channel> (j))
 -                                      );
 -                      }
 +      for (list<shared_ptr<ContentImageSubtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
 +              if (!(*i)->image) {
 +                      continue;
                }
 +
 +              dcpomatic::Rect<double> in_rect = (*i)->rectangle;
 +              dcp::Size scaled_size;
 +              
 +              in_rect.x += content->subtitle_x_offset ();
 +              in_rect.y += content->subtitle_y_offset ();
 +              
 +              /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
 +              scaled_size.width = in_rect.width * _video_container_size.width * content->subtitle_scale ();
 +              scaled_size.height = in_rect.height * _video_container_size.height * content->subtitle_scale ();
 +              
 +              /* Then we need a corrective translation, consisting of two parts:
 +               *
 +               * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
 +               *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
 +               *
 +               * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
 +               *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
 +               *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
 +               *
 +               * Combining these two translations gives these expressions.
 +               */
 +
 +              all.push_back (
 +                      PositionImage (
 +                              (*i)->image->scale (
 +                                      scaled_size,
 +                                      Scaler::from_id ("bicubic"),
 +                                      (*i)->image->pixel_format (),
 +                                      true
 +                                      ),
 +                              Position<int> (
 +                                      rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - content->subtitle_scale ()) / 2))),
 +                                      rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - content->subtitle_scale ()) / 2)))
 +                                      )
 +                              )
 +                      );
        }
  
 -      audio = dcp_mapped;
 +      return all;
 +}
  
 -      /* We must cut off anything that comes before the start of all time */
 -      if (time < 0) {
 -              int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
 -              if (frames >= audio->frames ()) {
 -                      return;
 +list<PositionImage>
 +Player::process_content_text_subtitles (list<shared_ptr<ContentTextSubtitle> > sub) const
 +{
 +      list<PositionImage> all;
 +      for (list<shared_ptr<ContentTextSubtitle> >::const_iterator i = sub.begin(); i != sub.end(); ++i) {
 +              if (!(*i)->subs.empty ()) {
 +                      all.push_back (render_subtitles ((*i)->subs, _video_container_size));
                }
 -
 -              shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
 -              trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
 -
 -              audio = trimmed;
 -              time = 0;
        }
  
 -      _audio_merger.push (audio, time);
 -      piece->audio_position += _film->audio_frames_to_time (audio->frames ());
 +      return all;
  }
  
  void
 -Player::flush ()
 +Player::set_approximate_size ()
  {
 -      TimedAudioBuffers<Time> tb = _audio_merger.flush ();
 -      if (_audio && tb.audio) {
 -              Audio (tb.audio, tb.time);
 -              _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
 -      }
 +      _approximate_size = true;
 +}
  
 -      while (_video && _video_position < _audio_position) {
 -              emit_black ();
 -      }
 +shared_ptr<PlayerVideoFrame>
 +Player::black_player_video_frame () const
 +{
 +      return shared_ptr<PlayerVideoFrame> (
 +              new PlayerVideoFrame (
 +                      shared_ptr<const ImageProxy> (new RawImageProxy (_black_image, _film->log ())),
 +                      Crop (),
 +                      _video_container_size,
 +                      _video_container_size,
 +                      Scaler::from_id ("bicubic"),
 +                      EYES_BOTH,
 +                      PART_WHOLE,
 +                      Config::instance()->colour_conversions().front().conversion
 +              )
 +      );
 +}
  
 -      while (_audio && _audio_position < _video_position) {
 -              emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
 +shared_ptr<PlayerVideoFrame>
 +Player::content_to_player_video_frame (
 +      shared_ptr<VideoContent> content,
 +      ContentVideo content_video,
 +      list<shared_ptr<Piece> > subs,
 +      DCPTime time,
 +      dcp::Size image_size) const
 +{
 +      shared_ptr<PlayerVideoFrame> pvf (
 +              new PlayerVideoFrame (
 +                      content_video.image,
 +                      content->crop (),
 +                      image_size,
 +                      _video_container_size,
 +                      _film->scaler(),
 +                      content_video.eyes,
 +                      content_video.part,
 +                      content->colour_conversion ()
 +                      )
 +              );
 +      
 +      
 +      /* Add subtitles */
 +      
 +      list<PositionImage> sub_images;
 +      
 +      for (list<shared_ptr<Piece> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
 +              shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*i)->decoder);
 +              shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*i)->content);
 +              ContentTime const from = dcp_to_content_subtitle (*i, time);
 +              ContentTime const to = from + ContentTime::from_frames (1, content->video_frame_rate ());
 +              
 +              list<shared_ptr<ContentImageSubtitle> > image_subtitles = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to));
 +              if (!image_subtitles.empty ()) {
 +                      list<PositionImage> im = process_content_image_subtitles (
 +                              subtitle_content,
 +                              image_subtitles
 +                              );
 +                      
 +                      copy (im.begin(), im.end(), back_inserter (sub_images));
 +              }
 +              
 +              if (_burn_subtitles) {
 +                      list<shared_ptr<ContentTextSubtitle> > text_subtitles = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to));
 +                      if (!text_subtitles.empty ()) {
 +                              list<PositionImage> im = process_content_text_subtitles (text_subtitles);
 +                              copy (im.begin(), im.end(), back_inserter (sub_images));
 +                      }
 +              }
        }
        
 +      if (!sub_images.empty ()) {
 +              pvf->set_subtitle (merge (sub_images));
 +      }
 +
 +      return pvf;
  }
  
 -/** Seek so that the next pass() will yield (approximately) the requested frame.
 - *  Pass accurate = true to try harder to get close to the request.
 - *  @return true on error
 - */
 -void
 -Player::seek (Time t, bool accurate)
 +/** @return All PlayerVideoFrames at the given time (there may be two frames for 3D) */
 +list<shared_ptr<PlayerVideoFrame> >
 +Player::get_video (DCPTime time, bool accurate)
  {
        if (!_have_valid_pieces) {
                setup_pieces ();
        }
 +      
 +      list<shared_ptr<Piece> > ov = overlaps<VideoContent> (
 +              time,
 +              time + DCPTime::from_frames (1, _film->video_frame_rate ())
 +              );
  
 -      if (_pieces.empty ()) {
 -              return;
 +      list<shared_ptr<PlayerVideoFrame> > pvf;
 +              
 +      if (ov.empty ()) {
 +              /* No video content at this time */
 +              pvf.push_back (black_player_video_frame ());
 +              return pvf;
        }
  
 -      for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
 -              shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
 -              if (!vc) {
 -                      continue;
 -              }
 -
 -              /* s is the offset of t from the start position of this content */
 -              Time s = t - vc->position ();
 -              s = max (static_cast<Time> (0), s);
 -              s = min (vc->length_after_trim(), s);
 -
 -              /* Hence set the piece positions to the `global' time */
 -              (*i)->video_position = (*i)->audio_position = vc->position() + s;
 +      /* Create a PlayerVideoFrame from the content's video at this time */
  
 -              /* And seek the decoder */
 -              dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
 -                      vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
 -                      );
 +      shared_ptr<Piece> piece = ov.back ();
 +      shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
 +      assert (decoder);
 +      shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
 +      assert (content);
  
 -              (*i)->reset_repeat ();
 +      list<ContentVideo> content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate);
 +      if (content_video.empty ()) {
 +              pvf.push_back (black_player_video_frame ());
 +              return pvf;
        }
  
 -      _video_position = _audio_position = t;
 +      dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
 +      if (_approximate_size) {
 +              image_size.width &= ~3;
 +              image_size.height &= ~3;
 +      }
  
 -      /* XXX: don't seek audio because we don't need to... */
 +      for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
 +              list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (
 +                      time,
 +                      time + DCPTime::from_frames (1, _film->video_frame_rate ())
 +                      );
 +              
 +              pvf.push_back (content_to_player_video_frame (content, *i, subs, time, image_size));
 +      }
 +              
 +      return pvf;
  }
  
 -void
 -Player::setup_pieces ()
 +shared_ptr<AudioBuffers>
 +Player::get_audio (DCPTime time, DCPTime length, bool accurate)
  {
 -      list<shared_ptr<Piece> > old_pieces = _pieces;
 +      if (!_have_valid_pieces) {
 +              setup_pieces ();
 +      }
  
 -      _pieces.clear ();
 +      AudioFrame const length_frames = length.frames (_film->audio_frame_rate ());
  
 -      ContentList content = _playlist->content ();
 -      sort (content.begin(), content.end(), ContentSorter ());
 +      shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
 +      audio->make_silent ();
 +      
 +      list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length);
 +      if (ov.empty ()) {
 +              return audio;
 +      }
  
 -      for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
 +      for (list<shared_ptr<Piece> >::iterator i = ov.begin(); i != ov.end(); ++i) {
  
 -              if (!(*i)->paths_valid ()) {
 +              shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> ((*i)->content);
 +              assert (content);
 +              shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
 +              assert (decoder);
 +
 +              if (content->audio_frame_rate() == 0) {
 +                      /* This AudioContent has no audio (e.g. if it is an FFmpegContent with no
 +                       * audio stream).
 +                       */
                        continue;
                }
  
 -              shared_ptr<Piece> piece (new Piece (*i));
 -
 -              /* XXX: into content? */
 -
 -              shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
 -              if (fc) {
 -                      shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
 -                      
 -                      fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, _5, 0));
 -                      fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2, false));
 -                      fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
 -
 -                      fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
 -                      piece->decoder = fd;
 +              /* The time that we should request from the content */
 +              DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0);
 +              DCPTime offset;
 +              if (request < DCPTime ()) {
 +                      /* We went off the start of the content, so we will need to offset
 +                         the stuff we get back.
 +                      */
 +                      offset = -request;
 +                      request = DCPTime ();
                }
 -              
 -              shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
 -              if (ic) {
 -                      bool reusing = false;
 -                      
 -                      /* See if we can re-use an old ImageDecoder */
 -                      for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
 -                              shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
 -                              if (imd && imd->content() == ic) {
 -                                      piece = *j;
 -                                      reusing = true;
 -                              }
 -                      }
  
 -                      if (!reusing) {
 -                              shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
 -                              id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, _5, 0));
 -                              piece->decoder = id;
 -                      }
 -              }
 +              AudioFrame const content_frame = dcp_to_content_audio (*i, request);
  
 -              shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
 -              if (sc) {
 -                      shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
 -                      sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2, false));
 +              /* Audio from this piece's decoder (which might be more or less than what we asked for) */
 +              shared_ptr<ContentAudio> all = decoder->get_audio (content_frame, length_frames, accurate);
  
 -                      piece->decoder = sd;
 +              /* Gain */
 +              if (content->audio_gain() != 0) {
 +                      shared_ptr<AudioBuffers> gain (new AudioBuffers (all->audio));
 +                      gain->apply_gain (content->audio_gain ());
 +                      all->audio = gain;
                }
  
 -              _pieces.push_back (piece);
 -      }
 -
 -      _have_valid_pieces = true;
 -}
 -
 -void
 -Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
 -{
 -      shared_ptr<Content> c = w.lock ();
 -      if (!c) {
 -              return;
 -      }
 -
 -      if (
 -              property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
 -              property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
 -              property == VideoContentProperty::VIDEO_FRAME_TYPE 
 -              ) {
 -              
 -              _have_valid_pieces = false;
 -              Changed (frequent);
 -
 -      } else if (
 -              property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
 -              property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
 -              property == SubtitleContentProperty::SUBTITLE_SCALE
 -              ) {
 -
 -              for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
 -                      i->update (_film, _video_container_size);
 +              /* Remap channels */
 +              shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all->audio->frames()));
 +              dcp_mapped->make_silent ();
 +              AudioMapping map = content->audio_mapping ();
 +              for (int i = 0; i < map.content_channels(); ++i) {
 +                      for (int j = 0; j < _film->audio_channels(); ++j) {
 +                              if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
 +                                      dcp_mapped->accumulate_channel (
 +                                              all->audio.get(),
 +                                              i,
 +                                              j,
 +                                              map.get (i, static_cast<dcp::Channel> (j))
 +                                              );
 +                              }
 +                      }
                }
                
 -              Changed (frequent);
 -
 -      } else if (
 -              property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_SCALE ||
 -              property == VideoContentProperty::VIDEO_FRAME_RATE
 -              ) {
 -              
 -              Changed (frequent);
 +              all->audio = dcp_mapped;
  
 -      } else if (property == ContentProperty::PATH) {
 -
 -              _have_valid_pieces = false;
 -              Changed (frequent);
 +              audio->accumulate_frames (
 +                      all->audio.get(),
 +                      content_frame - all->frame,
 +                      offset.frames (_film->audio_frame_rate()),
 +                      min (AudioFrame (all->audio->frames()), length_frames) - offset.frames (_film->audio_frame_rate ())
 +                      );
        }
  }
  
 -void
 -Player::playlist_changed ()
 -{
 -      _have_valid_pieces = false;
 -      Changed (false);
 -}
 -
 -void
 -Player::set_video_container_size (libdcp::Size s)
 -{
 -      _video_container_size = s;
 -
 -      shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
 -      im->make_black ();
 -      
 -      _black_frame.reset (
 -              new PlayerVideoFrame (
 -                      shared_ptr<ImageProxy> (new RawImageProxy (im, _film->log ())),
 -                      Crop(),
 -                      _video_container_size,
 -                      _video_container_size,
 -                      Scaler::from_id ("bicubic"),
 -                      EYES_BOTH,
 -                      PART_WHOLE,
 -                      ColourConversion ()
 -                      )
 -              );
 -}
 -
 -shared_ptr<Resampler>
 -Player::resampler (shared_ptr<AudioContent> c, bool create)
 +VideoFrame
 +Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
  {
 -      map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
 -      if (i != _resamplers.end ()) {
 -              return i->second;
 -      }
 -
 -      if (!create) {
 -              return shared_ptr<Resampler> ();
 -      }
 -
 -      LOG_GENERAL (
 -              "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
 -              );
 +      /* s is the offset of t from the start position of this content */
 +      DCPTime s = t - piece->content->position ();
 +      s = DCPTime (max (int64_t (0), s.get ()));
 +      s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
  
 -      shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
 -      _resamplers[c] = r;
 -      return r;
 +      /* Convert this to the content frame */
 +      return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor ();
  }
  
 -void
 -Player::emit_black ()
 +AudioFrame
 +Player::dcp_to_content_audio (shared_ptr<const Piece> piece, DCPTime t) const
  {
 -#ifdef DCPOMATIC_DEBUG
 -      _last_video.reset ();
 -#endif
 -
 -      Video (_black_frame, _last_emit_was_black, _video_position);
 -      _video_position += _film->video_frames_to_time (1);
 -      _last_emit_was_black = true;
 -}
 +      /* s is the offset of t from the start position of this content */
 +      DCPTime s = t - piece->content->position ();
 +      s = DCPTime (max (int64_t (0), s.get ()));
 +      s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
  
 -void
 -Player::emit_silence (OutputAudioFrame most)
 -{
 -      if (most == 0) {
 -              return;
 -      }
 -      
 -      OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
 -      shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
 -      silence->make_silent ();
 -      Audio (silence, _audio_position);
 -      _audio_position += _film->audio_frames_to_time (N);
 +      /* Convert this to the content frame */
 +      return DCPTime (s + piece->content->trim_start()).frames (_film->audio_frame_rate());
  }
  
 -void
 -Player::film_changed (Film::Property p)
 +ContentTime
 +Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
  {
 -      /* Here we should notice Film properties that affect our output, and
 -         alert listeners that our output now would be different to how it was
 -         last time we were run.
 -      */
 +      /* s is the offset of t from the start position of this content */
 +      DCPTime s = t - piece->content->position ();
 +      s = DCPTime (max (int64_t (0), s.get ()));
 +      s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
  
 -      if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
 -              Changed (false);
 -      }
 +      return ContentTime (s + piece->content->trim_start(), piece->frc);
  }
  
  void
 -Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
 +PlayerStatistics::dump (shared_ptr<Log> log) const
  {
 -      if (!image) {
 -              /* A null image means that we should stop any current subtitles at `from' */
 -              for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
 -                      i->set_stop (from);
 -              }
 -      } else {
 -              _subtitles.push_back (Subtitle (_film, _video_container_size, weak_piece, image, rect, from, to));
 -      }
 +      log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat), Log::TYPE_GENERAL);
 +      log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds()), Log::TYPE_GENERAL);
  }
  
 -/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
 - *  @return false if this could not be done.
 - */
 -bool
 -Player::repeat_last_video ()
 +PlayerStatistics const &
 +Player::statistics () const
  {
 -      if (!_last_incoming_video.image || !_have_valid_pieces) {
 -              return false;
 -      }
 -
 -      process_video (
 -              _last_incoming_video.weak_piece,
 -              _last_incoming_video.image,
 -              _last_incoming_video.eyes,
 -              _last_incoming_video.part,
 -              _last_incoming_video.same,
 -              _last_incoming_video.frame,
 -              _last_incoming_video.extra
 -              );
 -
 -      return true;
 +      return _statistics;
  }
diff --combined src/lib/playlist.h
index 6280b1219057d0e2b00e2200ca8a9e08b7692946,effc521012dfbc70779f5bfd9f4befe2a79d4616..7c29b85887c82e55791fdc85209125adc198ac53
@@@ -25,7 -25,7 +25,8 @@@
  #include <boost/enable_shared_from_this.hpp>
  #include "ffmpeg_content.h"
  #include "audio_mapping.h"
 +#include "util.h"
+ #include "frame_rate_change.h"
  
  class Content;
  class FFmpegContent;
@@@ -38,15 -38,18 +39,15 @@@ class Job
  class Film;
  class Region;
  
 -/** @class Playlist
 - *  @brief A set of content files (video and audio), with knowledge of how they should be arranged into
 - *  a DCP.
 - *
 - * This class holds Content objects, and it knows how they should be arranged.
 - */
 -
  struct ContentSorter
  {
        bool operator() (boost::shared_ptr<Content> a, boost::shared_ptr<Content> b);
  };
  
 +/** @class Playlist
 + *  @brief A set of Content objects with knowledge of how they should be arranged into
 + *  a DCP.
 + */
  class Playlist : public boost::noncopyable
  {
  public:
@@@ -54,7 -57,7 +55,7 @@@
        ~Playlist ();
  
        void as_xml (xmlpp::Node *);
 -      void set_from_xml (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int, std::list<std::string> &);
 +      void set_from_xml (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int, std::list<std::string> &);
  
        void add (boost::shared_ptr<Content>);
        void remove (boost::shared_ptr<Content>);
  
        std::string video_identifier () const;
  
 -      Time length () const;
 +      DCPTime length () const;
        
        int best_dcp_frame_rate () const;
 -      Time video_end () const;
 -      FrameRateChange active_frame_rate_change (Time, int dcp_frame_rate) const;
 +      DCPTime video_end () const;
 +      FrameRateChange active_frame_rate_change (DCPTime, int dcp_frame_rate) const;
  
        void set_sequence_video (bool);
        void maybe_sequence_video ();
diff --combined src/lib/ratio.cc
index 275f4ef15ab5215a14b6679b6e52ed58b01b77dd,52577d3bbc5158f7ccdfea8a561caa0f055107c1..fbd7022322c5b0e9e02d8e94785baaa54e23c4c5
@@@ -17,7 -17,7 +17,7 @@@
  
  */
  
 -#include <libdcp/types.h>
 +#include <dcp/types.h>
  #include "ratio.h"
  #include "util.h"
  
@@@ -32,12 -32,12 +32,12 @@@ vector<Ratio const *> Ratio::_ratios
  void
  Ratio::setup_ratios ()
  {
-       _ratios.push_back (new Ratio (float(1290) / 1080, "119", _("1.19"), "F"));
-       _ratios.push_back (new Ratio (float(1440) / 1080, "133", _("4:3"), "F"));
-       _ratios.push_back (new Ratio (float(1480) / 1080, "137", _("Academy"), "F"));
-       _ratios.push_back (new Ratio (float(1485) / 1080, "138", _("1.375"), "F"));
-       _ratios.push_back (new Ratio (float(1800) / 1080, "166", _("1.66"), "F"));
-       _ratios.push_back (new Ratio (float(1920) / 1080, "178", _("16:9"), "F"));
+       _ratios.push_back (new Ratio (float(1290) / 1080, "119", _("1.19"), "119"));
+       _ratios.push_back (new Ratio (float(1440) / 1080, "133", _("4:3"), "133"));
+       _ratios.push_back (new Ratio (float(1480) / 1080, "137", _("Academy"), "137"));
+       _ratios.push_back (new Ratio (float(1485) / 1080, "138", _("1.375"), "137"));
+       _ratios.push_back (new Ratio (float(1800) / 1080, "166", _("1.66"), "166"));
+       _ratios.push_back (new Ratio (float(1920) / 1080, "178", _("16:9"), "178"));
        _ratios.push_back (new Ratio (float(1998) / 1080, "185", _("Flat"), "F"));
        _ratios.push_back (new Ratio (float(2048) /  858, "239", _("Scope"), "S"));
        _ratios.push_back (new Ratio (float(2048) / 1080, "full-frame", _("Full frame"), "C"));
diff --combined src/lib/ratio.h
index cd7d0d6e4de276b138f92733b3bbcee99ad5d10a,8b1a1fc716dc61d9175c6144e30f34ea27bdf39f..22fc7662c9c9cf28d2a625d45256f8cfcaf789a7
@@@ -22,7 -22,7 +22,7 @@@
  
  #include <vector>
  #include <boost/utility.hpp>
 -#include <libdcp/util.h>
 +#include <dcp/util.h>
  
  class Ratio : public boost::noncopyable
  {
@@@ -31,7 -31,7 +31,7 @@@ public
                : _ratio (ratio)
                , _id (id)
                , _nickname (n)
-               , _dci_name (d)
+               , _isdcf_name (d)
        {}
  
        std::string id () const {
@@@ -42,8 -42,8 +42,8 @@@
                return _nickname;
        }
  
-       std::string dci_name () const {
-               return _dci_name;
+       std::string isdcf_name () const {
+               return _isdcf_name;
        }
  
        float ratio () const {
@@@ -62,7 -62,7 +62,7 @@@ private
        std::string _id;
        /** nickname (e.g. Flat, Scope) */
        std::string _nickname;
-       std::string _dci_name;
+       std::string _isdcf_name;
  
        static std::vector<Ratio const *> _ratios;      
  };
diff --combined src/lib/server.cc
index 507ff2ae593ae2bfa5c15e818d16d561281e83de,ed7fb6145c6a4b4498bbf6e3ff552a8fd3b8e01b..59364fadd65aba787d1bfac5579c581eeba9a4c2
@@@ -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,8 -62,8 +62,8 @@@ 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)
@@@ -104,6 -104,7 +104,7 @@@ Server::process (shared_ptr<Socket> soc
        try {
                encoded->send (socket);
        } catch (std::exception& e) {
+               cerr << "Send failed; frame " << dcp_video_frame.index() << "\n";
                LOG_ERROR ("Send failed; frame %1", dcp_video_frame.index());
                throw;
        }
@@@ -139,6 -140,7 +140,7 @@@ Server::worker_thread (
                        frame = process (socket, after_read, after_encode);
                        ip = socket->socket().remote_endpoint().address().to_string();
                } catch (std::exception& e) {
+                       cerr << "Error: " << e.what() << "\n";
                        LOG_ERROR ("Error: %1", e.what());
                }
  
index ed4f8ffd527ebde2358319d8344456829d25448c,3efba6fd5db219c38e6f93249d28155786020934..37f2055359aa8a9d39dcced316a5476cd57bcec8
@@@ -18,7 -18,7 +18,7 @@@
  */
  
  #include <libcxml/cxml.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/raw_convert.h>
  #include "sndfile_content.h"
  #include "sndfile_decoder.h"
  #include "film.h"
@@@ -32,7 -32,7 +32,7 @@@ using std::string
  using std::stringstream;
  using std::cout;
  using boost::shared_ptr;
 -using libdcp::raw_convert;
 +using dcp::raw_convert;
  
  SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::path p)
        : Content (f, p)
  
  }
  
 -SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
 +SndfileContent::SndfileContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
        : Content (f, node)
        , AudioContent (f, node)
        , _audio_mapping (node->node_child ("AudioMapping"), version)
  {
        _audio_channels = node->number_child<int> ("AudioChannels");
 -      _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
 +      _audio_length = ContentTime (node->number_child<int64_t> ("AudioLength"));
        _audio_frame_rate = node->number_child<int> ("AudioFrameRate");
  }
  
@@@ -81,8 -81,8 +81,8 @@@ SndfileContent::information () cons
        s << String::compose (
                _("%1 channels, %2kHz, %3 samples"),
                audio_channels(),
 -              content_audio_frame_rate() / 1000.0,
 -              audio_length()
 +              audio_frame_rate() / 1000.0,
 +              audio_length().frames (audio_frame_rate ())
                );
        
        return s.str ();
@@@ -103,7 -103,10 +103,7 @@@ SndfileContent::examine (shared_ptr<Job
        job->set_progress_unknown ();
        Content::examine (job);
  
 -      shared_ptr<const Film> film = _film.lock ();
 -      assert (film);
 -
 -      SndfileDecoder dec (film, shared_from_this());
 +      SndfileDecoder dec (shared_from_this());
  
        {
                boost::mutex::scoped_lock lm (_mutex);
@@@ -134,17 -137,25 +134,17 @@@ SndfileContent::as_xml (xmlpp::Node* no
        AudioContent::as_xml (node);
  
        node->add_child("AudioChannels")->add_child_text (raw_convert<string> (audio_channels ()));
 -      node->add_child("AudioLength")->add_child_text (raw_convert<string> (audio_length ()));
 -      node->add_child("AudioFrameRate")->add_child_text (raw_convert<string> (content_audio_frame_rate ()));
 +      node->add_child("AudioLength")->add_child_text (raw_convert<string> (audio_length().get ()));
 +      node->add_child("AudioFrameRate")->add_child_text (raw_convert<string> (audio_frame_rate ()));
        _audio_mapping.as_xml (node->add_child("AudioMapping"));
  }
  
 -Time
 +DCPTime
  SndfileContent::full_length () const
  {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
 -
 -      FrameRateChange frc = film->active_frame_rate_change (position ());
 -
 -      OutputAudioFrame const len = divide_with_round (
 -              audio_length() * output_audio_frame_rate() * frc.source,
 -              content_audio_frame_rate() * film->video_frame_rate()
 -              );
 -      
 -      return film->audio_frames_to_time (len);
 +      return DCPTime (audio_length(), film->active_frame_rate_change (position ()));
  }
  
  void
@@@ -155,6 -166,5 +155,5 @@@ SndfileContent::set_audio_mapping (Audi
                _audio_mapping = m;
        }
  
 -      signal_changed (AudioContentProperty::AUDIO_MAPPING);
 +      AudioContent::set_audio_mapping (m);
  }
diff --combined src/lib/util.cc
index 074e08cb70e819617391f5c4a6115e830d6444cc,86046bcf8cf8a7ff1a2657b87ec07584bb850d17..55df5cc83798ba126eeebc82a0e9ca0d7f71aa34
  #endif
  #include <glib.h>
  #include <openjpeg.h>
 +#include <pangomm/init.h>
  #include <magick/MagickCore.h>
  #include <magick/version.h>
 -#include <libdcp/version.h>
 -#include <libdcp/util.h>
 -#include <libdcp/signer_chain.h>
 -#include <libdcp/signer.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/version.h>
 +#include <dcp/util.h>
 +#include <dcp/signer_chain.h>
 +#include <dcp/signer.h>
 +#include <dcp/raw_convert.h>
  extern "C" {
  #include <libavcodec/avcodec.h>
  #include <libavformat/avformat.h>
@@@ -70,7 -69,6 +70,7 @@@
  #include "job.h"
  #include "cross.h"
  #include "video_content.h"
 +#include "rect.h"
  #include "md5_digester.h"
  #ifdef DCPOMATIC_WINDOWS
  #include "stack.hpp"
@@@ -102,8 -100,8 +102,8 @@@ using std::set_terminate
  using boost::shared_ptr;
  using boost::thread;
  using boost::optional;
 -using libdcp::Size;
 -using libdcp::raw_convert;
 +using dcp::Size;
 +using dcp::raw_convert;
  
  static boost::thread::id ui_thread;
  static boost::filesystem::path backtrace_file;
@@@ -239,6 -237,24 +239,6 @@@ ffmpeg_version_to_string (int v
        return s.str ();
  }
  
 -/** Return a user-readable string summarising the versions of our dependencies */
 -string
 -dependency_version_summary ()
 -{
 -      stringstream s;
 -      s << N_("libopenjpeg ") << opj_version () << N_(", ")
 -        << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
 -        << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
 -        << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
 -        << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
 -        << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
 -        << MagickVersion << N_(", ")
 -        << N_("libssh ") << ssh_version (0) << N_(", ")
 -        << N_("libdcp ") << libdcp::version << N_(" git ") << libdcp::git_commit;
 -
 -      return s.str ();
 -}
 -
  double
  seconds (struct timeval t)
  {
@@@ -267,7 -283,8 +267,8 @@@ terminate (
  
        try {
                // try once to re-throw currently active exception
-               if (!tried_throw++) {
+               if (!tried_throw) {
+                       tried_throw = true;
                        throw;
                }
        }
@@@ -326,8 -343,7 +327,8 @@@ dcpomatic_setup (
  
        set_terminate (terminate);
  
 -      libdcp::init ();
 +      Pango::init ();
 +      dcp::init ();
        
        Ratio::setup_ratios ();
        VideoContentScale::setup_scales ();
@@@ -709,6 -725,17 +710,6 @@@ ensure_ui_thread (
        assert (boost::this_thread::get_id() == ui_thread);
  }
  
 -/** @param v Content video frame.
 - *  @param audio_sample_rate Source audio sample rate.
 - *  @param frames_per_second Number of video frames per second.
 - *  @return Equivalent number of audio frames for `v'.
 - */
 -int64_t
 -video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
 -{
 -      return ((int64_t) v * audio_sample_rate / frames_per_second);
 -}
 -
  string
  audio_channel_name (int c)
  {
@@@ -759,7 -786,7 +760,7 @@@ tidy_for_filename (string f
        return t;
  }
  
 -shared_ptr<const libdcp::Signer>
 +shared_ptr<const dcp::Signer>
  make_signer ()
  {
        boost::filesystem::path const sd = Config::instance()->signer_chain_directory ();
                if (!boost::filesystem::exists (p)) {
                        boost::filesystem::remove_all (sd);
                        boost::filesystem::create_directories (sd);
 -                      libdcp::make_signer_chain (sd, openssl_path ());
 +                      dcp::make_signer_chain (sd, openssl_path ());
                        break;
                }
  
                ++i;
        }
        
 -      libdcp::CertificateChain chain;
 +      dcp::CertificateChain chain;
  
        {
                boost::filesystem::path p (sd);
                p /= "ca.self-signed.pem";
 -              chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
 +              chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
        }
  
        {
                boost::filesystem::path p (sd);
                p /= "intermediate.signed.pem";
 -              chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
 +              chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
        }
  
        {
                boost::filesystem::path p (sd);
                p /= "leaf.signed.pem";
 -              chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
 +              chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
        }
  
        boost::filesystem::path signer_key (sd);
        signer_key /= "leaf.key";
  
 -      return shared_ptr<const libdcp::Signer> (new libdcp::Signer (chain, signer_key));
 +      return shared_ptr<const dcp::Signer> (new dcp::Signer (chain, signer_key));
  }
  
  map<string, string>
@@@ -858,14 -885,14 +859,14 @@@ split_get_request (string url
        return r;
  }
  
 -libdcp::Size
 -fit_ratio_within (float ratio, libdcp::Size full_frame)
 +dcp::Size
 +fit_ratio_within (float ratio, dcp::Size full_frame)
  {
        if (ratio < full_frame.ratio ()) {
 -              return libdcp::Size (rint (full_frame.height * ratio), full_frame.height);
 +              return dcp::Size (rint (full_frame.height * ratio), full_frame.height);
        }
        
 -      return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
 +      return dcp::Size (full_frame.width, rint (full_frame.width / ratio));
  }
  
  void *
@@@ -896,34 -923,12 +897,34 @@@ divide_with_round (int64_t a, int64_t b
        }
  }
  
 +/** Return a user-readable string summarising the versions of our dependencies */
 +string
 +dependency_version_summary ()
 +{
 +      stringstream s;
 +      s << N_("libopenjpeg ") << opj_version () << N_(", ")
 +        << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
 +        << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
 +        << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
 +        << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
 +        << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
 +        << MagickVersion << N_(", ")
 +        << N_("libssh ") << ssh_version (0) << N_(", ")
 +        << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit;
 +
 +      return s.str ();
 +}
 +
 +/** Construct a ScopedTemporary.  A temporary filename is decided but the file is not opened
 + *  until ::open() is called.
 + */
  ScopedTemporary::ScopedTemporary ()
        : _open (0)
  {
        _file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
  }
  
 +/** Close and delete the temporary file */
  ScopedTemporary::~ScopedTemporary ()
  {
        close ();       
        boost::filesystem::remove (_file, ec);
  }
  
 +/** @return temporary filename */
  char const *
  ScopedTemporary::c_str () const
  {
        return _file.string().c_str ();
  }
  
 +/** Open the temporary file.
 + *  @return File's FILE pointer.
 + */
  FILE*
  ScopedTemporary::open (char const * params)
  {
        return _open;
  }
  
 +/** Close the file */
  void
  ScopedTemporary::close ()
  {
                _open = 0;
        }
  }
 +
 +ContentTimePeriod
 +subtitle_period (AVSubtitle const & sub)
 +{
 +      ContentTime const packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE);
 +
 +      ContentTimePeriod period (
 +              packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3),
 +              packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3)
 +              );
 +
 +      return period;
 +}
diff --combined src/lib/video_content.cc
index 58053c822b9a61f5c08e643d50bd007f8ef94f45,ec5890b4549c1a43d5873d0035417a95bfa9a814..f871f2df666438a1846d465642f8cb991e729723
@@@ -19,8 -19,8 +19,8 @@@
  
  #include <iomanip>
  #include <libcxml/cxml.h>
 -#include <libdcp/colour_matrix.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/colour_matrix.h>
 +#include <dcp/raw_convert.h>
  #include "video_content.h"
  #include "video_examiner.h"
  #include "compose.hpp"
@@@ -30,6 -30,7 +30,7 @@@
  #include "util.h"
  #include "film.h"
  #include "exceptions.h"
+ #include "frame_rate_change.h"
  
  #include "i18n.h"
  
@@@ -48,7 -49,7 +49,7 @@@ using std::vector
  using boost::shared_ptr;
  using boost::optional;
  using boost::dynamic_pointer_cast;
 -using libdcp::raw_convert;
 +using dcp::raw_convert;
  
  vector<VideoContentScale> VideoContentScale::_scales;
  
@@@ -62,7 -63,7 +63,7 @@@ VideoContent::VideoContent (shared_ptr<
        setup_default_colour_conversion ();
  }
  
 -VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
 +VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, ContentTime len)
        : Content (f, s)
        , _video_length (len)
        , _video_frame_rate (0)
@@@ -82,20 -83,13 +83,20 @@@ VideoContent::VideoContent (shared_ptr<
        setup_default_colour_conversion ();
  }
  
 -VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
 +VideoContent::VideoContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
        : Content (f, node)
  {
 -      _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
        _video_size.width = node->number_child<int> ("VideoWidth");
        _video_size.height = node->number_child<int> ("VideoHeight");
        _video_frame_rate = node->number_child<float> ("VideoFrameRate");
 +
 +      if (version < 32) {
 +              /* DCP-o-matic 1.0 branch */
 +              _video_length = ContentTime::from_frames (node->number_child<int64_t> ("VideoLength"), _video_frame_rate);
 +      } else {
 +              _video_length = ContentTime (node->number_child<int64_t> ("VideoLength"));
 +      }
 +      
        _video_frame_type = static_cast<VideoFrameType> (node->number_child<int> ("VideoFrameType"));
        _crop.left = node->number_child<int> ("LeftCrop");
        _crop.right = node->number_child<int> ("RightCrop");
@@@ -163,7 -157,7 +164,7 @@@ voi
  VideoContent::as_xml (xmlpp::Node* node) const
  {
        boost::mutex::scoped_lock lm (_mutex);
 -      node->add_child("VideoLength")->add_child_text (raw_convert<string> (_video_length));
 +      node->add_child("VideoLength")->add_child_text (raw_convert<string> (_video_length.get ()));
        node->add_child("VideoWidth")->add_child_text (raw_convert<string> (_video_size.width));
        node->add_child("VideoHeight")->add_child_text (raw_convert<string> (_video_size.height));
        node->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate));
  void
  VideoContent::setup_default_colour_conversion ()
  {
 -      _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
 +      _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
  }
  
  void
  VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
  {
        /* These examiner calls could call other content methods which take a lock on the mutex */
 -      libdcp::Size const vs = d->video_size ();
 +      dcp::Size const vs = d->video_size ();
        float const vfr = d->video_frame_rate ();
        
        {
@@@ -324,17 -318,14 +325,17 @@@ VideoContent::technical_summary () cons
  {
        return String::compose (
                "video: length %1, size %2x%3, rate %4",
 -              video_length_after_3d_combine(), video_size().width, video_size().height, video_frame_rate()
 +              video_length_after_3d_combine().seconds(),
 +              video_size().width,
 +              video_size().height,
 +              video_frame_rate()
                );
  }
  
 -libdcp::Size
 +dcp::Size
  VideoContent::video_size_after_3d_split () const
  {
 -      libdcp::Size const s = video_size ();
 +      dcp::Size const s = video_size ();
        switch (video_frame_type ()) {
        case VIDEO_FRAME_TYPE_2D:
        case VIDEO_FRAME_TYPE_3D_ALTERNATE:
        case VIDEO_FRAME_TYPE_3D_RIGHT:
                return s;
        case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
 -              return libdcp::Size (s.width / 2, s.height);
 +              return dcp::Size (s.width / 2, s.height);
        case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
 -              return libdcp::Size (s.width, s.height / 2);
 +              return dcp::Size (s.width, s.height / 2);
        }
  
        assert (false);
@@@ -362,21 -353,28 +363,21 @@@ VideoContent::set_colour_conversion (Co
  }
  
  /** @return Video size after 3D split and crop */
 -libdcp::Size
 +dcp::Size
  VideoContent::video_size_after_crop () const
  {
        return crop().apply (video_size_after_3d_split ());
  }
  
  /** @param t A time offset from the start of this piece of content.
 - *  @return Corresponding frame index.
 + *  @return Corresponding time with respect to the content.
   */
 -VideoContent::Frame
 -VideoContent::time_to_content_video_frames (Time t) const
 +ContentTime
 +VideoContent::dcp_time_to_content_time (DCPTime t) const
  {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
 -      
 -      FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
 -
 -      /* Here we are converting from time (in the DCP) to a frame number in the content.
 -         Hence we need to use the DCP's frame rate and the double/skip correction, not
 -         the source's rate.
 -      */
 -      return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
 +      return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate()));
  }
  
  VideoContentScale::VideoContentScale (Ratio const * r)
@@@ -400,7 -398,7 +401,7 @@@ VideoContentScale::VideoContentScale (b
  
  }
  
 -VideoContentScale::VideoContentScale (shared_ptr<cxml::Node> node)
 +VideoContentScale::VideoContentScale (cxml::NodePtr node)
        : _ratio (0)
        , _scale (true)
  {
@@@ -453,14 -451,14 +454,14 @@@ VideoContentScale::name () cons
  /** @param display_container Size of the container that we are displaying this content in.
   *  @param film_container The size of the film's image.
   */
 -libdcp::Size
 -VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_container, libdcp::Size film_container) const
 +dcp::Size
 +VideoContentScale::size (shared_ptr<const VideoContent> c, dcp::Size display_container, dcp::Size film_container) const
  {
        if (_ratio) {
                return fit_ratio_within (_ratio->ratio (), display_container);
        }
  
 -      libdcp::Size const ac = c->video_size_after_crop ();
 +      dcp::Size const ac = c->video_size_after_crop ();
  
        /* Force scale if the film_container is smaller than the content's image */
        if (_scale || film_container.width < ac.width || film_container.height < ac.height) {
        /* Scale the image so that it will be in the right place in film_container, even if display_container is a
           different size.
        */
 -      return libdcp::Size (
 +      return dcp::Size (
                c->video_size().width  * float(display_container.width)  / film_container.width,
                c->video_size().height * float(display_container.height) / film_container.height
                );
diff --combined src/lib/wscript
index 51aadb83f40718a549eac9e222bce49395bd69bb,9333687769fe1acd1ade6bbffd4e3adf4318092d..407d9cde41551abab31aa43179de352b1ea26561
@@@ -13,12 -13,10 +13,11 @@@ sources = ""
            config.cc
            content.cc
            content_factory.cc
 +          content_subtitle.cc
            cross.cc
-           dci_metadata.cc
            dcp_content_type.cc
            dcp_video_frame.cc
 -          decoder.cc
 +          dcpomatic_time.cc
            dolby_cp750.cc
            encoder.cc
            examine_content_job.cc
            file_group.cc
            filter_graph.cc
            ffmpeg.cc
 +          ffmpeg_audio_stream.cc
            ffmpeg_content.cc
            ffmpeg_decoder.cc
            ffmpeg_examiner.cc
 +          ffmpeg_stream.cc
 +          ffmpeg_subtitle_stream.cc
            film.cc
            filter.cc
            frame_rate_change.cc
            image_decoder.cc
            image_examiner.cc
            image_proxy.cc
+           isdcf_metadata.cc
            job.cc
            job_manager.cc
            kdm.cc
            json_server.cc
            log.cc
            md5_digester.cc
 -          piece.cc
            player.cc
            player_video_frame.cc
            playlist.cc
            ratio.cc
 +          render_subtitles.cc
            resampler.cc
            scp_dcp_job.cc
            scaler.cc
@@@ -61,9 -57,7 +61,9 @@@
            sndfile_content.cc
            sndfile_decoder.cc
            sound_processor.cc
 -          subtitle.cc
 +          subrip.cc
 +          subrip_content.cc
 +          subrip_decoder.cc
            subtitle_content.cc
            subtitle_decoder.cc
            timer.cc
@@@ -90,7 -84,7 +90,7 @@@ def build(bld)
                   AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE 
                   BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2
                   SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++
 -                 CURL ZIP QUICKMAIL
 +                 CURL ZIP QUICKMAIL PANGOMM CAIROMM
                   """
  
      if bld.env.TARGET_OSX:
index c75f8f9539ffe9f06d9b4d61936304ae038a7301,c30f4e51e7fc66c7963f3aaa2f65b3c7b9c1296d..b0a67c6d93d5630fc919731ff251114c292f2690
@@@ -74,7 -74,7 +74,7 @@@ main (int argc, char* argv[]
        dcpomatic_setup ();
  
        string name;
-       DCPContentType const * dcp_content_type = DCPContentType::from_dci_name ("TST");
+       DCPContentType const * dcp_content_type = DCPContentType::from_isdcf_name ("TST");
        Ratio const * container_ratio = 0;
        Ratio const * content_ratio = 0;
        int still_length = 10;
                        name = optarg;
                        break;
                case 'c':
-                       dcp_content_type = DCPContentType::from_dci_name (optarg);
+                       dcp_content_type = DCPContentType::from_isdcf_name (optarg);
                        if (dcp_content_type == 0) {
                                cerr << "Bad DCP content type.\n";
                                help (argv[0]);
                for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
                        shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (*i);
                        if (ic) {
 -                              ic->set_video_length (still_length * 24);
 +                              ic->set_video_length (ContentTime::from_seconds (still_length));
                        }
                }
  
diff --combined src/wx/about_dialog.cc
index 105831014eccc81354e7910b3fd0b76fce86c717,751b453ed248f41862b42a40b16d942d49cfdd5e..bd4a39811e158e235d34a62a8f836efb28ed6ab9
@@@ -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"
@@@ -114,6 -110,7 +114,7 @@@ AboutDialog::AboutDialog (wxWindow* par
        wxArrayString supported_by;
        supported_by.Add (wxT ("Manual AC"));
        supported_by.Add (wxT ("Kambiz Afshar"));
+       supported_by.Add (wxT ("Louis Belloisy"));
        supported_by.Add (wxT ("Jeff Boot"));
        supported_by.Add (wxT ("Kieran Carroll"));
        supported_by.Add (wxT ("Frank Cianciolo"));
        supported_by.Add (wxT ("Lindsay Morris"));
        supported_by.Add (wxT ("Tim O'Brien"));
        supported_by.Add (wxT ("Ivan Pullman"));
+       supported_by.Add (wxT ("Mark Rolfe"));
        supported_by.Add (wxT ("Andrä Steiner"));
        supported_by.Add (wxT ("Jussi Siponen"));
        supported_by.Add (wxT ("Lasse Salling"));
        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)
  {
diff --combined src/wx/audio_panel.cc
index ad1990cdcd2104a9528a2219e882390a0385eafd,72cb9fe6a2475cce46a7c483234cbecaac1e056c..26237db31d3e2aaf57552356bc3823cd62b5462a
@@@ -22,7 -22,6 +22,7 @@@
  #include "lib/config.h"
  #include "lib/sound_processor.h"
  #include "lib/ffmpeg_content.h"
 +#include "lib/ffmpeg_audio_stream.h"
  #include "audio_dialog.h"
  #include "audio_panel.h"
  #include "audio_mapping_view.h"
@@@ -51,9 -50,9 +51,9 @@@ AudioPanel::AudioPanel (FilmEditor* e
        ++r;
  
        add_label_to_grid_bag_sizer (grid, this, _("Audio Gain"), true, wxGBPosition (r, 0));
-       _gain = new ContentSpinCtrl<AudioContent> (
+       _gain = new ContentSpinCtrlDouble<AudioContent> (
                this,
-               new wxSpinCtrl (this),
+               new wxSpinCtrlDouble (this),
                AudioContentProperty::AUDIO_GAIN,
                boost::mem_fn (&AudioContent::audio_gain),
                boost::mem_fn (&AudioContent::set_audio_gain)
@@@ -89,6 -88,8 +89,8 @@@
        _sizer->Add (_mapping, 1, wxEXPAND | wxALL, 6);
  
        _gain->wrapped()->SetRange (-60, 60);
+       _gain->wrapped()->SetDigits (1);
+       _gain->wrapped()->SetIncrement (0.5);
        _delay->wrapped()->SetRange (-1000, 1000);
  
        _stream->Bind                (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&AudioPanel::stream_changed, this));
@@@ -238,7 -239,7 +240,7 @@@ AudioPanel::setup_stream_description (
                } else {
                        s << fcs->audio_channels() << wxT (" ") << _("channels");
                }
 -              s << wxT (", ") << fcs->content_audio_frame_rate() << _("Hz");
 +              s << wxT (", ") << fcs->audio_frame_rate() << _("Hz");
                _description->SetLabel (s);
        }
  }
diff --combined src/wx/config_dialog.cc
index 631628e1d1d0a13163a0800107d75fe9f2a55b9c,e65e931d023b2db5bda5381a096e03d269a1d3f0..68fbc3f1b391b05a0bc1d97de2a42282254e817a
@@@ -28,7 -28,7 +28,7 @@@
  #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"
@@@ -40,7 -40,7 +40,7 @@@
  #include "editable_list.h"
  #include "filter_dialog.h"
  #include "dir_picker_ctrl.h"
- #include "dci_metadata_dialog.h"
+ #include "isdcf_metadata_dialog.h"
  #include "preset_colour_conversion_dialog.h"
  #include "server_dialog.h"
  
@@@ -314,9 -314,9 +314,9 @@@ public
  #endif
                table->Add (_directory, 1, wxEXPAND);
                
-               add_label_to_sizer (table, panel, _("Default DCI name details"), true);
-               _dci_metadata_button = new wxButton (panel, wxID_ANY, _("Edit..."));
-               table->Add (_dci_metadata_button);
+               add_label_to_sizer (table, panel, _("Default ISDCF name details"), true);
+               _isdcf_metadata_button = new wxButton (panel, wxID_ANY, _("Edit..."));
+               table->Add (_isdcf_metadata_button);
                
                add_label_to_sizer (table, panel, _("Default container"), true);
                _container = new wxChoice (panel, wxID_ANY);
                _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
                _directory->Bind (wxEVT_COMMAND_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
                
-               _dci_metadata_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DefaultsPage::edit_dci_metadata_clicked, this, parent));
+               _isdcf_metadata_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DefaultsPage::edit_isdcf_metadata_clicked, this, parent));
                
                vector<Ratio const *> ratio = Ratio::all ();
                int n = 0;
@@@ -419,11 -419,11 +419,11 @@@ private
                Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
        }
  
-       void edit_dci_metadata_clicked (wxWindow* parent)
+       void edit_isdcf_metadata_clicked (wxWindow* parent)
        {
-               DCIMetadataDialog* d = new DCIMetadataDialog (parent, Config::instance()->default_dci_metadata ());
+               ISDCFMetadataDialog* d = new ISDCFMetadataDialog (parent, Config::instance()->default_isdcf_metadata ());
                d->ShowModal ();
-               Config::instance()->set_default_dci_metadata (d->dci_metadata ());
+               Config::instance()->set_default_isdcf_metadata (d->isdcf_metadata ());
                d->Destroy ();
        }
  
  
        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);
        }
        
        wxSpinCtrl* _j2k_bandwidth;
        wxSpinCtrl* _audio_delay;
-       wxButton* _dci_metadata_button;
+       wxButton* _isdcf_metadata_button;
        wxSpinCtrl* _still_length;
  #ifdef DCPOMATIC_USE_OWN_DIR_PICKER
        DirPickerCtrl* _directory;
diff --combined src/wx/content_widget.h
index 8b76160443fd0cf31fe34e75e710ec0faf5a3d64,ca94850065bf4f1e9e75a865a4b463435789bd24..9e1338b7c880eda03c5bfbeed58e00110d4fe94b
@@@ -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/content_widget.h
 + *  @brief ContentWidget class.
 + */
 +
  #ifndef DCPOMATIC_MULTIPLE_WIDGET_H
  #define DCPOMATIC_MULTIPLE_WIDGET_H
  
@@@ -30,8 -26,7 +30,8 @@@
  #include <boost/function.hpp>
  #include "wx_util.h"
  
 -/** A widget which represents some Content state and which can be used
 +/** @class ContentWidget
 + *  @brief A widget which represents some Content state and which can be used
   *  when multiple pieces of content are selected.
   *
   *  @param S Type containing the content being represented (e.g. VideoContent)
@@@ -227,6 -222,30 +227,30 @@@ public
        }
  };
  
+ template <class S>
+ class ContentSpinCtrlDouble : public ContentWidget<S, wxSpinCtrlDouble, double, double>
+ {
+ public:
+       ContentSpinCtrlDouble (
+               wxWindow* parent,
+               wxSpinCtrlDouble* wrapped,
+               int property,
+               boost::function<double (S*)> getter,
+               boost::function<void (S*, double)> setter
+               )
+               : ContentWidget<S, wxSpinCtrlDouble, double, double> (
+                       parent,
+                       wrapped,
+                       property,
+                       getter, setter,
+                       &caster<double, double>,
+                       &caster<double, double>
+                       )
+       {
+               wrapped->Bind (wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, boost::bind (&ContentWidget<S, wxSpinCtrlDouble, double, double>::view_changed, this));
+       }
+ };
  template <class S, class U>
  class ContentChoice : public ContentWidget<S, wxChoice, U, int>
  {
diff --combined src/wx/film_editor.cc
index a6cb77f8561cf36de0aed3594e62a5a8b058acc6,9c980b62568dbe037fd2456e215f7a848bb57cda..252a89719a5a718d832a807a1d45226118f41426
@@@ -48,7 -48,7 +48,7 @@@
  #include "timecode.h"
  #include "wx_util.h"
  #include "film_editor.h"
- #include "dci_metadata_dialog.h"
+ #include "isdcf_metadata_dialog.h"
  #include "timeline_dialog.h"
  #include "timing_panel.h"
  #include "subtitle_panel.h"
@@@ -125,10 -125,10 +125,10 @@@ FilmEditor::make_dcp_panel (
        flags |= wxALIGN_RIGHT;
  #endif        
  
-       _use_dci_name = new wxCheckBox (_dcp_panel, wxID_ANY, _("Use DCI name"));
-       grid->Add (_use_dci_name, wxGBPosition (r, 0), wxDefaultSpan, flags);
-       _edit_dci_button = new wxButton (_dcp_panel, wxID_ANY, _("Details..."));
-       grid->Add (_edit_dci_button, wxGBPosition (r, 1), wxDefaultSpan);
+       _use_isdcf_name = new wxCheckBox (_dcp_panel, wxID_ANY, _("Use ISDCF name"));
+       grid->Add (_use_isdcf_name, wxGBPosition (r, 0), wxDefaultSpan, flags);
+       _edit_isdcf_button = new wxButton (_dcp_panel, wxID_ANY, _("Details..."));
+       grid->Add (_edit_isdcf_button, wxGBPosition (r, 1), wxDefaultSpan);
        ++r;
  
        add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Container"), true, wxGBPosition (r, 0));
@@@ -232,8 -232,8 +232,8 @@@ voi
  FilmEditor::connect_to_widgets ()
  {
        _name->Bind             (wxEVT_COMMAND_TEXT_UPDATED,          boost::bind (&FilmEditor::name_changed, this));
-       _use_dci_name->Bind     (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::use_dci_name_toggled, this));
-       _edit_dci_button->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::edit_dci_button_clicked, this));
+       _use_isdcf_name->Bind   (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::use_isdcf_name_toggled, this));
+       _edit_isdcf_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::edit_isdcf_button_clicked, this));
        _container->Bind        (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::container_changed, this));
        _content->Bind          (wxEVT_COMMAND_LIST_ITEM_SELECTED,    boost::bind (&FilmEditor::content_selection_changed, this));
        _content->Bind          (wxEVT_COMMAND_LIST_ITEM_DESELECTED,  boost::bind (&FilmEditor::content_selection_changed, this));
@@@ -468,11 -468,11 +468,11 @@@ FilmEditor::film_changed (Film::Propert
        case Film::J2K_BANDWIDTH:
                checked_set (_j2k_bandwidth, _film->j2k_bandwidth() / 1000000);
                break;
-       case Film::USE_DCI_NAME:
-               checked_set (_use_dci_name, _film->use_dci_name ());
+       case Film::USE_ISDCF_NAME:
+               checked_set (_use_isdcf_name, _film->use_isdcf_name ());
                setup_dcp_name ();
                break;
-       case Film::DCI_METADATA:
+       case Film::ISDCF_METADATA:
                setup_dcp_name ();
                break;
        case Film::VIDEO_FRAME_RATE:
@@@ -609,7 -609,7 +609,7 @@@ FilmEditor::set_film (shared_ptr<Film> 
        }
  
        film_changed (Film::NAME);
-       film_changed (Film::USE_DCI_NAME);
+       film_changed (Film::USE_ISDCF_NAME);
        film_changed (Film::CONTENT);
        film_changed (Film::DCP_CONTENT_TYPE);
        film_changed (Film::CONTAINER);
        film_changed (Film::SIGNED);
        film_changed (Film::ENCRYPTED);
        film_changed (Film::J2K_BANDWIDTH);
-       film_changed (Film::DCI_METADATA);
+       film_changed (Film::ISDCF_METADATA);
        film_changed (Film::VIDEO_FRAME_RATE);
        film_changed (Film::AUDIO_CHANNELS);
        film_changed (Film::SEQUENCE_VIDEO);
@@@ -640,8 -640,8 +640,8 @@@ FilmEditor::set_general_sensitivity (bo
  
        /* Stuff in the Content / DCP tabs */
        _name->Enable (s);
-       _use_dci_name->Enable (s);
-       _edit_dci_button->Enable (s);
+       _use_isdcf_name->Enable (s);
+       _edit_isdcf_button->Enable (s);
        _content->Enable (s);
        _content_add_file->Enable (s);
        _content_add_folder->Enable (s);
@@@ -691,25 -691,25 +691,25 @@@ FilmEditor::scaler_changed (
  }
  
  void
- FilmEditor::use_dci_name_toggled ()
+ FilmEditor::use_isdcf_name_toggled ()
  {
        if (!_film) {
                return;
        }
  
-       _film->set_use_dci_name (_use_dci_name->GetValue ());
+       _film->set_use_isdcf_name (_use_isdcf_name->GetValue ());
  }
  
  void
- FilmEditor::edit_dci_button_clicked ()
+ FilmEditor::edit_isdcf_button_clicked ()
  {
        if (!_film) {
                return;
        }
  
-       DCIMetadataDialog* d = new DCIMetadataDialog (this, _film->dci_metadata ());
+       ISDCFMetadataDialog* d = new ISDCFMetadataDialog (this, _film->isdcf_metadata ());
        d->ShowModal ();
-       _film->set_dci_metadata (d->dci_metadata ());
+       _film->set_isdcf_metadata (d->isdcf_metadata ());
        d->Destroy ();
  }
  
@@@ -863,11 -863,11 +863,11 @@@ FilmEditor::setup_content_sensitivity (
        _content_remove->Enable   (selection.size() == 1 && _generally_sensitive);
        _content_earlier->Enable  (selection.size() == 1 && _generally_sensitive);
        _content_later->Enable    (selection.size() == 1 && _generally_sensitive);
-       _content_timeline->Enable (_generally_sensitive);
+       _content_timeline->Enable (!_film->content().empty() && _generally_sensitive);
  
        _video_panel->Enable    (video_selection.size() > 0 && _generally_sensitive);
        _audio_panel->Enable    (audio_selection.size() > 0 && _generally_sensitive);
 -      _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<FFmpegContent> (selection.front()) && _generally_sensitive);
 +      _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<SubtitleContent> (selection.front()) && _generally_sensitive);
        _timing_panel->Enable   (selection.size() == 1 && _generally_sensitive);
  }
  
diff --combined src/wx/timeline.cc
index d5643b556db5747c7e80099165217886435f0b87,d4df6d8b2be2b246005b6c543d3ec8d5eeab3f60..4b56168a1d0636be5d56642d212b8050e19b61ce
@@@ -35,9 -35,7 +35,9 @@@ using boost::dynamic_pointer_cast
  using boost::bind;
  using boost::optional;
  
 -/** Parent class for components of the timeline (e.g. a piece of content or an axis) */
 +/** @class View
 + *  @brief Parent class for components of the timeline (e.g. a piece of content or an axis).
 + */
  class View : public boost::noncopyable
  {
  public:
@@@ -66,9 -64,9 +66,9 @@@
  protected:
        virtual void do_paint (wxGraphicsContext *) = 0;
        
 -      int time_x (Time t) const
 +      int time_x (DCPTime t) const
        {
-               return _timeline.tracks_position().x + t.seconds() * _timeline.pixels_per_second ();
 -              return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit().get_value_or (0);
++              return _timeline.tracks_position().x + t.seconds() * _timeline.pixels_per_second().get_value_or (0);
        }
        
        Timeline& _timeline;
@@@ -78,9 -76,7 +78,9 @@@ private
  };
  
  
 -/** Parent class for views of pieces of content */
 +/** @class ContentView
 + *  @brief Parent class for views of pieces of content.
 + */
  class ContentView : public View
  {
  public:
                return dcpomatic::Rect<int> (
                        time_x (content->position ()) - 8,
                        y_pos (_track.get()) - 8,
-                       content->length_after_trim().seconds() * _timeline.pixels_per_second() + 16,
 -                      content->length_after_trim () * _timeline.pixels_per_time_unit().get_value_or(0) + 16,
++                      content->length_after_trim().seconds() * _timeline.pixels_per_second().get_value_or(0) + 16,
                        _timeline.track_height() + 16
                        );
        }
@@@ -146,8 -142,8 +146,8 @@@ private
                        return;
                }
  
 -              Time const position = cont->position ();
 -              Time const len = cont->length_after_trim ();
 +              DCPTime const position = cont->position ();
 +              DCPTime const len = cont->length_after_trim ();
  
                wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2);
  
                wxDouble name_leading;
                gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading);
                
-               gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len.seconds() * _timeline.pixels_per_second(), _timeline.track_height()));
 -              gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len * _timeline.pixels_per_time_unit().get_value_or(0), _timeline.track_height()));
++              gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len.seconds() * _timeline.pixels_per_second().get_value_or(0), _timeline.track_height()));
                gc->DrawText (name, time_x (position) + 12, y_pos (_track.get() + 1) - name_height - 4);
                gc->ResetClip ();
        }
                }
  
                if (!frequent) {
 -                      _timeline.setup_pixels_per_time_unit ();
 +                      _timeline.setup_pixels_per_second ();
                        _timeline.Refresh ();
                }
        }
@@@ -250,25 -246,6 +250,25 @@@ private
        }
  };
  
 +class SubtitleContentView : public ContentView
 +{
 +public:
 +      SubtitleContentView (Timeline& tl, shared_ptr<Content> c)
 +              : ContentView (tl, c)
 +      {}
 +
 +private:
 +      wxString type () const
 +      {
 +              return _("subtitles");
 +      }
 +
 +      wxColour colour () const
 +      {
 +              return wxColour (163, 255, 154, 255);
 +      }
 +};
 +
  class TimeAxisView : public View
  {
  public:
@@@ -292,20 -269,26 +292,26 @@@ private
  
        void do_paint (wxGraphicsContext* gc)
        {
 -              if (!_timeline.pixels_per_time_unit()) {
++              if (!_timeline.pixels_per_second()) {
+                       return;
+               }
 -              double const pptu = _timeline.pixels_per_time_unit().get ();
++              double const pps = _timeline.pixels_per_second().get ();
+               
                gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID));
                
-               double mark_interval = rint (128 / _timeline.pixels_per_second ());
 -              int mark_interval = rint (128 / (TIME_HZ * pptu));
++              double mark_interval = rint (128 / pps);
                if (mark_interval > 5) {
 -                      mark_interval -= mark_interval % 5;
 +                      mark_interval -= int (rint (mark_interval)) % 5;
                }
                if (mark_interval > 10) {
 -                      mark_interval -= mark_interval % 10;
 +                      mark_interval -= int (rint (mark_interval)) % 10;
                }
                if (mark_interval > 60) {
 -                      mark_interval -= mark_interval % 60;
 +                      mark_interval -= int (rint (mark_interval)) % 60;
                }
                if (mark_interval > 3600) {
 -                      mark_interval -= mark_interval % 3600;
 +                      mark_interval -= int (rint (mark_interval)) % 3600;
                }
                
                if (mark_interval < 1) {
                path.AddLineToPoint (_timeline.width(), _y);
                gc->StrokePath (path);
  
 -              Time t = 0;
 -              while ((t * pptu) < _timeline.width()) {
 +              /* Time in seconds */
 +              DCPTime t;
-               while ((t.seconds() * _timeline.pixels_per_second()) < _timeline.width()) {
++              while ((t.seconds() * pps) < _timeline.width()) {
                        wxGraphicsPath path = gc->CreatePath ();
                        path.MoveToPoint (time_x (t), _y - 4);
                        path.AddLineToPoint (time_x (t), _y + 4);
                        gc->StrokePath (path);
  
 -                      int tc = t / TIME_HZ;
 +                      double tc = t.seconds ();
                        int const h = tc / 3600;
                        tc -= h * 3600;
                        int const m = tc / 60;
                        wxDouble str_leading;
                        gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading);
                        
-                       int const tx = _timeline.x_offset() + t.seconds() * _timeline.pixels_per_second();
 -                      int const tx = _timeline.x_offset() + t * pptu;
++                      int const tx = _timeline.x_offset() + t.seconds() * pps;
                        if ((tx + str_width) < _timeline.width()) {
                                gc->DrawText (str, time_x (t), _y + 16);
                        }
                        
 -                      t += mark_interval * TIME_HZ;
 +                      t += DCPTime::from_seconds (mark_interval);
                }
        }
  
@@@ -359,7 -341,6 +365,6 @@@ Timeline::Timeline (wxWindow* parent, F
        , _film (film)
        , _time_axis_view (new TimeAxisView (*this, 32))
        , _tracks (0)
-       , _pixels_per_second (0)
        , _left_down (false)
        , _down_view_position (0)
        , _first_move (false)
@@@ -425,13 -406,10 +430,13 @@@ Timeline::playlist_changed (
                if (dynamic_pointer_cast<AudioContent> (*i)) {
                        _views.push_back (shared_ptr<View> (new AudioContentView (*this, *i)));
                }
 +              if (dynamic_pointer_cast<SubtitleContent> (*i)) {
 +                      _views.push_back (shared_ptr<View> (new SubtitleContentView (*this, *i)));
 +              }
        }
  
        assign_tracks ();
 -      setup_pixels_per_time_unit ();
 +      setup_pixels_per_second ();
        Refresh ();
  }
  
@@@ -443,11 -421,11 +448,11 @@@ Timeline::assign_tracks (
                if (!cv) {
                        continue;
                }
 -      
 +
                shared_ptr<Content> content = cv->content();
  
                int t = 0;
 -              while (1) {
 +              while (true) {
                        ViewList::iterator j = _views.begin();
                        while (j != _views.end()) {
                                shared_ptr<ContentView> test = dynamic_pointer_cast<ContentView> (*j);
@@@ -493,14 -471,14 +498,14 @@@ Timeline::tracks () cons
  }
  
  void
 -Timeline::setup_pixels_per_time_unit ()
 +Timeline::setup_pixels_per_second ()
  {
        shared_ptr<const Film> film = _film.lock ();
 -      if (!film || film->length() == 0) {
 +      if (!film || film->length() == DCPTime ()) {
                return;
        }
  
 -      _pixels_per_time_unit = static_cast<double>(width() - x_offset() * 2) / film->length ();
 +      _pixels_per_second = static_cast<double>(width() - x_offset() * 2) / film->length().seconds ();
  }
  
  shared_ptr<View>
@@@ -602,6 -580,12 +607,12 @@@ Timeline::right_down (wxMouseEvent& ev
  void
  Timeline::set_position_from_event (wxMouseEvent& ev)
  {
 -      if (!_pixels_per_time_unit) {
++      if (!_pixels_per_second) {
+               return;
+       }
 -      double const pptu = _pixels_per_time_unit.get ();
++      double const pps = _pixels_per_second.get ();
        wxPoint const p = ev.GetPosition();
  
        if (!_first_move) {
                return;
        }
        
-       DCPTime new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / _pixels_per_second);
 -      Time new_position = _down_view_position + (p.x - _down_point.x) / pptu;
++      DCPTime new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / pps);
        
        if (_snap) {
                
                bool first = true;
 -              Time nearest_distance = TIME_MAX;
 -              Time nearest_new_position = TIME_MAX;
 +              DCPTime nearest_distance = DCPTime::max ();
 +              DCPTime nearest_new_position = DCPTime::max ();
                
                /* Find the nearest content edge; this is inefficient */
                for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
                        
                        {
                                /* Snap starts to ends */
 -                              Time const d = abs (cv->content()->end() - new_position);
 +                              DCPTime const d = DCPTime (cv->content()->end() - new_position).abs ();
                                if (first || d < nearest_distance) {
                                        nearest_distance = d;
                                        nearest_new_position = cv->content()->end();
                        
                        {
                                /* Snap ends to starts */
 -                              Time const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim()));
 +                              DCPTime const d = DCPTime (
 +                                      cv->content()->position() - (new_position + _down_view->content()->length_after_trim())
 +                                      ).abs ();
 +                              
                                if (d < nearest_distance) {
                                        nearest_distance = d;
                                        nearest_new_position = cv->content()->position() - _down_view->content()->length_after_trim ();
                
                if (!first) {
                        /* Snap if it's close; `close' means within a proportion of the time on the timeline */
-                       if (nearest_distance < DCPTime::from_seconds ((width() / pixels_per_second()) / 32)) {
 -                      if (nearest_distance < (width() / pptu) / 32) {
++                      if (nearest_distance < DCPTime::from_seconds ((width() / pps) / 32)) {
                                new_position = nearest_new_position;
                        }
                }
        }
        
 -      if (new_position < 0) {
 -              new_position = 0;
 +      if (new_position < DCPTime ()) {
 +              new_position = DCPTime ();
        }
        
        _down_view->content()->set_position (new_position);
@@@ -692,7 -673,7 +703,7 @@@ Timeline::film () cons
  void
  Timeline::resized ()
  {
 -      setup_pixels_per_time_unit ();
 +      setup_pixels_per_second ();
  }
  
  void
diff --combined src/wx/timeline.h
index 35153dd175d4c13aa4f510656c3810a2a1bd38fc,7b4d75d091d7397e37be13476264977133402206..4ba1cc425f022b6fe11d634052f7915a5c012ee8
@@@ -52,8 -52,8 +52,8 @@@ public
                return 48;
        }
  
-       double pixels_per_second () const {
 -      boost::optional<double> pixels_per_time_unit () const {
 -              return _pixels_per_time_unit;
++      boost::optional<double> pixels_per_second () const {
 +              return _pixels_per_second;
        }
  
        Position<int> tracks_position () const {
@@@ -62,7 -62,7 +62,7 @@@
  
        int tracks () const;
  
 -      void setup_pixels_per_time_unit ();
 +      void setup_pixels_per_second ();
  
        void set_snap (bool s) {
                _snap = s;
@@@ -96,11 -96,11 +96,11 @@@ private
        ViewList _views;
        boost::shared_ptr<TimeAxisView> _time_axis_view;
        int _tracks;
-       double _pixels_per_second;
 -      boost::optional<double> _pixels_per_time_unit;
++      boost::optional<double> _pixels_per_second;
        bool _left_down;
        wxPoint _down_point;
        boost::shared_ptr<ContentView> _down_view;
 -      Time _down_view_position;
 +      DCPTime _down_view_position;
        bool _first_move;
        ContentMenu _menu;
        bool _snap;
diff --combined src/wx/timing_panel.cc
index f33e052a1ad41dad2f2adea9b0b7aa39f7823ceb,6d9bf45391a6564420808083df84c56dcd4517cc..cc0639b4ccfd500609c879e41c36df016d4b1b36
@@@ -19,7 -19,6 +19,6 @@@
  
  #include "lib/content.h"
  #include "lib/image_content.h"
- #include "lib/sndfile_content.h"
  #include "timing_panel.h"
  #include "wx_util.h"
  #include "timecode.h"
@@@ -88,7 -87,7 +87,7 @@@ TimingPanel::film_content_changed (int 
                if (content) {
                        _position->set (content->position (), film_video_frame_rate);
                } else {
 -                      _position->set (0, 24);
 +                      _position->set (DCPTime () , 24);
                }
        } else if (
                property == ContentProperty::LENGTH ||
                        _full_length->set (content->full_length (), film_video_frame_rate);
                        _play_length->set (content->length_after_trim (), film_video_frame_rate);
                } else {
 -                      _full_length->set (0, 24);
 -                      _play_length->set (0, 24);
 +                      _full_length->set (DCPTime (), 24);
 +                      _play_length->set (DCPTime (), 24);
                }
        } else if (property == ContentProperty::TRIM_START) {
                if (content) {
                        _trim_start->set (content->trim_start (), film_video_frame_rate);
                        _play_length->set (content->length_after_trim (), film_video_frame_rate);
                } else {
 -                      _trim_start->set (0, 24);
 -                      _play_length->set (0, 24);
 +                      _trim_start->set (DCPTime (), 24);
 +                      _play_length->set (DCPTime (), 24);
                }
        } else if (property == ContentProperty::TRIM_END) {
                if (content) {
                        _trim_end->set (content->trim_end (), film_video_frame_rate);
                        _play_length->set (content->length_after_trim (), film_video_frame_rate);
                } else {
 -                      _trim_end->set (0, 24);
 -                      _play_length->set (0, 24);
 +                      _trim_end->set (DCPTime (), 24);
 +                      _play_length->set (DCPTime (), 24);
                }
        }
  
        }
  
        shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (content);
-       shared_ptr<SndfileContent> sc = dynamic_pointer_cast<SndfileContent> (content);
        _full_length->set_editable (ic && ic->still ());
        _play_length->set_editable (!ic || !ic->still ());
-       _video_frame_rate->Enable ((ic && !ic->still ()) || sc);
+       _video_frame_rate->Enable (ic && !ic->still ());
        _set_video_frame_rate->Enable (false);
  }
  
@@@ -157,8 -155,7 +155,8 @@@ TimingPanel::full_length_changed (
        if (c.size() == 1) {
                shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (c.front ());
                if (ic && ic->still ()) {
 -                      ic->set_video_length (rint (_full_length->get (_editor->film()->video_frame_rate()) * ic->video_frame_rate() / TIME_HZ));
 +                      /* XXX: No effective FRC here... is this right? */
 +                      ic->set_video_length (ContentTime (_full_length->get (_editor->film()->video_frame_rate()), FrameRateChange (1, 1)));
                }
        }
  }
diff --combined src/wx/video_panel.cc
index 399e71aac26edba4d752847d0faa4240bb2ead69,2d874b9594e8997571516d805d3d396df253b366..2a5577bc19574f46628ba38e5028d345b8a9ac72
@@@ -24,6 -24,7 +24,7 @@@
  #include "lib/config.h"
  #include "lib/util.h"
  #include "lib/ratio.h"
+ #include "lib/frame_rate_change.h"
  #include "filter_dialog.h"
  #include "video_panel.h"
  #include "wx_util.h"
@@@ -297,8 -298,8 +298,8 @@@ VideoPanel::setup_description (
        }
  
        Crop const crop = vcs->crop ();
 -      if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != libdcp::Size (0, 0)) {
 -              libdcp::Size cropped = vcs->video_size_after_crop ();
 +      if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != dcp::Size (0, 0)) {
 +              dcp::Size cropped = vcs->video_size_after_crop ();
                d << wxString::Format (
                        _("Cropped to %dx%d (%.2f:1)\n"),
                        cropped.width, cropped.height,
                ++lines;
        }
  
 -      libdcp::Size const container_size = _editor->film()->frame_size ();
 -      libdcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
 +      dcp::Size const container_size = _editor->film()->frame_size ();
 +      dcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
  
        if (scaled != vcs->video_size_after_crop ()) {
                d << wxString::Format (
diff --combined src/wx/wscript
index cd78f064920081a7bbee364035af9f3f2e0c753b,8c142698b04d1d247950a484c1544e794a3fb94e..0c87c09be0d9ac321fac36ed57d9c31b39c969f0
@@@ -15,7 -15,7 +15,7 @@@ sources = ""
            config_dialog.cc
            content_colour_conversion_dialog.cc
            content_menu.cc
-           dci_metadata_dialog.cc
+           isdcf_metadata_dialog.cc
            dir_picker_ctrl.cc
            dolby_certificate_dialog.cc
            doremi_certificate_dialog.cc
@@@ -38,7 -38,6 +38,7 @@@
            server_dialog.cc
            servers_list_dialog.cc
            subtitle_panel.cc
 +          subtitle_view.cc
            table_dialog.cc
            timecode.cc
            timeline.cc
diff --combined src/wx/wx_util.cc
index 048f87908e8c72770e973f0450a36767c1b747f3,b73cd490dfffcae386c8f88b460d5e74be9cb23c..aac35e97a4a1a2b2eb4ba5b78b81f0eeb1b3a697
@@@ -123,7 -123,7 +123,7 @@@ int const ThreadedStaticText::_update_e
   *  @param initial Initial text for the wxStaticText while the computation is being run.
   *  @param fn Function which works out what the wxStaticText content should be and returns it.
   */
 -ThreadedStaticText::ThreadedStaticText (wxWindow* parent, wxString initial, function<string ()> fn)
 +ThreadedStaticText::ThreadedStaticText (wxWindow* parent, wxString initial, boost::function<string ()> fn)
        : wxStaticText (parent, wxID_ANY, initial)
  {
        Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ThreadedStaticText::thread_finished, this, _1), _update_event_id);
@@@ -139,7 -139,7 +139,7 @@@ ThreadedStaticText::~ThreadedStaticTex
  
  /** Run our thread and post the result to the GUI thread via AddPendingEvent */
  void
 -ThreadedStaticText::run (function<string ()> fn)
 +ThreadedStaticText::run (boost::function<string ()> fn)
  try
  {
        wxCommandEvent ev (wxEVT_COMMAND_TEXT_UPDATED, _update_event_id);
@@@ -189,6 -189,15 +189,15 @@@ checked_set (wxSpinCtrl* widget, int va
        }
  }
  
+ void
+ checked_set (wxSpinCtrlDouble* widget, double value)
+ {
+       /* XXX: completely arbitrary epsilon */
+       if (fabs (widget->GetValue() - value) < 1e-16) {
+               widget->SetValue (value);
+       }
+ }
  void
  checked_set (wxChoice* widget, int value)
  {
@@@ -297,6 -306,12 +306,12 @@@ wx_get (wxChoice* w
        return w->GetSelection ();
  }
  
+ double
+ wx_get (wxSpinCtrlDouble* w)
+ {
+       return w->GetValue ();
+ }
  void
  run_gui_loop ()
  {
diff --combined test/4k_test.cc
index 1e0b42697aa297cb84891039e05327993afddfb1,e65804aa5e5ff2908eb2434a95c79ebdf1b16769..fa5b33bb921feb348e4a0d6ad83fda54c40411b7
@@@ -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  test/4k_test.cc
 + *  @brief Run a 4K encode from a simple input.
 + *
 + *  The output is checked against test/data/4k_test.
 + */
 +
  #include <boost/test/unit_test.hpp>
  #include "lib/film.h"
  #include "lib/ffmpeg_content.h"
@@@ -39,7 -33,7 +39,7 @@@ BOOST_AUTO_TEST_CASE (fourk_test
        shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/test.mp4"));
        c->set_scale (VideoContentScale (Ratio::from_id ("185")));
        film->set_resolution (RESOLUTION_4K);
-       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
        film->set_container (Ratio::from_id ("185"));
        film->examine_and_add_content (c);
        wait_for_jobs ();
diff --combined test/audio_delay_test.cc
index 87e6071a08bdb7e8717ce9726ed6301e8846365a,8ac5f746c8fb46e3621d392f9267297e7e792804..68e14ff3ca353f470d9f09f8c14112f6713b34a4
@@@ -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  test/audio_delay_test.cc
 + *  @brief Test encode using some SndfileContents which have audio delays.
 + *
 + *  The output is checked algorithmically using knowledge of the input.
 + */
 +
  #include <boost/test/unit_test.hpp>
 -#include <libdcp/sound_frame.h>
 -#include <libdcp/cpl.h>
 -#include <libdcp/reel.h>
 -#include <libdcp/sound_asset.h>
 +#include <dcp/sound_frame.h>
 +#include <dcp/cpl.h>
 +#include <dcp/reel.h>
 +#include <dcp/sound_mxf.h>
 +#include <dcp/reel_sound_asset.h>
  #include "lib/sndfile_content.h"
  #include "lib/dcp_content_type.h"
  #include "lib/ratio.h"
@@@ -45,7 -38,7 +45,7 @@@ void test_audio_delay (int delay_in_ms
  {
        string const film_name = "audio_delay_test_" + lexical_cast<string> (delay_in_ms);
        shared_ptr<Film> film = new_test_film (film_name);
-       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
        film->set_container (Ratio::from_id ("185"));
        film->set_name (film_name);
  
        boost::filesystem::path path = "build/test";
        path /= film_name;
        path /= film->dcp_name ();
 -      libdcp::DCP check (path.string ());
 +      dcp::DCP check (path.string ());
        check.read ();
  
 -      shared_ptr<const libdcp::SoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
 +      shared_ptr<const dcp::ReelSoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
        BOOST_CHECK (sound_asset);
  
        /* Sample index in the DCP */
        /* Delay in frames */
        int const delay_in_frames = delay_in_ms * 48000 / 1000;
  
 -      while (n < sound_asset->intrinsic_duration()) {
 -              shared_ptr<const libdcp::SoundFrame> sound_frame = sound_asset->get_frame (frame++);
 +      while (n < sound_asset->mxf()->intrinsic_duration()) {
 +              shared_ptr<const dcp::SoundFrame> sound_frame = sound_asset->mxf()->get_frame (frame++);
                uint8_t const * d = sound_frame->data ();
                
 -              for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->channels())) {
 +              for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->mxf()->channels())) {
  
                        /* Mono input so it will appear on centre */
                        int const sample = d[i + 7] | (d[i + 8] << 8);
@@@ -93,6 -86,7 +93,6 @@@
        }
  }
  
 -
  /* Test audio delay when specified in a piece of audio content */
  BOOST_AUTO_TEST_CASE (audio_delay_test)
  {
diff --combined test/black_fill_test.cc
index 7741277a595e753cacd76ffdff1b5a2f67b3cf0f,5c594f68cc064f170d3290c83833422522012c52..148ec9738c74329c9c126506488fac020ff41037
@@@ -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
@@@ -25,7 -25,7 +25,7 @@@
  #include "test.h"
  
  /** @file test/black_fill_test.cc
 - *  @brief Test insertion of black frames between video content.
 + *  @brief Test insertion of black frames between separate bits of video content.
   */
  
  using boost::shared_ptr;
@@@ -33,7 -33,7 +33,7 @@@
  BOOST_AUTO_TEST_CASE (black_fill_test)
  {
        shared_ptr<Film> film = new_test_film ("black_fill_test");
-       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
        film->set_name ("black_fill_test");
        film->set_container (Ratio::from_id ("185"));
        film->set_sequence_video (false);
        film->examine_and_add_content (contentB);
        wait_for_jobs ();
  
 -      contentA->set_video_length (3);
 -      contentA->set_position (film->video_frames_to_time (2));
 -      contentB->set_video_length (1);
 -      contentB->set_position (film->video_frames_to_time (7));
 +      contentA->set_video_length (ContentTime::from_frames (3, 24));
 +      contentA->set_position (DCPTime::from_frames (2, film->video_frame_rate ()));
 +      contentB->set_video_length (ContentTime::from_frames (1, 24));
 +      contentB->set_position (DCPTime::from_frames (7, film->video_frame_rate ()));
  
        film->make_dcp ();
  
index c0eddd8f6a7f8eee16dcbbef3d27119a2cf45898,07af1255c486ea42380f570ba6a643d566aedde0..1816de8e693df8a1a4fac0bf29e324a12efede53
  
  */
  
 +/** @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)
+ 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;
                p += sub_image->stride()[0];
        }
  
-       shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test.log"));
+       shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test_rgb.log"));
+       shared_ptr<PlayerVideoFrame> pvf (
+               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,
+                       ColourConversion ()
+                       )
+               );
 -      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 (
+                       pvf,
+                       0,
+                       24,
+                       200000000,
+                       RESOLUTION_2K,
+                       log
+                       )
+               );
+       shared_ptr<EncodedData> locally_encoded = frame->encode_locally ();
+       BOOST_ASSERT (locally_encoded);
+       
+       Server* server = new Server (log, true);
+       new thread (boost::bind (&Server::run, server, 2));
+       /* Let the server get itself ready */
+       dcpomatic_sleep (1);
+       ServerDescription description ("localhost", 2);
+       list<thread*> threads;
+       for (int i = 0; i < 8; ++i) {
+               threads.push_back (new thread (boost::bind (do_remote_encode, frame, description, locally_encoded)));
+       }
+       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
+               (*i)->join ();
+       }
+       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
+               delete *i;
+       }
+ }
+ 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) {
+               uint8_t* p = image->data()[i];
+               for (int j = 0; j < image->line_size()[i]; ++j) {
+                       *p++ = j % 256;
+               }
+       }
 -      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;
+               for (int x = 0; x < 100; ++x) {
+                       *q++ = y % 256;
+                       *q++ = x % 256;
+                       *q++ = (x + y) % 256;
+                       *q++ = 1;
+               }
+               p += sub_image->stride()[0];
+       }
+       shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test_yuv.log"));
  
        shared_ptr<PlayerVideoFrame> pvf (
                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 (
index 6dba4b71cf43531a0a9a4be690f34d1508b0da7d,f850847b837164264ad81bdb28284c43dbdf4c6a..7de169dd3e305ad23d76dbaf12b756ef80ccaebb
@@@ -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  test/colour_conversion_test.cc
 + *  @brief Basic test of identifier() for ColourConversion (i.e. a hash of the numbers)
 + */
 +
  #include <boost/test/unit_test.hpp>
 -#include <libdcp/colour_matrix.h>
 +#include <dcp/colour_matrix.h>
  #include "lib/colour_conversion.h"
  
  using std::cout;
  
 -/* Basic test of identifier() for ColourConversion (i.e. a hash of the numbers) */
  BOOST_AUTO_TEST_CASE (colour_conversion_test)
  {
 -      ColourConversion A (2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6);
 -      ColourConversion B (2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6);
 +      ColourConversion A (2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6);
 +      ColourConversion B (2.4, false, dcp::colour_matrix::srgb_to_xyz, 2.6);
  
-       BOOST_CHECK_EQUAL (A.identifier(), "246ff9b7dc32c0488948a32a713924b3");
-       BOOST_CHECK_EQUAL (B.identifier(), "a8d1da30f96a121d8db06a03409758b3");
+       BOOST_CHECK_EQUAL (A.identifier(), "1e720d2d99add654d7816f3b72da815e");
+       BOOST_CHECK_EQUAL (B.identifier(), "18751a247b22682b725bf9c4caf71522");
  }
index cc29f4472193255f0e5acc2020b5bf7df802975b,7440e43381e27dcb82530391f470607c13939332..70f29b998933d355df2b793797ad36d38c9a0205
@@@ -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  test/film_metadata_test.cc
 + *  @brief Test some basic reading/writing of film metadata.
 + */
 +
  #include <sstream>
  #include <boost/test/unit_test.hpp>
  #include <boost/filesystem.hpp>
@@@ -37,10 -33,14 +37,10 @@@ using boost::shared_ptr
  
  BOOST_AUTO_TEST_CASE (film_metadata_test)
  {
 -      string const test_film = "build/test/film_metadata_test";
 -      
 -      if (boost::filesystem::exists (test_film)) {
 -              boost::filesystem::remove_all (test_film);
 -      }
 +      shared_ptr<Film> f = new_test_film ("film_metadata_test");
 +      boost::filesystem::path dir = test_film_dir ("film_metadata_test");
  
-       f->_dci_date = boost::gregorian::from_undelimited_string ("20130211");
 -      shared_ptr<Film> f (new Film (test_film));
+       f->_isdcf_date = boost::gregorian::from_undelimited_string ("20130211");
        BOOST_CHECK (f->container() == 0);
        BOOST_CHECK (f->dcp_content_type() == 0);
  
@@@ -52,9 -52,9 +52,9 @@@
  
        list<string> ignore;
        ignore.push_back ("Key");
 -      check_xml ("test/data/metadata.xml.ref", test_film + "/metadata.xml", ignore);
 +      check_xml ("test/data/metadata.xml.ref", dir.string() + "/metadata.xml", ignore);
  
 -      shared_ptr<Film> g (new Film (test_film));
 +      shared_ptr<Film> g (new Film (dir));
        g->read_metadata ();
  
        BOOST_CHECK_EQUAL (g->name(), "fred");
@@@ -62,5 -62,5 +62,5 @@@
        BOOST_CHECK_EQUAL (g->container(), Ratio::from_id ("185"));
        
        g->write_metadata ();
 -      check_xml ("test/data/metadata.xml.ref", test_film + "/metadata.xml", ignore);
 +      check_xml ("test/data/metadata.xml.ref", dir.string() + "/metadata.xml", ignore);
  }
diff --combined test/frame_rate_test.cc
index e1d4e43701270b8aeb99b8ec57e4d1fe5993e4db,7e197dc03bf12472c848579306d0be8d45360ea8..e8ebcea3b959833ccb1009c561c7b99f4394bbf5
@@@ -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/frame_rate_test.cc
 + *  @brief Tests for FrameRateChange and the computation of the best
 + *  frame rate for the DCP.
 + */
 +
  #include <boost/test/unit_test.hpp>
  #include "lib/film.h"
  #include "lib/config.h"
  #include "lib/ffmpeg_content.h"
  #include "lib/playlist.h"
 +#include "lib/ffmpeg_audio_stream.h"
+ #include "lib/frame_rate_change.h"
  #include "test.h"
  
  using boost::shared_ptr;
@@@ -58,7 -53,6 +59,7 @@@ BOOST_AUTO_TEST_CASE (best_dcp_frame_ra
        BOOST_CHECK_EQUAL (frc.skip, true);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
        
        content->_video_frame_rate = 50;
        best = film->playlist()->best_dcp_frame_rate ();
@@@ -67,7 -61,6 +68,7 @@@
        BOOST_CHECK_EQUAL (frc.skip, true);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
  
        content->_video_frame_rate = 48;
        best = film->playlist()->best_dcp_frame_rate ();
@@@ -76,7 -69,6 +77,7 @@@
        BOOST_CHECK_EQUAL (frc.skip, true);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
  
        content->_video_frame_rate = 30;
        best = film->playlist()->best_dcp_frame_rate ();
@@@ -85,7 -77,6 +86,7 @@@
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
  
        content->_video_frame_rate = 29.97;
        best = film->playlist()->best_dcp_frame_rate ();
@@@ -94,7 -85,6 +95,7 @@@
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 30 / 29.97, 0.1);
        
        content->_video_frame_rate = 25;
        best = film->playlist()->best_dcp_frame_rate ();
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
  
        content->_video_frame_rate = 24;
        best = film->playlist()->best_dcp_frame_rate ();
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
  
        content->_video_frame_rate = 14.5;
        best = film->playlist()->best_dcp_frame_rate ();
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 15 / 14.5, 0.1);
  
        content->_video_frame_rate = 12.6;
        best = film->playlist()->best_dcp_frame_rate ();
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 25 / 25.2, 0.1);
  
        content->_video_frame_rate = 12.4;
        best = film->playlist()->best_dcp_frame_rate ();
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 25 / 24.8, 0.1);
  
        content->_video_frame_rate = 12;
        best = film->playlist()->best_dcp_frame_rate ();
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
  
        /* Now add some more rates and see if it will use them
           in preference to skip/repeat.
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
        
        content->_video_frame_rate = 50;
        best = film->playlist()->best_dcp_frame_rate ();
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
  
        content->_video_frame_rate = 48;
        best = film->playlist()->best_dcp_frame_rate ();
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
  
        /* Check some out-there conversions (not the best) */
        
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 24 / (2 * 14.99), 0.1);
  
        /* Check some conversions with limited DCP targets */
  
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
 +      BOOST_CHECK_CLOSE (frc.speed_up, 24.0 / 25, 0.1);
  }
  
  /* Test Playlist::best_dcp_frame_rate and FrameRateChange
@@@ -254,43 -233,43 +255,43 @@@ BOOST_AUTO_TEST_CASE (audio_sampling_ra
        content->_video_frame_rate = 24;
        film->set_video_frame_rate (24);
        content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
 -      BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 48000);
 +      BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 48000);
  
        content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
 -      BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 48000);
 +      BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 48000);
  
        content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0)));
 -      BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 96000);
 +      BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 96000);
  
        content->_video_frame_rate = 23.976;
        film->set_video_frame_rate (24);
        content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
 -      BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 47952);
 +      BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 47952);
  
        content->_video_frame_rate = 29.97;
        film->set_video_frame_rate (30);
        BOOST_CHECK_EQUAL (film->video_frame_rate (), 30);
        content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
 -      BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 47952);
 +      BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 47952);
  
        content->_video_frame_rate = 25;
        film->set_video_frame_rate (24);
        content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
 -      BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 50000);
 +      BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 50000);
  
        content->_video_frame_rate = 25;
        film->set_video_frame_rate (24);
        content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
 -      BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 50000);
 +      BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 50000);
  
        /* Check some out-there conversions (not the best) */
        
        content->_video_frame_rate = 14.99;
        film->set_video_frame_rate (25);
        content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
 -      /* The FrameRateChange within output_audio_frame_rate should choose to double-up
 +      /* The FrameRateChange within resampled_audio_frame_rate should choose to double-up
           the 14.99 fps video to 30 and then run it slow at 25.
        */
 -      BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), rint (48000 * 2 * 14.99 / 25));
 +      BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), rint (48000 * 2 * 14.99 / 25));
  }
  
diff --combined test/recover_test.cc
index 31b882a2eaeeeb81ca4e99450c594f96fad73798,284895e0a75d3bcee73ae157e078fc1ad6b86925..ae1009ea7bfc6d28b3af3e6030f10ecfba92be37
  
  */
  
 +/** @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,17 -30,16 +34,17 @@@ 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::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->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
        film->set_container (Ratio::from_id ("185"));
        film->set_name ("recover_test");
        film->set_three_d (true);
        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::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/scaling_test.cc
index 704c2c7dab65086829bb19e271879cd40cd8e313,cdf1653cdf84a7c78b40f1518a3d81516a600063..441af6bf30514ecb2eaaafa58ae7deea3ffdaa16
@@@ -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 test/scaling_test.cc
 + *  @brief Test scaling and black-padding of images from a still-image source.
 + */
 +
  #include <boost/test/unit_test.hpp>
  #include "lib/image_content.h"
  #include "lib/ratio.h"
  #include "lib/dcp_content_type.h"
  #include "test.h"
  
 -/** @file test/scaling_test.cc
 - *  @brief Test scaling and black-padding of images from a still-image source.
 - */
 -
  using std::string;
  using boost::shared_ptr;
  
@@@ -56,7 -56,7 +56,7 @@@ static void scaling_test_for (shared_pt
  BOOST_AUTO_TEST_CASE (scaling_test)
  {
        shared_ptr<Film> film = new_test_film ("scaling_test");
-       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
        film->set_name ("scaling_test");
        shared_ptr<ImageContent> imc (new ImageContent (film, "test/data/simple_testcard_640x480.png"));
  
@@@ -64,7 -64,7 +64,7 @@@
  
        wait_for_jobs ();
        
 -      imc->set_video_length (1);
 +      imc->set_video_length (ContentTime::from_frames (1, 24));
  
        scaling_test_for (film, imc, "133", "185");
        scaling_test_for (film, imc, "185", "185");
index 82b9def0e12f3ccbfa471f7e977d1272576b0eca,f1136a3f1a91c159fa3a32d3158d8b139822de88..d876a0228d7acafa9cd7ab0bb8319097c127869d
@@@ -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  test/silence_padding_test.cc
 + *  @brief Test the padding (with silence) of a mono source to a 6-channel DCP.
 + */
 +
  #include <boost/test/unit_test.hpp>
 -#include <libdcp/cpl.h>
 -#include <libdcp/dcp.h>
 -#include <libdcp/sound_asset.h>
 -#include <libdcp/sound_frame.h>
 -#include <libdcp/reel.h>
 +#include <dcp/cpl.h>
 +#include <dcp/dcp.h>
 +#include <dcp/sound_mxf.h>
 +#include <dcp/sound_frame.h>
 +#include <dcp/reel.h>
 +#include <dcp/reel_sound_asset.h>
  #include "lib/sndfile_content.h"
  #include "lib/film.h"
  #include "lib/dcp_content_type.h"
@@@ -38,12 -33,11 +38,12 @@@ using std::string
  using boost::lexical_cast;
  using boost::shared_ptr;
  
 -static void test_silence_padding (int channels)
 +static void
 +test_silence_padding (int channels)
  {
        string const film_name = "silence_padding_test_" + lexical_cast<string> (channels);
        shared_ptr<Film> film = new_test_film (film_name);
-       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
        film->set_container (Ratio::from_id ("185"));
        film->set_name (film_name);
  
        boost::filesystem::path path = "build/test";
        path /= film_name;
        path /= film->dcp_name ();
 -      libdcp::DCP check (path.string ());
 +      dcp::DCP check (path.string ());
        check.read ();
  
 -      shared_ptr<const libdcp::SoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
 +      shared_ptr<const dcp::ReelSoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
        BOOST_CHECK (sound_asset);
 -      BOOST_CHECK (sound_asset->channels () == channels);
 +      BOOST_CHECK_EQUAL (sound_asset->mxf()->channels (), channels);
  
        /* Sample index in the DCP */
        int n = 0;
        /* DCP sound asset frame */
        int frame = 0;
  
 -      while (n < sound_asset->intrinsic_duration()) {
 -              shared_ptr<const libdcp::SoundFrame> sound_frame = sound_asset->get_frame (frame++);
 +      while (n < sound_asset->mxf()->intrinsic_duration()) {
 +              shared_ptr<const dcp::SoundFrame> sound_frame = sound_asset->mxf()->get_frame (frame++);
                uint8_t const * d = sound_frame->data ();
                
 -              for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->channels())) {
 +              for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->mxf()->channels())) {
  
 -                      if (sound_asset->channels() > 0) {
 +                      if (sound_asset->mxf()->channels() > 0) {
                                /* L should be silent */
                                int const sample = d[i + 0] | (d[i + 1] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
                        }
  
 -                      if (sound_asset->channels() > 1) {
 +                      if (sound_asset->mxf()->channels() > 1) {
                                /* R should be silent */
                                int const sample = d[i + 2] | (d[i + 3] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
                        }
                        
 -                      if (sound_asset->channels() > 2) {
 +                      if (sound_asset->mxf()->channels() > 2) {
                                /* Mono input so it will appear on centre */
                                int const sample = d[i + 7] | (d[i + 8] << 8);
                                BOOST_CHECK_EQUAL (sample, n);
                        }
  
 -                      if (sound_asset->channels() > 3) {
 +                      if (sound_asset->mxf()->channels() > 3) {
                                /* Lfe should be silent */
                                int const sample = d[i + 9] | (d[i + 10] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
                        }
  
 -                      if (sound_asset->channels() > 4) {
 +                      if (sound_asset->mxf()->channels() > 4) {
                                /* Ls should be silent */
                                int const sample = d[i + 11] | (d[i + 12] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
                        }
  
  
 -                      if (sound_asset->channels() > 5) {
 +                      if (sound_asset->mxf()->channels() > 5) {
                                /* Rs should be silent */
                                int const sample = d[i + 13] | (d[i + 14] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
diff --combined test/test.cc
index f57023836bcf5d38a2dbc4b23b8f635a26d76b5c,0b87b8062a67adc503bf076ba32624cf7678a2ff..1d8041656cf05e2acaf76081e327c00f7621661b
@@@ -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_dci_metadata (DCIMetadata ());
+               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 == libdcp::ERROR) {
 +      if (t == 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 ());
 +}
diff --combined test/wscript
index c825506fb601999b1c1995247fdbe2027e3346a9,09df146737a95c3cff4b1e28e5eef0d655f84218..380d40d84e265d47a1fd7afc09c0903628c8178d
@@@ -10,44 -10,38 +10,45 @@@ def configure(conf)
                                """, msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework%s' % boost_test_suffix, uselib_store = 'BOOST_TEST')
  
  def build(bld):
 -    obj = bld(features = 'cxx cxxprogram')
 +    obj = bld(features='cxx cxxprogram')
      obj.name   = 'unit-tests'
      obj.uselib = 'BOOST_TEST BOOST_THREAD DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
      obj.use    = 'libdcpomatic'
      obj.source = """
                   4k_test.cc
                   audio_analysis_test.cc
 +                 audio_buffers_test.cc
                   audio_delay_test.cc
 +                 audio_decoder_test.cc
                   audio_mapping_test.cc
 -                 audio_merger_test.cc
                   black_fill_test.cc
                   client_server_test.cc
                   colour_conversion_test.cc
                   ffmpeg_audio_test.cc
                   ffmpeg_dcp_test.cc
 +                 ffmpeg_decoder_seek_test.cc
 +                 ffmpeg_decoder_sequential_test.cc
                   ffmpeg_examiner_test.cc
 -                 ffmpeg_pts_offset.cc
 +                 ffmpeg_pts_offset_test.cc
                   file_group_test.cc
                   film_metadata_test.cc
                   frame_rate_test.cc
                   image_test.cc
+                  isdcf_name_test.cc
                   job_test.cc
                   make_black_test.cc
 +                 player_test.cc
                   pixel_formats_test.cc
 -                 play_test.cc
                   ratio_test.cc
 +                 repeat_frame_test.cc
                   recover_test.cc
                   resampler_test.cc
                   scaling_test.cc
 +                 seek_zero_test.cc
                   silence_padding_test.cc
 +                 skip_frame_test.cc
                   stream_test.cc
 +                 subrip_test.cc
                   test.cc
                   threed_test.cc
                   util_test.cc
  
      obj.target = 'unit-tests'
      obj.install_path = ''
 +
 +    obj = bld(features='cxx cxxprogram')
 +    obj.name   = 'long-unit-tests'
 +    obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
 +    obj.use    = 'libdcpomatic'
 +    obj.source = """
 +                 test.cc
 +                 """
 +
 +    obj.target = 'long-unit-tests'
 +    obj.install_path = ''