Merge master into direct-mxf.
authorCarl Hetherington <cth@carlh.net>
Sat, 26 Jan 2013 22:51:54 +0000 (22:51 +0000)
committerCarl Hetherington <cth@carlh.net>
Sat, 26 Jan 2013 22:51:54 +0000 (22:51 +0000)
1  2 
src/lib/config.cc
src/lib/config.h
src/lib/film.cc
src/lib/film.h
src/lib/format.cc
src/lib/subtitle.cc
src/lib/wscript
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/film_viewer.cc

diff --combined src/lib/config.cc
index 307b9684414359ffb823a937f3febc515711db84,c4659eecf6d66722c00ca4e5fbb467a1ad01d23a..c165859b01bd25b3d7e1d768c86f8bca71af8c85
@@@ -44,10 -44,6 +44,10 @@@ Config::Config (
        , _tms_path (".")
        , _sound_processor (SoundProcessor::from_id ("dolby_cp750"))
  {
 +      _allowed_dcp_frame_rates.push_back (24);
 +      _allowed_dcp_frame_rates.push_back (25);
 +      _allowed_dcp_frame_rates.push_back (30);
 +      
        ifstream f (file().c_str ());
        string line;
        while (getline (f, line)) {
@@@ -90,6 -86,8 +90,8 @@@
                } else if (k == "sound_processor") {
                        _sound_processor = SoundProcessor::from_id (v);
                }
+               _default_dci_metadata.read (k, v);
        }
  }
  
@@@ -136,7 -134,9 +138,9 @@@ Config::write () cons
        f << "tms_path " << _tms_path << "\n";
        f << "tms_user " << _tms_user << "\n";
        f << "tms_password " << _tms_password << "\n";
-       f << "sound_processor " << _sound_processor->id ();
+       f << "sound_processor " << _sound_processor->id () << "\n";
+       _default_dci_metadata.write (f);
  }
  
  string
diff --combined src/lib/config.h
index c41437efbe290f6aacad37ec5387fd21dc4d99db,98cbf67e576b3da04bdb648496ce154bc1d81177..fed297ad00f91018029f3b51b214bbf3b46c3bfd
@@@ -27,6 -27,7 +27,7 @@@
  #include <vector>
  #include <boost/shared_ptr.hpp>
  #include <boost/signals2.hpp>
+ #include "dci_metadata.h"
  
  class ServerDescription;
  class Scaler;
@@@ -94,10 -95,10 +95,14 @@@ public
                return _sound_processor;
        }
  
 +      std::list<int> allowed_dcp_frame_rates () const {
 +              return _allowed_dcp_frame_rates;
 +      }
 +      
+       DCIMetadata default_dci_metadata () const {
+               return _default_dci_metadata;
+       }
        /** @param n New number of local encoding threads */
        void set_num_local_encoding_threads (int n) {
                _num_local_encoding_threads = n;
                _tms_password = p;
        }
  
 +      void set_allowed_dcp_frame_rates (std::list<int> const & r) {
 +              _allowed_dcp_frame_rates = r;
 +      }
 +
+       void set_default_dci_metadata (DCIMetadata d) {
+               _default_dci_metadata = d;
+       }
+       
        void write () const;
  
        static Config* instance ();
@@@ -180,7 -181,8 +189,9 @@@ private
        std::string _tms_password;
        /** Our sound processor */
        SoundProcessor const * _sound_processor;
 +      std::list<int> _allowed_dcp_frame_rates;
+       /** Default DCI metadata for newly-created Films */
+       DCIMetadata _default_dci_metadata;
  
        /** Singleton instance, or 0 */
        static Config* _instance;
diff --combined src/lib/film.cc
index f6eb032fd7e7b0c4f98df7ea969d4140ec72b32c,f5522b74a1add591d2829cc2e00b4c0e5176d09a..bdc4cca734d7075fc58bfaa52b46c2046f368bdd
@@@ -39,6 -39,7 +39,6 @@@
  #include "ab_transcode_job.h"
  #include "transcode_job.h"
  #include "scp_dcp_job.h"
 -#include "make_dcp_job.h"
  #include "log.h"
  #include "options.h"
  #include "exceptions.h"
@@@ -46,6 -47,7 +46,6 @@@
  #include "scaler.h"
  #include "decoder_factory.h"
  #include "config.h"
 -#include "check_hashes_job.h"
  #include "version.h"
  #include "ui_signaller.h"
  #include "video_decoder.h"
@@@ -70,9 -72,8 +70,9 @@@ using boost::to_upper_copy
  using boost::ends_with;
  using boost::starts_with;
  using boost::optional;
 +using libdcp::Size;
  
 -int const Film::state_version = 1;
 +int const Film::state_version = 2;
  
  /** Construct a Film object in a given directory, reading any metadata
   *  file that exists in that directory.  An exception will be thrown if
@@@ -88,8 -89,8 +88,8 @@@ Film::Film (string d, bool must_exist
        , _dcp_content_type (0)
        , _format (0)
        , _scaler (Scaler::from_id ("bicubic"))
 -      , _dcp_trim_start (0)
 -      , _dcp_trim_end (0)
 +      , _trim_start (0)
 +      , _trim_end (0)
        , _dcp_ab (false)
        , _use_content_audio (true)
        , _audio_gain (0)
        , _subtitle_scale (1)
        , _colour_lut (0)
        , _j2k_bandwidth (200000000)
+       , _dci_metadata (Config::instance()->default_dci_metadata ())
        , _frames_per_second (0)
        , _dirty (false)
  {
@@@ -154,8 -156,8 +155,8 @@@ Film::Film (Film const & o
        , _crop              (o._crop)
        , _filters           (o._filters)
        , _scaler            (o._scaler)
 -      , _dcp_trim_start    (o._dcp_trim_start)
 -      , _dcp_trim_end      (o._dcp_trim_end)
 +      , _trim_start        (o._trim_start)
 +      , _trim_end          (o._trim_end)
        , _dcp_ab            (o._dcp_ab)
        , _content_audio_stream (o._content_audio_stream)
        , _external_audio    (o._external_audio)
        , _subtitle_scale    (o._subtitle_scale)
        , _colour_lut        (o._colour_lut)
        , _j2k_bandwidth     (o._j2k_bandwidth)
-       , _audio_language    (o._audio_language)
-       , _subtitle_language (o._subtitle_language)
-       , _territory         (o._territory)
-       , _rating            (o._rating)
-       , _studio            (o._studio)
-       , _facility          (o._facility)
-       , _package_type      (o._package_type)
+       , _dci_metadata      (o._dci_metadata)
        , _size              (o._size)
        , _length            (o._length)
 +      , _dcp_intrinsic_duration (o._dcp_intrinsic_duration)
        , _content_digest    (o._content_digest)
        , _content_audio_streams (o._content_audio_streams)
        , _external_audio_stream (o._external_audio_stream)
@@@ -193,14 -188,24 +188,14 @@@ Film::~Film (
  {
        delete _log;
  }
 -        
 -/** @return The path to the directory to write JPEG2000 files to */
 +
  string
 -Film::j2k_dir () const
 +Film::video_state_identifier () const
  {
 -      assert (format());
 -
 -      boost::filesystem::path p;
 -
 -      /* Start with j2c */
 -      p /= "j2c";
 +      assert (format ());
  
        pair<string, string> f = Filter::ffmpeg_strings (filters());
  
 -      /* Write stuff to specify the filter / post-processing settings that are in use,
 -         so that we don't get confused about J2K files generated using different
 -         settings.
 -      */
        stringstream s;
        s << format()->id()
          << "_" << content_digest()
          << "_" << j2k_bandwidth()
          << "_" << boost::lexical_cast<int> (colour_lut());
  
 -      p /= s.str ();
 -
 -      /* Similarly for the A/B case */
        if (dcp_ab()) {
 -              stringstream s;
                pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
                s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
 -              p /= s.str ();
        }
 -      
 +
 +      return s.str ();
 +}
 +        
 +/** @return The path to the directory to write video frame hash files to */
 +string
 +Film::hash_dir () const
 +{
 +      boost::filesystem::path p;
 +      p /= "hash";
 +      p /= video_state_identifier ();
        return dir (p.string());
  }
  
 +string
 +Film::video_mxf_dir () const
 +{
 +      boost::filesystem::path p;
 +      return dir ("video");
 +}
 +
 +string
 +Film::video_mxf_filename () const
 +{
 +      return video_state_identifier() + ".mxf";
 +}
 +
  /** Add suitable Jobs to the JobManager to create a DCP for this Film.
   *  @param true to transcode, false to use the WAV and J2K files that are already there.
   */
@@@ -262,9 -249,7 +257,9 @@@ Film::make_dcp (bool transcode
        }
        
        log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? "still" : "video")));
 -      log()->log (String::compose ("Content length %1", length().get()));
 +      if (length()) {
 +              log()->log (String::compose ("Content length %1", length().get()));
 +      }
        log()->log (String::compose ("Content digest %1", content_digest()));
        log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
        log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
                throw MissingSettingError ("name");
        }
  
 -      shared_ptr<EncodeOptions> oe (new EncodeOptions (j2k_dir(), ".j2c", dir ("wavs")));
 -      oe->out_size = format()->dcp_size ();
 -      oe->padding = format()->dcp_padding (shared_from_this ());
 -      if (dcp_length ()) {
 -              oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
 -              if (audio_stream()) {
 -                      oe->audio_range = make_pair (
 -
 -                              video_frames_to_audio_frames (
 -                                      oe->video_range.get().first,
 -                                      dcp_audio_sample_rate (audio_stream()->sample_rate()),
 -                                      dcp_frame_rate (frames_per_second()).frames_per_second
 -                                      ),
 -                              
 -                              video_frames_to_audio_frames (
 -                                      oe->video_range.get().second,
 -                                      dcp_audio_sample_rate (audio_stream()->sample_rate()),
 -                                      dcp_frame_rate (frames_per_second()).frames_per_second
 -                                      )
 -                              );
 -              }
 -                      
 -      }
 -      
 -      oe->video_skip = dcp_frame_rate (frames_per_second()).skip;
 -
 -      shared_ptr<DecodeOptions> od (new DecodeOptions);
 -      od->decode_subtitles = with_subtitles ();
 +      DecodeOptions od;
 +      od.decode_subtitles = with_subtitles ();
  
        shared_ptr<Job> r;
  
        if (transcode) {
                if (dcp_ab()) {
 -                      r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
 +                      r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, shared_ptr<Job> ())));
                } else {
 -                      r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
 +                      r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od, shared_ptr<Job> ())));
                }
        }
 -
 -      r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), od, oe, r)));
 -      JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), oe, r)));
  }
  
  /** Start a job to examine our content file */
@@@ -349,7 -363,7 +344,7 @@@ Film::encoded_frames () cons
        }
  
        int N = 0;
 -      for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (j2k_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
 +      for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (hash_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
                ++N;
                boost::this_thread::interruption_point ();
        }
@@@ -392,8 -406,8 +387,8 @@@ Film::write_metadata () cons
                f << "filter " << (*i)->id () << "\n";
        }
        f << "scaler " << _scaler->id () << "\n";
 -      f << "dcp_trim_start " << _dcp_trim_start << "\n";
 -      f << "dcp_trim_end " << _dcp_trim_end << "\n";
 +      f << "trim_start " << _trim_start << "\n";
 +      f << "trim_end " << _trim_end << "\n";
        f << "dcp_ab " << (_dcp_ab ? "1" : "0") << "\n";
        if (_content_audio_stream) {
                f << "selected_content_audio_stream " << _content_audio_stream->to_string() << "\n";
        f << "subtitle_scale " << _subtitle_scale << "\n";
        f << "colour_lut " << _colour_lut << "\n";
        f << "j2k_bandwidth " << _j2k_bandwidth << "\n";
-       f << "audio_language " << _audio_language << "\n";
-       f << "subtitle_language " << _subtitle_language << "\n";
-       f << "territory " << _territory << "\n";
-       f << "rating " << _rating << "\n";
-       f << "studio " << _studio << "\n";
-       f << "facility " << _facility << "\n";
-       f << "package_type " << _package_type << "\n";
+       _dci_metadata.write (f);
        f << "width " << _size.width << "\n";
        f << "height " << _size.height << "\n";
        f << "length " << _length.get_value_or(0) << "\n";
 +      f << "dcp_intrinsic_duration " << _dcp_intrinsic_duration.get_value_or(0) << "\n";
        f << "content_digest " << _content_digest << "\n";
  
        for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
@@@ -505,10 -511,10 +493,10 @@@ Film::read_metadata (
                        _filters.push_back (Filter::from_id (v));
                } else if (k == "scaler") {
                        _scaler = Scaler::from_id (v);
 -              } else if (k == "dcp_trim_start") {
 -                      _dcp_trim_start = atoi (v.c_str ());
 -              } else if (k == "dcp_trim_end") {
 -                      _dcp_trim_end = atoi (v.c_str ());
 +              } else if ( ((!version || version < 2) && k == "trim_start") || k == "trim_start") {
 +                      _trim_start = atoi (v.c_str ());
 +              } else if ( ((!version || version < 2) && k == "trim_end") || k == "trim_end") {
 +                      _trim_end = atoi (v.c_str ());
                } else if (k == "dcp_ab") {
                        _dcp_ab = (v == "1");
                } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
                        _colour_lut = atoi (v.c_str ());
                } else if (k == "j2k_bandwidth") {
                        _j2k_bandwidth = atoi (v.c_str ());
-               } else if (k == "audio_language") {
-                       _audio_language = v;
-               } else if (k == "subtitle_language") {
-                       _subtitle_language = v;
-               } else if (k == "territory") {
-                       _territory = v;
-               } else if (k == "rating") {
-                       _rating = v;
-               } else if (k == "studio") {
-                       _studio = v;
-               } else if (k == "facility") {
-                       _facility = v;
-               } else if (k == "package_type") {
-                       _package_type = v;
                }
+               _dci_metadata.read (k, v);
                
                /* Cached stuff */
                if (k == "width") {
                        if (vv) {
                                _length = vv;
                        }
 +              } else if (k == "dcp_intrinsic_duration") {
 +                      int const vv = atoi (v.c_str ());
 +                      if (vv) {
 +                              _dcp_intrinsic_duration = vv;
 +                      }
                } else if (k == "content_digest") {
                        _content_digest = v;
                } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
@@@ -685,25 -674,30 +661,25 @@@ Film::target_audio_sample_rate () cons
        /* Resample to a DCI-approved sample rate */
        double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
  
 -      DCPFrameRate dfr = dcp_frame_rate (frames_per_second ());
 +      DCPFrameRate dfr (frames_per_second ());
  
 -      /* Compensate for the fact that video will be rounded to the
 -         nearest integer number of frames per second.
 +      /* 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 (dfr.run_fast) {
 -              t *= _frames_per_second * dfr.skip / dfr.frames_per_second;
 +
 +      if (dfr.change_speed) {
 +              t *= _frames_per_second * dfr.factor() / dfr.frames_per_second;
        }
  
        return rint (t);
  }
  
 -boost::optional<int>
 -Film::dcp_length () const
 +int
 +Film::still_duration_in_frames () const
  {
 -      if (content_type() == STILL) {
 -              return _still_duration * frames_per_second();
 -      }
 -      
 -      if (!length()) {
 -              return boost::optional<int> ();
 -      }
 -
 -      return length().get() - dcp_trim_start() - dcp_trim_end();
 +      return still_duration() * frames_per_second();
  }
  
  /** @return a DCI-compliant name for a DCP of this film */
@@@ -734,10 -728,12 +710,12 @@@ Film::dci_name () cons
                d << format()->dci_name() << "_";
        }
  
-       if (!audio_language().empty ()) {
-               d << audio_language();
-               if (!subtitle_language().empty() && with_subtitles()) {
-                       d << "-" << subtitle_language();
+       DCIMetadata const dm = dci_metadata ();
+       if (!dm.audio_language.empty ()) {
+               d << dm.audio_language;
+               if (!dm.subtitle_language.empty() && with_subtitles()) {
+                       d << "-" << dm.subtitle_language;
                } else {
                        d << "-XX";
                }
                d << "_";
        }
  
-       if (!territory().empty ()) {
-               d << territory();
-               if (!rating().empty ()) {
-                       d << "-" << rating();
+       if (!dm.territory.empty ()) {
+               d << dm.territory;
+               if (!dm.rating.empty ()) {
+                       d << "-" << dm.rating;
                }
                d << "_";
        }
  
        d << "2K_";
  
-       if (!studio().empty ()) {
-               d << studio() << "_";
+       if (!dm.studio.empty ()) {
+               d << dm.studio << "_";
        }
  
        d << boost::gregorian::to_iso_string (_dci_date) << "_";
  
-       if (!facility().empty ()) {
-               d << facility() << "_";
+       if (!dm.facility.empty ()) {
+               d << dm.facility << "_";
        }
  
-       if (!package_type().empty ()) {
-               d << package_type();
+       if (!dm.package_type.empty ()) {
+               d << dm.package_type;
        }
  
        return d.str ();
@@@ -875,7 -871,8 +853,7 @@@ Film::set_content (string c
        */
  
        try {
 -              shared_ptr<DecodeOptions> o (new DecodeOptions);
 -              Decoders d = decoder_factory (shared_from_this(), o, 0);
 +              Decoders d = decoder_factory (shared_from_this(), DecodeOptions(), 0);
                
                set_size (d.video->native_size ());
                set_frames_per_second (d.video->frames_per_second ());
@@@ -1050,23 -1047,23 +1028,23 @@@ Film::set_scaler (Scaler const * s
  }
  
  void
 -Film::set_dcp_trim_start (int t)
 +Film::set_trim_start (int t)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _dcp_trim_start = t;
 +              _trim_start = t;
        }
 -      signal_changed (DCP_TRIM_START);
 +      signal_changed (TRIM_START);
  }
  
  void
 -Film::set_dcp_trim_end (int t)
 +Film::set_trim_end (int t)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _dcp_trim_end = t;
 +              _trim_end = t;
        }
 -      signal_changed (DCP_TRIM_END);
 +      signal_changed (TRIM_END);
  }
  
  void
@@@ -1097,7 -1094,8 +1075,7 @@@ Film::set_external_audio (vector<string
                _external_audio = a;
        }
  
 -      shared_ptr<DecodeOptions> o (new DecodeOptions);
 -      shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), o, 0));
 +      shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), DecodeOptions(), 0));
        if (decoder->audio_stream()) {
                _external_audio_stream = decoder->audio_stream ();
        }
@@@ -1207,71 -1205,11 +1185,11 @@@ Film::set_j2k_bandwidth (int b
  }
  
  void
- Film::set_audio_language (string l)
- {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _audio_language = l;
-       }
-       signal_changed (DCI_METADATA);
- }
- void
- Film::set_subtitle_language (string l)
- {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _subtitle_language = l;
-       }
-       signal_changed (DCI_METADATA);
- }
- void
- Film::set_territory (string t)
- {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _territory = t;
-       }
-       signal_changed (DCI_METADATA);
- }
- void
- Film::set_rating (string r)
- {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _rating = r;
-       }
-       signal_changed (DCI_METADATA);
- }
- void
- Film::set_studio (string s)
- {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _studio = s;
-       }
-       signal_changed (DCI_METADATA);
- }
- void
- Film::set_facility (string f)
- {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _facility = f;
-       }
-       signal_changed (DCI_METADATA);
- }
- void
- Film::set_package_type (string p)
+ Film::set_dci_metadata (DCIMetadata m)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _package_type = p;
+               _dci_metadata = m;
        }
        signal_changed (DCI_METADATA);
  }
@@@ -1304,17 -1242,7 +1222,17 @@@ Film::unset_length (
                _length = boost::none;
        }
        signal_changed (LENGTH);
 -}     
 +}
 +
 +void
 +Film::set_dcp_intrinsic_duration (int d)
 +{
 +      {
 +              boost::mutex::scoped_lock lm (_state_mutex);
 +              _dcp_intrinsic_duration = d;
 +      }
 +      signal_changed (DCP_INTRINSIC_DURATION);
 +}
  
  void
  Film::set_content_digest (string d)
@@@ -1395,38 -1323,3 +1313,38 @@@ Film::audio_stream () cons
  
        return _external_audio_stream;
  }
 +
 +string
 +Film::hash_path (int f) const
 +{
 +      boost::filesystem::path p;
 +      p /= hash_dir ();
 +
 +      stringstream s;
 +      s.width (8);
 +      s << setfill('0') << f << ".md5";
 +
 +      p /= s.str();
 +      return p.string ();
 +}
 +
 +string
 +Film::j2c_path (int f, bool t) const
 +{
 +      boost::filesystem::path p;
 +      p /= "j2c";
 +      p /= video_state_identifier ();
 +
 +      stringstream s;
 +      s.width (8);
 +      s << setfill('0') << f << ".j2c";
 +
 +      if (t) {
 +              s << ".tmp";
 +      }
 +
 +      p /= s.str();
 +      return p.string ();
 +}
 +
 +
diff --combined src/lib/film.h
index 07764dac89da6be57bbe009d49af6c02378cee05,af7ec670134bd9bdda2f32298ce9f5aa3a5c6cc0..7c4c72f7b2689835cc7963b02f80956995c34fc8
@@@ -38,6 -38,7 +38,7 @@@ extern "C" 
  #include "dcp_content_type.h"
  #include "util.h"
  #include "stream.h"
+ #include "dci_metadata.h"
  
  class Format;
  class Job;
@@@ -59,11 -60,7 +60,11 @@@ public
        Film (Film const &);
        ~Film ();
  
 -      std::string j2k_dir () const;
 +      std::string hash_dir () const;
 +      std::string j2c_path (int f, bool t) const;
 +      std::string hash_path (int f) const;
 +      std::string video_mxf_dir () const;
 +      std::string video_mxf_filename () const;
  
        void examine_content ();
        void send_dcp_to_tms ();
        void read_metadata ();
  
        libdcp::Size cropped_size (libdcp::Size) const;
 -      boost::optional<int> dcp_length () const;
        std::string dci_name () const;
        std::string dcp_name () const;
  
 +      boost::optional<int> dcp_intrinsic_duration () const {
 +              return _dcp_intrinsic_duration;
 +      }
 +
        /** @return true if our state has changed since we last saved it */
        bool dirty () const {
                return _dirty;
                CROP,
                FILTERS,
                SCALER,
 -              DCP_TRIM_START,
 -              DCP_TRIM_END,
 +              TRIM_START,
 +              TRIM_END,
                DCP_AB,
                CONTENT_AUDIO_STREAM,
                EXTERNAL_AUDIO,
                DCI_METADATA,
                SIZE,
                LENGTH,
 +              DCP_INTRINSIC_DURATION,
                CONTENT_AUDIO_STREAMS,
                SUBTITLE_STREAMS,
                FRAMES_PER_SECOND,
                return _scaler;
        }
  
 -      SourceFrame dcp_trim_start () const {
 +      int trim_start () const {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              return _dcp_trim_start;
 +              return _trim_start;
        }
  
 -      SourceFrame dcp_trim_end () const {
 +      int trim_end () const {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              return _dcp_trim_end;
 +              return _trim_end;
        }
  
        bool dcp_ab () const {
                return _still_duration;
        }
  
 +      int still_duration_in_frames () const;
 +
        boost::shared_ptr<SubtitleStream> subtitle_stream () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _subtitle_stream;
                return _j2k_bandwidth;
        }
  
-       std::string audio_language () const {
+       DCIMetadata dci_metadata () const {
                boost::mutex::scoped_lock lm (_state_mutex);
-               return _audio_language;
+               return _dci_metadata;
        }
        
-       std::string subtitle_language () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _subtitle_language;
-       }
-       
-       std::string territory () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _territory;
-       }
-       
-       std::string rating () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _rating;
-       }
-       
-       std::string studio () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _studio;
-       }
-       
-       std::string facility () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _facility;
-       }
-       
-       std::string package_type () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _package_type;
-       }
        libdcp::Size size () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _size;
        void set_bottom_crop (int);
        void set_filters (std::vector<Filter const *>);
        void set_scaler (Scaler const *);
 -      void set_dcp_trim_start (int);
 -      void set_dcp_trim_end (int);
 +      void set_trim_start (int);
 +      void set_trim_end (int);
        void set_dcp_ab (bool);
        void set_content_audio_stream (boost::shared_ptr<AudioStream>);
        void set_external_audio (std::vector<std::string>);
        void set_subtitle_scale (float);
        void set_colour_lut (int);
        void set_j2k_bandwidth (int);
-       void set_audio_language (std::string);
-       void set_subtitle_language (std::string);
-       void set_territory (std::string);
-       void set_rating (std::string);
-       void set_studio (std::string);
-       void set_facility (std::string);
-       void set_package_type (std::string);
+       void set_dci_metadata (DCIMetadata);
        void set_size (libdcp::Size);
        void set_length (SourceFrame);
        void unset_length ();
 +      void set_dcp_intrinsic_duration (int);
        void set_content_digest (std::string);
        void set_content_audio_streams (std::vector<boost::shared_ptr<AudioStream> >);
        void set_subtitle_streams (std::vector<boost::shared_ptr<SubtitleStream> >);
@@@ -413,7 -367,6 +378,7 @@@ private
  
        void signal_changed (Property);
        void examine_content_finished ();
 +      std::string video_state_identifier () const;
  
        /** Complete path to directory containing the film metadata;
         *  must not be relative.
        /** Scaler algorithm to use */
        Scaler const * _scaler;
        /** Frames to trim off the start of the DCP */
 -      int _dcp_trim_start;
 +      int _trim_start;
        /** Frames to trim off the end of the DCP */
 -      int _dcp_trim_end;
 +      int _trim_end;
        /** true to create an A/B comparison DCP, where the left half of the image
            is the video without any filters or post-processing, and the right half
            has the specified filters and post-processing.
        int _colour_lut;
        /** bandwidth for J2K files in bits per second */
        int _j2k_bandwidth;
-       
-       /* DCI naming stuff */
-       std::string _audio_language;
-       std::string _subtitle_language;
-       std::string _territory;
-       std::string _rating;
-       std::string _studio;
-       std::string _facility;
-       std::string _package_type;
+       /** DCI naming stuff */
+       DCIMetadata _dci_metadata;
  
        /* Data which are cached to speed things up */
  
 -      /** libdcp::Size, in pixels, of the source (ignoring cropping) */
 +      /** Size, in pixels, of the source (ignoring cropping) */
        libdcp::Size _size;
        /** The length of the source, in video frames (as far as we know) */
        boost::optional<SourceFrame> _length;
 +      boost::optional<int> _dcp_intrinsic_duration;
        /** MD5 digest of our content file */
        std::string _content_digest;
        /** The audio streams in our content */
diff --combined src/lib/format.cc
index 4583dd0e58602840c95cb5b1724c5ab557a3fb6a,6615e16e04c202732380dfcd56aae343d21e3c8c..016c21fdea09fb60d45636f91d2ee9b5d888879b
@@@ -35,7 -35,6 +35,7 @@@ using std::setprecision
  using std::stringstream;
  using std::vector;
  using boost::shared_ptr;
 +using libdcp::Size;
  
  vector<Format const *> Format::_formats;
  
@@@ -148,6 -147,9 +148,9 @@@ FixedFormat::FixedFormat (int r, libdcp
  
  }
  
+ /** @return Number of pixels (int the DCP image) to pad either side of the film
+  *  (so there are dcp_padding() pixels on the left and dcp_padding() on the right)
+  */
  int
  Format::dcp_padding (shared_ptr<const Film> f) const
  {
        return p;
  }
  
+ float
+ Format::container_ratio_as_float () const
+ {
+       return static_cast<float> (_dcp_size.width) / _dcp_size.height;
+ }
  VariableFormat::VariableFormat (libdcp::Size dcp, string id, string n, string d)
        : Format (dcp, id, n, d)
  {
diff --combined src/lib/subtitle.cc
index 3754e5acf012c9a8dfe921fab46bb02440f397ef,b4ac14285714b1e085a9fffcee5d80daf1906fc9..bd5f0c87969cc76b473c4af506c0c88e913ec235
@@@ -27,7 -27,6 +27,7 @@@
  
  using namespace std;
  using namespace boost;
 +using libdcp::Size;
  
  /** Construct a TimedSubtitle.  This is a subtitle image, position,
   *  and a range of time over which it should be shown.
@@@ -35,7 -34,7 +35,7 @@@
   */
  TimedSubtitle::TimedSubtitle (AVSubtitle const & sub)
  {
-       assert (sub.rects > 0);
+       assert (sub.num_rects > 0);
        
        /* Subtitle PTS in seconds (within the source, not taking into account any of the
           source that we may have chopped off for the DCP)
diff --combined src/lib/wscript
index 5d676f249f35c135dd2ba117b8c6d4b1d76a632a,cada2b0c30b683f1979af043ebf3dad96aea9a31..454565cdc447d55c386e64773c5ea48779fee939
@@@ -6,7 -6,7 +6,7 @@@ def build(bld)
  
      obj.name = 'libdvdomatic'
      obj.export_includes = ['.']
-     obj.uselib = 'AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE SNDFILE BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 OPENJPEG POSTPROC TIFF MAGICK SSH DCP GLIB'
+     obj.uselib = 'AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE SNDFILE BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 OPENJPEG POSTPROC TIFF MAGICK SSH DCP GLIB LZMA'
      if bld.env.TARGET_WINDOWS:
          obj.uselib += ' WINSOCK2'
      obj.source = """
                 ab_transcoder.cc
                   audio_decoder.cc
                   audio_source.cc
 -                 check_hashes_job.cc
                 config.cc
                   combiner.cc
                   cross.cc
+                dci_metadata.cc
                 dcp_content_type.cc
                 dcp_video_frame.cc
                   decoder.cc
@@@ -39,6 -41,7 +40,6 @@@
                 job_manager.cc
                 log.cc
                 lut.cc
 -               make_dcp_job.cc
                   matcher.cc
                   scp_dcp_job.cc
                 scaler.cc
@@@ -54,7 -57,6 +55,7 @@@
                 version.cc
                   video_decoder.cc
                   video_source.cc
 +                 writer.cc
                 """
  
      obj.target = 'dvdomatic'
diff --combined src/wx/film_editor.cc
index 4e99bcd960da73a6ce9fe99b40c065ab7c98baf4,f058afa54d21463f955a35e510bbbfd505735a50..f274416fb6225cfa4b3f42fd93b8b06b7e8c1d29
@@@ -43,7 -43,7 +43,7 @@@
  #include "film_editor.h"
  #include "gain_calculator_dialog.h"
  #include "sound_processor.h"
- #include "dci_name_dialog.h"
+ #include "dci_metadata_dialog.h"
  #include "scaler.h"
  
  using std::string;
@@@ -120,7 -120,7 +120,7 @@@ FilmEditor::make_film_panel (
        _film_sizer->AddSpacer (0);
  
        add_label_to_sizer (_film_sizer, _film_panel, "Content Type");
-       _dcp_content_type = new wxComboBox (_film_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
+       _dcp_content_type = new wxChoice (_film_panel, wxID_ANY);
        _film_sizer->Add (_dcp_content_type);
  
        video_control (add_label_to_sizer (_film_sizer, _film_panel, "Frames Per Second"));
                video_control (add_label_to_sizer (_film_sizer, _film_panel, "Trim frames"));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                video_control (add_label_to_sizer (s, _film_panel, "Start"));
 -              _dcp_trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
 -              s->Add (video_control (_dcp_trim_start));
 +              _trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
 +              s->Add (video_control (_trim_start));
                video_control (add_label_to_sizer (s, _film_panel, "End"));
 -              _dcp_trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
 -              s->Add (video_control (_dcp_trim_end));
 +              _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
 +              s->Add (video_control (_trim_end));
  
                _film_sizer->Add (s);
        }
@@@ -177,7 -177,7 +177,7 @@@ FilmEditor::connect_to_widgets (
        _name->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (FilmEditor::name_changed), 0, this);
        _use_dci_name->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this);
        _edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
-       _format->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this);
+       _format->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this);
        _content->Connect (wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::content_changed), 0, this);
        _trust_content_header->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::trust_content_header_changed), 0, this);
        _left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
        _top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
        _bottom_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this);
        _filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this);
-       _scaler->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this);
-       _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
+       _scaler->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this);
+       _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
        _dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this);
        _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this);
 -      _dcp_trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_start_changed), 0, this);
 -      _dcp_trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_end_changed), 0, this);
 +      _trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_start_changed), 0, this);
 +      _trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_end_changed), 0, this);
        _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
        _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this);
        _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
-       _colour_lut->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::colour_lut_changed), 0, this);
+       _colour_lut->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::colour_lut_changed), 0, this);
        _j2k_bandwidth->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::j2k_bandwidth_changed), 0, this);
-       _subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
-       _audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this);
+       _subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
+       _audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this);
        _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
        _audio_gain_calculate_button->Connect (
                wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this
@@@ -222,7 -222,7 +222,7 @@@ FilmEditor::make_video_panel (
        _video_panel->SetSizer (pad);
  
        add_label_to_sizer (_video_sizer, _video_panel, "Format");
-       _format = new wxComboBox (_video_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
+       _format = new wxChoice (_video_panel, wxID_ANY);
        _video_sizer->Add (_format);
  
        {
        }
  
        video_control (add_label_to_sizer (_video_sizer, _video_panel, "Scaler"));
-       _scaler = new wxComboBox (_video_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
+       _scaler = new wxChoice (_video_panel, wxID_ANY);
        _video_sizer->Add (video_control (_scaler), 1);
  
        vector<Scaler const *> const sc = Scaler::all ();
        }
  
        add_label_to_sizer (_video_sizer, _video_panel, "Colour look-up table");
-       _colour_lut = new wxComboBox (_video_panel, wxID_ANY);
+       _colour_lut = new wxChoice (_video_panel, wxID_ANY);
        for (int i = 0; i < 2; ++i) {
                _colour_lut->Append (std_to_wx (colour_lut_index_to_name (i)));
        }
        _right_crop->SetRange (0, 1024);
        _bottom_crop->SetRange (0, 1024);
        _still_duration->SetRange (1, 60 * 60);
 -      _dcp_trim_start->SetRange (0, 100);
 -      _dcp_trim_end->SetRange (0, 100);
 +      _trim_start->SetRange (0, 100);
 +      _trim_end->SetRange (0, 100);
        _j2k_bandwidth->SetRange (50, 250);
  }
  
@@@ -328,7 -328,7 +328,7 @@@ FilmEditor::make_audio_panel (
                _use_content_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use content's audio"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
                _audio_sizer->Add (video_control (_use_content_audio));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _audio_stream = new wxComboBox (_audio_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
+               _audio_stream = new wxChoice (_audio_panel, wxID_ANY);
                s->Add (video_control (_audio_stream), 1);
                _audio = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
                s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
@@@ -373,7 -373,7 +373,7 @@@ FilmEditor::make_subtitle_panel (
        video_control (_with_subtitles);
        _subtitle_sizer->Add (_with_subtitles, 1);
        
-       _subtitle_stream = new wxComboBox (_subtitle_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
+       _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
        _subtitle_sizer->Add (video_control (_subtitle_stream));
  
        video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Offset"));
@@@ -620,12 -620,10 +620,12 @@@ FilmEditor::film_changed (Film::Propert
                } 
                _length->SetLabel (std_to_wx (s.str ()));
                if (_film->length()) {
 -                      _dcp_trim_start->SetRange (0, _film->length().get());
 -                      _dcp_trim_end->SetRange (0, _film->length().get());
 +                      _trim_start->SetRange (0, _film->length().get());
 +                      _trim_end->SetRange (0, _film->length().get());
                }
                break;
 +      case Film::DCP_INTRINSIC_DURATION:
 +              break;
        case Film::DCP_CONTENT_TYPE:
                checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
                _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
        case Film::SCALER:
                checked_set (_scaler, Scaler::as_index (_film->scaler ()));
                break;
 -      case Film::DCP_TRIM_START:
 -              checked_set (_dcp_trim_start, _film->dcp_trim_start());
 +      case Film::TRIM_START:
 +              checked_set (_trim_start, _film->trim_start());
                break;
 -      case Film::DCP_TRIM_END:
 -              checked_set (_dcp_trim_end, _film->dcp_trim_end());
 +      case Film::TRIM_END:
 +              checked_set (_trim_end, _film->trim_end());
                break;
        case Film::AUDIO_GAIN:
                checked_set (_audio_gain, _film->audio_gain ());
@@@ -763,8 -761,8 +763,8 @@@ FilmEditor::set_film (shared_ptr<Film> 
        film_changed (Film::CROP);
        film_changed (Film::FILTERS);
        film_changed (Film::SCALER);
 -      film_changed (Film::DCP_TRIM_START);
 -      film_changed (Film::DCP_TRIM_END);
 +      film_changed (Film::TRIM_START);
 +      film_changed (Film::TRIM_END);
        film_changed (Film::DCP_AB);
        film_changed (Film::CONTENT_AUDIO_STREAM);
        film_changed (Film::EXTERNAL_AUDIO);
@@@ -807,8 -805,8 +807,8 @@@ FilmEditor::set_things_sensitive (bool 
        _scaler->Enable (s);
        _audio_stream->Enable (s);
        _dcp_content_type->Enable (s);
 -      _dcp_trim_start->Enable (s);
 -      _dcp_trim_end->Enable (s);
 +      _trim_start->Enable (s);
 +      _trim_end->Enable (s);
        _dcp_ab->Enable (s);
        _colour_lut->Enable (s);
        _j2k_bandwidth->Enable (s);
@@@ -922,23 -920,23 +922,23 @@@ FilmEditor::still_duration_changed (wxC
  }
  
  void
 -FilmEditor::dcp_trim_start_changed (wxCommandEvent &)
 +FilmEditor::trim_start_changed (wxCommandEvent &)
  {
        if (!_film) {
                return;
        }
  
 -      _film->set_dcp_trim_start (_dcp_trim_start->GetValue ());
 +      _film->set_trim_start (_trim_start->GetValue ());
  }
  
  void
 -FilmEditor::dcp_trim_end_changed (wxCommandEvent &)
 +FilmEditor::trim_end_changed (wxCommandEvent &)
  {
        if (!_film) {
                return;
        }
  
 -      _film->set_dcp_trim_end (_dcp_trim_end->GetValue ());
 +      _film->set_trim_end (_trim_end->GetValue ());
  }
  
  void
@@@ -1058,8 -1056,9 +1058,9 @@@ FilmEditor::edit_dci_button_clicked (wx
                return;
        }
  
-       DCINameDialog* d = new DCINameDialog (this, _film);
+       DCIMetadataDialog* d = new DCIMetadataDialog (this, _film->dci_metadata ());
        d->ShowModal ();
+       _film->set_dci_metadata (d->dci_metadata ());
        d->Destroy ();
  }
  
@@@ -1086,7 -1085,7 +1087,7 @@@ FilmEditor::setup_streams (
        if (_film->subtitle_stream()) {
                checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
        } else {
-               _subtitle_stream->SetValue (wxT (""));
+               _subtitle_stream->SetSelection (wxNOT_FOUND);
        }
  }
  
diff --combined src/wx/film_editor.h
index 7272315fcfedc88af94bf8bc9b9f3f977057652e,581ae3f69692ba6e7b0dc2d27b4bf9a6fa20df41..90be752d832a84d20611a9164016294bcab53607
@@@ -63,8 -63,8 +63,8 @@@ private
        void content_changed (wxCommandEvent &);
        void trust_content_header_changed (wxCommandEvent &);
        void format_changed (wxCommandEvent &);
 -      void dcp_trim_start_changed (wxCommandEvent &);
 -      void dcp_trim_end_changed (wxCommandEvent &);
 +      void trim_start_changed (wxCommandEvent &);
 +      void trim_end_changed (wxCommandEvent &);
        void dcp_content_type_changed (wxCommandEvent &);
        void dcp_ab_toggled (wxCommandEvent &);
        void scaler_changed (wxCommandEvent &);
        wxCheckBox* _use_dci_name;
        wxButton* _edit_dci_button;
        /** The Film's format */
-       wxComboBox* _format;
+       wxChoice* _format;
        /** The Film's content file */
        wxFilePickerCtrl* _content;
        wxCheckBox* _trust_content_header;
        /** Button to open the filters dialogue */
        wxButton* _filters_button;
        /** The Film's scaler */
-       wxComboBox* _scaler;
+       wxChoice* _scaler;
        wxRadioButton* _use_content_audio;
-       wxComboBox* _audio_stream;
+       wxChoice* _audio_stream;
        wxRadioButton* _use_external_audio;
        wxFilePickerCtrl* _external_audio[MAX_AUDIO_CHANNELS];
        /** The Film's audio gain */
        /** The Film's audio delay */
        wxSpinCtrl* _audio_delay;
        wxCheckBox* _with_subtitles;
-       wxComboBox* _subtitle_stream;
+       wxChoice* _subtitle_stream;
        wxSpinCtrl* _subtitle_offset;
        wxSpinCtrl* _subtitle_scale;
-       wxComboBox* _colour_lut;
+       wxChoice* _colour_lut;
        wxSpinCtrl* _j2k_bandwidth;
        /** The Film's DCP content type */
-       wxComboBox* _dcp_content_type;
+       wxChoice* _dcp_content_type;
        /** The Film's frames per second */
        wxStaticText* _frames_per_second;
        /** The Film's original size */
        /** The Film's duration for still sources */
        wxSpinCtrl* _still_duration;
  
 -      wxSpinCtrl* _dcp_trim_start;
 -      wxSpinCtrl* _dcp_trim_end;
 +      wxSpinCtrl* _trim_start;
 +      wxSpinCtrl* _trim_end;
        /** Selector to generate an A/B comparison DCP */
        wxCheckBox* _dcp_ab;
  
diff --combined src/wx/film_viewer.cc
index 4176f4f426e8287f1dffc16b335542ed132a0e05,16b3ccd9a7ac7c1219697cb6d59227acbdb4105c..7b8f0222006560ad3611cfbde747d25376e032a3
@@@ -44,18 -44,14 +44,15 @@@ using std::max
  using std::cout;
  using std::list;
  using boost::shared_ptr;
 +using libdcp::Size;
  
  FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        : wxPanel (p)
        , _panel (new wxPanel (this))
        , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
        , _play_button (new wxToggleButton (this, wxID_ANY, wxT ("Play")))
+       , _display_frame_x (0)
        , _got_frame (false)
-       , _out_width (0)
-       , _out_height (0)
-       , _panel_width (0)
-       , _panel_height (0)
        , _clear_required (false)
  {
        _panel->SetDoubleBuffered (true);
@@@ -99,10 -95,10 +96,10 @@@ FilmViewer::film_changed (Film::Propert
                break;
        case Film::CONTENT:
        {
 -              shared_ptr<DecodeOptions> o (new DecodeOptions);
 -              o->decode_audio = false;
 -              o->decode_subtitles = true;
 -              o->video_sync = false;
 +              DecodeOptions o;
 +              o.decode_audio = false;
 +              o.decode_subtitles = true;
 +              o.video_sync = false;
                _decoders = decoder_factory (_film, o, 0);
                _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3));
                _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this));
@@@ -196,14 -192,21 +193,21 @@@ FilmViewer::paint_panel (wxPaintEvent &
                _clear_required = false;
        }
  
-       if (!_display_frame || !_film || !_out_width || !_out_height) {
+       if (!_display_frame || !_film || !_out_size.width || !_out_size.height) {
                dc.Clear ();
                return;
        }
  
-       wxImage frame (_out_width, _out_height, _display_frame->data()[0], true);
+       if (_display_frame_x) {
+               dc.SetPen(*wxBLACK_PEN);
+               dc.SetBrush(*wxBLACK_BRUSH);
+               dc.DrawRectangle (0, 0, _display_frame_x, _film_size.height);
+               dc.DrawRectangle (_display_frame_x + _film_size.width, 0, _display_frame_x * 2 + _film_size.width, _film_size.height);
+       }
+       wxImage frame (_film_size.width, _film_size.height, _display_frame->data()[0], true);
        wxBitmap frame_bitmap (frame);
-       dc.DrawBitmap (frame_bitmap, 0, 0);
+       dc.DrawBitmap (frame_bitmap, _display_frame_x, 0);
  
        if (_film->with_subtitles() && _display_sub) {
                wxImage sub (_display_sub->size().width, _display_sub->size().height, _display_sub->data()[0], _display_sub->alpha(), true);
@@@ -232,8 -235,8 +236,8 @@@ FilmViewer::slider_moved (wxScrollEven
  void
  FilmViewer::panel_sized (wxSizeEvent& ev)
  {
-       _panel_width = ev.GetSize().GetWidth();
-       _panel_height = ev.GetSize().GetHeight();
+       _panel_size.width = ev.GetSize().GetWidth();
+       _panel_size.height = ev.GetSize().GetHeight();
        calculate_sizes ();
        update_from_raw ();
  }
@@@ -254,7 -257,7 +258,7 @@@ FilmViewer::update_from_raw (
  void
  FilmViewer::raw_to_display ()
  {
-       if (!_raw_frame || _out_width < 64 || _out_height < 64 || !_film) {
+       if (!_raw_frame || _out_size.width < 64 || _out_size.height < 64 || !_film) {
                return;
        }
  
        }
  
        /* Get a compacted image as we have to feed it to wxWidgets */
-       _display_frame = _raw_frame->scale_and_convert_to_rgb (libdcp::Size (_out_width, _out_height), 0, _film->scaler(), false);
+       _display_frame = _raw_frame->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false);
  
        if (old_size != _display_frame->size()) {
                _clear_required = true;
  
        if (_raw_sub) {
                Rect tx = subtitle_transformed_area (
-                       float (_out_width) / _film->size().width,
-                       float (_out_height) / _film->size().height,
+                       float (_film_size.width) / _film->size().width,
+                       float (_film_size.height) / _film->size().height,
                        _raw_sub->area(), _film->subtitle_offset(), _film->subtitle_scale()
                        );
                
                _display_sub.reset (new RGBPlusAlphaImage (_raw_sub->image()->scale (tx.size(), _film->scaler(), false)));
                _display_sub_position = tx.position();
+               _display_sub_position.x += _display_frame_x;
        } else {
                _display_sub.reset ();
        }
@@@ -290,17 -294,36 +295,36 @@@ FilmViewer::calculate_sizes (
        if (!_film) {
                return;
        }
+       Format const * format = _film->format ();
        
-       float const panel_ratio = static_cast<float> (_panel_width) / _panel_height;
-       float const film_ratio = _film->format() ? _film->format()->ratio_as_float(_film) : 1.78;
+       float const panel_ratio = static_cast<float> (_panel_size.width) / _panel_size.height;
+       float const film_ratio = format ? format->container_ratio_as_float () : 1.78;
+                       
        if (panel_ratio < film_ratio) {
                /* panel is less widscreen than the film; clamp width */
-               _out_width = _panel_width;
-               _out_height = _out_width / film_ratio;
+               _out_size.width = _panel_size.width;
+               _out_size.height = _out_size.width / film_ratio;
        } else {
-               /* panel is more widescreen than the film; clamp heignt */
-               _out_height = _panel_height;
-               _out_width = _out_height * film_ratio;
+               /* panel is more widescreen than the film; clamp height */
+               _out_size.height = _panel_size.height;
+               _out_size.width = _out_size.height * film_ratio;
+       }
+       /* Work out how much padding there is in terms of our display; this will be the x position
+          of our _display_frame.
+       */
+       _display_frame_x = 0;
+       if (format) {
+               _display_frame_x = static_cast<float> (format->dcp_padding (_film)) * _out_size.width / format->dcp_size().width;
+       }
+       _film_size = _out_size;
+       _film_size.width -= _display_frame_x * 2;
+       /* Catch silly values */
+       if (_out_size.width < 64) {
+               _out_size.width = 64;
        }
  }