Merge master.
authorCarl Hetherington <cth@carlh.net>
Sun, 31 Aug 2014 23:06:19 +0000 (00:06 +0100)
committerCarl Hetherington <cth@carlh.net>
Sun, 31 Aug 2014 23:06:19 +0000 (00:06 +0100)
1  2 
ChangeLog
run/dcpomatic
src/lib/config.cc
src/lib/config.h
src/lib/util.cc
src/lib/writer.cc
src/wx/about_dialog.cc
src/wx/config_dialog.cc
src/wx/video_panel.cc

diff --combined ChangeLog
index 36a282919097ce8a2bd73f110cf54f7af97bb105,c919f85b5c1e3b15b5bd6ca6d9950310e8b9df59..bdfdadfef8e7eb7b072633771838ce766010999c
+++ b/ChangeLog
@@@ -1,5 -1,10 +1,10 @@@
  2014-08-31  Carl Hetherington  <cth@carlh.net>
  
+       * Remove configurable CPL <Creator> and use "DCP-o-matic (version) (git)"
+       instead.
+       * Fix lack of i18n of strings from src/lib/po on OS X.
        * Give a hint when content and container aspect ratios are not
        the same (#392).
  
  
  2014-08-29  Carl Hetherington  <cth@carlh.net>
  
 +      * Version 2.0.4 released.
 +
 +2014-08-24  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.3 released.
 +
 +2014-08-24  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.2 released.
 +
 +2014-08-06  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.1 released.
 +
 +2014-07-15  Carl Hetherington  <cth@carlh.net>
 +
 +      * A variety of changes were made on the 2.0 branch
 +      but not documented in the ChangeLog.  Most sigificantly:
 +
 +      - DCP import
 +      - Creation of DCPs with proper XML subtitles
 +      - Import of .srt and .xml subtitles
 +      - Audio processing framework (with some basic processors).
 +
 +2014-03-07  Carl Hetherington  <cth@carlh.net>
 +
 +      * Add subtitle view.
        * Some improvements to the manual.
  
  2014-08-26  Carl Hetherington  <cth@carlh.net>
@@@ -66,7 -44,6 +71,7 @@@
        * Attempt to fix random crashes on OS X (especially during encodes)
        thought to be caused by multiple threads using (different) stringstreams
        at the same time; see src/lib/safe_stringstream.
 +>>>>>>> origin/master
  
  2014-08-09  Carl Hetherington  <cth@carlh.net>
  
  2014-07-10  Carl Hetherington  <cth@carlh.net>
  
        * Version 1.72.2 released.
 +>>>>>>> origin/master
  
  2014-07-10  Carl Hetherington  <cth@carlh.net>
  
diff --combined run/dcpomatic
index b0036fb6ff1b5743a55e3e0f8366741725a17fa5,9a2f21ea53101c1659adce6fbddd90b456c4b347..9cf5a26194961e92fb790b8cc9d4292e14829699
@@@ -4,8 -4,8 +4,8 @@@ if [ `uname -s` == "Darwin" ]; the
    # Some time ago we could start DCP-o-matic on OS X just by running the executable, in
    # the same way as Linux.  This doesn't work any more.
    
-   ENV=$HOME/Environments/osx/64/lib
-   DEP=$HOME/cdist/64/lib
+   ENV=$HOME/Environments/osx/10.6/64/lib
+   DEP=$HOME/dcpomatic-deps/lib
   
    app=build/platform/osx/DCP-o-matic.app
    macos=$app/Contents/MacOS
    cp $ENV/libquickmail*.dylib $lib
    cp $ENV/libcurl*.dylib $lib
    cp $ENV/libffi*.dylib $lib
+   cp $ENV/libiconv*.dylib $lib
    cp icons/defaults.png $resources
    cp icons/servers.png $resources
    cp icons/tms.png $resources
    cp icons/colour_conversions.png $resources
    cp icons/kdm_email.png $resources
+   for lang in de_DE es_ES fr_FR it_IT sv_SE nl_NL; do
+     mkdir "$resources/$lang"
+     cp build/src/lib/mo/$lang/*.mo "$resources/$lang"
+     cp build/src/wx/mo/$lang/*.mo "$resources/$lang"
+     cp build/src/tools/mo/$lang/*.mo "$resources/$lang"
+   done
   
    sed -e "s/@VERSION@/test/g" platform/osx/Info.plist.in > $app/Contents/Info.plist
  
      fi
    done  
    
-   cd build/platform/osx/DCP-o-matic.app/Contents/MacOS
-   ./dcpomatic $*
+   open build/platform/osx/DCP-o-matic.app
  
  else
    export LD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:$LD_LIBRARY_PATH
    if [ "$1" == "--debug" ]; then
        shift
 -      gdb --args build/src/tools/dcpomatic $*
 +      gdb --args build/src/tools/dcpomatic2 $*
    elif [ "$1" == "--valgrind" ]; then
        shift
 -      valgrind --tool="memcheck" build/src/tools/dcpomatic $*
 +      valgrind --tool="memcheck" build/src/tools/dcpomatic2 $*
    elif [ "$1" == "--callgrind" ]; then
        shift
 -      valgrind --tool="callgrind" build/src/tools/dcpomatic $*
 +      valgrind --tool="callgrind" build/src/tools/dcpomatic2 $*
    elif [ "$1" == "--massif" ]; then
        shift
 -      valgrind --tool="massif" build/src/tools/dcpomatic $*
 +      valgrind --tool="massif" build/src/tools/dcpomatic2 $*
    elif [ "$1" == "--i18n" ]; then
        shift
 -      LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 LC_ALL=fr_FR.UTF8 build/src/tools/dcpomatic "$*"
 +      LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 LC_ALL=fr_FR.UTF8 build/src/tools/dcpomatic2 "$*"
    elif [ "$1" == "--perf" ]; then
        shift
 -      perf record build/src/tools/dcpomatic $*
 +      perf record build/src/tools/dcpomatic2 $*
    else
 -      build/src/tools/dcpomatic $*
 +      build/src/tools/dcpomatic2 $*
    fi
  fi
  
diff --combined src/lib/config.cc
index 7e37625878191a87db0e7bf1cf3959e5b3fb68e3,67abc63c2e8df4207ea339b3c909b1f69ffdf1b5..1f5a25ae4dcb52f785930d8948bb5998f25dbb23
  #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 <dcp/signer.h>
 +#include <dcp/certificate_chain.h>
  #include <libcxml/cxml.h>
  #include "config.h"
  #include "server.h"
  #include "filter.h"
  #include "ratio.h"
  #include "dcp_content_type.h"
 -#include "sound_processor.h"
 +#include "cinema_sound_processor.h"
  #include "colour_conversion.h"
  #include "cinema.h"
  #include "util.h"
 +#include "cross.h"
  
  #include "i18n.h"
  
@@@ -54,7 -51,7 +54,7 @@@ using boost::shared_ptr
  using boost::optional;
  using boost::algorithm::is_any_of;
  using boost::algorithm::split;
 -using libdcp::raw_convert;
 +using dcp::raw_convert;
  
  Config* Config::_instance = 0;
  
@@@ -64,7 -61,7 +64,7 @@@ Config::Config (
        , _server_port_base (6192)
        , _use_any_servers (true)
        , _tms_path (".")
 -      , _sound_processor (SoundProcessor::from_id (N_("dolby_cp750")))
 +      , _cinema_sound_processor (CinemaSoundProcessor::from_id (N_("dolby_cp750")))
        , _allow_any_dcp_frame_rate (false)
        , _default_still_length (10)
        , _default_scale (Ratio::from_id ("185"))
@@@ -84,9 -81,9 +84,9 @@@
        _allowed_dcp_frame_rates.push_back (50);
        _allowed_dcp_frame_rates.push_back (60);
  
 -      _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6));
 -      _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6));
 -      _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
 +      _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6));
 +      _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, dcp::colour_matrix::srgb_to_xyz, 2.6));
 +      _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
  
        reset_kdm_email ();
  }
@@@ -95,10 -92,7 +95,10 @@@ voi
  Config::read ()
  {
        if (!boost::filesystem::exists (file (false))) {
 -              read_old_metadata ();
 +              /* Make a new set of signing certificates and key */
 +              _signer.reset (new dcp::Signer (openssl_path ()));
 +              /* And decryption keys */
 +              make_decryption_keys ();
                return;
        }
  
  
        c = f.optional_string_child ("SoundProcessor");
        if (c) {
 -              _sound_processor = SoundProcessor::from_id (c.get ());
 +              _cinema_sound_processor = CinemaSoundProcessor::from_id (c.get ());
 +      }
 +      c = f.optional_string_child ("CinemaSoundProcessor");
 +      if (c) {
 +              _cinema_sound_processor = CinemaSoundProcessor::from_id (c.get ());
        }
  
        _language = f.optional_string_child ("Language");
                _default_dcp_content_type = DCPContentType::from_isdcf_name (c.get ());
        }
  
-       _dcp_metadata.issuer = f.optional_string_child ("DCPMetadataIssuer").get_value_or ("");
-       _dcp_metadata.creator = f.optional_string_child ("DCPMetadataCreator").get_value_or ("");
+       if (f.optional_string_child ("DCPMetadataIssuer")) {
+               _dcp_issuer = f.string_child ("DCPMetadataIssuer");
+       } else if (f.optional_string_child ("DCPIssuer")) {
+               _dcp_issuer = f.string_child ("DCPIssuer");
+       }
+       
        if (version && version.get() >= 2) {
                _default_isdcf_metadata = ISDCFMetadata (f.node_child ("ISDCFMetadata"));
        } else {
                /* 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");
        for (list<cxml::NodePtr>::const_iterator i = his.begin(); i != his.end(); ++i) {
                _history.push_back ((*i)->content ());
        }
 -}
  
 -void
 -Config::read_old_metadata ()
 -{
 -      /* XXX: this won't work with non-Latin filenames */
 -      ifstream f (file(true).string().c_str ());
 -      string line;
 -
 -      while (getline (f, line)) {
 -              if (line.empty ()) {
 -                      continue;
 +      cxml::NodePtr signer = f.optional_node_child ("Signer");
 +      dcp::CertificateChain signer_chain;
 +      if (signer) {
 +              /* Read the signing certificates and private key in from the config file */
 +              list<cxml::NodePtr> certificates = signer->node_children ("Certificate");
 +              for (list<cxml::NodePtr>::const_iterator i = certificates.begin(); i != certificates.end(); ++i) {
 +                      signer_chain.add (dcp::Certificate ((*i)->content ()));
                }
  
 -              if (line[0] == '#') {
 -                      continue;
 -              }
 +              _signer.reset (new dcp::Signer (signer_chain, signer->string_child ("PrivateKey")));
 +      } else {
 +              /* Make a new set of signing certificates and key */
 +              _signer.reset (new dcp::Signer (openssl_path ()));
 +      }
  
 -              size_t const s = line.find (' ');
 -              if (s == string::npos) {
 -                      continue;
 -              }
 -              
 -              string const k = line.substr (0, s);
 -              string const v = line.substr (s + 1);
 -
 -              if (k == N_("num_local_encoding_threads")) {
 -                      _num_local_encoding_threads = atoi (v.c_str ());
 -              } else if (k == N_("default_directory")) {
 -                      _default_directory = v;
 -              } else if (k == N_("server_port")) {
 -                      _server_port_base = atoi (v.c_str ());
 -              } else if (k == N_("server")) {
 -                      vector<string> b;
 -                      split (b, v, is_any_of (" "));
 -                      if (b.size() == 2) {
 -                              _servers.push_back (b[0]);
 -                      }
 -              } else if (k == N_("tms_ip")) {
 -                      _tms_ip = v;
 -              } else if (k == N_("tms_path")) {
 -                      _tms_path = v;
 -              } else if (k == N_("tms_user")) {
 -                      _tms_user = v;
 -              } else if (k == N_("tms_password")) {
 -                      _tms_password = v;
 -              } else if (k == N_("sound_processor")) {
 -                      _sound_processor = SoundProcessor::from_id (v);
 -              } else if (k == "language") {
 -                      _language = v;
 -              } else if (k == "default_container") {
 -                      _default_container = Ratio::from_id (v);
 -              } else if (k == "default_dcp_content_type") {
 -                      _default_dcp_content_type = DCPContentType::from_isdcf_name (v);
 -              } else if (k == "dcp_metadata_issuer") {
 -                      _dcp_issuer = v;
 -              }
 +      if (f.optional_string_child ("DecryptionCertificate")) {
 +              _decryption_certificate = dcp::Certificate (f.string_child ("DecryptionCertificate"));
 +      }
 +
 +      if (f.optional_string_child ("DecryptionPrivateKey")) {
 +              _decryption_private_key = f.string_child ("DecryptionPrivateKey");
 +      }
  
 -              _default_isdcf_metadata.read_old_metadata (k, v);
 +      if (!f.optional_string_child ("DecryptionCertificate") || !f.optional_string_child ("DecryptionPrivateKey")) {
 +              /* Generate our own decryption certificate and key if either is not present in config */
 +              make_decryption_keys ();
        }
  }
  
 +void
 +Config::make_decryption_keys ()
 +{
 +      boost::filesystem::path p = dcp::make_certificate_chain (openssl_path ());
 +      _decryption_certificate = dcp::Certificate (dcp::file_to_string (p / "leaf.signed.pem"));
 +      _decryption_private_key = dcp::file_to_string (p / "leaf.key");
 +      boost::filesystem::remove_all (p);
 +}
 +
  /** @return Filename to write configuration to */
  boost::filesystem::path
  Config::file (bool old) const
        return p;
  }
  
 -boost::filesystem::path
 -Config::signer_chain_directory () const
 -{
 -      boost::filesystem::path p;
 -      p /= g_get_user_config_dir ();
 -      p /= "dcpomatic";
 -      p /= "crypt";
 -      boost::filesystem::create_directories (p);
 -      return p;
 -}
 -
  /** @return Singleton instance */
  Config *
  Config::instance ()
@@@ -320,8 -347,8 +323,8 @@@ Config::write () cons
        root->add_child("TMSPath")->add_child_text (_tms_path);
        root->add_child("TMSUser")->add_child_text (_tms_user);
        root->add_child("TMSPassword")->add_child_text (_tms_password);
 -      if (_sound_processor) {
 -              root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ());
 +      if (_cinema_sound_processor) {
 +              root->add_child("CinemaSoundProcessor")->add_child_text (_cinema_sound_processor->id ());
        }
        if (_language) {
                root->add_child("Language")->add_child_text (_language.get());
        if (_default_dcp_content_type) {
                root->add_child("DefaultDCPContentType")->add_child_text (_default_dcp_content_type->isdcf_name ());
        }
-       root->add_child("DCPMetadataIssuer")->add_child_text (_dcp_metadata.issuer);
-       root->add_child("DCPMetadataCreator")->add_child_text (_dcp_metadata.creator);
+       root->add_child("DCPIssuer")->add_child_text (_dcp_issuer);
  
        _default_isdcf_metadata.as_xml (root->add_child ("ISDCFMetadata"));
  
        root->add_child("AllowAnyDCPFrameRate")->add_child_text (_allow_any_dcp_frame_rate ? "1" : "0");
        root->add_child("LogTypes")->add_child_text (raw_convert<string> (_log_types));
  
 +      xmlpp::Element* signer = root->add_child ("Signer");
 +      dcp::CertificateChain::List certs = _signer->certificates().root_to_leaf ();
 +      for (dcp::CertificateChain::List::const_iterator i = certs.begin(); i != certs.end(); ++i) {
 +              signer->add_child("Certificate")->add_child_text (i->certificate (true));
 +      }
 +      signer->add_child("PrivateKey")->add_child_text (_signer->key ());
 +
 +      root->add_child("DecryptionCertificate")->add_child_text (_decryption_certificate.certificate (true));
 +      root->add_child("DecryptionPrivateKey")->add_child_text (_decryption_private_key);
 +
        for (vector<boost::filesystem::path>::const_iterator i = _history.begin(); i != _history.end(); ++i) {
                root->add_child("History")->add_child_text (i->string ());
        }
diff --combined src/lib/config.h
index 05bc7945f1df80bb77c6f846d01c0a161ac80810,4f6b57f5652c2ef14f1bf9ade48c6a40db8a96d0..9a18086829af77194d973941399b93ee80c1a52a
  #include <boost/shared_ptr.hpp>
  #include <boost/signals2.hpp>
  #include <boost/filesystem.hpp>
 +#include <dcp/metadata.h>
 +#include <dcp/certificates.h>
 +#include <dcp/signer.h>
  #include "isdcf_metadata.h"
  #include "colour_conversion.h"
 -#include "server.h"
  
  class ServerDescription;
  class Scaler;
  class Filter;
 -class SoundProcessor;
 +class CinemaSoundProcessor;
  class DCPContentType;
  class Ratio;
  class Cinema;
@@@ -105,9 -103,9 +105,9 @@@ public
                return _tms_password;
        }
  
 -      /** @return The sound processor that we are using */
 -      SoundProcessor const * sound_processor () const {
 -              return _sound_processor;
 +      /** @return The cinema sound processor that we are using */
 +      CinemaSoundProcessor const * cinema_sound_processor () const {
 +              return _cinema_sound_processor;
        }
  
        std::list<boost::shared_ptr<Cinema> > cinemas () const {
                return _default_dcp_content_type;
        }
  
-       dcp::XMLMetadata dcp_metadata () const {
-               return _dcp_metadata;
+       std::string dcp_issuer () const {
+               return _dcp_issuer;
        }
  
        int default_j2k_bandwidth () const {
                return _kdm_email;
        }
  
 +      boost::shared_ptr<const dcp::Signer> signer () const {
 +              return _signer;
 +      }
 +
 +      dcp::Certificate decryption_certificate () const {
 +              return _decryption_certificate;
 +      }
 +
 +      std::string decryption_private_key () const {
 +              return _decryption_private_key;
 +      }
 +
        bool check_for_updates () const {
                return _check_for_updates;
        }
                changed ();
        }
  
-       void set_dcp_metadata (dcp::XMLMetadata m) {
-               _dcp_metadata = m;
+       void set_dcp_issuer (std::string i) {
+               _dcp_issuer = i;
                changed ();
        }
  
  
        void reset_kdm_email ();
  
 +      void set_signer (boost::shared_ptr<const dcp::Signer> s) {
 +              _signer = s;
 +              changed ();
 +      }
 +
 +      void set_decryption_certificate (dcp::Certificate c) {
 +              _decryption_certificate = c;
 +              changed ();
 +      }
 +
 +      void set_decryption_private_key (std::string k) {
 +              _decryption_private_key = k;
 +              changed ();
 +      }
 +
        void set_check_for_updates (bool c) {
                _check_for_updates = c;
                changed ();
  
        void add_to_history (boost::filesystem::path p);
        
 -      boost::filesystem::path signer_chain_directory () const;
 -
        void changed ();
        boost::signals2::signal<void ()> Changed;
  
@@@ -436,8 -409,8 +436,8 @@@ private
        Config ();
        boost::filesystem::path file (bool) const;
        void read ();
 -      void read_old_metadata ();
        void write () const;
 +      void make_decryption_keys ();
  
        /** number of threads to use for J2K encoding on the local machine */
        int _num_local_encoding_threads;
        std::string _tms_user;
        /** Password to log into the TMS with */
        std::string _tms_password;
 -      /** Our sound processor */
 -      SoundProcessor const * _sound_processor;
 +      /** Our cinema sound processor */
 +      CinemaSoundProcessor const * _cinema_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;
        Ratio const * _default_scale;
        Ratio const * _default_container;
        DCPContentType const * _default_dcp_content_type;
-       dcp::XMLMetadata _dcp_metadata;
+       std::string _dcp_issuer;
        int _default_j2k_bandwidth;
        int _default_audio_delay;
        std::vector<PresetColourConversion> _colour_conversions;
        std::string _kdm_cc;
        std::string _kdm_bcc;
        std::string _kdm_email;
 +      boost::shared_ptr<const dcp::Signer> _signer;
 +      dcp::Certificate _decryption_certificate;
 +      std::string _decryption_private_key;
        /** true to check for updates on startup */
        bool _check_for_updates;
        bool _check_for_test_updates;
diff --combined src/lib/util.cc
index c9685aa44ff3ee811e1f7e77e052b8047ca8ff1d,5f1d589d669d92f53cbf29d381f06f97fa4438a5..c09ed9cb24bb5dbdcb1f9c4c26d4e31b853022c6
  #endif
  #include <glib.h>
  #include <openjpeg.h>
 +#include <pangomm/init.h>
  #include <magick/MagickCore.h>
  #include <magick/version.h>
 -#include <libdcp/version.h>
 -#include <libdcp/util.h>
 -#include <libdcp/signer_chain.h>
 -#include <libdcp/signer.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/version.h>
 +#include <dcp/util.h>
 +#include <dcp/signer.h>
 +#include <dcp/raw_convert.h>
  extern "C" {
  #include <libavcodec/avcodec.h>
  #include <libavformat/avformat.h>
  #include "scaler.h"
  #include "dcp_content_type.h"
  #include "filter.h"
 -#include "sound_processor.h"
 +#include "cinema_sound_processor.h"
  #include "config.h"
  #include "ratio.h"
  #include "job.h"
  #include "cross.h"
  #include "video_content.h"
 +#include "rect.h"
  #include "md5_digester.h"
 +#include "audio_processor.h"
  #include "safe_stringstream.h"
  #ifdef DCPOMATIC_WINDOWS
  #include "stack.hpp"
@@@ -101,8 -99,8 +101,8 @@@ using std::set_terminate
  using boost::shared_ptr;
  using boost::thread;
  using boost::optional;
 -using libdcp::Size;
 -using libdcp::raw_convert;
 +using dcp::Size;
 +using dcp::raw_convert;
  
  static boost::thread::id ui_thread;
  static boost::filesystem::path backtrace_file;
@@@ -267,6 -265,24 +267,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 ()
 -{
 -      SafeStringStream 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)
  {
@@@ -355,16 -371,14 +355,16 @@@ dcpomatic_setup (
  
        set_terminate (terminate);
  
 -      libdcp::init ();
 +      Pango::init ();
 +      dcp::init ();
        
        Ratio::setup_ratios ();
        VideoContentScale::setup_scales ();
        DCPContentType::setup_dcp_content_types ();
        Scaler::setup_scalers ();
        Filter::setup_filters ();
 -      SoundProcessor::setup_sound_processors ();
 +      CinemaSoundProcessor::setup_cinema_sound_processors ();
 +      AudioProcessor::setup_audio_processors ();
  
        ui_thread = boost::this_thread::get_id ();
  }
@@@ -383,35 -397,42 +383,42 @@@ mo_path (
  }
  #endif
  
+ #ifdef DCPOMATIC_OSX
+ boost::filesystem::path
+ mo_path ()
+ {
+       return "DCP-o-matic.app/Contents/Resources";
+ }
+ #endif
  void
  dcpomatic_setup_gettext_i18n (string lang)
  {
- #ifdef DCPOMATIC_POSIX
+ #ifdef DCPOMATIC_LINUX
        lang += ".UTF8";
  #endif
  
        if (!lang.empty ()) {
-               /* Override our environment language; this is essential on
-                  Windows.
+               /* Override our environment language.  Note that the caller must not
+                  free the string passed into putenv().
                */
-               char cmd[64];
-               snprintf (cmd, sizeof(cmd), "LANGUAGE=%s", lang.c_str ());
-               putenv (cmd);
-               snprintf (cmd, sizeof(cmd), "LANG=%s", lang.c_str ());
-               putenv (cmd);
-               snprintf (cmd, sizeof(cmd), "LC_ALL=%s", lang.c_str ());
-               putenv (cmd);
+               string s = String::compose ("LANGUAGE=%1", lang);
+               putenv (strdup (s.c_str ()));
+               s = String::compose ("LANG=%1", lang);
+               putenv (strdup (s.c_str ()));
+               s = String::compose ("LC_ALL=%1", lang);
+               putenv (strdup (s.c_str ()));
        }
  
        setlocale (LC_ALL, "");
        textdomain ("libdcpomatic");
  
- #ifdef DCPOMATIC_WINDOWS
+ #if defined(DCPOMATIC_WINDOWS) || defined(DCPOMATIC_OSX)
        bindtextdomain ("libdcpomatic", mo_path().string().c_str());
        bind_textdomain_codeset ("libdcpomatic", "UTF8");
  #endif        
  
- #ifdef DCPOMATIC_POSIX
+ #ifdef DCPOMATIC_LINUX
        bindtextdomain ("libdcpomatic", POSIX_LOCALE_PREFIX);
  #endif
  }
@@@ -635,17 -656,6 +642,17 @@@ stride_round_up (int c, int const * str
        return a - (a % t);
  }
  
 +/** @param n A number.
 + *  @param r Rounding `boundary' (must be a power of 2)
 + *  @return n rounded to the nearest r
 + */
 +int
 +round_to (float n, int r)
 +{
 +      assert (r == 1 || r == 2 || r == 4);
 +      return int (n + float(r) / 2) &~ (r - 1);
 +}
 +
  /** Read a sequence of key / value pairs from a text stream;
   *  the keys are the first words on the line, and the values are
   *  the remainder of the line following the key.  Lines beginning
@@@ -750,6 -760,17 +757,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)
  {
@@@ -800,6 -821,59 +807,6 @@@ tidy_for_filename (string f
        return t;
  }
  
 -shared_ptr<const libdcp::Signer>
 -make_signer ()
 -{
 -      boost::filesystem::path const sd = Config::instance()->signer_chain_directory ();
 -
 -      /* Remake the chain if any of it is missing */
 -      
 -      list<boost::filesystem::path> files;
 -      files.push_back ("ca.self-signed.pem");
 -      files.push_back ("intermediate.signed.pem");
 -      files.push_back ("leaf.signed.pem");
 -      files.push_back ("leaf.key");
 -
 -      list<boost::filesystem::path>::const_iterator i = files.begin();
 -      while (i != files.end()) {
 -              boost::filesystem::path p (sd);
 -              p /= *i;
 -              if (!boost::filesystem::exists (p)) {
 -                      boost::filesystem::remove_all (sd);
 -                      boost::filesystem::create_directories (sd);
 -                      libdcp::make_signer_chain (sd, openssl_path ());
 -                      break;
 -              }
 -
 -              ++i;
 -      }
 -      
 -      libdcp::CertificateChain chain;
 -
 -      {
 -              boost::filesystem::path p (sd);
 -              p /= "ca.self-signed.pem";
 -              chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
 -      }
 -
 -      {
 -              boost::filesystem::path p (sd);
 -              p /= "intermediate.signed.pem";
 -              chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
 -      }
 -
 -      {
 -              boost::filesystem::path p (sd);
 -              p /= "leaf.signed.pem";
 -              chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
 -      }
 -
 -      boost::filesystem::path signer_key (sd);
 -      signer_key /= "leaf.key";
 -
 -      return shared_ptr<const libdcp::Signer> (new libdcp::Signer (chain, signer_key));
 -}
 -
  map<string, string>
  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, int round)
  {
        if (ratio < full_frame.ratio ()) {
 -              return libdcp::Size (rint (full_frame.height * ratio), full_frame.height);
 +              return dcp::Size (round_to (full_frame.height * ratio, round), full_frame.height);
        }
        
 -      return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
 +      return dcp::Size (full_frame.width, round_to (full_frame.width / ratio, round));
  }
  
  void *
@@@ -884,34 -958,12 +891,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 ()
 +{
 +      SafeStringStream s;
 +      s << N_("libopenjpeg ") << opj_version () << N_(", ")
 +        << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
 +        << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
 +        << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
 +        << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
 +        << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
 +        << MagickVersion << N_(", ")
 +        << N_("libssh ") << ssh_version (0) << N_(", ")
 +        << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit;
 +
 +      return s.str ();
 +}
 +
 +/** Construct a ScopedTemporary.  A temporary filename is decided but the file is not opened
 + *  until ::open() is called.
 + */
  ScopedTemporary::ScopedTemporary ()
        : _open (0)
  {
        _file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
  }
  
 +/** Close and delete the temporary file */
  ScopedTemporary::~ScopedTemporary ()
  {
        close ();       
        boost::filesystem::remove (_file, ec);
  }
  
 +/** @return temporary filename */
  char const *
  ScopedTemporary::c_str () const
  {
        return _file.string().c_str ();
  }
  
 +/** Open the temporary file.
 + *  @return File's FILE pointer.
 + */
  FILE*
  ScopedTemporary::open (char const * params)
  {
        return _open;
  }
  
 +/** Close the file */
  void
  ScopedTemporary::close ()
  {
                _open = 0;
        }
  }
 +
 +ContentTimePeriod
 +subtitle_period (AVSubtitle const & sub)
 +{
 +      ContentTime const packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE);
 +
 +      ContentTimePeriod period (
 +              packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3),
 +              packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3)
 +              );
 +
 +      return period;
 +}
diff --combined src/lib/writer.cc
index eda82f27729c6b810b6850679c0562eeadb24ba2,dd2e98eeed5e0b33e484773fea63890a95e4aaa9..a023d5cd2e1cc28992573e180bc2ccd9cc540cbc
  
  #include <fstream>
  #include <cerrno>
 -#include <libdcp/mono_picture_asset.h>
 -#include <libdcp/stereo_picture_asset.h>
 -#include <libdcp/sound_asset.h>
 -#include <libdcp/reel.h>
 -#include <libdcp/dcp.h>
 -#include <libdcp/cpl.h>
 +#include <dcp/mono_picture_mxf.h>
 +#include <dcp/stereo_picture_mxf.h>
 +#include <dcp/sound_mxf.h>
 +#include <dcp/sound_mxf_writer.h>
 +#include <dcp/reel.h>
 +#include <dcp/reel_mono_picture_asset.h>
 +#include <dcp/reel_stereo_picture_asset.h>
 +#include <dcp/reel_sound_asset.h>
 +#include <dcp/reel_subtitle_asset.h>
 +#include <dcp/dcp.h>
 +#include <dcp/cpl.h>
 +#include <dcp/signer.h>
  #include "writer.h"
  #include "compose.hpp"
  #include "film.h"
  #include "ratio.h"
  #include "log.h"
 -#include "dcp_video_frame.h"
 +#include "dcp_video.h"
  #include "dcp_content_type.h"
 -#include "player.h"
  #include "audio_mapping.h"
  #include "config.h"
  #include "job.h"
  #include "cross.h"
 +#include "audio_buffers.h"
  #include "md5_digester.h"
 +#include "encoded_data.h"
+ #include "version.h"
  
  #include "i18n.h"
  
@@@ -62,7 -56,6 +63,7 @@@ using std::list
  using std::cout;
  using boost::shared_ptr;
  using boost::weak_ptr;
 +using boost::dynamic_pointer_cast;
  
  int const Writer::_maximum_frames_in_memory = Config::instance()->num_local_encoding_threads() + 4;
  
@@@ -77,6 -70,7 +78,6 @@@ Writer::Writer (shared_ptr<const Film> 
        , _last_written_eyes (EYES_RIGHT)
        , _full_written (0)
        , _fake_written (0)
 -      , _repeat_written (0)
        , _pushed_to_disk (0)
  {
        /* Remove any old DCP */
        */
  
        if (_film->three_d ()) {
 -              _picture_asset.reset (new libdcp::StereoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
 +              _picture_mxf.reset (new dcp::StereoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
        } else {
 -              _picture_asset.reset (new libdcp::MonoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
 +              _picture_mxf.reset (new dcp::MonoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
        }
  
 -      _picture_asset->set_edit_rate (_film->video_frame_rate ());
 -      _picture_asset->set_size (_film->frame_size ());
 -      _picture_asset->set_interop (_film->interop ());
 +      _picture_mxf->set_size (_film->frame_size ());
  
        if (_film->encrypted ()) {
 -              _picture_asset->set_key (_film->key ());
 +              _picture_mxf->set_key (_film->key ());
        }
        
 -      _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
 +      _picture_mxf_writer = _picture_mxf->start_write (
 +              _film->internal_video_mxf_dir() / _film->internal_video_mxf_filename(),
 +              _film->interop() ? dcp::INTEROP : dcp::SMPTE,
 +              _first_nonexistant_frame > 0
 +              );
  
        if (_film->audio_channels ()) {
 -              _sound_asset.reset (new libdcp::SoundAsset (_film->directory (), _film->audio_mxf_filename ()));
 -              _sound_asset->set_edit_rate (_film->video_frame_rate ());
 -              _sound_asset->set_channels (_film->audio_channels ());
 -              _sound_asset->set_sampling_rate (_film->audio_frame_rate ());
 -              _sound_asset->set_interop (_film->interop ());
 +              _sound_mxf.reset (new dcp::SoundMXF (dcp::Fraction (_film->video_frame_rate(), 1), _film->audio_frame_rate (), _film->audio_channels ()));
  
                if (_film->encrypted ()) {
 -                      _sound_asset->set_key (_film->key ());
 +                      _sound_mxf->set_key (_film->key ());
                }
 -              
 -              /* Write the sound asset into the film directory so that we leave the creation
 +      
 +              /* Write the sound MXF into the film directory so that we leave the creation
                   of the DCP directory until the last minute.
                */
 -              _sound_asset_writer = _sound_asset->start_write ();
 +              _sound_mxf_writer = _sound_mxf->start_write (_film->directory() / _film->audio_mxf_filename(), _film->interop() ? dcp::INTEROP : dcp::SMPTE);
 +      }
 +
 +      /* Check that the signer is OK if we need one */
 +      if (_film->is_signed() && !Config::instance()->signer()->valid ()) {
 +              throw InvalidSignerError ();
        }
  
        _thread = new boost::thread (boost::bind (&Writer::thread, this));
@@@ -183,7 -174,7 +184,7 @@@ Writer::fake_write (int frame, Eyes eye
        }
        
        FILE* ifi = fopen_boost (_film->info_path (frame, eyes), "r");
 -      libdcp::FrameInfo info (ifi);
 +      dcp::FrameInfo info (ifi);
        fclose (ifi);
        
        QueueItem qi;
  void
  Writer::write (shared_ptr<const AudioBuffers> audio)
  {
 -      if (_sound_asset) {
 -              _sound_asset_writer->write (audio->data(), audio->frames());
 +      if (_sound_mxf_writer) {
 +              _sound_mxf_writer->write (audio->data(), audio->frames());
        }
  }
  
@@@ -285,7 -276,7 +286,7 @@@ tr
                                        qi.encoded.reset (new EncodedData (_film->j2c_path (qi.frame, qi.eyes, false)));
                                }
  
 -                              libdcp::FrameInfo fin = _picture_asset_writer->write (qi.encoded->data(), qi.encoded->size());
 +                              dcp::FrameInfo fin = _picture_mxf_writer->write (qi.encoded->data(), qi.encoded->size());
                                qi.encoded->write_info (_film, qi.frame, qi.eyes, fin);
                                _last_written[qi.eyes] = qi.encoded;
                                ++_full_written;
                        }
                        case QueueItem::FAKE:
                                LOG_GENERAL (N_("Writer FAKE-writes %1 to MXF"), qi.frame);
 -                              _picture_asset_writer->fake_write (qi.size);
 +                              _picture_mxf_writer->fake_write (qi.size);
                                _last_written[qi.eyes].reset ();
                                ++_fake_written;
                                break;
 -                      case QueueItem::REPEAT:
 -                      {
 -                              LOG_GENERAL (N_("Writer REPEAT-writes %1 to MXF"), qi.frame);
 -                              libdcp::FrameInfo fin = _picture_asset_writer->write (
 -                                      _last_written[qi.eyes]->data(),
 -                                      _last_written[qi.eyes]->size()
 -                                      );
 -                              
 -                              _last_written[qi.eyes]->write_info (_film, qi.frame, qi.eyes, fin);
 -                              ++_repeat_written;
 -                              break;
 -                      }
                        }
                        lock.lock ();
  
                        _last_written_frame = qi.frame;
                        _last_written_eyes = qi.eyes;
                        
 -                      if (_film->length()) {
 -                              shared_ptr<Job> job = _job.lock ();
 -                              assert (job);
 -                              int total = _film->time_to_video_frames (_film->length ());
 -                              if (_film->three_d ()) {
 -                                      /* _full_written and so on are incremented for each eye, so we need to double the total
 -                                         frames to get the correct progress.
 -                                      */
 -                                      total *= 2;
 -                              }
 -                              job->set_progress (float (_full_written + _fake_written + _repeat_written) / total);
 +                      shared_ptr<Job> job = _job.lock ();
 +                      assert (job);
 +                      int64_t total = _film->length().frames (_film->video_frame_rate ());
 +                      if (_film->three_d ()) {
 +                              /* _full_written and so on are incremented for each eye, so we need to double the total
 +                                 frames to get the correct progress.
 +                              */
 +                              total *= 2;
 +                      }
 +                      if (total) {
 +                              job->set_progress (float (_full_written + _fake_written) / total);
                        }
                }
  
@@@ -388,11 -391,15 +389,11 @@@ Writer::finish (
        
        terminate_thread (true);
  
 -      _picture_asset_writer->finalize ();
 -      if (_sound_asset_writer) {
 -              _sound_asset_writer->finalize ();
 +      _picture_mxf_writer->finalize ();
 +      if (_sound_mxf_writer) {
 +              _sound_mxf_writer->finalize ();
        }
        
 -      int const frames = _last_written_frame + 1;
 -
 -      _picture_asset->set_duration (frames);
 -
        /* Hard-link the video MXF into the DCP */
        boost::filesystem::path video_from;
        video_from /= _film->internal_video_mxf_dir();
                LOG_WARNING_NC ("Hard-link failed; fell back to copying");
        }
  
 -      /* And update the asset */
 -
 -      _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
 -      _picture_asset->set_file_name (_film->video_mxf_filename ());
 +      _picture_mxf->set_file (video_to);
  
        /* Move the audio MXF into the DCP */
  
 -      if (_sound_asset) {
 +      if (_sound_mxf) {
                boost::filesystem::path audio_to;
                audio_to /= _film->dir (_film->dcp_name ());
                audio_to /= _film->audio_mxf_filename ();
                                String::compose (_("could not move audio MXF into the DCP (%1)"), ec.value ()), _film->file (_film->audio_mxf_filename ())
                                );
                }
 -              
 -              _sound_asset->set_directory (_film->dir (_film->dcp_name ()));
 -              _sound_asset->set_duration (frames);
 +
 +              _sound_mxf->set_file (audio_to);
        }
 -      
 -      libdcp::DCP dcp (_film->dir (_film->dcp_name()));
  
 -      shared_ptr<libdcp::CPL> cpl (
 -              new libdcp::CPL (
 -                      _film->dir (_film->dcp_name()),
 +      dcp::DCP dcp (_film->dir (_film->dcp_name()));
 +
 +      shared_ptr<dcp::CPL> cpl (
 +              new dcp::CPL (
                        _film->dcp_name(),
 -                      _film->dcp_content_type()->libdcp_kind (),
 -                      frames,
 -                      _film->video_frame_rate ()
 +                      _film->dcp_content_type()->libdcp_kind ()
                        )
                );
        
 -      dcp.add_cpl (cpl);
 +      dcp.add (cpl);
 +
 +      shared_ptr<dcp::Reel> reel (new dcp::Reel ());
 +
 +      shared_ptr<dcp::MonoPictureMXF> mono = dynamic_pointer_cast<dcp::MonoPictureMXF> (_picture_mxf);
 +      if (mono) {
 +              reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelMonoPictureAsset (mono, 0)));
 +              dcp.add (mono);
 +      }
 +
 +      shared_ptr<dcp::StereoPictureMXF> stereo = dynamic_pointer_cast<dcp::StereoPictureMXF> (_picture_mxf);
 +      if (stereo) {
 +              reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelStereoPictureAsset (stereo, 0)));
 +              dcp.add (stereo);
 +      }
 +
 +      if (_sound_mxf) {
 +              reel->add (shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (_sound_mxf, 0)));
 +              dcp.add (_sound_mxf);
 +      }
  
 -      cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
 -                                                       _picture_asset,
 -                                                       _sound_asset,
 -                                                       shared_ptr<libdcp::SubtitleAsset> ()
 -                                                       )
 -                             ));
 +      if (_subtitle_content) {
 +              _subtitle_content->write_xml (_film->dir (_film->dcp_name ()) / _film->subtitle_xml_filename ());
 +              reel->add (shared_ptr<dcp::ReelSubtitleAsset> (
 +                                 new dcp::ReelSubtitleAsset (
 +                                         _subtitle_content,
 +                                         dcp::Fraction (_film->video_frame_rate(), 1),
 +                                         _picture_mxf->intrinsic_duration (),
 +                                         0
 +                                         )
 +                                 ));
 +              
 +              dcp.add (_subtitle_content);
 +      }
 +      
 +      cpl->add (reel);
  
        shared_ptr<Job> job = _job.lock ();
        assert (job);
  
        job->sub (_("Computing image digest"));
 -      _picture_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
 +      _picture_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
  
 -      if (_sound_asset) {
 +      if (_sound_mxf) {
                job->sub (_("Computing audio digest"));
 -              _sound_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
 +              _sound_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
        }
  
-       dcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
 -      libdcp::XMLMetadata meta;
++      dcp::XMLMetadata meta;
+       meta.issuer = Config::instance()->dcp_issuer ();
+       meta.creator = String::compose ("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
        meta.set_issue_date_now ();
 -      dcp.write_xml (_film->interop (), meta, _film->is_signed() ? make_signer () : shared_ptr<const libdcp::Signer> ());
 -
 -      LOG_GENERAL (
 -              N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
 -              );
 -}
  
 -/** Tell the writer that frame `f' should be a repeat of the frame before it */
 -void
 -Writer::repeat (int f, Eyes e)
 -{
 -      boost::mutex::scoped_lock lock (_mutex);
 -
 -      while (_queued_full_in_memory > _maximum_frames_in_memory) {
 -              /* The queue is too big; wait until that is sorted out */
 -              _full_condition.wait (lock);
 -      }
 -      
 -      QueueItem qi;
 -      qi.type = QueueItem::REPEAT;
 -      qi.frame = f;
 -      if (_film->three_d() && e == EYES_BOTH) {
 -              qi.eyes = EYES_LEFT;
 -              _queue.push_back (qi);
 -              qi.eyes = EYES_RIGHT;
 -              _queue.push_back (qi);
 -      } else {
 -              qi.eyes = e;
 -              _queue.push_back (qi);
 +      shared_ptr<const dcp::Signer> signer;
 +      if (_film->is_signed ()) {
 +              signer = Config::instance()->signer ();
 +              /* We did check earlier, but check again here to be on the safe side */
 +              if (!signer->valid ()) {
 +                      throw InvalidSignerError ();
 +              }
        }
  
 -      /* Now there's something to do: wake anything wait()ing on _empty_condition */
 -      _empty_condition.notify_all ();
 +      dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, signer);
 +
 +      LOG_GENERAL (
 +              N_("Wrote %1 FULL, %2 FAKE, %3 pushed to disk"), _full_written, _fake_written, _pushed_to_disk
 +              );
  }
  
  bool
@@@ -515,7 -521,7 +518,7 @@@ Writer::check_existing_picture_mxf_fram
                return false;
        }
        
 -      libdcp::FrameInfo info (ifi);
 +      dcp::FrameInfo info (ifi);
        fclose (ifi);
        if (info.size == 0) {
                LOG_GENERAL ("Existing frame %1 has no info file", f);
@@@ -600,24 -606,6 +603,24 @@@ Writer::can_fake_write (int frame) cons
        return (frame != 0 && frame < _first_nonexistant_frame);
  }
  
 +void
 +Writer::write (PlayerSubtitles subs)
 +{
 +      if (subs.text.empty ()) {
 +              return;
 +      }
 +      
 +      if (!_subtitle_content) {
 +              _subtitle_content.reset (
 +                      new dcp::SubtitleContent (_film->name(), _film->isdcf_metadata().subtitle_language)
 +                      );
 +      }
 +      
 +      for (list<dcp::SubtitleString>::const_iterator i = subs.text.begin(); i != subs.text.end(); ++i) {
 +              _subtitle_content->add (*i);
 +      }
 +}
 +
  bool
  operator< (QueueItem const & a, QueueItem const & b)
  {
diff --combined src/wx/about_dialog.cc
index 349581972525889c8110f7656f7d9f1ec85301c0,a620b77e7aed739af1350a633926cd962ceb19eb..b62683c8f81bff6e5018362aab85cf4e370ea0b3
@@@ -1,5 -1,5 +1,5 @@@
  /*
 -    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
 +    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
  
      This program is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
  
  */
  
 +/** @file  src/wx/about_dialog.cc
 + *  @brief The "about DCP-o-matic" dialogue box.
 + */
 +
  #include <wx/notebook.h>
  #include <wx/hyperlink.h>
  #include "lib/version.h"
@@@ -149,6 -145,7 +149,7 @@@ AboutDialog::AboutDialog (wxWindow* par
        supported_by.Add (wxT ("Tim O'Brien"));
        supported_by.Add (wxT ("Ivan Pullman"));
        supported_by.Add (wxT ("Mark Rolfe"));
+       supported_by.Add (wxT ("David Rosenthal"));
        supported_by.Add (wxT ("Andrä Steiner"));
        supported_by.Add (wxT ("Jussi Siponen"));
        supported_by.Add (wxT ("Lasse Salling"));
        SetSizerAndFit (overall_sizer);
  }
  
 +/** Add a section of credits.
 + *  @param name Name of section.
 + *  @param credits List of names.
 + */
  void
  AboutDialog::add_section (wxString name, wxArrayString credits)
  {
diff --combined src/wx/config_dialog.cc
index c1eee3c7b1cec0026370d61072f73f3fa5db543a,463b77e97e82d6d6f0d33c236e21eb8cfec8696c..009467afa0fbfdbc7aceaf1eb0de191323a59be9
  #include <wx/preferences.h>
  #include <wx/filepicker.h>
  #include <wx/spinctrl.h>
 -#include <libdcp/colour_matrix.h>
 +#include <dcp/colour_matrix.h>
 +#include <dcp/exceptions.h>
 +#include <dcp/signer.h>
  #include "lib/config.h"
  #include "lib/ratio.h"
  #include "lib/scaler.h"
  #include "lib/filter.h"
  #include "lib/dcp_content_type.h"
  #include "lib/colour_conversion.h"
 +#include "lib/log.h"
 +#include "lib/util.h"
 +#include "lib/cross.h"
 +#include "lib/exceptions.h"
  #include "config_dialog.h"
  #include "wx_util.h"
  #include "editable_list.h"
@@@ -112,6 -106,7 +112,6 @@@ public
                _num_local_encoding_threads = new wxSpinCtrl (panel);
                table->Add (_num_local_encoding_threads, 1);
  
 -              
                _check_for_updates = new wxCheckBox (panel, wxID_ANY, _("Check for updates on startup"));
                table->Add (_check_for_updates, 1, wxEXPAND | wxALL);
                table->AddSpacer (0);
@@@ -306,10 -301,6 +306,6 @@@ public
                _issuer = new wxTextCtrl (panel, wxID_ANY);
                table->Add (_issuer, 1, wxEXPAND);
  
-               add_label_to_sizer (table, panel, _("Default creator"), true);
-               _creator = new wxTextCtrl (panel, wxID_ANY);
-               table->Add (_creator, 1, wxEXPAND);
-               
                Config* config = Config::instance ();
                
                _still_length->SetRange (1, 3600);
                _audio_delay->SetValue (config->default_audio_delay ());
                _audio_delay->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::audio_delay_changed, this));
  
-               _issuer->SetValue (std_to_wx (config->dcp_metadata().issuer));
+               _issuer->SetValue (std_to_wx (config->dcp_issuer ()));
                _issuer->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DefaultsPage::issuer_changed, this));
-               _creator->SetValue (std_to_wx (config->dcp_metadata().creator));
-               _creator->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DefaultsPage::creator_changed, this));
  
                return panel;
        }
@@@ -415,16 -404,7 +409,7 @@@ private
  
        void issuer_changed ()
        {
-               dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
-               m.issuer = wx_to_std (_issuer->GetValue ());
-               Config::instance()->set_dcp_metadata (m);
-       }
-       
-       void creator_changed ()
-       {
-               dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
-               m.creator = wx_to_std (_creator->GetValue ());
-               Config::instance()->set_dcp_metadata (m);
+               Config::instance()->set_dcp_issuer (wx_to_std (_issuer->GetValue ()));
        }
        
        wxSpinCtrl* _j2k_bandwidth;
        wxChoice* _container;
        wxChoice* _dcp_content_type;
        wxTextCtrl* _issuer;
-       wxTextCtrl* _creator;
  };
  
  class EncodingServersPage : public wxPreferencesPage, public Page
@@@ -551,310 -530,6 +535,310 @@@ private
        }
  };
  
 +class KeysPage : public wxPreferencesPage, public Page
 +{
 +public:
 +      KeysPage (wxSize panel_size, int border)
 +              : Page (panel_size, border)
 +      {}
 +
 +      wxString GetName () const
 +      {
 +              return _("Keys");
 +      }
 +
 +#ifdef DCPOMATIC_OSX
 +      wxBitmap GetLargeIcon () const
 +      {
 +              return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
 +      }
 +#endif        
 +
 +      wxWindow* CreateWindow (wxWindow* parent)
 +      {
 +              _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
 +              wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
 +              _panel->SetSizer (overall_sizer);
 +
 +              wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Certificate chain for signing DCPs and KDMs:"));
 +              overall_sizer->Add (m, 0, wxALL, _border);
 +              
 +              wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
 +              overall_sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, _border);
 +              
 +              _certificates = new wxListCtrl (_panel, wxID_ANY, wxDefaultPosition, wxSize (400, 200), wxLC_REPORT | wxLC_SINGLE_SEL);
 +
 +              {
 +                      wxListItem ip;
 +                      ip.SetId (0);
 +                      ip.SetText (_("Type"));
 +                      ip.SetWidth (100);
 +                      _certificates->InsertColumn (0, ip);
 +              }
 +
 +              {
 +                      wxListItem ip;
 +                      ip.SetId (1);
 +                      ip.SetText (_("Thumbprint"));
 +                      ip.SetWidth (300);
 +
 +                      wxFont font = ip.GetFont ();
 +                      font.SetFamily (wxFONTFAMILY_TELETYPE);
 +                      ip.SetFont (font);
 +                      
 +                      _certificates->InsertColumn (1, ip);
 +              }
 +
 +              certificates_sizer->Add (_certificates, 1, wxEXPAND);
 +
 +              {
 +                      wxSizer* s = new wxBoxSizer (wxVERTICAL);
 +                      _add_certificate = new wxButton (_panel, wxID_ANY, _("Add..."));
 +                      s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
 +                      _remove_certificate = new wxButton (_panel, wxID_ANY, _("Remove"));
 +                      s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
 +                      certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
 +              }
 +
 +              wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
 +              table->AddGrowableCol (1, 1);
 +              overall_sizer->Add (table, 1, wxALL | wxEXPAND, _border);
 +
 +              add_label_to_sizer (table, _panel, _("Private key for leaf certificate"), true);
 +              {
 +                      wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 +                      _signer_private_key = new wxStaticText (_panel, wxID_ANY, wxT (""));
 +                      wxFont font = _signer_private_key->GetFont ();
 +                      font.SetFamily (wxFONTFAMILY_TELETYPE);
 +                      _signer_private_key->SetFont (font);
 +                      s->Add (_signer_private_key, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
 +                      _load_signer_private_key = new wxButton (_panel, wxID_ANY, _("Load..."));
 +                      s->Add (_load_signer_private_key, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
 +                      table->Add (s, 0);
 +              }
 +
 +              add_label_to_sizer (table, _panel, _("Certificate for decrypting DCPs"), true);
 +              {
 +                      wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 +                      _decryption_certificate = new wxStaticText (_panel, wxID_ANY, wxT (""));
 +                      wxFont font = _decryption_certificate->GetFont ();
 +                      font.SetFamily (wxFONTFAMILY_TELETYPE);
 +                      _decryption_certificate->SetFont (font);
 +                      s->Add (_decryption_certificate, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
 +                      _load_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Load..."));
 +                      s->Add (_load_decryption_certificate, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
 +                      table->Add (s, 0);
 +              }
 +
 +              add_label_to_sizer (table, _panel, _("Private key for decrypting DCPs"), true);
 +              {
 +                      wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 +                      _decryption_private_key = new wxStaticText (_panel, wxID_ANY, wxT (""));
 +                      wxFont font = _decryption_private_key->GetFont ();
 +                      font.SetFamily (wxFONTFAMILY_TELETYPE);
 +                      _decryption_private_key->SetFont (font);
 +                      s->Add (_decryption_private_key, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
 +                      _load_decryption_private_key = new wxButton (_panel, wxID_ANY, _("Load..."));
 +                      s->Add (_load_decryption_private_key, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
 +                      table->Add (s, 0);
 +              }
 +
 +              _export_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Export DCP decryption certificate..."));
 +              table->Add (_export_decryption_certificate);
 +              table->AddSpacer (0);
 +              
 +              _add_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::add_certificate, this));
 +              _remove_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::remove_certificate, this));
 +              _certificates->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&KeysPage::update_sensitivity, this));
 +              _certificates->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&KeysPage::update_sensitivity, this));
 +              _load_signer_private_key->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_signer_private_key, this));
 +              _load_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_decryption_certificate, this));
 +              _load_decryption_private_key->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_decryption_private_key, this));
 +              _export_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::export_decryption_certificate, this));
 +
 +              _signer.reset (new dcp::Signer (*Config::instance()->signer().get ()));
 +
 +              update_certificate_list ();
 +              update_signer_private_key ();
 +              update_decryption_certificate ();
 +              update_decryption_private_key ();
 +              update_sensitivity ();
 +
 +              return _panel;
 +      }
 +
 +private:
 +      void add_certificate ()
 +      {
 +              wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File"));
 +              
 +              if (d->ShowModal() == wxID_OK) {
 +                      try {
 +                              dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
 +                              _signer->certificates().add (c);
 +                              Config::instance()->set_signer (_signer);
 +                              update_certificate_list ();
 +                      } catch (dcp::MiscError& e) {
 +                              error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
 +                      }
 +              }
 +              
 +              d->Destroy ();
 +
 +              update_sensitivity ();
 +      }
 +
 +      void remove_certificate ()
 +      {
 +              int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
 +              if (i == -1) {
 +                      return;
 +              }
 +              
 +              _certificates->DeleteItem (i);
 +              _signer->certificates().remove (i);
 +              Config::instance()->set_signer (_signer);
 +
 +              update_sensitivity ();
 +      }
 +
 +      void update_certificate_list ()
 +      {
 +              _certificates->DeleteAllItems ();
 +              dcp::CertificateChain::List certs = _signer->certificates().root_to_leaf ();
 +              size_t n = 0;
 +              for (dcp::CertificateChain::List::const_iterator i = certs.begin(); i != certs.end(); ++i) {
 +                      wxListItem item;
 +                      item.SetId (n);
 +                      _certificates->InsertItem (item);
 +                      _certificates->SetItem (n, 1, std_to_wx (i->thumbprint ()));
 +
 +                      if (n == 0) {
 +                              _certificates->SetItem (n, 0, _("Root"));
 +                      } else if (n == (certs.size() - 1)) {
 +                              _certificates->SetItem (n, 0, _("Leaf"));
 +                      } else {
 +                              _certificates->SetItem (n, 0, _("Intermediate"));
 +                      }
 +
 +                      ++n;
 +              }
 +      }
 +
 +      void update_sensitivity ()
 +      {
 +              _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
 +      }
 +
 +      void update_signer_private_key ()
 +      {
 +              _signer_private_key->SetLabel (std_to_wx (dcp::private_key_fingerprint (_signer->key ())));
 +      }       
 +
 +      void load_signer_private_key ()
 +      {
 +              wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File"));
 +
 +              if (d->ShowModal() == wxID_OK) {
 +                      try {
 +                              boost::filesystem::path p (wx_to_std (d->GetPath ()));
 +                              if (boost::filesystem::file_size (p) > 1024) {
 +                                      error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), std_to_wx (p.string ())));
 +                                      return;
 +                              }
 +                              
 +                              _signer->set_key (dcp::file_to_string (p));
 +                              Config::instance()->set_signer (_signer);
 +                              update_signer_private_key ();
 +                      } catch (dcp::MiscError& e) {
 +                              error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
 +                      }
 +              }
 +              
 +              d->Destroy ();
 +
 +              update_sensitivity ();
 +
 +      }
 +
 +      void load_decryption_certificate ()
 +      {
 +              wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File"));
 +              
 +              if (d->ShowModal() == wxID_OK) {
 +                      try {
 +                              dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
 +                              Config::instance()->set_decryption_certificate (c);
 +                              update_decryption_certificate ();
 +                      } catch (dcp::MiscError& e) {
 +                              error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
 +                      }
 +              }
 +              
 +              d->Destroy ();
 +      }
 +
 +      void update_decryption_certificate ()
 +      {
 +              _decryption_certificate->SetLabel (std_to_wx (Config::instance()->decryption_certificate().thumbprint ()));
 +      }
 +
 +      void load_decryption_private_key ()
 +      {
 +              wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File"));
 +
 +              if (d->ShowModal() == wxID_OK) {
 +                      try {
 +                              boost::filesystem::path p (wx_to_std (d->GetPath ()));
 +                              Config::instance()->set_decryption_private_key (dcp::file_to_string (p));
 +                              update_decryption_private_key ();
 +                      } catch (dcp::MiscError& e) {
 +                              error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), e.what ()));
 +                      }
 +              }
 +              
 +              d->Destroy ();
 +      }
 +
 +      void update_decryption_private_key ()
 +      {
 +              _decryption_private_key->SetLabel (std_to_wx (dcp::private_key_fingerprint (Config::instance()->decryption_private_key())));
 +      }
 +
 +      void export_decryption_certificate ()
 +      {
 +              wxFileDialog* d = new wxFileDialog (
 +                      _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
 +                      wxFD_SAVE | wxFD_OVERWRITE_PROMPT
 +                      );
 +              
 +              if (d->ShowModal () == wxID_OK) {
 +                      FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
 +                      if (!f) {
 +                              throw OpenFileError (wx_to_std (d->GetPath ()));
 +                      }
 +
 +                      string const s = Config::instance()->decryption_certificate().certificate (true);
 +                      fwrite (s.c_str(), 1, s.length(), f);
 +                      fclose (f);
 +              }
 +              d->Destroy ();
 +      }
 +
 +      wxPanel* _panel;
 +      wxListCtrl* _certificates;
 +      wxButton* _add_certificate;
 +      wxButton* _remove_certificate;
 +      wxStaticText* _signer_private_key;
 +      wxButton* _load_signer_private_key;
 +      wxStaticText* _decryption_certificate;
 +      wxButton* _load_decryption_certificate;
 +      wxStaticText* _decryption_private_key;
 +      wxButton* _load_decryption_private_key;
 +      wxButton* _export_decryption_certificate;
 +      shared_ptr<dcp::Signer> _signer;
 +};
 +
  class TMSPage : public wxPreferencesPage, public Page
  {
  public:
@@@ -1097,9 -772,6 +1081,9 @@@ private
        wxButton* _reset_kdm_email;
  };
  
 +/** @class AdvancedPage
 + *  @brief Advanced page of the preferences dialog.
 + */
  class AdvancedPage : public wxStockPreferencesPage, public Page
  {
  public:
@@@ -1224,7 -896,6 +1208,7 @@@ create_config_dialog (
        e->AddPage (new DefaultsPage (ps, border));
        e->AddPage (new EncodingServersPage (ps, border));
        e->AddPage (new ColourConversionsPage (ps, border));
 +      e->AddPage (new KeysPage (ps, border));
        e->AddPage (new TMSPage (ps, border));
        e->AddPage (new KDMEmailPage (ps, border));
        e->AddPage (new AdvancedPage (ps, border));
diff --combined src/wx/video_panel.cc
index cd831baed7cbc5b97324d97a1a603773d0ee0614,b33a97591c6617e45fe3a2da2a04bd1fca0fbe24..a5d197c2a15324e81b32e557ed8d45ad0a889931
@@@ -28,9 -28,9 +28,9 @@@
  #include "filter_dialog.h"
  #include "video_panel.h"
  #include "wx_util.h"
 -#include "film_editor.h"
  #include "content_colour_conversion_dialog.h"
  #include "content_widget.h"
 +#include "content_panel.h"
  
  using std::vector;
  using std::string;
@@@ -64,8 -64,8 +64,8 @@@ scale_to_index (VideoContentScale scale
        assert (false);
  }
  
 -VideoPanel::VideoPanel (FilmEditor* e)
 -      : FilmEditorPanel (e, _("Video"))
 +VideoPanel::VideoPanel (ContentPanel* p)
 +      : ContentSubPanel (p, _("Video"))
  {
        wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        _sizer->Add (grid, 0, wxALL, 8);
@@@ -222,7 -222,7 +222,7 @@@ VideoPanel::film_changed (Film::Propert
  void
  VideoPanel::film_content_changed (int property)
  {
 -      VideoContentList vc = _editor->selected_video_content ();
 +      VideoContentList vc = _parent->selected_video ();
        shared_ptr<VideoContent> vcs;
        shared_ptr<FFmpegContent> fcs;
        if (!vc.empty ()) {
  void
  VideoPanel::edit_filters_clicked ()
  {
 -      FFmpegContentList c = _editor->selected_ffmpeg_content ();
 +      FFmpegContentList c = _parent->selected_ffmpeg ();
        if (c.size() != 1) {
                return;
        }
  void
  VideoPanel::setup_description ()
  {
 -      VideoContentList vc = _editor->selected_video_content ();
 +      VideoContentList vc = _parent->selected_video ();
        if (vc.empty ()) {
                _description->SetLabel ("");
                return;
        }
  
        Crop const crop = vcs->crop ();
 -      if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != libdcp::Size (0, 0)) {
 -              libdcp::Size cropped = vcs->video_size_after_crop ();
 +      if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != dcp::Size (0, 0)) {
 +              dcp::Size cropped = vcs->video_size_after_crop ();
                d << wxString::Format (
                        _("Cropped to %dx%d (%.2f:1)\n"),
                        cropped.width, cropped.height,
                ++lines;
        }
  
 -      libdcp::Size const container_size = _editor->film()->frame_size ();
 -      libdcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
 +      dcp::Size const container_size = _parent->film()->frame_size ();
 +      dcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size, 1);
  
        if (scaled != vcs->video_size_after_crop ()) {
                d << wxString::Format (
  
        d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ());
        ++lines;
 -      FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
 +      FrameRateChange frc (vcs->video_frame_rate(), _parent->film()->video_frame_rate ());
        d << std_to_wx (frc.description ()) << "\n";
        ++lines;
  
  void
  VideoPanel::edit_colour_conversion_clicked ()
  {
 -      VideoContentList vc = _editor->selected_video_content ();
 +      VideoContentList vc = _parent->selected_video ();
        if (vc.size() != 1) {
                return;
        }
  void
  VideoPanel::content_selection_changed ()
  {
-       VideoContentList sel = _parent->selected_video ();
-       bool const single = sel.size() == 1;
-       _left_crop->set_content (sel);
-       _right_crop->set_content (sel);
-       _top_crop->set_content (sel);
-       _bottom_crop->set_content (sel);
-       _frame_type->set_content (sel);
-       _scale->set_content (sel);
-       /* Things that are only allowed with single selections */
-       _filters_button->Enable (single);
 -      VideoContentList video_sel = _editor->selected_video_content ();
 -      FFmpegContentList ffmpeg_sel = _editor->selected_ffmpeg_content ();
++      VideoContentList video_sel = _parent->selected_video ();
++      FFmpegContentList ffmpeg_sel = _parent->selected_ffmpeg ();
+       
+       bool const single = video_sel.size() == 1;
+       _left_crop->set_content (video_sel);
+       _right_crop->set_content (video_sel);
+       _top_crop->set_content (video_sel);
+       _bottom_crop->set_content (video_sel);
+       _frame_type->set_content (video_sel);
+       _scale->set_content (video_sel);
+       _filters_button->Enable (single && !ffmpeg_sel.empty ());
        _colour_conversion_button->Enable (single);
  
        film_content_changed (VideoContentProperty::VIDEO_CROP);