Merge master.
authorCarl Hetherington <cth@carlh.net>
Fri, 16 May 2014 11:32:04 +0000 (12:32 +0100)
committerCarl Hetherington <cth@carlh.net>
Fri, 16 May 2014 11:32:04 +0000 (12:32 +0100)
31 files changed:
1  2 
ChangeLog
src/lib/audio_content.cc
src/lib/audio_mapping.cc
src/lib/colour_conversion.cc
src/lib/config.cc
src/lib/config.h
src/lib/content.cc
src/lib/dci_metadata.cc
src/lib/dcp_video_frame.cc
src/lib/ffmpeg.cc
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/film.cc
src/lib/image_examiner.cc
src/lib/kdm.cc
src/lib/playlist.cc
src/lib/server.cc
src/lib/server_finder.cc
src/lib/sndfile_content.cc
src/lib/subrip_content.cc
src/lib/subtitle_content.cc
src/lib/update.cc
src/lib/util.cc
src/lib/util.h
src/lib/video_content.cc
src/tools/dcpomatic.cc
src/wx/audio_mapping_view.cc
src/wx/config_dialog.cc
src/wx/film_editor.cc
test/ratio_test.cc

diff --combined ChangeLog
index 662983a74a4598610f8752a66c8dc5d800c8c61c,807130428281c596b6e42fe0175bc87d36332e9c..33b7e2e2122049f2fdb9bc98245bce6f6b3d9c6b
+++ b/ChangeLog
@@@ -1,7 -1,51 +1,55 @@@
 +2014-03-07  Carl Hetherington  <cth@carlh.net>
 +
 +      * Add subtitle view.
 +
+ 2014-05-16  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.8 released.
+ 2014-05-16  Carl Hetherington  <cth@carlh.net>
+       * Fix various confusions in translations of abbreviated
+       channel names (Lc, Rc etc.)
+ 2014-05-14  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.7 released.
+ 2014-05-14  Carl Hetherington  <cth@carlh.net>
+       * Bump libdcp to remove checks on PCM MXF edit rates,
+       so we can generate strange ones in DCP-o-matic.
+ 2014-05-13  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.6 released.
+ 2014-05-13  Carl Hetherington  <cth@carlh.net>
+       * Remove artificial 100fps limit when using
+       "any" DCP frame rate.
+ 2014-05-12  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.5 released.
+ 2014-05-12  Carl Hetherington  <cth@carlh.net>
+       * Add option to use any DCP frame rate, rather than just
+       the "allowed" set.
+       * Version 1.69.4 released.
+ 2014-05-12  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.3 released.
+ 2014-05-12  Carl Hetherington  <cth@carlh.net>
+       * Use libdcp::raw_convert instead of boost::lexical_cast and
+       LocaleGuard, hopefully to fix large numbers being written with
+       thousands separators on some locales.
  2014-05-10  Carl Hetherington  <cth@carlh.net>
  
        * Version 1.69.2 released.
diff --combined src/lib/audio_content.cc
index d9e00ff1401ca4e3335ea51c2944aff404a94fae,1896c4d5c356ad5105ab952c2e9f0067a2c77931..e8fd4bbd39d0e93813bd78b325624307e60e4750
@@@ -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
@@@ -18,6 -18,7 +18,7 @@@
  */
  
  #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"
@@@ -30,8 -31,8 +31,8 @@@
  using std::string;
  using std::vector;
  using boost::shared_ptr;
- using boost::lexical_cast;
  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;
@@@ -40,7 -41,7 +41,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 ())
@@@ -59,8 -60,6 +60,6 @@@ AudioContent::AudioContent (shared_ptr<
  AudioContent::AudioContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
        : Content (f, node)
  {
-       LocaleGuard lg;
-       
        _audio_gain = node->number_child<float> ("AudioGain");
        _audio_delay = node->number_child<int> ("AudioDelay");
  }
@@@ -90,11 -89,9 +89,9 @@@ AudioContent::AudioContent (shared_ptr<
  void
  AudioContent::as_xml (xmlpp::Node* node) const
  {
-       LocaleGuard lg;
-       
        boost::mutex::scoped_lock lm (_mutex);
-       node->add_child("AudioGain")->add_child_text (lexical_cast<string> (_audio_gain));
-       node->add_child("AudioDelay")->add_child_text (lexical_cast<string> (_audio_delay));
+       node->add_child("AudioGain")->add_child_text (raw_convert<string> (_audio_gain));
+       node->add_child("AudioDelay")->add_child_text (raw_convert<string> (_audio_delay));
  }
  
  
@@@ -149,43 -146,5 +146,43 @@@ 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::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 (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).
 +      */
 +
 +      if (frc.change_speed) {
 +              t /= frc.speed_up;
 +      }
 +
 +      return rint (t);
  }
diff --combined src/lib/audio_mapping.cc
index 969397b0bdf9a1a43f696cfc9d4b22ac04e125ef,b1810c97349e0652767e0940084b3db0b7add927..496300b48a746931f419748231f24f70364f4493
@@@ -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
@@@ -17,9 -17,9 +17,9 @@@
  
  */
  
- #include <boost/lexical_cast.hpp>
  #include <libxml++/libxml++.h>
  #include <libcxml/cxml.h>
 -#include <libdcp/raw_convert.h>
++#include <dcp/raw_convert.h>
  #include "audio_mapping.h"
  #include "util.h"
  
@@@ -30,8 -30,8 +30,8 @@@ using std::pair
  using std::string;
  using std::min;
  using boost::shared_ptr;
- using boost::lexical_cast;
  using boost::dynamic_pointer_cast;
 -using libdcp::raw_convert;
++using dcp::raw_convert;
  
  AudioMapping::AudioMapping ()
        : _content_channels (0)
  }
  
  /** Create a default AudioMapping for a given channel count.
 - *  @param c Number of channels.
 + *  @param channels Number of channels.
   */
 -AudioMapping::AudioMapping (int c)
 +AudioMapping::AudioMapping (int channels)
  {
 -      setup (c);
 +      setup (channels);
  }
  
  void
@@@ -69,11 -69,11 +69,11 @@@ AudioMapping::make_default (
  
        if (_content_channels == 1) {
                /* Mono -> Centre */
 -              set (0, libdcp::CENTRE, 1);
 +              set (0, dcp::CENTRE, 1);
        } else {
                /* 1:1 mapping */
                for (int i = 0; i < min (_content_channels, MAX_DCP_AUDIO_CHANNELS); ++i) {
 -                      set (i, static_cast<libdcp::Channel> (i), 1);
 +                      set (i, static_cast<dcp::Channel> (i), 1);
                }
        }
  }
@@@ -86,28 -86,28 +86,28 @@@ AudioMapping::AudioMapping (shared_ptr<
                /* Old-style: on/off mapping */
                list<cxml::NodePtr> const c = node->node_children ("Map");
                for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
 -                      set ((*i)->number_child<int> ("ContentIndex"), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP")), 1);
 +                      set ((*i)->number_child<int> ("ContentIndex"), static_cast<dcp::Channel> ((*i)->number_child<int> ("DCP")), 1);
                }
        } else {
                list<cxml::NodePtr> const c = node->node_children ("Gain");
                for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
                        set (
                                (*i)->number_attribute<int> ("Content"),
 -                              static_cast<libdcp::Channel> ((*i)->number_attribute<int> ("DCP")),
 +                              static_cast<dcp::Channel> ((*i)->number_attribute<int> ("DCP")),
-                               lexical_cast<float> ((*i)->content ())
+                               raw_convert<float> ((*i)->content ())
                                );
                }
        }
  }
  
  void
 -AudioMapping::set (int c, libdcp::Channel d, float g)
 +AudioMapping::set (int c, dcp::Channel d, float g)
  {
        _gain[c][d] = g;
  }
  
  float
 -AudioMapping::get (int c, libdcp::Channel d) const
 +AudioMapping::get (int c, dcp::Channel d) const
  {
        return _gain[c][d];
  }
  void
  AudioMapping::as_xml (xmlpp::Node* node) const
  {
-       node->add_child ("ContentChannels")->add_child_text (lexical_cast<string> (_content_channels));
+       node->add_child ("ContentChannels")->add_child_text (raw_convert<string> (_content_channels));
  
        for (int c = 0; c < _content_channels; ++c) {
                for (int d = 0; d < MAX_DCP_AUDIO_CHANNELS; ++d) {
                        xmlpp::Element* t = node->add_child ("Gain");
-                       t->set_attribute ("Content", lexical_cast<string> (c));
-                       t->set_attribute ("DCP", lexical_cast<string> (d));
-                       t->add_child_text (lexical_cast<string> (get (c, static_cast<dcp::Channel> (d))));
+                       t->set_attribute ("Content", raw_convert<string> (c));
+                       t->set_attribute ("DCP", raw_convert<string> (d));
 -                      t->add_child_text (raw_convert<string> (get (c, static_cast<libdcp::Channel> (d))));
++                      t->add_child_text (raw_convert<string> (get (c, static_cast<dcp::Channel> (d))));
                }
        }
  }
index e4a2a84bff4bc826355d0d20c60bbba4eb9b49e0,cd1a81b257dcadfa76b1c6196ae7e7267f9dfce6..73ee722490eaa33a7b46c1182459f9ea0164d67a
@@@ -17,9 -17,9 +17,9 @@@
  
  */
  
- #include <boost/lexical_cast.hpp>
  #include <libxml++/libxml++.h>
 -#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 "colour_conversion.h"
@@@ -32,8 -32,8 +32,8 @@@ using std::string
  using std::cout;
  using std::vector;
  using boost::shared_ptr;
- using boost::lexical_cast;
  using boost::optional;
 -using libdcp::raw_convert;
++using dcp::raw_convert;
  
  ColourConversion::ColourConversion ()
        : input_gamma (2.4)
@@@ -43,7 -43,7 +43,7 @@@
  {
        for (int i = 0; i < 3; ++i) {
                for (int j = 0; j < 3; ++j) {
 -                      matrix (i, j) = libdcp::colour_matrix::srgb_to_xyz[i][j];
 +                      matrix (i, j) = dcp::colour_matrix::srgb_to_xyz[i][j];
                }
        }
  }
@@@ -64,8 -64,6 +64,6 @@@ ColourConversion::ColourConversion (dou
  ColourConversion::ColourConversion (cxml::NodePtr node)
        : matrix (3, 3)
  {
-       LocaleGuard lg;
-       
        input_gamma = node->number_child<double> ("InputGamma");
        input_gamma_linearised = node->bool_child ("InputGammaLinearised");
  
@@@ -79,7 -77,7 +77,7 @@@
        for (list<cxml::NodePtr>::iterator i = m.begin(); i != m.end(); ++i) {
                int const ti = (*i)->number_attribute<int> ("i");
                int const tj = (*i)->number_attribute<int> ("j");
-               matrix(ti, tj) = lexical_cast<double> ((*i)->content ());
+               matrix(ti, tj) = raw_convert<double> ((*i)->content ());
        }
  
        output_gamma = node->number_child<double> ("OutputGamma");
  void
  ColourConversion::as_xml (xmlpp::Node* node) const
  {
-       LocaleGuard lg;
-       
-       node->add_child("InputGamma")->add_child_text (lexical_cast<string> (input_gamma));
+       node->add_child("InputGamma")->add_child_text (raw_convert<string> (input_gamma));
        node->add_child("InputGammaLinearised")->add_child_text (input_gamma_linearised ? "1" : "0");
  
        for (int i = 0; i < 3; ++i) {
                for (int j = 0; j < 3; ++j) {
                        xmlpp::Element* m = node->add_child("Matrix");
-                       m->set_attribute ("i", lexical_cast<string> (i));
-                       m->set_attribute ("j", lexical_cast<string> (j));
-                       m->add_child_text (lexical_cast<string> (matrix (i, j)));
+                       m->set_attribute ("i", raw_convert<string> (i));
+                       m->set_attribute ("j", raw_convert<string> (j));
+                       m->add_child_text (raw_convert<string> (matrix (i, j)));
                }
        }
  
-       node->add_child("OutputGamma")->add_child_text (lexical_cast<string> (output_gamma));
+       node->add_child("OutputGamma")->add_child_text (raw_convert<string> (output_gamma));
  }
  
  optional<size_t>
diff --combined src/lib/config.cc
index ca8d0bc53c407182c8b39dc0770648574f1d9e51,40ae3971be9bd0ba5781bb9496bb7f9bf2f7b257..754346418e2f4c83b66b0972a1e7868497620fd5
@@@ -23,7 -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"
@@@ -46,10 -47,10 +47,10 @@@ using std::max
  using std::exception;
  using std::cerr;
  using boost::shared_ptr;
- using boost::lexical_cast;
  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;
  
@@@ -60,6 -61,7 +61,7 @@@ Config::Config (
        , _use_any_servers (true)
        , _tms_path (".")
        , _sound_processor (SoundProcessor::from_id (N_("dolby_cp750")))
+       , _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"))
        _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
  Config::read ()
  {
-       LocaleGuard lg;
-       
        if (!boost::filesystem::exists (file (false))) {
                read_old_metadata ();
                return;
                /* 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");
        _check_for_test_updates = f.optional_bool_child("CheckForTestUpdates").get_value_or (false);
  
        _maximum_j2k_bandwidth = f.optional_number_child<int> ("MaximumJ2KBandwidth").get_value_or (250000000);
+       _allow_any_dcp_frame_rate = f.optional_bool_child ("AllowAnyDCPFrameRate");
  }
  
  void
@@@ -308,15 -309,13 +309,13 @@@ Config::instance (
  void
  Config::write () const
  {
-       LocaleGuard lg;
-       
        xmlpp::Document doc;
        xmlpp::Element* root = doc.create_root_node ("Config");
  
        root->add_child("Version")->add_child_text ("1");
-       root->add_child("NumLocalEncodingThreads")->add_child_text (lexical_cast<string> (_num_local_encoding_threads));
+       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 (lexical_cast<string> (_server_port_base));
+       root->add_child("ServerPortBase")->add_child_text (raw_convert<string> (_server_port_base));
        root->add_child("UseAnyServers")->add_child_text (_use_any_servers ? "1" : "0");
        
        for (vector<string>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) {
  
        _default_dci_metadata.as_xml (root->add_child ("DCIMetadata"));
  
-       root->add_child("DefaultStillLength")->add_child_text (lexical_cast<string> (_default_still_length));
-       root->add_child("DefaultJ2KBandwidth")->add_child_text (lexical_cast<string> (_default_j2k_bandwidth));
-       root->add_child("DefaultAudioDelay")->add_child_text (lexical_cast<string> (_default_audio_delay));
+       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));
+       root->add_child("DefaultAudioDelay")->add_child_text (raw_convert<string> (_default_audio_delay));
  
        for (vector<PresetColourConversion>::const_iterator i = _colour_conversions.begin(); i != _colour_conversions.end(); ++i) {
                i->as_xml (root->add_child ("ColourConversion"));
        root->add_child("CheckForUpdates")->add_child_text (_check_for_updates ? "1" : "0");
        root->add_child("CheckForTestUpdates")->add_child_text (_check_for_test_updates ? "1" : "0");
  
-       root->add_child("MaximumJ2KBandwidth")->add_child_text (lexical_cast<string> (_maximum_j2k_bandwidth));
+       root->add_child("MaximumJ2KBandwidth")->add_child_text (raw_convert<string> (_maximum_j2k_bandwidth));
+       root->add_child("AllowAnyDCPFrameRate")->add_child_text (_allow_any_dcp_frame_rate ? "1" : "0");
+       
        doc.write_to_file_formatted (file(false).string ());
  }
  
diff --combined src/lib/config.h
index ee11dcadbefb071cf606a07e9114275a5974c469,87b7038def5844843db76892d76ae6c20a2d8969..ffaacf8f17870fcfc559732d7b41c9b39b7b29ec
@@@ -28,7 -28,7 +28,7 @@@
  #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 "colour_conversion.h"
  #include "server.h"
@@@ -116,6 -116,10 +116,10 @@@ public
        std::list<int> allowed_dcp_frame_rates () const {
                return _allowed_dcp_frame_rates;
        }
+       bool allow_any_dcp_frame_rate () const {
+               return _allow_any_dcp_frame_rate;
+       }
        
        DCIMetadata default_dci_metadata () const {
                return _default_dci_metadata;
                return _default_dcp_content_type;
        }
  
 -      libdcp::XMLMetadata dcp_metadata () const {
 +      dcp::XMLMetadata dcp_metadata () const {
                return _dcp_metadata;
        }
  
                changed ();
        }
  
+       void set_allow_any_dcp_frame_rate (bool a) {
+               _allow_any_dcp_frame_rate = a;
+               changed ();
+       }
        void set_default_dci_metadata (DCIMetadata d) {
                _default_dci_metadata = d;
                changed ();
                changed ();
        }
  
 -      void set_dcp_metadata (libdcp::XMLMetadata m) {
 +      void set_dcp_metadata (dcp::XMLMetadata m) {
                _dcp_metadata = m;
                changed ();
        }
@@@ -369,13 -378,15 +378,15 @@@ private
        /** Our sound processor */
        SoundProcessor const * _sound_processor;
        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;
        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 0c37d938637cd8af26ad8e9db32b5f7bce0f9409,1ec607d394896b8ec0dbdc7ad1dbeacb7cfb7393..c4836cfa89a643c1838bc94ef302a99c661d9418
@@@ -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,7 -37,7 +41,7 @@@ using std::list
  using std::cout;
  using std::vector;
  using boost::shared_ptr;
- using boost::lexical_cast;
 -using libdcp::raw_convert;
++using dcp::raw_convert;
  
  int const ContentProperty::PATH = 400;
  int const ContentProperty::POSITION = 401;
@@@ -58,7 -55,7 +59,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)
@@@ -87,9 -84,9 +88,9 @@@ Content::Content (shared_ptr<const Film
                _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."));
                }
  
@@@ -123,9 -120,9 +124,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 (lexical_cast<string> (_position.get ()));
-       node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start.get ()));
-       node->add_child("TrimEnd")->add_child_text (lexical_cast<string> (_trim_end.get ()));
 -      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
@@@ -150,7 -147,7 +151,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);
@@@ -208,15 -205,24 +209,15 @@@ 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();
  }
  
 -/** @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 string which includes everything about how this content affects
   *  its playlist.
   */
@@@ -226,9 -232,9 +227,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 ();
  }
diff --combined src/lib/dci_metadata.cc
index 27306a15e48f2c1f6c44c7eb10bf90867a98a7f2,ccdc1ee1ca73d70720481497f392881f53caf101..2c6e43654915b239d94f4ff6acb61df9389d42fa
@@@ -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
  
  #include <iostream>
  #include <libcxml/cxml.h>
 -#include <libdcp/raw_convert.h>
++#include <dcp/raw_convert.h>
  #include "dci_metadata.h"
  
  #include "i18n.h"
  
  using std::string;
- using boost::lexical_cast;
  using boost::shared_ptr;
 -using libdcp::raw_convert;
++using dcp::raw_convert;
  
  DCIMetadata::DCIMetadata (shared_ptr<const cxml::Node> node)
  {
@@@ -42,7 -43,7 +43,7 @@@
  void
  DCIMetadata::as_xml (xmlpp::Node* root) const
  {
-       root->add_child("ContentVersion")->add_child_text (lexical_cast<string> (content_version));
+       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);
index 2b7a2e18f6b2ff712ba6baf47b9e074f44681676,1c12eb7fd7b9a6022de7e0a1b26895d3256669b7..59f356a5a78b297e0f54da14143a8ef43e2345a1
  #include <boost/array.hpp>
  #include <boost/asio.hpp>
  #include <boost/filesystem.hpp>
 -#include <libdcp/rec709_linearised_gamma_lut.h>
 -#include <libdcp/srgb_linearised_gamma_lut.h>
 -#include <libdcp/gamma_lut.h>
 -#include <libdcp/xyz_frame.h>
 -#include <libdcp/rgb_xyz.h>
 -#include <libdcp/colour_matrix.h>
 -#include <libdcp/raw_convert.h>
 +#include <boost/lexical_cast.hpp>
 +#include <dcp/gamma_lut.h>
 +#include <dcp/xyz_frame.h>
 +#include <dcp/rgb_xyz.h>
 +#include <dcp/colour_matrix.h>
++#include <dcp/raw_convert.h>
  #include <libcxml/cxml.h>
  #include "film.h"
  #include "dcp_video_frame.h"
@@@ -65,8 -67,8 +66,9 @@@ using std::string
  using std::stringstream;
  using std::cout;
  using boost::shared_ptr;
 -using libdcp::Size;
 -using libdcp::raw_convert;
 +using boost::lexical_cast;
 +using dcp::Size;
++using dcp::raw_convert;
  
  #define DCI_COEFFICENT (48.0 / 52.37)
  
@@@ -118,8 -120,12 +120,8 @@@ DCPVideoFrame::DCPVideoFrame (shared_pt
  shared_ptr<EncodedData>
  DCPVideoFrame::encode_locally ()
  {
 -      shared_ptr<libdcp::LUT> in_lut;
 -      if (_conversion.input_gamma_linearised) {
 -              in_lut = libdcp::SRGBLinearisedGammaLUT::cache.get (12, _conversion.input_gamma);
 -      } else {
 -              in_lut = libdcp::GammaLUT::cache.get (12, _conversion.input_gamma);
 -      }
 +      shared_ptr<dcp::GammaLUT> in_lut;
 +      in_lut = dcp::GammaLUT::cache.get (12, _conversion.input_gamma, _conversion.input_gamma_linearised);
  
        /* XXX: libdcp should probably use boost */
        
                }
        }
        
 -      shared_ptr<libdcp::XYZFrame> xyz = libdcp::rgb_to_xyz (
 +      shared_ptr<dcp::XYZFrame> xyz = dcp::rgb_to_xyz (
                _image,
                in_lut,
 -              libdcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma),
 +              dcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma, false),
                matrix
                );
                
@@@ -270,7 -276,7 +272,7 @@@ DCPVideoFrame::encode_remotely (ServerD
  {
        boost::asio::io_service io_service;
        boost::asio::ip::tcp::resolver resolver (io_service);
-       boost::asio::ip::tcp::resolver::query query (serv.host_name(), boost::lexical_cast<string> (Config::instance()->server_port_base ()));
+       boost::asio::ip::tcp::resolver::query query (serv.host_name(), raw_convert<string> (Config::instance()->server_port_base ()));
        boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve (query);
  
        shared_ptr<Socket> socket (new Socket);
        xmlpp::Document doc;
        xmlpp::Element* root = doc.create_root_node ("EncodingRequest");
  
-       root->add_child("Version")->add_child_text (lexical_cast<string> (SERVER_LINK_VERSION));
-       root->add_child("Width")->add_child_text (lexical_cast<string> (_image->size().width));
-       root->add_child("Height")->add_child_text (lexical_cast<string> (_image->size().height));
+       root->add_child("Version")->add_child_text (raw_convert<string> (SERVER_LINK_VERSION));
+       root->add_child("Width")->add_child_text (raw_convert<string> (_image->size().width));
+       root->add_child("Height")->add_child_text (raw_convert<string> (_image->size().height));
        add_metadata (root);
  
        stringstream xml;
  void
  DCPVideoFrame::add_metadata (xmlpp::Element* el) const
  {
-       el->add_child("Frame")->add_child_text (lexical_cast<string> (_frame));
+       el->add_child("Frame")->add_child_text (raw_convert<string> (_frame));
  
        switch (_eyes) {
        case EYES_BOTH:
        
        _conversion.as_xml (el->add_child("ColourConversion"));
  
-       el->add_child("FramesPerSecond")->add_child_text (lexical_cast<string> (_frames_per_second));
-       el->add_child("J2KBandwidth")->add_child_text (lexical_cast<string> (_j2k_bandwidth));
-       el->add_child("Resolution")->add_child_text (lexical_cast<string> (int (_resolution)));
+       el->add_child("FramesPerSecond")->add_child_text (raw_convert<string> (_frames_per_second));
+       el->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth));
+       el->add_child("Resolution")->add_child_text (raw_convert<string> (int (_resolution)));
  }
  
  EncodedData::EncodedData (int s)
@@@ -386,7 -392,7 +388,7 @@@ EncodedData::write (shared_ptr<const Fi
  }
  
  void
 -EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, libdcp::FrameInfo fin) const
 +EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, dcp::FrameInfo fin) const
  {
        boost::filesystem::path const info = film->info_path (frame, eyes);
        FILE* h = fopen_boost (info, "w");
diff --combined src/lib/ffmpeg.cc
index a98aa98289e6839dedf04ccc557c33b68d1e9c71,7ecc811be879a5af330402829377c7697a7204c2..316b9614de6f27bb18b26278e2fd21006e65d95f
@@@ -22,6 -22,7 +22,7 @@@ extern "C" 
  #include <libavformat/avformat.h>
  #include <libswscale/swscale.h>
  }
 -#include <libdcp/raw_convert.h>
++#include <dcp/raw_convert.h>
  #include "ffmpeg.h"
  #include "ffmpeg_content.h"
  #include "exceptions.h"
@@@ -33,7 -34,7 +34,7 @@@ using std::string
  using std::cout;
  using std::stringstream;
  using boost::shared_ptr;
- using boost::lexical_cast;
 -using libdcp::raw_convert;
++using dcp::raw_convert;
  
  boost::mutex FFmpeg::_mutex;
  
@@@ -94,8 -95,8 +95,8 @@@ FFmpeg::setup_general (
        /* These durations are in microseconds, and represent how far into the content file
           we will look for streams.
        */
-       av_dict_set (&options, "analyzeduration", lexical_cast<string> (5 * 60 * 1e6).c_str(), 0);
-       av_dict_set (&options, "probesize", lexical_cast<string> (5 * 60 * 1e6).c_str(), 0);
+       av_dict_set (&options, "analyzeduration", raw_convert<string> (5 * 60 * 1000000).c_str(), 0);
+       av_dict_set (&options, "probesize", raw_convert<string> (5 * 60 * 1000000).c_str(), 0);
        
        if (avformat_open_input (&_format_context, 0, 0, &options) < 0) {
                throw OpenFileError (_ffmpeg_content->path(0).string ());
@@@ -192,10 -193,6 +193,10 @@@ FFmpeg::video_codec_context () cons
  AVCodecContext *
  FFmpeg::audio_codec_context () const
  {
 +      if (!_ffmpeg_content->audio_stream ()) {
 +              return 0;
 +      }
 +      
        return _ffmpeg_content->audio_stream()->stream(_format_context)->codec;
  }
  
index a51cb3de805cdcdefe8910e9c8bade812601441d,f810d53be21374e27cd3e929214d04130fcb98b6..a374bcf3e7496932b5f843c16e4b07458286a4fa
@@@ -21,6 -21,7 +21,7 @@@ extern "C" 
  #include <libavformat/avformat.h>
  }
  #include <libcxml/cxml.h>
+ #include <libdcp/raw_convert.h>
  #include "ffmpeg_content.h"
  #include "ffmpeg_examiner.h"
  #include "compose.hpp"
@@@ -40,8 -41,8 +41,8 @@@ using std::list
  using std::cout;
  using std::pair;
  using boost::shared_ptr;
- using boost::lexical_cast;
  using boost::dynamic_pointer_cast;
+ using libdcp::raw_convert;
  
  int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
  int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
@@@ -152,7 -153,7 +153,7 @@@ FFmpegContent::as_xml (xmlpp::Node* nod
        }
  
        if (_first_video) {
-               node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get().get()));
 -              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()));
        }
  }
  
@@@ -163,14 -164,14 +164,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 ();
 -      film->log()->log (String::compose ("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);
 +      film->log()->log (String::compose ("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);
@@@ -231,13 -234,13 +232,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 ();
@@@ -265,14 -268,19 +266,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
@@@ -288,7 -296,7 +289,7 @@@ FFmpegContent::audio_channels () cons
  }
  
  int
 -FFmpegContent::content_audio_frame_rate () const
 +FFmpegContent::audio_frame_rate () const
  {
        boost::mutex::scoped_lock lm (_mutex);
  
        return _audio_stream->frame_rate;
  }
  
 -int
 -FFmpegContent::output_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 ());
 -
 -      FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
 -
 -      /* 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) {
 -              t *= video_frame_rate() * frc.factor() / film->video_frame_rate();
 -      }
 -
 -      return rint (t);
 -}
 -
  bool
  operator== (FFmpegStream const & a, FFmpegStream const & b)
  {
@@@ -322,7 -354,7 +323,7 @@@ voi
  FFmpegStream::as_xml (xmlpp::Node* root) const
  {
        root->add_child("Name")->add_child_text (name);
-       root->add_child("Id")->add_child_text (lexical_cast<string> (_id));
+       root->add_child("Id")->add_child_text (raw_convert<string> (_id));
  }
  
  FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node, int version)
@@@ -338,10 -370,10 +339,10 @@@ voi
  FFmpegAudioStream::as_xml (xmlpp::Node* root) const
  {
        FFmpegStream::as_xml (root);
-       root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
-       root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
+       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 (lexical_cast<string> (first_audio.get().get()));
 -              root->add_child("FirstAudio")->add_child_text (raw_convert<string> (first_audio.get ()));
++              root->add_child("FirstAudio")->add_child_text (raw_convert<string> (first_audio.get().get()));
        }
        mapping.as_xml (root->add_child("Mapping"));
  }
@@@ -391,12 -423,14 +392,12 @@@ FFmpegSubtitleStream::as_xml (xmlpp::No
        FFmpegStream::as_xml (root);
  }
  
 -Time
 +DCPTime
  FFmpegContent::full_length () const
  {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
 -      
 -      FrameRateConversion 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
@@@ -426,7 -460,7 +427,7 @@@ voi
  FFmpegContent::set_audio_mapping (AudioMapping m)
  {
        audio_stream()->mapping = m;
 -      signal_changed (AudioContentProperty::AUDIO_MAPPING);
 +      AudioContent::set_audio_mapping (m);
  }
  
  string
diff --combined src/lib/ffmpeg_content.h
index 1ab0a92d0ed2f89dc329d66a0b392581a499af5c,6ab95d2fe9d2bf53a08acd050845721ed40fc1a1..37746ac9db213ed73ec9a63931347c3ed2475360
@@@ -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
@@@ -21,6 -21,6 +21,7 @@@
  #define DCPOMATIC_FFMPEG_CONTENT_H
  
  #include <boost/enable_shared_from_this.hpp>
++#include <boost/lexical_cast.hpp>
  #include "video_content.h"
  #include "audio_content.h"
  #include "subtitle_content.h"
@@@ -87,7 -87,7 +88,7 @@@ public
        int frame_rate;
        int channels;
        AudioMapping mapping;
 -      boost::optional<double> first_audio;
 +      boost::optional<ContentTime> first_audio;
  
  private:
        friend class ffmpeg_pts_offset_test;
@@@ -139,14 -139,15 +140,14 @@@ public
        std::string technical_summary () const;
        std::string information () const;
        void as_xml (xmlpp::Node *) const;
 -      Time full_length () const;
 +      DCPTime full_length () const;
  
        std::string identifier () const;
        
        /* AudioContent */
        int audio_channels () const;
 -      AudioContent::Frame audio_length () const;
 -      int content_audio_frame_rate () const;
 -      int output_audio_frame_rate () const;
 +      ContentTime audio_length () const;
 +      int audio_frame_rate () const;
        AudioMapping audio_mapping () const;
        void set_audio_mapping (AudioMapping);
        boost::filesystem::path audio_analysis_path () const;
        void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
        void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>);
  
 -      boost::optional<double> first_video () const {
 +      boost::optional<ContentTime> first_video () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _first_video;
        }
@@@ -193,7 -194,7 +194,7 @@@ private
        boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
        std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
        boost::shared_ptr<FFmpegAudioStream> _audio_stream;
 -      boost::optional<double> _first_video;
 +      boost::optional<ContentTime> _first_video;
        /** Video filters that should be used when generating DCPs */
        std::vector<Filter const *> _filters;
  };
index 0a4624569f9239a952ca0c6824a5adf09f16ab55,c93012608406cab6aeaf8c78414f5215366898df..9ae5f0485246d2f88b42432adcfcf479a1eb711a
@@@ -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
  #include <iomanip>
  #include <iostream>
  #include <stdint.h>
- #include <boost/lexical_cast.hpp>
  #include <sndfile.h>
  extern "C" {
  #include <libavcodec/avcodec.h>
  #include <libavformat/avformat.h>
  }
 -#include "film.h"
  #include "filter.h"
  #include "exceptions.h"
  #include "image.h"
@@@ -55,15 -55,20 +54,15 @@@ using std::pair
  using boost::shared_ptr;
  using boost::optional;
  using boost::dynamic_pointer_cast;
 -using libdcp::Size;
 +using dcp::Size;
  
 -FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
 -      : Decoder (f)
 -      , VideoDecoder (f, c)
 -      , AudioDecoder (f, c)
 -      , SubtitleDecoder (f)
 +FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log> log)
 +      : VideoDecoder (c)
 +      , AudioDecoder (c)
        , FFmpeg (c)
 +      , _log (log)
        , _subtitle_codec_context (0)
        , _subtitle_codec (0)
 -      , _decode_video (video)
 -      , _decode_audio (audio)
 -      , _pts_offset (0)
 -      , _just_sought (false)
  {
        setup_subtitle ();
  
           Then we remove big initial gaps in PTS and we allow our
           insertion of black frames to work.
  
 -         We will do:
 -           audio_pts_to_use = audio_pts_from_ffmpeg + pts_offset;
 -           video_pts_to_use = video_pts_from_ffmpeg + pts_offset;
 +         We will do pts_to_use = pts_from_ffmpeg + pts_offset;
        */
  
 -      bool const have_video = video && c->first_video();
 -      bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio;
 +      bool const have_video = c->first_video();
 +      bool const have_audio = c->audio_stream () && c->audio_stream()->first_audio;
  
        /* First, make one of them start at 0 */
  
  
        /* Now adjust both so that the video pts starts on a frame */
        if (have_video && have_audio) {
 -              double first_video = c->first_video().get() + _pts_offset;
 -              double const old_first_video = first_video;
 -              
 -              /* Round the first video up to a frame boundary */
 -              if (fabs (rint (first_video * c->video_frame_rate()) - first_video * c->video_frame_rate()) > 1e-6) {
 -                      first_video = ceil (first_video * c->video_frame_rate()) / c->video_frame_rate ();
 -              }
 -
 -              _pts_offset += first_video - old_first_video;
 +              ContentTime first_video = c->first_video().get() + _pts_offset;
 +              ContentTime const old_first_video = first_video;
 +              _pts_offset += first_video.round_up (c->video_frame_rate ()) - old_first_video;
        }
  }
  
@@@ -119,15 -132,20 +118,15 @@@ FFmpegDecoder::flush (
        
        /* XXX: should we reset _packet.data and size after each *_decode_* call? */
        
 -      if (_decode_video) {
 -              while (decode_video_packet ()) {}
 -      }
 +      while (decode_video_packet ()) {}
        
 -      if (_ffmpeg_content->audio_stream() && _decode_audio) {
 +      if (_ffmpeg_content->audio_stream()) {
                decode_audio_packet ();
 +              AudioDecoder::flush ();
        }
 -
 -      /* Stop us being asked for any more data */
 -      _video_position = _ffmpeg_content->video_length_after_3d_combine ();
 -      _audio_position = _ffmpeg_content->audio_length ();
  }
  
 -void
 +bool
  FFmpegDecoder::pass ()
  {
        int r = av_read_frame (_format_context, &_packet);
                        /* Maybe we should fail here, but for now we'll just finish off instead */
                        char buf[256];
                        av_strerror (r, buf, sizeof(buf));
 -                      shared_ptr<const Film> film = _film.lock ();
 -                      assert (film);
 -                      film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
 +                      _log->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
                }
  
                flush ();
 -              return;
 +              return true;
        }
  
 -      shared_ptr<const Film> film = _film.lock ();
 -      assert (film);
 -
        int const si = _packet.stream_index;
        
 -      if (si == _video_stream && _decode_video) {
 +      if (si == _video_stream) {
                decode_video_packet ();
 -      } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _decode_audio) {
 +      } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) {
                decode_audio_packet ();
 -      } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && film->with_subtitles ()) {
 +      } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si)) {
                decode_subtitle_packet ();
        }
  
        av_free_packet (&_packet);
 +      return false;
  }
  
  /** @param data pointer to array of pointers to buffers.
@@@ -287,131 -309,77 +286,131 @@@ FFmpegDecoder::bytes_per_audio_sample (
        return av_get_bytes_per_sample (audio_sample_format ());
  }
  
 -void
 -FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
 +int
 +FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished)
  {
 -      double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
 +      int frames_read = 0;
 +      optional<ContentTime> last_video;
 +      optional<ContentTime> last_audio;
  
 -      /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being
 -         a number plucked from the air) earlier than we want to end up.  The loop below
 -         will hopefully then step through to where we want to be.
 -      */
 -      int initial = frame;
 +      while (!finished (last_video, last_audio, frames_read)) {
 +              int r = av_read_frame (_format_context, &_packet);
 +              if (r < 0) {
 +                      /* We should flush our decoders here, possibly yielding a few more frames,
 +                         but the consequence of having to do that is too hideous to contemplate.
 +                         Instead we give up and say that you can't seek too close to the end
 +                         of a file.
 +                      */
 +                      return frames_read;
 +              }
 +
 +              ++frames_read;
 +
 +              double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
 +
 +              if (_packet.stream_index == _video_stream) {
 +
 +                      avcodec_get_frame_defaults (_frame);
 +                      
 +                      int got_picture = 0;
 +                      r = avcodec_decode_video2 (video_codec_context(), _frame, &got_picture, &_packet);
 +                      if (r >= 0 && got_picture) {
 +                              last_video = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
 +                      }
 +
 +              } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, _packet.stream_index)) {
 +                      AVPacket copy_packet = _packet;
 +                      while (copy_packet.size > 0) {
  
 -      if (accurate) {
 -              initial -= 5;
 +                              int got_frame;
 +                              r = avcodec_decode_audio4 (audio_codec_context(), _frame, &got_frame, &_packet);
 +                              if (r >= 0 && got_frame) {
 +                                      last_audio = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
 +                              }
 +                                      
 +                              copy_packet.data += r;
 +                              copy_packet.size -= r;
 +                      }
 +              }
 +              
 +              av_free_packet (&_packet);
        }
  
 -      if (initial < 0) {
 -              initial = 0;
 +      return frames_read;
 +}
 +
 +bool
 +FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const
 +{
 +      return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek);
 +}
 +
 +bool
 +FFmpegDecoder::seek_final_finished (int n, int done) const
 +{
 +      return n == done;
 +}
 +
 +void
 +FFmpegDecoder::seek_and_flush (ContentTime t)
 +{
 +      ContentTime const u = t - _pts_offset;
 +      int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base);
 +
 +      if (_ffmpeg_content->audio_stream ()) {
 +              s = min (
 +                      s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base))
 +                      );
        }
  
 -      /* Initial seek time in the stream's timebase */
 -      int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _pts_offset) / time_base;
 +      /* Ridiculous empirical hack */
 +      s--;
 +      if (s < 0) {
 +              s = 0;
 +      }
  
 -      av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
 +      av_seek_frame (_format_context, _video_stream, s, 0);
  
        avcodec_flush_buffers (video_codec_context());
 +      if (audio_codec_context ()) {
 +              avcodec_flush_buffers (audio_codec_context ());
 +      }
        if (_subtitle_codec_context) {
                avcodec_flush_buffers (_subtitle_codec_context);
        }
 +}
  
 -      /* This !accurate is piling hack upon hack; setting _just_sought to true
 -         even with accurate == true defeats our attempt to align the start
 -         of the video and audio.  Here we disable that defeat when accurate == true
 -         i.e. when we are making a DCP rather than just previewing one.
 -         Ewww.  This should be gone in 2.0.
 +void
 +FFmpegDecoder::seek (ContentTime time, bool accurate)
 +{
 +      VideoDecoder::seek (time, accurate);
 +      AudioDecoder::seek (time, accurate);
 +      
 +      /* If we are doing an accurate seek, our initial shot will be 2s (2 being
 +         a number plucked from the air) earlier than we want to end up.  The loop below
 +         will hopefully then step through to where we want to be.
        */
 -      if (!accurate) {
 -              _just_sought = true;
 +
 +      ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0);
 +      ContentTime initial_seek = time - pre_roll;
 +      if (initial_seek < ContentTime (0)) {
 +              initial_seek = ContentTime (0);
        }
 -      
 -      _video_position = frame;
 -      
 -      if (frame == 0 || !accurate) {
 -              /* We're already there, or we're as close as we need to be */
 +
 +      /* Initial seek time in the video stream's timebase */
 +
 +      seek_and_flush (initial_seek);
 +
 +      if (!accurate) {
 +              /* That'll do */
                return;
        }
  
 -      while (1) {
 -              int r = av_read_frame (_format_context, &_packet);
 -              if (r < 0) {
 -                      return;
 -              }
 -
 -              if (_packet.stream_index != _video_stream) {
 -                      av_free_packet (&_packet);
 -                      continue;
 -              }
 -              
 -              int finished = 0;
 -              r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
 -              if (r >= 0 && finished) {
 -                      _video_position = rint (
 -                              (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * _ffmpeg_content->video_frame_rate()
 -                              );
 +      int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2));
  
 -                      if (_video_position >= (frame - 1)) {
 -                              av_free_packet (&_packet);
 -                              break;
 -                      }
 -              }
 -              
 -              av_free_packet (&_packet);
 +      seek_and_flush (initial_seek);
 +      if (N > 0) {
 +              minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3));
        }
  }
  
@@@ -428,23 -396,39 +427,23 @@@ FFmpegDecoder::decode_audio_packet (
  
                int frame_finished;
                int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
 +
                if (decode_result < 0) {
 -                      shared_ptr<const Film> film = _film.lock ();
 -                      assert (film);
 -                      film->log()->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
 +                      _log->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
                        return;
                }
  
                if (frame_finished) {
 -                      
 -                      if (_audio_position == 0) {
 -                              /* Where we are in the source, in seconds */
 -                              double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
 -                                      * av_frame_get_best_effort_timestamp(_frame) + _pts_offset;
 -
 -                              if (pts > 0) {
 -                                      /* Emit some silence */
 -                                      shared_ptr<AudioBuffers> silence (
 -                                              new AudioBuffers (
 -                                                      _ffmpeg_content->audio_channels(),
 -                                                      pts * _ffmpeg_content->content_audio_frame_rate()
 -                                                      )
 -                                              );
 -                                      
 -                                      silence->make_silent ();
 -                                      audio (silence, _audio_position);
 -                              }
 -                      }
 +                      ContentTime const ct = ContentTime::from_seconds (
 +                              av_frame_get_best_effort_timestamp (_frame) *
 +                              av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base))
 +                              + _pts_offset;
                        
                        int const data_size = av_samples_get_buffer_size (
                                0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
                                );
 -                      
 -                      audio (deinterleave_audio (_frame->data, data_size), _audio_position);
 +
 +                      audio (deinterleave_audio (_frame->data, data_size), ct);
                }
                        
                copy_packet.data += decode_result;
@@@ -465,14 -449,18 +464,14 @@@ FFmpegDecoder::decode_video_packet (
        shared_ptr<FilterGraph> graph;
        
        list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
 -      while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
 +      while (i != _filter_graphs.end() && !(*i)->can_process (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
                ++i;
        }
  
        if (i == _filter_graphs.end ()) {
 -              shared_ptr<const Film> film = _film.lock ();
 -              assert (film);
 -
 -              graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
 +              graph.reset (new FilterGraph (_ffmpeg_content, dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
                _filter_graphs.push_back (graph);
 -
 -              film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
 +              _log->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
        } else {
                graph = *i;
        }
                shared_ptr<Image> image = i->first;
                
                if (i->second != AV_NOPTS_VALUE) {
 -
 -                      double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset;
 -
 -                      if (_just_sought) {
 -                              /* We just did a seek, so disable any attempts to correct for where we
 -                                 are / should be.
 -                              */
 -                              _video_position = rint (pts * _ffmpeg_content->video_frame_rate ());
 -                              _just_sought = false;
 -                      }
 -
 -                      double const next = _video_position / _ffmpeg_content->video_frame_rate();
 -                      double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
 -                      double delta = pts - next;
 -
 -                      while (delta > one_frame) {
 -                              /* This PTS is more than one frame forward in time of where we think we should be; emit
 -                                 a black frame.
 -                              */
 -
 -                              /* XXX: I think this should be a copy of the last frame... */
 -                              boost::shared_ptr<Image> black (
 -                                      new Image (
 -                                              static_cast<AVPixelFormat> (_frame->format),
 -                                              libdcp::Size (video_codec_context()->width, video_codec_context()->height),
 -                                              true
 -                                              )
 -                                      );
 -                              
 -                              black->make_black ();
 -                              video (image, false, _video_position);
 -                              delta -= one_frame;
 -                      }
 -
 -                      if (delta > -one_frame) {
 -                              /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
 -                              video (image, false, _video_position);
 -                      }
 -                              
 +                      double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset.seconds ();
 +                      video (image, rint (pts * _ffmpeg_content->video_frame_rate ()));
                } else {
 -                      shared_ptr<const Film> film = _film.lock ();
 -                      assert (film);
 -                      film->log()->log ("Dropping frame without PTS");
 +                      _log->log ("Dropping frame without PTS");
                }
        }
  
@@@ -520,6 -547,14 +519,6 @@@ FFmpegDecoder::setup_subtitle (
        }
  }
  
 -bool
 -FFmpegDecoder::done () const
 -{
 -      bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
 -      bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
 -      return vd && ad;
 -}
 -      
  void
  FFmpegDecoder::decode_subtitle_packet ()
  {
           indicate that the previous subtitle should stop.
        */
        if (sub.num_rects <= 0) {
 -              subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
 +              image_subtitle (ContentTime (), ContentTime (), shared_ptr<Image> (), dcpomatic::Rect<double> ());
                return;
        } else if (sub.num_rects > 1) {
                throw DecodeError (_("multi-part subtitles not yet supported"));
        }
                
 -      /* Subtitle PTS in seconds (within the source, not taking into account any of the
 +      /* Subtitle PTS (within the source, not taking into account any of the
           source that we may have chopped off for the DCP)
        */
 -      double const packet_time = (static_cast<double> (sub.pts ) / AV_TIME_BASE) + _pts_offset;
 -
 +      ContentTime packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE) + _pts_offset;
 +      
        /* hence start time for this sub */
 -      Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
 -      Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
 +      ContentTime const from = packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3);
 +      ContentTime const to = packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3);
  
        AVSubtitleRect const * rect = sub.rects[0];
  
        if (rect->type != SUBTITLE_BITMAP) {
 -              throw DecodeError (_("non-bitmap subtitles not yet supported"));
 +              /* XXX */
 +              // throw DecodeError (_("non-bitmap subtitles not yet supported"));
 +              return;
        }
  
        /* Note RGBA is expressed little-endian, so the first byte in the word is R, second
           G, third B, fourth A.
        */
 -      shared_ptr<Image> image (new Image (PIX_FMT_RGBA, libdcp::Size (rect->w, rect->h), true));
 +      shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (rect->w, rect->h), true));
  
        /* Start of the first line in the subtitle */
        uint8_t* sub_p = rect->pict.data[0];
                out_p += image->stride()[0] / sizeof (uint32_t);
        }
  
 -      libdcp::Size const vs = _ffmpeg_content->video_size ();
 +      dcp::Size const vs = _ffmpeg_content->video_size ();
  
 -      subtitle (
 +      image_subtitle (
 +              from,
 +              to,
                image,
                dcpomatic::Rect<double> (
                        static_cast<double> (rect->x) / vs.width,
                        static_cast<double> (rect->y) / vs.height,
                        static_cast<double> (rect->w) / vs.width,
                        static_cast<double> (rect->h) / vs.height
 -                      ),
 -              from,
 -              to
 +                      )
                );
 -                        
        
        avsubtitle_free (&sub);
  }
diff --combined src/lib/film.cc
index 33cb304600cda8146deb44a6d1ada1e214925729,12a57753f4aa5a03806cbc20ce3cad2f14080754..bdd650437568cd62ce2702ac931383384cc20666
  #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"
@@@ -71,14 -72,14 +72,14 @@@ using std::cout
  using std::list;
  using boost::shared_ptr;
  using boost::weak_ptr;
- using boost::lexical_cast;
  using boost::dynamic_pointer_cast;
  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;
  
  /* 5 -> 6
   * AudioMapping XML changed.
@@@ -151,9 -152,10 +152,10 @@@ strin
  Film::video_identifier () const
  {
        assert (container ());
-       LocaleGuard lg;
  
        stringstream s;
+       s.imbue (std::locale::classic ());
+       
        s << container()->id()
          << "_" << resolution_to_string (_resolution)
          << "_" << _playlist->video_identifier()
@@@ -339,12 -341,10 +341,10 @@@ Film::encoded_frames () cons
  shared_ptr<xmlpp::Document>
  Film::metadata () const
  {
-       LocaleGuard lg;
        shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
        xmlpp::Element* root = doc->create_root_node ("Metadata");
  
-       root->add_child("Version")->add_child_text (lexical_cast<string> (current_state_version));
+       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("Resolution")->add_child_text (resolution_to_string (_resolution));
        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 (lexical_cast<string> (_j2k_bandwidth));
+       root->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth));
        _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
-       root->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate));
+       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("AudioChannels")->add_child_text (lexical_cast<string> (_audio_channels));
+       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");
        root->add_child("Interop")->add_child_text (_interop ? "1" : "0");
@@@ -391,8 -391,6 +391,6 @@@ Film::write_metadata () cons
  list<string>
  Film::read_metadata ()
  {
-       LocaleGuard lg;
        if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
                throw StringError (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version.  You will need to create a new Film, re-add your content and set it up again.  Sorry!"));
        }
        _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) */
@@@ -765,7 -763,7 +763,7 @@@ Film::j2c_path (int f, Eyes e, bool t) 
        return file (p);
  }
  
 -/** @return List of subdirectories (not full paths) containing DCPs that can be successfully libdcp::DCP::read() */
 +/** @return List of subdirectories (not full paths) containing DCPs that can be successfully dcp::DCP::read() */
  list<boost::filesystem::path>
  Film::dcps () const
  {
                        ) {
  
                        try {
 -                              libdcp::DCP dcp (*i);
 +                              dcp::DCP dcp (*i);
                                dcp.read ();
                                out.push_back (i->path().leaf ());
                        } catch (...) {
@@@ -878,7 -876,7 +876,7 @@@ Film::move_content_later (shared_ptr<Co
        _playlist->move_later (c);
  }
  
 -Time
 +DCPTime
  Film::length () const
  {
        return _playlist->length ();
@@@ -890,18 -888,12 +888,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)
  {
@@@ -920,7 -912,31 +918,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 */
@@@ -936,38 -952,41 +934,38 @@@ 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 dcp_dir,
 -      boost::posix_time::ptime from,
 -      boost::posix_time::ptime until
 +      dcp::LocalTime from,
 +      dcp::LocalTime until
        ) const
  {
        shared_ptr<const Signer> signer = make_signer ();
  
 -      libdcp::DCP dcp (dir (dcp_dir.string ()));
 +      dcp::DCP dcp (dir (dcp_dir.string ()));
        
        try {
                dcp.read ();
                throw KDMError (_("Could not read DCP to make KDM for"));
        }
        
 -      time_t now = time (0);
 -      struct tm* tm = localtime (&now);
 -      string const issue_date = libdcp::tm_to_string (tm);
 -      
        dcp.cpls().front()->set_mxf_keys (key ());
        
 -      return libdcp::KDM (dcp.cpls().front(), signer, target, from, until, "DCP-o-matic", issue_date);
 +      return dcp::DecryptedKDM (
 +              dcp.cpls().front(), from, until, "DCP-o-matic", dcp.cpls().front()->content_title_text(), dcp::LocalTime().as_string()
 +              ).encrypt (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
index 3897bbf37b0a5f4c2fc219e0b47c12712ae3708e,4ff324f68d4d76ba29e6437e353e48d1b178be65..004b89e659d47ebd24cb594bc06848d2651585d4
@@@ -18,7 -18,6 +18,6 @@@
  */
  
  #include <iostream>
- #include <boost/lexical_cast.hpp>
  #include <Magick++.h>
  #include "image_content.h"
  #include "image_examiner.h"
@@@ -33,26 -32,25 +32,24 @@@ using std::cout
  using std::list;
  using std::sort;
  using boost::shared_ptr;
- using boost::lexical_cast;
- using boost::bad_lexical_cast;
  
  ImageExaminer::ImageExaminer (shared_ptr<const Film> film, shared_ptr<const ImageContent> content, shared_ptr<Job>)
        : _film (film)
        , _image_content (content)
 -      , _video_length (0)
  {
        using namespace MagickCore;
        Magick::Image* image = new Magick::Image (content->path(0).string());
 -      _video_size = libdcp::Size (image->columns(), image->rows());
 +      _video_size = dcp::Size (image->columns(), image->rows());
        delete image;
  
        if (content->still ()) {
 -              _video_length = Config::instance()->default_still_length() * video_frame_rate();
 +              _video_length = ContentTime::from_seconds (Config::instance()->default_still_length());
        } else {
 -              _video_length = _image_content->number_of_paths ();
 +              _video_length = ContentTime::from_frames (_image_content->number_of_paths (), video_frame_rate ());
        }
  }
  
 -libdcp::Size
 +dcp::Size
  ImageExaminer::video_size () const
  {
        return _video_size.get ();
diff --combined src/lib/kdm.cc
index 793a3fa0eb5c0cea99d0edc60424742490ea3fae,2a8e191e7e24719f3031e5c75ffb0e539a805c4b..902f0d33322a48617e71e193801780704a46ca39
@@@ -21,7 -21,7 +21,7 @@@
  #include <boost/shared_ptr.hpp>
  #include <quickmail.h>
  #include <zip.h>
 -#include <libdcp/kdm.h>
 +#include <dcp/encrypted_kdm.h>
  #include "kdm.h"
  #include "cinema.h"
  #include "exceptions.h"
@@@ -36,13 -36,13 +36,13 @@@ using boost::shared_ptr
  
  struct ScreenKDM
  {
 -      ScreenKDM (shared_ptr<Screen> s, libdcp::KDM k)
 +      ScreenKDM (shared_ptr<Screen> s, dcp::EncryptedKDM k)
                : screen (s)
                , kdm (k)
        {}
        
        shared_ptr<Screen> screen;
 -      libdcp::KDM kdm;
 +      dcp::EncryptedKDM kdm;
  };
  
  static string
@@@ -103,16 -103,16 +103,16 @@@ make_screen_kdms 
        shared_ptr<const Film> film,
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
 -      boost::posix_time::ptime from,
 -      boost::posix_time::ptime to
 +      dcp::LocalTime from,
 +      dcp::LocalTime to
        )
  {
 -      list<libdcp::KDM> kdms = film->make_kdms (screens, dcp, from, to);
 +      list<dcp::EncryptedKDM> kdms = film->make_kdms (screens, dcp, from, to);
           
        list<ScreenKDM> screen_kdms;
        
        list<shared_ptr<Screen> >::iterator i = screens.begin ();
 -      list<libdcp::KDM>::iterator j = kdms.begin ();
 +      list<dcp::EncryptedKDM>::iterator j = kdms.begin ();
        while (i != screens.end() && j != kdms.end ()) {
                screen_kdms.push_back (ScreenKDM (*i, *j));
                ++i;
@@@ -127,8 -127,8 +127,8 @@@ make_cinema_kdms 
        shared_ptr<const Film> film,
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
 -      boost::posix_time::ptime from,
 -      boost::posix_time::ptime to
 +      dcp::LocalTime from,
 +      dcp::LocalTime to
        )
  {
        list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, dcp, from, to);
        return cinema_kdms;
  }
  
+ /** @param from KDM from time in local time.
+  *  @param to KDM to time in local time.
+  */
  void
  write_kdm_files (
        shared_ptr<const Film> film,
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
 -      boost::posix_time::ptime from,
 -      boost::posix_time::ptime to,
 +      dcp::LocalTime from,
 +      dcp::LocalTime to,
        boost::filesystem::path directory
        )
  {
@@@ -189,8 -192,8 +192,8 @@@ write_kdm_zip_files 
        shared_ptr<const Film> film,
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
 -      boost::posix_time::ptime from,
 -      boost::posix_time::ptime to,
 +      dcp::LocalTime from,
 +      dcp::LocalTime to,
        boost::filesystem::path directory
        )
  {
@@@ -208,8 -211,8 +211,8 @@@ email_kdms 
        shared_ptr<const Film> film,
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
 -      boost::posix_time::ptime from,
 -      boost::posix_time::ptime to
 +      dcp::LocalTime from,
 +      dcp::LocalTime to
        )
  {
        list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, dcp, from, to);
diff --combined src/lib/playlist.cc
index 1e8a3319c54ed0569f0d555902d973eb61155d81,eb9a49d30b3947cca95da47328b30c5bd0335bbe..9a048980c0b02aebadd481db236149df8544de8e
@@@ -19,7 -19,6 +19,6 @@@
  
  #include <libcxml/cxml.h>
  #include <boost/shared_ptr.hpp>
- #include <boost/lexical_cast.hpp>
  #include "playlist.h"
  #include "sndfile_content.h"
  #include "sndfile_decoder.h"
@@@ -46,7 -45,6 +45,6 @@@ using boost::optional
  using boost::shared_ptr;
  using boost::weak_ptr;
  using boost::dynamic_pointer_cast;
- using boost::lexical_cast;
  
  Playlist::Playlist ()
        : _sequence_video (true)
@@@ -81,20 -79,20 +79,20 @@@ Playlist::maybe_sequence_video (
        _sequencing_video = true;
        
        ContentList cl = _content;
 -      Time next_left = 0;
 -      Time next_right = 0;
 +      DCPTime next_left;
 +      DCPTime next_right;
        for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
                shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
                if (!vc) {
                        continue;
                }
 -      
 +              
                if (vc->video_frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) {
                        vc->set_position (next_right);
 -                      next_right = vc->end() + 1;
 +                      next_right = vc->end() + DCPTime::delta ();
                } else {
                        vc->set_position (next_left);
 -                      next_left = vc->end() + 1;
 +                      next_left = vc->end() + DCPTime::delta ();
                }
        }
  
@@@ -261,12 -259,12 +259,12 @@@ Playlist::best_dcp_frame_rate () cons
        return best->dcp;
  }
  
 -Time
 +DCPTime
  Playlist::length () const
  {
 -      Time len = 0;
 +      DCPTime len;
        for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
 -              len = max (len, (*i)->end() + 1);
 +              len = max (len, (*i)->end() + DCPTime::delta ());
        }
  
        return len;
@@@ -286,10 -284,10 +284,10 @@@ Playlist::reconnect (
        }
  }
  
 -Time
 +DCPTime
  Playlist::video_end () const
  {
 -      Time end = 0;
 +      DCPTime end;
        for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
                if (dynamic_pointer_cast<const VideoContent> (*i)) {
                        end = max (end, (*i)->end ());
        return end;
  }
  
 +FrameRateChange
 +Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const
 +{
 +      for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
 +              shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
 +              if (!vc) {
 +                      break;
 +              }
 +
 +              if (vc->position() >= t && t < vc->end()) {
 +                      return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate);
 +              }
 +      }
 +
 +      return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
 +}
 +
  void
  Playlist::set_sequence_video (bool s)
  {
@@@ -338,7 -319,7 +336,7 @@@ Playlist::content () cons
  void
  Playlist::repeat (ContentList c, int n)
  {
 -      pair<Time, Time> range (TIME_MAX, 0);
 +      pair<DCPTime, DCPTime> range (DCPTime::max (), DCPTime ());
        for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
                range.first = min (range.first, (*i)->position ());
                range.second = max (range.second, (*i)->position ());
                range.second = max (range.second, (*i)->end ());
        }
  
 -      Time pos = range.second;
 +      DCPTime pos = range.second;
        for (int i = 0; i < n; ++i) {
                for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
                        shared_ptr<Content> copy = (*i)->clone ();
@@@ -379,7 -360,7 +377,7 @@@ Playlist::move_earlier (shared_ptr<Cont
                return;
        }
        
 -      Time const p = (*previous)->position ();
 +      DCPTime const p = (*previous)->position ();
        (*previous)->set_position (p + c->length_after_trim ());
        c->set_position (p);
        sort (_content.begin(), _content.end(), ContentSorter ());
@@@ -406,7 -387,7 +404,7 @@@ Playlist::move_later (shared_ptr<Conten
                return;
        }
  
 -      Time const p = (*next)->position ();
 +      DCPTime const p = (*next)->position ();
        (*next)->set_position (c->position ());
        c->set_position (p + c->length_after_trim ());
        sort (_content.begin(), _content.end(), ContentSorter ());
diff --combined src/lib/server.cc
index bf7541c3379f1f294f735f4c130b06994b0311d8,0c5792ae0da6b8d07d678e5244b6b04b381645e6..6bcff7e6e93db1204cfc2d2ec73c7179a99b55ce
@@@ -27,9 -27,9 +27,9 @@@
  #include <sstream>
  #include <iostream>
  #include <boost/algorithm/string.hpp>
- #include <boost/lexical_cast.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"
@@@ -56,8 -56,8 +56,8 @@@ using boost::thread
  using boost::bind;
  using boost::scoped_array;
  using boost::optional;
- using boost::lexical_cast;
 -using libdcp::Size;
 -using libdcp::raw_convert;
 +using dcp::Size;
++using dcp::raw_convert;
  
  Server::Server (shared_ptr<Log> log, bool verbose)
        : _log (log)
@@@ -85,7 -85,7 +85,7 @@@ Server::process (shared_ptr<Socket> soc
                return -1;
        }
  
 -      libdcp::Size size (
 +      dcp::Size size (
                xml->number_child<int> ("Width"), xml->number_child<int> ("Height")
                );
  
@@@ -246,7 -246,7 +246,7 @@@ Server::broadcast_received (
                /* Reply to the client saying what we can do */
                xmlpp::Document doc;
                xmlpp::Element* root = doc.create_root_node ("ServerAvailable");
-               root->add_child("Threads")->add_child_text (lexical_cast<string> (_worker_threads.size ()));
+               root->add_child("Threads")->add_child_text (raw_convert<string> (_worker_threads.size ()));
                stringstream xml;
                doc.write_to_stream (xml, "UTF-8");
  
diff --combined src/lib/server_finder.cc
index 1080d24c4103765fce705019b177c19b4a415275,ed6016c6723f51e1be2f028b67fc952fda482978..de8a3852c5cf45c0abd52faaf94b4f08ee24e94c
@@@ -18,6 -18,7 +18,7 @@@
  */
  
  #include <libcxml/cxml.h>
 -#include <libdcp/raw_convert.h>
++#include <dcp/raw_convert.h>
  #include "server_finder.h"
  #include "exceptions.h"
  #include "util.h"
@@@ -32,7 -33,7 +33,7 @@@ using std::vector
  using std::cout;
  using boost::shared_ptr;
  using boost::scoped_array;
- using boost::lexical_cast;
 -using libdcp::raw_convert;
++using dcp::raw_convert;
  
  ServerFinder* ServerFinder::_instance = 0;
  
@@@ -82,7 -83,7 +83,7 @@@ tr
                        }
                        try {
                                boost::asio::ip::udp::resolver resolver (io_service);
-                               boost::asio::ip::udp::resolver::query query (*i, lexical_cast<string> (Config::instance()->server_port_base() + 1));
+                               boost::asio::ip::udp::resolver::query query (*i, raw_convert<string> (Config::instance()->server_port_base() + 1));
                                boost::asio::ip::udp::endpoint end_point (*resolver.resolve (query));
                                socket.send_to (boost::asio::buffer (data.c_str(), data.size() + 1), end_point);
                        } catch (...) {
index 0cf65967f2172ac1f8ad7d57a6a648cfcd05a45d,fcdf88778ac81173080aebfbda9cbf832d8a169c..cad5eb8e73704d2ba15bdf6d2b0a58210c112ade
@@@ -18,6 -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"
@@@ -31,7 -32,9 +32,7 @@@ using std::string
  using std::stringstream;
  using std::cout;
  using boost::shared_ptr;
- using boost::lexical_cast;
 -using libdcp::raw_convert;
 -
 -int const SndfileContentProperty::VIDEO_FRAME_RATE = 600;
++using dcp::raw_convert;
  
  SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::path p)
        : Content (f, p)
@@@ -49,7 -52,7 +50,7 @@@ SndfileContent::SndfileContent (shared_
        , _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");
  }
  
@@@ -80,8 -83,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 ();
@@@ -102,7 -105,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);
@@@ -132,18 -138,34 +133,18 @@@ SndfileContent::as_xml (xmlpp::Node* no
        Content::as_xml (node);
        AudioContent::as_xml (node);
  
-       node->add_child("AudioChannels")->add_child_text (lexical_cast<string> (audio_channels ()));
-       node->add_child("AudioLength")->add_child_text (lexical_cast<string> (audio_length().get ()));
-       node->add_child("AudioFrameRate")->add_child_text (lexical_cast<string> (audio_frame_rate ()));
+       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);
 -
 -      float const rate = _video_frame_rate.get_value_or (film->video_frame_rate ());
 -      OutputAudioFrame const len = divide_with_round (
 -              audio_length() * output_audio_frame_rate() * rate,
 -              content_audio_frame_rate() * film->video_frame_rate()
 -              );
 -      
 -      return film->audio_frames_to_time (len);
 -}
 -
 -int
 -SndfileContent::output_audio_frame_rate () const
 -{
 -      shared_ptr<const Film> film = _film.lock ();
 -      assert (film);
 -      
 -      return film->audio_frame_rate ();
 +      return DCPTime (audio_length(), film->active_frame_rate_change (position ()));
  }
  
  void
@@@ -154,6 -176,20 +155,6 @@@ SndfileContent::set_audio_mapping (Audi
                _audio_mapping = m;
        }
  
 -      signal_changed (AudioContentProperty::AUDIO_MAPPING);
 +      AudioContent::set_audio_mapping (m);
  }
  
 -float
 -SndfileContent::video_frame_rate () const
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_mutex);
 -              if (_video_frame_rate) {
 -                      return _video_frame_rate.get ();
 -              }
 -      }
 -
 -      shared_ptr<const Film> film = _film.lock ();
 -      assert (film);
 -      return film->video_frame_rate ();
 -}
index eb9c67d9a1948b709544c9cee4588e7e431545ce,0000000000000000000000000000000000000000..8f9c28e293c15bfe9335e0f9f7fe9bc24edca1de
mode 100644,000000..100644
--- /dev/null
@@@ -1,113 -1,0 +1,111 @@@
-       LocaleGuard lg;
-       
 +/*
 +    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +*/
 +
 +#include "subrip_content.h"
 +#include "util.h"
 +#include "subrip.h"
 +#include "film.h"
++#include <dcp/raw_convert.h>
 +
 +#include "i18n.h"
 +
 +using std::stringstream;
 +using std::string;
 +using std::cout;
++using dcp::raw_convert;
 +using boost::shared_ptr;
 +using boost::lexical_cast;
 +
 +SubRipContent::SubRipContent (shared_ptr<const Film> film, boost::filesystem::path path)
 +      : Content (film, path)
 +      , SubtitleContent (film, path)
 +{
 +
 +}
 +
 +SubRipContent::SubRipContent (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version)
 +      : Content (film, node)
 +      , SubtitleContent (film, node, version)
 +      , _length (node->number_child<int64_t> ("Length"))
 +{
 +
 +}
 +
 +void
 +SubRipContent::examine (boost::shared_ptr<Job> job)
 +{
 +      Content::examine (job);
 +      SubRip s (shared_from_this ());
 +
 +      shared_ptr<const Film> film = _film.lock ();
 +      assert (film);
 +      
 +      DCPTime len (s.length (), film->active_frame_rate_change (position ()));
 +
 +      boost::mutex::scoped_lock lm (_mutex);
 +      _length = len;
 +}
 +
 +string
 +SubRipContent::summary () const
 +{
 +      return path_summary() + " " + _("[subtitles]");
 +}
 +
 +string
 +SubRipContent::technical_summary () const
 +{
 +      return Content::technical_summary() + " - " + _("SubRip subtitles");
 +}
 +
 +string
 +SubRipContent::information () const
 +{
 +      
 +}
 +      
 +void
 +SubRipContent::as_xml (xmlpp::Node* node) const
 +{
-       node->add_child("Length")->add_child_text (lexical_cast<string> (_length.get ()));
 +      node->add_child("Type")->add_child_text ("SubRip");
 +      Content::as_xml (node);
 +      SubtitleContent::as_xml (node);
-       LocaleGuard lg;
++      node->add_child("Length")->add_child_text (raw_convert<string> (_length.get ()));
 +}
 +
 +DCPTime
 +SubRipContent::full_length () const
 +{
 +      /* XXX: this assumes that the timing of the SubRip file is appropriate
 +         for the DCP's frame rate.
 +      */
 +      return _length;
 +}
 +
 +string
 +SubRipContent::identifier () const
 +{
-         << "_" << subtitle_scale()
-         << "_" << subtitle_x_offset()
-         << "_" << subtitle_y_offset();
 +      stringstream s;
 +      s << Content::identifier()
++        << "_" << raw_convert<string> (subtitle_scale())
++        << "_" << raw_convert<string> (subtitle_x_offset())
++        << "_" << raw_convert<string> (subtitle_y_offset());
 +
 +      return s.str ();
 +}
index 4c6e601926d2ebf096450a443ee7560bda152fba,0abb7d491ec57ffff13e2d483fc67e282fd473fb..783c73e254aaa75b743adae6b5c5851b84d4b3b7
@@@ -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
@@@ -18,6 -18,7 +18,7 @@@
  */
  
  #include <libcxml/cxml.h>
 -#include <libdcp/raw_convert.h>
++#include <dcp/raw_convert.h>
  #include "subtitle_content.h"
  #include "util.h"
  #include "exceptions.h"
  
  using std::string;
  using std::vector;
 +using std::cout;
  using boost::shared_ptr;
- using boost::lexical_cast;
  using boost::dynamic_pointer_cast;
 -using libdcp::raw_convert;
++using dcp::raw_convert;
  
  int const SubtitleContentProperty::SUBTITLE_X_OFFSET = 500;
  int const SubtitleContentProperty::SUBTITLE_Y_OFFSET = 501;
@@@ -50,8 -50,6 +51,6 @@@ SubtitleContent::SubtitleContent (share
        , _subtitle_y_offset (0)
        , _subtitle_scale (1)
  {
-       LocaleGuard lg;
        if (version >= 7) {
                _subtitle_x_offset = node->number_child<float> ("SubtitleXOffset");
                _subtitle_y_offset = node->number_child<float> ("SubtitleYOffset");
@@@ -92,11 -90,9 +91,9 @@@ SubtitleContent::SubtitleContent (share
  void
  SubtitleContent::as_xml (xmlpp::Node* root) const
  {
-       LocaleGuard lg;
-       
-       root->add_child("SubtitleXOffset")->add_child_text (lexical_cast<string> (_subtitle_x_offset));
-       root->add_child("SubtitleYOffset")->add_child_text (lexical_cast<string> (_subtitle_y_offset));
-       root->add_child("SubtitleScale")->add_child_text (lexical_cast<string> (_subtitle_scale));
+       root->add_child("SubtitleXOffset")->add_child_text (raw_convert<string> (_subtitle_x_offset));
+       root->add_child("SubtitleYOffset")->add_child_text (raw_convert<string> (_subtitle_y_offset));
+       root->add_child("SubtitleScale")->add_child_text (raw_convert<string> (_subtitle_scale));
  }
  
  void
diff --combined src/lib/update.cc
index 019db8e761ea619c26848ec822c16b517f363ea1,34eaf385c92e77340b361ad634705083a938a459..c7527ee493624247468b365cde6b2363acb5496f
@@@ -22,6 -22,7 +22,7 @@@
  #include <boost/algorithm/string.hpp>
  #include <curl/curl.h>
  #include <libcxml/cxml.h>
 -#include <libdcp/raw_convert.h>
++#include <dcp/raw_convert.h>
  #include "update.h"
  #include "version.h"
  #include "ui_signaller.h"
@@@ -32,9 -33,8 +33,9 @@@ using std::cout
  using std::min;
  using std::string;
  using std::stringstream;
- using boost::lexical_cast;
 -using libdcp::raw_convert;
++using dcp::raw_convert;
  
 +/** Singleton instance */
  UpdateChecker* UpdateChecker::_instance = 0;
  
  static size_t
@@@ -43,9 -43,6 +44,9 @@@ write_callback_wrapper (void* data, siz
        return reinterpret_cast<UpdateChecker*>(user)->write_callback (data, size, nmemb);
  }
  
 +/** Construct an UpdateChecker.  This sets things up and starts a thread to
 + *  do the work.
 + */
  UpdateChecker::UpdateChecker ()
        : _buffer (new char[BUFFER_SIZE])
        , _offset (0)
@@@ -77,7 -74,6 +78,7 @@@ UpdateChecker::~UpdateChecker (
        delete[] _buffer;
  }
  
 +/** Start running the update check */
  void
  UpdateChecker::run ()
  {
@@@ -90,7 -86,6 +91,7 @@@ voi
  UpdateChecker::thread ()
  {
        while (1) {
 +              /* Block until there is something to do */
                boost::mutex::scoped_lock lock (_process_mutex);
                while (_to_do == 0) {
                        _condition.wait (lock);
                
                try {
                        _offset = 0;
 +
 +                      /* Perform the request */
                        
                        int r = curl_easy_perform (_curl);
                        if (r != CURLE_OK) {
                                set_state (FAILED);
                                return;
                        }
 +
 +                      /* Parse the reply */
                        
                        _buffer[_offset] = '\0';
                        stringstream s;
                                current_pre = true;
                        }
                        
-                       float current_float = lexical_cast<float> (current);
+                       float current_float = raw_convert<float> (current);
                        if (current_pre) {
                                current_float -= 0.005;
                        }
                        
-                       if (current_float < lexical_cast<float> (_stable)) {
+                       if (current_float < raw_convert<float> (_stable)) {
                                set_state (YES);
                        } else {
                                set_state (NO);
diff --combined src/lib/util.cc
index 0eb14845d7da6dba601412a34c72544cd590db4b,a5111b7dc121d18c65211757ffb7c96791b7add7..14dfd1fa558b6afa86c7dc03a2b05b139d9bce4d
@@@ -37,7 -37,6 +37,6 @@@
  #include <boost/algorithm/string.hpp>
  #include <boost/bind.hpp>
  #include <boost/lambda/lambda.hpp>
- #include <boost/lexical_cast.hpp>
  #include <boost/thread.hpp>
  #include <boost/filesystem.hpp>
  #ifdef DCPOMATIC_WINDOWS
  #include <glib.h>
  #include <openjpeg.h>
  #include <openssl/md5.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>
@@@ -71,7 -70,6 +71,7 @@@
  #include "job.h"
  #include "cross.h"
  #include "video_content.h"
 +#include "rect.h"
  #ifdef DCPOMATIC_WINDOWS
  #include "stack.hpp"
  #endif
@@@ -101,9 -99,9 +101,9 @@@ using std::streampos
  using std::set_terminate;
  using boost::shared_ptr;
  using boost::thread;
- using boost::lexical_cast;
  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)
  {
@@@ -325,8 -341,7 +325,8 @@@ dcpomatic_setup (
  
        set_terminate (terminate);
  
 -      libdcp::init ();
 +      Pango::init ();
 +      dcp::init ();
        
        Ratio::setup_ratios ();
        VideoContentScale::setup_scales ();
@@@ -475,6 -490,33 +475,6 @@@ md5_digest (vector<boost::filesystem::p
        return s.str ();
  }
  
 -static bool
 -about_equal (float a, float b)
 -{
 -      /* A film of F seconds at f FPS will be Ff frames;
 -         Consider some delta FPS d, so if we run the same
 -         film at (f + d) FPS it will last F(f + d) seconds.
 -
 -         Hence the difference in length over the length of the film will
 -         be F(f + d) - Ff frames
 -          = Ff + Fd - Ff frames
 -          = Fd frames
 -          = Fd/f seconds
 - 
 -         So if we accept a difference of 1 frame, ie 1/f seconds, we can
 -         say that
 -
 -         1/f = Fd/f
 -      ie 1 = Fd
 -      ie d = 1/F
 - 
 -         So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
 -         FPS error is 1/F ~= 0.0001 ~= 10-e4
 -      */
 -
 -      return (fabs (a - b) < 1e-4);
 -}
 -
  /** @param An arbitrary audio frame rate.
   *  @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
   */
@@@ -687,14 -729,14 +687,14 @@@ in
  get_required_int (multimap<string, string> const & kv, string k)
  {
        string const v = get_required_string (kv, k);
-       return lexical_cast<int> (v);
+       return raw_convert<int> (v);
  }
  
  float
  get_required_float (multimap<string, string> const & kv, string k)
  {
        string const v = get_required_string (kv, k);
-       return lexical_cast<float> (v);
+       return raw_convert<float> (v);
  }
  
  string
@@@ -724,7 -766,7 +724,7 @@@ get_optional_int (multimap<string, stri
                return 0;
        }
  
-       return lexical_cast<int> (i->second);
+       return raw_convert<int> (i->second);
  }
  
  /** Trip an assert if the caller is not in the UI thread */
@@@ -734,6 -776,17 +734,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)
  {
        return channels[c];
  }
  
- LocaleGuard::LocaleGuard ()
-       : _old (0)
 -FrameRateConversion::FrameRateConversion (float source, int dcp)
 -      : skip (false)
 -      , repeat (1)
 -      , change_speed (false)
--{
-       char const * old = setlocale (LC_NUMERIC, 0);
 -      if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) {
 -              /* The difference between source and DCP frame rate will be lower
 -                 (i.e. better) if we skip.
 -              */
 -              skip = true;
 -      } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) {
 -              /* The difference between source and DCP frame rate would be better
 -                 if we repeated each frame once; it may be better still if we
 -                 repeated more than once.  Work out the required repeat.
 -              */
 -              repeat = round (dcp / source);
 -      }
--
-       if (old) {
-               _old = strdup (old);
-               if (strcmp (_old, "C")) {
-                       setlocale (LC_NUMERIC, "C");
 -      change_speed = !about_equal (source * factor(), dcp);
 -
 -      if (!skip && repeat == 1 && !change_speed) {
 -              description = _("Content and DCP have the same rate.\n");
 -      } else {
 -              if (skip) {
 -                      description = _("DCP will use every other frame of the content.\n");
 -              } else if (repeat == 2) {
 -                      description = _("Each content frame will be doubled in the DCP.\n");
 -              } else if (repeat > 2) {
 -                      description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1);
--              }
-       }
- }
--
- LocaleGuard::~LocaleGuard ()
- {
-       setlocale (LC_NUMERIC, _old);
-       free (_old);
 -              if (change_speed) {
 -                      float const pc = dcp * 100 / (source * factor());
 -                      description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc);
 -              }
 -      }
--}
--
  bool
  valid_image_file (boost::filesystem::path f)
  {
@@@ -803,7 -875,7 +784,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>
@@@ -902,14 -974,14 +883,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 *
@@@ -940,34 -1012,12 +921,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 ()
  {
diff --combined src/lib/util.h
index 8e65bbb5415cc236e496e56a83b92dbd45503082,e85abf402d8ec1637570222a923c7708c6242601..58c2771b7cebd8e46b9d700c3218041abd3dadd4
@@@ -31,8 -31,7 +31,8 @@@
  #include <boost/asio.hpp>
  #include <boost/optional.hpp>
  #include <boost/filesystem.hpp>
 -#include <libdcp/util.h>
 +#include <dcp/util.h>
 +#include <dcp/signer.h>
  extern "C" {
  #include <libavcodec/avcodec.h>
  #include <libavfilter/avfilter.h>
@@@ -77,10 -76,44 +77,10 @@@ extern bool valid_image_file (boost::fi
  extern boost::filesystem::path mo_path ();
  #endif
  extern std::string tidy_for_filename (std::string);
 -extern boost::shared_ptr<const libdcp::Signer> make_signer ();
 -extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size);
 +extern boost::shared_ptr<const dcp::Signer> make_signer ();
 +extern dcp::Size fit_ratio_within (float ratio, dcp::Size);
  extern std::string entities_to_text (std::string e);
  extern std::map<std::string, std::string> split_get_request (std::string url);
 -
 -struct FrameRateConversion
 -{
 -      FrameRateConversion (float, int);
 -
 -      /** @return factor by which to multiply a source frame rate
 -          to get the effective rate after any skip or repeat has happened.
 -      */
 -      float factor () const {
 -              if (skip) {
 -                      return 0.5;
 -              }
 -
 -              return repeat;
 -      }
 -
 -      /** 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) */
 -      int repeat;
 -      /** true if this DCP will run its video faster or slower than the source
 -       *  without taking into account `repeat' nor `skip'.
 -       *  (e.g. change_speed will be true if
 -       *          source is 29.97fps, DCP is 30fps
 -       *          source is 14.50fps, DCP is 30fps
 -       *  but not if
 -       *          source is 15.00fps, DCP is 30fps
 -       *          source is 12.50fps, DCP is 25fps)
 -       */
 -      bool change_speed;
 -
 -      std::string description;
 -};
 -
  extern int dcp_audio_frame_rate (int);
  extern int stride_round_up (int, int const *, int);
  extern std::multimap<std::string, std::string> read_key_value (std::istream& s);
@@@ -131,30 -164,18 +131,22 @@@ private
        int _timeout;
  };
  
- class LocaleGuard
- {
- public:
-       LocaleGuard ();
-       ~LocaleGuard ();
-       
- private:
-       char* _old;
- };
+ extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
  
 +/** @class ScopedTemporary
 + *  @brief A temporary file which is deleted when the ScopedTemporary object goes out of scope.
 + */
  class ScopedTemporary
  {
  public:
        ScopedTemporary ();
        ~ScopedTemporary ();
  
 +      /** @return temporary filename */
        boost::filesystem::path file () const {
                return _file;
        }
 -      
 +
        char const * c_str () const;
        FILE* open (char const *);
        void close ();
diff --combined src/lib/video_content.cc
index 9edbc104a92d3daa77a0a19b46dd18d55317c14e,783cddafad958fcb5ee81cc3806bea3dc2dff8e8..bd24621f79fe72c144d04bf33edaf908cadb61b8
@@@ -19,7 -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"
@@@ -45,9 -46,9 +46,9 @@@ using std::setprecision
  using std::cout;
  using std::vector;
  using boost::shared_ptr;
- using boost::lexical_cast;
  using boost::optional;
  using boost::dynamic_pointer_cast;
 -using libdcp::raw_convert;
++using dcp::raw_convert;
  
  vector<VideoContentScale> VideoContentScale::_scales;
  
@@@ -61,7 -62,7 +62,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)
@@@ -84,7 -85,7 +85,7 @@@ VideoContent::VideoContent (shared_ptr<
  VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
        : Content (f, node)
  {
 -      _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
 +      _video_length = ContentTime (node->number_child<int64_t> ("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");
@@@ -155,15 -156,15 +156,15 @@@ voi
  VideoContent::as_xml (xmlpp::Node* node) const
  {
        boost::mutex::scoped_lock lm (_mutex);
-       node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length.get ()));
-       node->add_child("VideoWidth")->add_child_text (lexical_cast<string> (_video_size.width));
-       node->add_child("VideoHeight")->add_child_text (lexical_cast<string> (_video_size.height));
-       node->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate));
-       node->add_child("VideoFrameType")->add_child_text (lexical_cast<string> (static_cast<int> (_video_frame_type)));
-       node->add_child("LeftCrop")->add_child_text (boost::lexical_cast<string> (_crop.left));
-       node->add_child("RightCrop")->add_child_text (boost::lexical_cast<string> (_crop.right));
-       node->add_child("TopCrop")->add_child_text (boost::lexical_cast<string> (_crop.top));
-       node->add_child("BottomCrop")->add_child_text (boost::lexical_cast<string> (_crop.bottom));
 -      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));
+       node->add_child("VideoFrameType")->add_child_text (raw_convert<string> (static_cast<int> (_video_frame_type)));
+       node->add_child("LeftCrop")->add_child_text (raw_convert<string> (_crop.left));
+       node->add_child("RightCrop")->add_child_text (raw_convert<string> (_crop.right));
+       node->add_child("TopCrop")->add_child_text (raw_convert<string> (_crop.top));
+       node->add_child("BottomCrop")->add_child_text (raw_convert<string> (_crop.bottom));
        _scale.as_xml (node->add_child("Scale"));
        _colour_conversion.as_xml (node->add_child("ColourConversion"));
  }
  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 ();
        
        {
@@@ -319,17 -320,14 +320,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);
@@@ -357,21 -355,28 +358,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);
 -      
 -      FrameRateConversion 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)
@@@ -448,14 -453,14 +449,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/tools/dcpomatic.cc
index 8bb9b230a2a3d9540fa2620be50db554770358f1,28074a4ba39e8703d493665f24159cefb88a589f..f2aff9359a35412684f4262bbfe7afdd9c6aa110
@@@ -457,7 -457,8 +457,8 @@@ private
        
        void file_exit ()
        {
-               Close (true);
+               /* false here allows the close handler to veto the close request */
+               Close (false);
        }
  
        void edit_preferences ()
@@@ -615,9 -616,6 +616,9 @@@ static const wxCmdLineEntryDesc command
        { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
  };
  
 +/** @class App
 + *  @brief The magic App class for wxWidgets.
 + */
  class App : public wxApp
  {
        bool OnInit ()
index 9375010865dbc0cc2a9c0dd34834edd97d53934f,c1f3e2067a150a3e4790468700103034a74d29f8..ac85407a2f4a5938fb9fd2538a022189f98d87ca
@@@ -1,5 -1,5 +1,5 @@@
  /*
 -    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
 +    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
  
      This program is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
  
  */
  
 +/** @file  src/wx/audio_mapping_view.cc
 + *  @brief AudioMappingView class and helpers.
 + */
 +
  #include <wx/wx.h>
  #include <wx/renderer.h>
  #include <wx/grid.h>
 -#include <libdcp/types.h>
 +#include <dcp/types.h>
  #include "lib/audio_mapping.h"
  #include "lib/util.h"
  #include "audio_mapping_view.h"
  #include "wx_util.h"
  #include "audio_gain_dialog.h"
++#include <boost/lexical_cast.hpp>
  
  using std::cout;
  using std::list;
@@@ -56,17 -52,12 +57,15 @@@ public
        }
  };
  
 +/** @class ValueRenderer
 + *  @brief wxGridCellRenderer for a gain value.
 + */
  class ValueRenderer : public wxGridCellRenderer
  {
  public:
  
        void Draw (wxGrid& grid, wxGridCellAttr &, wxDC& dc, const wxRect& rect, int row, int col, bool)
        {
-               LocaleGuard lg;
-       
                dc.SetPen (*wxThePenList->FindOrCreatePen (wxColour (255, 255, 255), 1, wxPENSTYLE_SOLID));
                dc.SetBrush (*wxTheBrushList->FindOrCreateBrush (wxColour (255, 255, 255), wxBRUSHSTYLE_SOLID));
                dc.DrawRectangle (rect);
@@@ -163,7 -154,7 +162,7 @@@ AudioMappingView::left_click (wxGridEve
                return;
        }
  
 -      libdcp::Channel d = static_cast<libdcp::Channel> (ev.GetCol() - 1);
 +      dcp::Channel d = static_cast<dcp::Channel> (ev.GetCol() - 1);
        
        if (_map.get (ev.GetRow(), d) > 0) {
                _map.set (ev.GetRow(), d, 0);
@@@ -189,28 -180,28 +188,28 @@@ AudioMappingView::right_click (wxGridEv
  void
  AudioMappingView::off ()
  {
 -      _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 0);
 +      _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 0);
        map_changed ();
  }
  
  void
  AudioMappingView::full ()
  {
 -      _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 1);
 +      _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 1);
        map_changed ();
  }
  
  void
  AudioMappingView::minus3dB ()
  {
 -      _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 1 / sqrt (2));
 +      _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 1 / sqrt (2));
        map_changed ();
  }
  
  void
  AudioMappingView::edit ()
  {
 -      libdcp::Channel d = static_cast<libdcp::Channel> (_menu_column - 1);
 +      dcp::Channel d = static_cast<dcp::Channel> (_menu_column - 1);
        
        AudioGainDialog* dialog = new AudioGainDialog (this, _menu_row, _menu_column - 1, _map.get (_menu_row, d));
        if (dialog->ShowModal () == wxID_OK) {
@@@ -231,8 -222,6 +230,6 @@@ AudioMappingView::set (AudioMapping map
  void
  AudioMappingView::update_cells ()
  {
-       LocaleGuard lg;
-       
        if (_grid->GetNumberRows ()) {
                _grid->DeleteRows (0, _grid->GetNumberRows ());
        }
                _grid->SetCellValue (i, 0, wxString::Format (wxT("%d"), i + 1));
  
                for (int j = 1; j < _grid->GetNumberCols(); ++j) {
 -                      _grid->SetCellValue (i, j, std_to_wx (lexical_cast<string> (_map.get (i, static_cast<libdcp::Channel> (j - 1)))));
 +                      _grid->SetCellValue (i, j, std_to_wx (lexical_cast<string> (_map.get (i, static_cast<dcp::Channel> (j - 1)))));
                }
        }
  
@@@ -353,7 -342,7 +350,7 @@@ AudioMappingView::mouse_moved (wxMouseE
        if (row != _last_tooltip_row || column != _last_tooltip_column) {
  
                wxString s;
 -              float const gain = _map.get (row, static_cast<libdcp::Channel> (column - 1));
 +              float const gain = _map.get (row, static_cast<dcp::Channel> (column - 1));
                if (gain == 0) {
                        s = wxString::Format (_("No audio will be passed from content channel %d to DCP channel %d."), row + 1, column);
                } else if (gain == 1) {
diff --combined src/wx/config_dialog.cc
index 799067920d0fbed4aad634dc484eb4c53ce1645b,e27b79172ea0ab8d9ca7dde851b4ae251b3feb83..f06670dfb222c7d2e613b48ae7055cfd41e335c3
@@@ -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"
@@@ -109,6 -109,10 +109,10 @@@ public
                add_label_to_sizer (table, panel, _("Maximum JPEG2000 bandwidth"), true);
                _maximum_j2k_bandwidth = new wxSpinCtrl (panel);
                table->Add (_maximum_j2k_bandwidth, 1);
+               _allow_any_dcp_frame_rate = new wxCheckBox (panel, wxID_ANY, _("Allow any DCP frame rate"));
+               table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
+               table->AddSpacer (0);
                
                add_label_to_sizer (table, panel, _("Outgoing mail server"), true);
                _mail_server = new wxTextCtrl (panel, wxID_ANY);
                _check_for_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_updates_changed, this));
                _check_for_test_updates->SetValue (config->check_for_test_updates ());
                _check_for_test_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
+               _allow_any_dcp_frame_rate->SetValue (config->allow_any_dcp_frame_rate ());
+               _allow_any_dcp_frame_rate->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::allow_any_dcp_frame_rate_changed, this));
                
                return panel;
        }
@@@ -269,11 -275,17 +275,17 @@@ private
        {
                Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
        }
+       void allow_any_dcp_frame_rate_changed ()
+       {
+               Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
+       }
        
        wxCheckBox* _set_language;
        wxChoice* _language;
        wxSpinCtrl* _num_local_encoding_threads;
        wxSpinCtrl* _maximum_j2k_bandwidth;
+       wxCheckBox* _allow_any_dcp_frame_rate;
        wxTextCtrl* _mail_server;
        wxTextCtrl* _mail_user;
        wxTextCtrl* _mail_password;
@@@ -460,14 -472,14 +472,14 @@@ private
  
        void issuer_changed ()
        {
 -              libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
 +              dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
                m.issuer = wx_to_std (_issuer->GetValue ());
                Config::instance()->set_dcp_metadata (m);
        }
        
        void creator_changed ()
        {
 -              libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
 +              dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
                m.creator = wx_to_std (_creator->GetValue ());
                Config::instance()->set_dcp_metadata (m);
        }
diff --combined src/wx/film_editor.cc
index 3fd63e9224c6e363e74027f5371f17840bb2d41c,ca638d8ae638c387f4946322927438b2e21ede9f..c20a1232402f1b4f5bc337aa9b724219d40f3fb7
@@@ -143,12 -143,15 +143,15 @@@ FilmEditor::make_dcp_panel (
  
        {
                add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Frame Rate"), true, wxGBPosition (r, 0));
-               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _frame_rate = new wxChoice (_dcp_panel, wxID_ANY);
-               s->Add (_frame_rate, 1, wxALIGN_CENTER_VERTICAL);
+               _frame_rate_sizer = new wxBoxSizer (wxHORIZONTAL);
+               _frame_rate_choice = new wxChoice (_dcp_panel, wxID_ANY);
+               _frame_rate_sizer->Add (_frame_rate_choice, 1, wxALIGN_CENTER_VERTICAL);
+               _frame_rate_spin = new wxSpinCtrl (_dcp_panel, wxID_ANY);
+               _frame_rate_sizer->Add (_frame_rate_spin, 1, wxALIGN_CENTER_VERTICAL);
+               setup_frame_rate_widget ();
                _best_frame_rate = new wxButton (_dcp_panel, wxID_ANY, _("Use best"));
-               s->Add (_best_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND);
-               grid->Add (s, wxGBPosition (r, 1));
+               _frame_rate_sizer->Add (_best_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND);
+               grid->Add (_frame_rate_sizer, wxGBPosition (r, 1));
        }
        ++r;
  
  
        list<int> const dfr = Config::instance()->allowed_dcp_frame_rates ();
        for (list<int>::const_iterator i = dfr.begin(); i != dfr.end(); ++i) {
-               _frame_rate->Append (std_to_wx (boost::lexical_cast<string> (*i)));
+               _frame_rate_choice->Append (std_to_wx (boost::lexical_cast<string> (*i)));
        }
  
        _audio_channels->SetRange (0, MAX_DCP_AUDIO_CHANNELS);
        _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000);
+       _frame_rate_spin->SetRange (1, 480);
  
        _resolution->Append (_("2K"));
        _resolution->Append (_("4K"));
@@@ -242,7 -246,8 +246,8 @@@ FilmEditor::connect_to_widgets (
        _content_timeline->Bind (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::content_timeline_clicked, this));
        _scaler->Bind           (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::scaler_changed, this));
        _dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::dcp_content_type_changed, this));
-       _frame_rate->Bind       (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::frame_rate_changed, this));
+       _frame_rate_choice->Bind(wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::frame_rate_choice_changed, this));
+       _frame_rate_spin->Bind  (wxEVT_COMMAND_SPINCTRL_UPDATED,      boost::bind (&FilmEditor::frame_rate_spin_changed, this));
        _best_frame_rate->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::best_frame_rate_clicked, this));
        _signed->Bind           (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::signed_toggled, this));
        _encrypted->Bind        (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::encrypted_toggled, this));
@@@ -352,9 -357,9 +357,9 @@@ FilmEditor::encrypted_toggled (
        _film->set_encrypted (_encrypted->GetValue ());
  }
                               
- /** Called when the name widget has been changed */
+ /** Called when the frame rate choice widget has been changed */
  void
- FilmEditor::frame_rate_changed ()
+ FilmEditor::frame_rate_choice_changed ()
  {
        if (!_film) {
                return;
  
        _film->set_video_frame_rate (
                boost::lexical_cast<int> (
-                       wx_to_std (_frame_rate->GetString (_frame_rate->GetSelection ()))
+                       wx_to_std (_frame_rate_choice->GetString (_frame_rate_choice->GetSelection ()))
                        )
                );
  }
  
+ /** Called when the frame rate spin widget has been changed */
+ void
+ FilmEditor::frame_rate_spin_changed ()
+ {
+       if (!_film) {
+               return;
+       }
+       _film->set_video_frame_rate (_frame_rate_spin->GetValue ());
+ }
  void
  FilmEditor::audio_channels_changed ()
  {
@@@ -468,18 -484,20 +484,20 @@@ FilmEditor::film_changed (Film::Propert
        case Film::VIDEO_FRAME_RATE:
        {
                bool done = false;
-               for (unsigned int i = 0; i < _frame_rate->GetCount(); ++i) {
-                       if (wx_to_std (_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->video_frame_rate())) {
-                               checked_set (_frame_rate, i);
+               for (unsigned int i = 0; i < _frame_rate_choice->GetCount(); ++i) {
+                       if (wx_to_std (_frame_rate_choice->GetString(i)) == boost::lexical_cast<string> (_film->video_frame_rate())) {
+                               checked_set (_frame_rate_choice, i);
                                done = true;
                                break;
                        }
                }
  
                if (!done) {
-                       checked_set (_frame_rate, -1);
+                       checked_set (_frame_rate_choice, -1);
                }
  
+               _frame_rate_spin->SetValue (_film->video_frame_rate ());
                _best_frame_rate->Enable (_film->best_video_frame_rate () != _film->video_frame_rate ());
                break;
        }
@@@ -646,7 -664,8 +664,8 @@@ FilmEditor::set_general_sensitivity (bo
        _signed->Enable (si);
        
        _encrypted->Enable (s);
-       _frame_rate->Enable (s);
+       _frame_rate_choice->Enable (s);
+       _frame_rate_spin->Enable (s);
        _audio_channels->Enable (s);
        _j2k_bandwidth->Enable (s);
        _container->Enable (s);
@@@ -854,7 -873,7 +873,7 @@@ FilmEditor::setup_content_sensitivity (
  
        _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);
  }
  
  FilmEditor::config_changed ()
  {
        _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000);
+       setup_frame_rate_widget ();
+ }
+ void
+ FilmEditor::setup_frame_rate_widget ()
+ {
+       if (Config::instance()->allow_any_dcp_frame_rate ()) {
+               _frame_rate_choice->Hide ();
+               _frame_rate_spin->Show ();
+       } else {
+               _frame_rate_choice->Show ();
+               _frame_rate_spin->Hide ();
+       }
+       _frame_rate_sizer->Layout ();
  }
diff --combined test/ratio_test.cc
index f5ac3ca64bff3f2b39bc939233b1c29c7aabafe9,f3cbb504f0b874b9d5ef97b65d681b2e91ff33f3..c941248bf01fa485302d070c54b6a48ea7bb261a
@@@ -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/ratio_test.cc
 + *  @brief Test Ratio and fit_ratio_within().
 + */
 +
  #include <iostream>
  #include <boost/test/unit_test.hpp>
 -#include <libdcp/util.h>
 +#include <dcp/util.h>
  #include "lib/ratio.h"
  #include "lib/util.h"
  
  using std::ostream;
  
--namespace libdcp {
++namespace dcp {
        
  ostream&
 -operator<< (ostream& s, libdcp::Size const & t)
 +operator<< (ostream& s, dcp::Size const & t)
  {
        s << t.width << "x" << t.height;
        return s;
@@@ -46,38 -42,38 +46,38 @@@ BOOST_AUTO_TEST_CASE (ratio_test
  
        Ratio const * r = Ratio::from_id ("119");
        BOOST_CHECK (r);
 -      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1290, 1080));
 +      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1290, 1080));
  
        r = Ratio::from_id ("133");
        BOOST_CHECK (r);
 -      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1440, 1080));
 +      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1440, 1080));
  
        r = Ratio::from_id ("137");
        BOOST_CHECK (r);
 -      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1480, 1080));
 +      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1480, 1080));
  
        r = Ratio::from_id ("138");
        BOOST_CHECK (r);
 -      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1485, 1080));
 +      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1485, 1080));
  
        r = Ratio::from_id ("166");
        BOOST_CHECK (r);
 -      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1800, 1080));
 +      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1800, 1080));
  
        r = Ratio::from_id ("178");
        BOOST_CHECK (r);
 -      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1920, 1080));
 +      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1920, 1080));
  
        r = Ratio::from_id ("185");
        BOOST_CHECK (r);
 -      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1998, 1080));
 +      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1998, 1080));
  
        r = Ratio::from_id ("239");
        BOOST_CHECK (r);
 -      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (2048, 858));
 +      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (2048, 858));
  
        r = Ratio::from_id ("full-frame");
        BOOST_CHECK (r);
 -      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (2048, 1080));
 +      BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (2048, 1080));
  }