Merge master.
authorCarl Hetherington <cth@carlh.net>
Tue, 16 Apr 2013 10:43:29 +0000 (11:43 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 16 Apr 2013 10:43:29 +0000 (11:43 +0100)
1  2 
src/lib/film.cc
src/lib/film.h
src/lib/po/sv_SE.po
src/lib/writer.cc
src/lib/wscript
src/tools/dcpomatic.cc
src/wx/config_dialog.cc
src/wx/film_editor.cc
src/wx/film_editor.h
test/test.cc

diff --combined src/lib/film.cc
index 67605ffcaba2c6836fd69be1be20d7202db5792a,a42b874e856d3470e9d7526bc8d5b6b53a9cd771..c8aee7a0aa6bdf6bc6b925209a6ae92043f72098
  #include <boost/algorithm/string.hpp>
  #include <boost/lexical_cast.hpp>
  #include <boost/date_time.hpp>
 +#include <libxml++/libxml++.h>
 +#include <libcxml/cxml.h>
  #include "film.h"
  #include "format.h"
  #include "job.h"
  #include "filter.h"
 -#include "transcoder.h"
  #include "util.h"
  #include "job_manager.h"
  #include "ab_transcode_job.h"
  #include "transcode_job.h"
  #include "scp_dcp_job.h"
  #include "log.h"
 -#include "options.h"
  #include "exceptions.h"
  #include "examine_content_job.h"
  #include "scaler.h"
 -#include "decoder_factory.h"
  #include "config.h"
  #include "version.h"
  #include "ui_signaller.h"
 -#include "video_decoder.h"
 -#include "audio_decoder.h"
 -#include "sndfile_decoder.h"
  #include "analyse_audio_job.h"
 +#include "playlist.h"
 +#include "player.h"
 +#include "ffmpeg_content.h"
 +#include "imagemagick_content.h"
 +#include "sndfile_content.h"
 +#include "dcp_content_type.h"
  
  #include "i18n.h"
  
@@@ -69,8 -67,6 +69,8 @@@ using std::setfill
  using std::min;
  using std::make_pair;
  using std::endl;
 +using std::cout;
 +using std::list;
  using boost::shared_ptr;
  using boost::lexical_cast;
  using boost::to_upper_copy;
@@@ -90,17 -86,19 +90,18 @@@ int const Film::state_version = 4
   */
  
  Film::Film (string d, bool must_exist)
 -      : _use_dci_name (true)
 -      , _trust_content_header (true)
 +      : _playlist (new Playlist)
 +      , _use_dci_name (true)
 +      , _trust_content_headers (true)
        , _dcp_content_type (0)
 -      , _format (0)
 +      , _format (Format::from_id ("185"))
        , _scaler (Scaler::from_id ("bicubic"))
        , _trim_start (0)
        , _trim_end (0)
 -      , _dcp_ab (false)
 -      , _use_content_audio (true)
+       , _trim_type (CPL)
 +      , _ab (false)
        , _audio_gain (0)
        , _audio_delay (0)
 -      , _still_duration (10)
        , _with_subtitles (false)
        , _subtitle_offset (0)
        , _subtitle_scale (1)
        , _j2k_bandwidth (200000000)
        , _dci_metadata (Config::instance()->default_dci_metadata ())
        , _dcp_frame_rate (0)
 -      , _source_frame_rate (0)
        , _dirty (false)
  {
        set_dci_date_today ();
 +
 +      _playlist->ContentChanged.connect (bind (&Film::content_changed, this, _1, _2));
        
        /* Make state.directory a complete path without ..s (where possible)
           (Code swiped from Adam Bowen on stackoverflow)
                }
        }
  
 -      _sndfile_stream = SndfileStream::create ();
 -      
        if (must_exist) {
                read_metadata ();
        }
@@@ -153,11 -152,11 +154,11 @@@ Film::Film (Film const & o
        : boost::enable_shared_from_this<Film> (o)
        /* note: the copied film shares the original's log */
        , _log               (o._log)
 +      , _playlist          (new Playlist)
        , _directory         (o._directory)
        , _name              (o._name)
        , _use_dci_name      (o._use_dci_name)
 -      , _content           (o._content)
 -      , _trust_content_header (o._trust_content_header)
 +      , _trust_content_headers (o._trust_content_headers)
        , _dcp_content_type  (o._dcp_content_type)
        , _format            (o._format)
        , _crop              (o._crop)
        , _scaler            (o._scaler)
        , _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)
 -      , _use_content_audio (o._use_content_audio)
+       , _trim_type         (o._trim_type)
 +      , _ab                (o._ab)
        , _audio_gain        (o._audio_gain)
        , _audio_delay       (o._audio_delay)
 -      , _still_duration    (o._still_duration)
 -      , _subtitle_stream   (o._subtitle_stream)
        , _with_subtitles    (o._with_subtitles)
        , _subtitle_offset   (o._subtitle_offset)
        , _subtitle_scale    (o._subtitle_scale)
        , _colour_lut        (o._colour_lut)
        , _j2k_bandwidth     (o._j2k_bandwidth)
        , _dci_metadata      (o._dci_metadata)
 -      , _dci_date          (o._dci_date)
        , _dcp_frame_rate    (o._dcp_frame_rate)
 -      , _size              (o._size)
 -      , _length            (o._length)
 -      , _content_digest    (o._content_digest)
 -      , _content_audio_streams (o._content_audio_streams)
 -      , _sndfile_stream    (o._sndfile_stream)
 -      , _subtitle_streams  (o._subtitle_streams)
 -      , _source_frame_rate (o._source_frame_rate)
 +      , _dci_date          (o._dci_date)
        , _dirty             (o._dirty)
  {
 +      for (ContentList::const_iterator i = o._content.begin(); i != o._content.end(); ++i) {
 +              _content.push_back ((*i)->clone ());
 +      }
        
 -}
 -
 -Film::~Film ()
 -{
 -
 +      _playlist->ContentChanged.connect (bind (&Film::content_changed, this, _1, _2));
 +      
 +      _playlist->setup (_content);
  }
  
  string
@@@ -196,7 -207,7 +198,7 @@@ Film::video_state_identifier () cons
  
        stringstream s;
        s << format()->id()
 -        << "_" << content_digest()
 +        << "_" << _playlist->video_digest()
          << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
          << "_" << _dcp_frame_rate
          << "_" << f.first << "_" << f.second
          << "_" << j2k_bandwidth()
          << "_" << boost::lexical_cast<int> (colour_lut());
  
 -      if (dcp_ab()) {
 +      if (ab()) {
                pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
                s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
        }
@@@ -223,24 -234,52 +225,52 @@@ Film::info_dir () cons
  }
  
  string
- Film::video_mxf_dir () const
+ Film::internal_video_mxf_dir () const
  {
        boost::filesystem::path p;
        return dir ("video");
  }
  
  string
- Film::video_mxf_filename () const
+ Film::internal_video_mxf_filename () const
  {
        return video_state_identifier() + ".mxf";
  }
  
+ string
+ Film::dcp_video_mxf_filename () const
+ {
+       return filename_safe_name() + "_video.mxf";
+ }
+ string
+ Film::dcp_audio_mxf_filename () const
+ {
+       return filename_safe_name() + "_audio.mxf";
+ }
+ string
+ Film::filename_safe_name () const
+ {
+       string const n = name ();
+       string o;
+       for (size_t i = 0; i < n.length(); ++i) {
+               if (isalnum (n[i])) {
+                       o += n[i];
+               } else {
+                       o += "_";
+               }
+       }
+       return o;
+ }
  string
  Film::audio_analysis_path () const
  {
        boost::filesystem::path p;
        p /= "analysis";
 -      p /= content_digest();
 +      p /= _playlist->audio_digest();
        return file (p.string ());
  }
  
@@@ -254,7 -293,7 +284,7 @@@ Film::make_dcp (
                throw BadSettingError (_("name"), _("cannot contain slashes"));
        }
        
 -      log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary()));
 +      log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
  
        {
                char buffer[128];
                log()->log (String::compose ("Starting to make DCP on %1", buffer));
        }
        
 -      log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
 -      if (length()) {
 -              log()->log (String::compose ("Content length %1", length().get()));
 -      }
 -      log()->log (String::compose ("Content digest %1", content_digest()));
 -      log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
 +//    log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
 +//    if (length()) {
 +//            log()->log (String::compose ("Content length %1", length().get()));
 +//    }
 +//    log()->log (String::compose ("Content digest %1", content_digest()));
 +//    log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
        log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
        log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
 -#ifdef DVDOMATIC_DEBUG
 -      log()->log ("DVD-o-matic built in debug mode.");
 +#ifdef DCPOMATIC_DEBUG
 +      log()->log ("DCP-o-matic built in debug mode.");
  #else
 -      log()->log ("DVD-o-matic built in optimised mode.");
 +      log()->log ("DCP-o-matic built in optimised mode.");
  #endif
  #ifdef LIBDCP_DEBUG
        log()->log ("libdcp built in debug mode.");
                throw MissingSettingError (_("name"));
        }
  
 -      DecodeOptions od;
 -      od.decode_subtitles = with_subtitles ();
 -
        shared_ptr<Job> r;
  
 -      if (dcp_ab()) {
 -              r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od)));
 +      if (ab()) {
 +              r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this())));
        } else {
 -              r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od)));
 +              r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
        }
  }
  
 -/** Start a job to analyse the audio of our content file */
 +/** Start a job to analyse the audio in our Playlist */
  void
  Film::analyse_audio ()
  {
        JobManager::instance()->add (_analyse_audio_job);
  }
  
 -/** Start a job to examine our content file */
 +/** Start a job to examine a piece of content */
  void
 -Film::examine_content ()
 +Film::examine_content (shared_ptr<Content> c)
  {
 -      if (_examine_content_job) {
 -              return;
 -      }
 -
 -      _examine_content_job.reset (new ExamineContentJob (shared_from_this()));
 -      _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this));
 -      JobManager::instance()->add (_examine_content_job);
 +      shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c, trust_content_headers ()));
 +      JobManager::instance()->add (j);
  }
  
  void
@@@ -341,6 -388,12 +371,6 @@@ Film::analyse_audio_finished (
        _analyse_audio_job.reset ();
  }
  
 -void
 -Film::examine_content_finished ()
 -{
 -      _examine_content_job.reset ();
 -}
 -
  /** Start a job to send our DCP to the configured TMS */
  void
  Film::send_dcp_to_tms ()
@@@ -372,55 -425,85 +402,66 @@@ Film::encoded_frames () cons
  void
  Film::write_metadata () const
  {
 +      ContentList the_content = content ();
 +      
        boost::mutex::scoped_lock lm (_state_mutex);
  
        boost::filesystem::create_directories (directory());
  
 -      string const m = file ("metadata");
 -      ofstream f (m.c_str ());
 -      if (!f.good ()) {
 -              throw CreateFileError (m);
 -      }
 +      xmlpp::Document doc;
 +      xmlpp::Element* root = doc.create_root_node ("Metadata");
  
 -      f << "version " << state_version << endl;
 +      root->add_child("Version")->add_child_text (boost::lexical_cast<string> (state_version));
 +      root->add_child("Name")->add_child_text (_name);
 +      root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0");
 +      root->add_child("TrustContentHeaders")->add_child_text (_trust_content_headers ? "1" : "0");
 -      /* User stuff */
 -      f << "name " << _name << endl;
 -      f << "use_dci_name " << _use_dci_name << endl;
 -      f << "content " << _content << endl;
 -      f << "trust_content_header " << (_trust_content_header ? "1" : "0") << endl;
        if (_dcp_content_type) {
 -              f << "dcp_content_type " << _dcp_content_type->dci_name () << endl;
 +              root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
        }
++
        if (_format) {
 -              f << "format " << _format->as_metadata () << endl;
 -      }
 -      f << "left_crop " << _crop.left << endl;
 -      f << "right_crop " << _crop.right << endl;
 -      f << "top_crop " << _crop.top << endl;
 -      f << "bottom_crop " << _crop.bottom << endl;
 -      for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
 -              f << "filter " << (*i)->id () << endl;
 +              root->add_child("Format")->add_child_text (_format->id ());
        }
 -      f << "scaler " << _scaler->id () << endl;
 -      f << "trim_start " << _trim_start << endl;
 -      f << "trim_end " << _trim_end << endl;
++
+       switch (_trim_type) {
+       case CPL:
 -              f << "trim_type cpl\n";
++              root->add_child("TrimType")->add_child_text ("CPL");
+               break;
+       case ENCODE:
 -              f << "trim_type encode\n";
 -              break;
 -      }
 -      f << "dcp_ab " << (_dcp_ab ? "1" : "0") << endl;
 -      if (_content_audio_stream) {
 -              f << "selected_content_audio_stream " << _content_audio_stream->to_string() << endl;
++              root->add_child("TrimType")->add_child_text ("Encode");
+       }
 -      for (vector<string>::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) {
 -              f << "external_audio " << *i << endl;
 -      }
 -      f << "use_content_audio " << (_use_content_audio ? "1" : "0") << endl;
 -      f << "audio_gain " << _audio_gain << endl;
 -      f << "audio_delay " << _audio_delay << endl;
 -      f << "still_duration " << _still_duration << endl;
 -      if (_subtitle_stream) {
 -              f << "selected_subtitle_stream " << _subtitle_stream->to_string() << endl;
 -      }
 -      f << "with_subtitles " << _with_subtitles << endl;
 -      f << "subtitle_offset " << _subtitle_offset << endl;
 -      f << "subtitle_scale " << _subtitle_scale << endl;
 -      f << "colour_lut " << _colour_lut << endl;
 -      f << "j2k_bandwidth " << _j2k_bandwidth << endl;
 -      _dci_metadata.write (f);
 -      f << "dci_date " << boost::gregorian::to_iso_string (_dci_date) << endl;
 -      f << "dcp_frame_rate " << _dcp_frame_rate << endl;
 -      f << "width " << _size.width << endl;
 -      f << "height " << _size.height << endl;
 -      f << "length " << _length.get_value_or(0) << endl;
 -      f << "content_digest " << _content_digest << endl;
 -
 -      for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
 -              f << "content_audio_stream " << (*i)->to_string () << endl;
 -      }
 -
 -      f << "external_audio_stream " << _sndfile_stream->to_string() << endl;
++                      
 +      root->add_child("LeftCrop")->add_child_text (boost::lexical_cast<string> (_crop.left));
 +      root->add_child("RightCrop")->add_child_text (boost::lexical_cast<string> (_crop.right));
 +      root->add_child("TopCrop")->add_child_text (boost::lexical_cast<string> (_crop.top));
 +      root->add_child("BottomCrop")->add_child_text (boost::lexical_cast<string> (_crop.bottom));
  
 -      for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
 -              f << "subtitle_stream " << (*i)->to_string () << endl;
 +      for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
 +              root->add_child("Filter")->add_child_text ((*i)->id ());
        }
 -
 -      f << "source_frame_rate " << _source_frame_rate << endl;
 +      
 +      root->add_child("Scaler")->add_child_text (_scaler->id ());
 +      root->add_child("TrimStart")->add_child_text (boost::lexical_cast<string> (_trim_start));
 +      root->add_child("TrimEnd")->add_child_text (boost::lexical_cast<string> (_trim_end));
 +      root->add_child("AB")->add_child_text (_ab ? "1" : "0");
 +      root->add_child("AudioGain")->add_child_text (boost::lexical_cast<string> (_audio_gain));
 +      root->add_child("AudioDelay")->add_child_text (boost::lexical_cast<string> (_audio_delay));
 +      root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
 +      root->add_child("SubtitleOffset")->add_child_text (boost::lexical_cast<string> (_subtitle_offset));
 +      root->add_child("SubtitleScale")->add_child_text (boost::lexical_cast<string> (_subtitle_scale));
 +      root->add_child("ColourLUT")->add_child_text (boost::lexical_cast<string> (_colour_lut));
 +      root->add_child("J2KBandwidth")->add_child_text (boost::lexical_cast<string> (_j2k_bandwidth));
 +      _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
 +      root->add_child("DCPFrameRate")->add_child_text (boost::lexical_cast<string> (_dcp_frame_rate));
 +      root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
 +      _audio_mapping.as_xml (root->add_child("AudioMapping"));
 +
 +      for (ContentList::iterator i = the_content.begin(); i != the_content.end(); ++i) {
 +              (*i)->as_xml (root->add_child ("Content"));
 +      }
 +
 +      doc.write_to_file_formatted (file ("metadata.xml"));
        
        _dirty = false;
  }
@@@ -431,80 -514,166 +472,89 @@@ Film::read_metadata (
  {
        boost::mutex::scoped_lock lm (_state_mutex);
  
 -      _external_audio.clear ();
 -      _content_audio_streams.clear ();
 -      _subtitle_streams.clear ();
 -
 -      boost::optional<int> version;
 -
 -      /* Backward compatibility things */
 -      boost::optional<int> audio_sample_rate;
 -      boost::optional<int> audio_stream_index;
 -      boost::optional<int> subtitle_stream_index;
 -
 -      ifstream f (file ("metadata").c_str());
 -      if (!f.good()) {
 -              throw OpenFileError (file ("metadata"));
 +      if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
 +              throw StringError (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version.  You will need to create a new Film, re-add your content and set it up again.  Sorry!"));
        }
 -      
 -      multimap<string, string> kv = read_key_value (f);
  
 -      /* We need version before anything else */
 -      multimap<string, string>::iterator v = kv.find ("version");
 -      if (v != kv.end ()) {
 -              version = atoi (v->second.c_str());
 -      }
 +      cxml::File f (file ("metadata.xml"), "Metadata");
        
 -      for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
 -              string const k = i->first;
 -              string const v = i->second;
 +      _name = f.string_child ("Name");
 +      _use_dci_name = f.bool_child ("UseDCIName");
 +      _trust_content_headers = f.bool_child ("TrustContentHeaders");
  
 -              if (k == "audio_sample_rate") {
 -                      audio_sample_rate = atoi (v.c_str());
 +      {
 +              optional<string> c = f.optional_string_child ("DCPContentType");
 +              if (c) {
 +                      _dcp_content_type = DCPContentType::from_dci_name (c.get ());
                }
 +      }
  
 -              /* User-specified stuff */
 -              if (k == "name") {
 -                      _name = v;
 -              } else if (k == "use_dci_name") {
 -                      _use_dci_name = (v == "1");
 -              } else if (k == "content") {
 -                      _content = v;
 -              } else if (k == "trust_content_header") {
 -                      _trust_content_header = (v == "1");
 -              } else if (k == "dcp_content_type") {
 -                      if (version < 3) {
 -                              _dcp_content_type = DCPContentType::from_pretty_name (v);
 -                      } else {
 -                              _dcp_content_type = DCPContentType::from_dci_name (v);
 -                      }
 -              } else if (k == "format") {
 -                      _format = Format::from_metadata (v);
 -              } else if (k == "left_crop") {
 -                      _crop.left = atoi (v.c_str ());
 -              } else if (k == "right_crop") {
 -                      _crop.right = atoi (v.c_str ());
 -              } else if (k == "top_crop") {
 -                      _crop.top = atoi (v.c_str ());
 -              } else if (k == "bottom_crop") {
 -                      _crop.bottom = atoi (v.c_str ());
 -              } else if (k == "filter") {
 -                      _filters.push_back (Filter::from_id (v));
 -              } else if (k == "scaler") {
 -                      _scaler = Scaler::from_id (v);
 -              } else if ( ((!version || version < 2) && k == "dcp_trim_start") || k == "trim_start") {
 -                      _trim_start = atoi (v.c_str ());
 -              } else if ( ((!version || version < 2) && k == "dcp_trim_end") || k == "trim_end") {
 -                      _trim_end = atoi (v.c_str ());
 -              } else if (k == "trim_type") {
 -                      if (v == "cpl") {
 -                              _trim_type = CPL;
 -                      } else if (v == "encode") {
 -                              _trim_type = ENCODE;
 -                      }
 -              } else if (k == "dcp_ab") {
 -                      _dcp_ab = (v == "1");
 -              } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
 -                      if (!version) {
 -                              audio_stream_index = atoi (v.c_str ());
 -                      } else {
 -                              _content_audio_stream = audio_stream_factory (v, version);
 -                      }
 -              } else if (k == "external_audio") {
 -                      _external_audio.push_back (v);
 -              } else if (k == "use_content_audio") {
 -                      _use_content_audio = (v == "1");
 -              } else if (k == "audio_gain") {
 -                      _audio_gain = atof (v.c_str ());
 -              } else if (k == "audio_delay") {
 -                      _audio_delay = atoi (v.c_str ());
 -              } else if (k == "still_duration") {
 -                      _still_duration = atoi (v.c_str ());
 -              } else if (k == "selected_subtitle_stream") {
 -                      if (!version) {
 -                              subtitle_stream_index = atoi (v.c_str ());
 -                      } else {
 -                              _subtitle_stream = subtitle_stream_factory (v, version);
 -                      }
 -              } else if (k == "with_subtitles") {
 -                      _with_subtitles = (v == "1");
 -              } else if (k == "subtitle_offset") {
 -                      _subtitle_offset = atoi (v.c_str ());
 -              } else if (k == "subtitle_scale") {
 -                      _subtitle_scale = atof (v.c_str ());
 -              } else if (k == "colour_lut") {
 -                      _colour_lut = atoi (v.c_str ());
 -              } else if (k == "j2k_bandwidth") {
 -                      _j2k_bandwidth = atoi (v.c_str ());
 -              } else if (k == "dci_date") {
 -                      _dci_date = boost::gregorian::from_undelimited_string (v);
 -              } else if (k == "dcp_frame_rate") {
 -                      _dcp_frame_rate = atoi (v.c_str ());
 +      {
 +              optional<string> c = f.optional_string_child ("Format");
 +              if (c) {
 +                      _format = Format::from_id (c.get ());
                }
 +      }
  
 -              _dci_metadata.read (k, v);
 -              
 -              /* Cached stuff */
 -              if (k == "width") {
 -                      _size.width = atoi (v.c_str ());
 -              } else if (k == "height") {
 -                      _size.height = atoi (v.c_str ());
 -              } else if (k == "length") {
 -                      int const vv = atoi (v.c_str ());
 -                      if (vv) {
 -                              _length = vv;
 -                      }
 -              } else if (k == "content_digest") {
 -                      _content_digest = v;
 -              } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
 -                      _content_audio_streams.push_back (audio_stream_factory (v, version));
 -              } else if (k == "external_audio_stream") {
 -                      _sndfile_stream = audio_stream_factory (v, version);
 -              } else if (k == "subtitle_stream") {
 -                      _subtitle_streams.push_back (subtitle_stream_factory (v, version));
 -              } else if (k == "source_frame_rate") {
 -                      _source_frame_rate = atof (v.c_str ());
 -              } else if (version < 4 && k == "frames_per_second") {
 -                      _source_frame_rate = atof (v.c_str ());
 -                      /* Fill in what would have been used for DCP frame rate by the older version */
 -                      _dcp_frame_rate = best_dcp_frame_rate (_source_frame_rate);
++      {
++              optional<string> c = f.optional_string_child ("TrimType");
++              if (!c || c.get() == "CPL") {
++                      _trim_type = CPL;
++              } else if (c && c.get() == "Encode") {
++                      _trim_type = ENCODE;
+               }
+       }
 -      if (!version) {
 -              if (audio_sample_rate) {
 -                      /* version < 1 didn't specify sample rate in the audio streams, so fill it in here */
 -                      for (vector<shared_ptr<AudioStream> >::iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
 -                              (*i)->set_sample_rate (audio_sample_rate.get());
 -                      }
 -              }
 +      _crop.left = f.number_child<int> ("LeftCrop");
 +      _crop.right = f.number_child<int> ("RightCrop");
 +      _crop.top = f.number_child<int> ("TopCrop");
 +      _crop.bottom = f.number_child<int> ("BottomCrop");
  
 -              /* also the selected stream was specified as an index */
 -              if (audio_stream_index && audio_stream_index.get() >= 0 && audio_stream_index.get() < (int) _content_audio_streams.size()) {
 -                      _content_audio_stream = _content_audio_streams[audio_stream_index.get()];
 +      {
 +              list<shared_ptr<cxml::Node> > c = f.node_children ("Filter");
 +              for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
 +                      _filters.push_back (Filter::from_id ((*i)->content ()));
                }
 +      }
  
 -              /* similarly the subtitle */
 -              if (subtitle_stream_index && subtitle_stream_index.get() >= 0 && subtitle_stream_index.get() < (int) _subtitle_streams.size()) {
 -                      _subtitle_stream = _subtitle_streams[subtitle_stream_index.get()];
 +      _scaler = Scaler::from_id (f.string_child ("Scaler"));
 +      _trim_start = f.number_child<int> ("TrimStart");
 +      _trim_end = f.number_child<int> ("TrimEnd");
 +      _ab = f.bool_child ("AB");
 +      _audio_gain = f.number_child<float> ("AudioGain");
 +      _audio_delay = f.number_child<int> ("AudioDelay");
 +      _with_subtitles = f.bool_child ("WithSubtitles");
 +      _subtitle_offset = f.number_child<float> ("SubtitleOffset");
 +      _subtitle_scale = f.number_child<float> ("SubtitleScale");
 +      _colour_lut = f.number_child<int> ("ColourLUT");
 +      _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
 +      _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
 +      _dcp_frame_rate = f.number_child<int> ("DCPFrameRate");
 +      _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
 +
 +      list<shared_ptr<cxml::Node> > c = f.node_children ("Content");
 +      for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
 +
 +              string const type = (*i)->string_child ("Type");
 +              boost::shared_ptr<Content> c;
 +              
 +              if (type == "FFmpeg") {
 +                      c.reset (new FFmpegContent (*i));
 +              } else if (type == "ImageMagick") {
 +                      c.reset (new ImageMagickContent (*i));
 +              } else if (type == "Sndfile") {
 +                      c.reset (new SndfileContent (*i));
                }
 +
 +              _content.push_back (c);
        }
 -              
 +
 +      /* This must come after we've loaded the content, as we're looking things up in _content */
 +      _audio_mapping.set_from_xml (_content, f.node_child ("AudioMapping"));
 +
        _dirty = false;
 +
 +      _playlist->setup (_content);
  }
  
  libdcp::Size
@@@ -551,18 -720,47 +601,18 @@@ Film::file (string f) cons
        return p.string ();
  }
  
 -/** @return full path of the content (actual video) file
 - *  of the Film.
 - */
 -string
 -Film::content_path () const
 -{
 -      boost::mutex::scoped_lock lm (_state_mutex);
 -      if (boost::filesystem::path(_content).has_root_directory ()) {
 -              return _content;
 -      }
 -
 -      return file (_content);
 -}
 -
 -ContentType
 -Film::content_type () const
 -{
 -      if (boost::filesystem::is_directory (_content)) {
 -              /* Directory of images, we assume */
 -              return VIDEO;
 -      }
 -
 -      if (still_image_file (_content)) {
 -              return STILL;
 -      }
 -
 -      return VIDEO;
 -}
 -
  /** @return The sampling rate that we will resample the audio to */
  int
  Film::target_audio_sample_rate () const
  {
 -      if (!audio_stream()) {
 +      if (!has_audio ()) {
                return 0;
        }
        
        /* Resample to a DCI-approved sample rate */
 -      double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
 +      double t = dcp_audio_sample_rate (audio_frame_rate());
  
 -      FrameRateConversion frc (source_frame_rate(), dcp_frame_rate());
 +      FrameRateConversion frc (video_frame_rate(), dcp_frame_rate());
  
        /* Compensate if the DCP is being run at a different frame rate
           to the source; that is, if the video is run such that it will
        */
  
        if (frc.change_speed) {
 -              t *= source_frame_rate() * frc.factor() / dcp_frame_rate();
 +              t *= video_frame_rate() * frc.factor() / dcp_frame_rate();
        }
  
        return rint (t);
  }
  
 -int
 -Film::still_duration_in_frames () const
 -{
 -      return still_duration() * source_frame_rate();
 -}
 -
  /** @return a DCI-compliant name for a DCP of this film */
  string
  Film::dci_name (bool if_created_now) const
                }
        }
  
 -      switch (audio_channels()) {
 +      switch (audio_channels ()) {
        case 1:
                d << "_10";
                break;
@@@ -702,21 -906,110 +752,21 @@@ Film::set_use_dci_name (bool u
  }
  
  void
 -Film::set_content (string c)
 -{
 -      string check = directory ();
 -
 -      boost::filesystem::path slash ("/");
 -      string platform_slash = slash.make_preferred().string ();
 -
 -      if (!ends_with (check, platform_slash)) {
 -              check += platform_slash;
 -      }
 -      
 -      if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) {
 -              c = c.substr (_directory.length() + 1);
 -      }
 -
 -      string old_content;
 -      
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              if (c == _content) {
 -                      return;
 -              }
 -
 -              old_content = _content;
 -              _content = c;
 -      }
 -
 -      /* Reset streams here in case the new content doesn't have one or the other */
 -      _content_audio_stream = shared_ptr<AudioStream> ();
 -      _subtitle_stream = shared_ptr<SubtitleStream> ();
 -
 -      /* Start off using content audio */
 -      set_use_content_audio (true);
 -
 -      /* Create a temporary decoder so that we can get information
 -         about the content.
 -      */
 -
 -      try {
 -              Decoders d = decoder_factory (shared_from_this(), DecodeOptions());
 -              
 -              set_size (d.video->native_size ());
 -              set_source_frame_rate (d.video->frames_per_second ());
 -              set_dcp_frame_rate (best_dcp_frame_rate (source_frame_rate ()));
 -              set_subtitle_streams (d.video->subtitle_streams ());
 -              if (d.audio) {
 -                      set_content_audio_streams (d.audio->audio_streams ());
 -              }
 -
 -              {
 -                      boost::mutex::scoped_lock lm (_state_mutex);
 -                      _content = c;
 -              }
 -              
 -              signal_changed (CONTENT);
 -              
 -              /* Start off with the first audio and subtitle streams */
 -              if (d.audio && !d.audio->audio_streams().empty()) {
 -                      set_content_audio_stream (d.audio->audio_streams().front());
 -              }
 -              
 -              if (!d.video->subtitle_streams().empty()) {
 -                      set_subtitle_stream (d.video->subtitle_streams().front());
 -              }
 -              
 -              examine_content ();
 -
 -      } catch (...) {
 -
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content = old_content;
 -              throw;
 -
 -      }
 -
 -      /* Default format */
 -      switch (content_type()) {
 -      case STILL:
 -              set_format (Format::from_id ("var-185"));
 -              break;
 -      case VIDEO:
 -              set_format (Format::from_id ("185"));
 -              break;
 -      }
 -
 -      /* Still image DCPs must use external audio */
 -      if (content_type() == STILL) {
 -              set_use_content_audio (false);
 -      }
 -}
 -
 -void
 -Film::set_trust_content_header (bool t)
 +Film::set_trust_content_headers (bool t)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _trust_content_header = t;
 +              _trust_content_headers = t;
        }
        
 -      signal_changed (TRUST_CONTENT_HEADER);
 +      signal_changed (TRUST_CONTENT_HEADERS);
  
 -      if (!_trust_content_header && !content().empty()) {
 +      if (!_trust_content_headers && !content().empty()) {
                /* We just said that we don't trust the content's header */
 -              examine_content ();
 +              ContentList c = content ();
 +              for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
 +                      examine_content (*i);
 +              }
        }
  }
               
@@@ -847,14 -1140,61 +897,24 @@@ Film::set_trim_end (int t
        signal_changed (TRIM_END);
  }
  
+ void
+ Film::set_trim_type (TrimType t)
+ {
+       {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               _trim_type = t;
+       }
+       signal_changed (TRIM_TYPE);
+ }
  void
 -Film::set_dcp_ab (bool a)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _dcp_ab = a;
 -      }
 -      signal_changed (DCP_AB);
 -}
 -
 -void
 -Film::set_content_audio_stream (shared_ptr<AudioStream> s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_audio_stream = s;
 -      }
 -      signal_changed (CONTENT_AUDIO_STREAM);
 -}
 -
 -void
 -Film::set_external_audio (vector<string> a)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _external_audio = a;
 -      }
 -
 -      shared_ptr<SndfileDecoder> decoder (new SndfileDecoder (shared_from_this(), DecodeOptions()));
 -      if (decoder->audio_stream()) {
 -              _sndfile_stream = decoder->audio_stream ();
 -      }
 -      
 -      signal_changed (EXTERNAL_AUDIO);
 -}
 -
 -void
 -Film::set_use_content_audio (bool e)
 +Film::set_ab (bool a)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _use_content_audio = e;
 +              _ab = a;
        }
 -
 -      signal_changed (USE_CONTENT_AUDIO);
 +      signal_changed (AB);
  }
  
  void
@@@ -877,6 -1217,26 +937,6 @@@ Film::set_audio_delay (int d
        signal_changed (AUDIO_DELAY);
  }
  
 -void
 -Film::set_still_duration (int d)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _still_duration = d;
 -      }
 -      signal_changed (STILL_DURATION);
 -}
 -
 -void
 -Film::set_subtitle_stream (shared_ptr<SubtitleStream> s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _subtitle_stream = s;
 -      }
 -      signal_changed (SUBTITLE_STREAM);
 -}
 -
  void
  Film::set_with_subtitles (bool w)
  {
@@@ -949,306 -1309,184 +1009,306 @@@ Film::set_dcp_frame_rate (int f
  }
  
  void
 -Film::set_size (libdcp::Size s)
 +Film::signal_changed (Property p)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _size = s;
 +              _dirty = true;
 +      }
 +
 +      switch (p) {
 +      case Film::CONTENT:
 +              _playlist->setup (content ());
 +              set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ()));
 +              set_audio_mapping (_playlist->default_audio_mapping ());
 +              break;
 +      default:
 +              break;
 +      }
 +
 +      if (ui_signaller) {
 +              ui_signaller->emit (boost::bind (boost::ref (Changed), p));
        }
 -      signal_changed (SIZE);
  }
  
  void
 -Film::set_length (SourceFrame l)
 +Film::set_dci_date_today ()
  {
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _length = l;
 -      }
 -      signal_changed (LENGTH);
 +      _dci_date = boost::gregorian::day_clock::local_day ();
  }
  
 -void
 -Film::unset_length ()
 +string
 +Film::info_path (int f) const
  {
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _length = boost::none;
 +      boost::filesystem::path p;
 +      p /= info_dir ();
 +
 +      stringstream s;
 +      s.width (8);
 +      s << setfill('0') << f << ".md5";
 +
 +      p /= s.str();
 +
 +      /* info_dir() will already have added any initial bit of the path,
 +         so don't call file() on this.
 +      */
 +      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";
        }
 -      signal_changed (LENGTH);
 +
 +      p /= s.str();
 +      return file (p.string ());
  }
  
 -void
 -Film::set_content_digest (string d)
 +/** Make an educated guess as to whether we have a complete DCP
 + *  or not.
 + *  @return true if we do.
 + */
 +
 +bool
 +Film::have_dcp () const
  {
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_digest = d;
 +      try {
 +              libdcp::DCP dcp (dir (dcp_name()));
 +              dcp.read ();
 +      } catch (...) {
 +              return false;
        }
 -      _dirty = true;
 +
 +      return true;
 +}
 +
 +shared_ptr<Player>
 +Film::player () const
 +{
 +      boost::mutex::scoped_lock lm (_state_mutex);
 +      return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
  }
  
  void
 -Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s)
 +Film::add_content (shared_ptr<Content> c)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_audio_streams = s;
 +              _content.push_back (c);
        }
 -      signal_changed (CONTENT_AUDIO_STREAMS);
 +
 +      signal_changed (CONTENT);
 +
 +      examine_content (c);
  }
  
  void
 -Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s)
 +Film::remove_content (shared_ptr<Content> c)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _subtitle_streams = s;
 +              ContentList::iterator i = find (_content.begin(), _content.end(), c);
 +              if (i != _content.end ()) {
 +                      _content.erase (i);
 +              }
        }
 -      signal_changed (SUBTITLE_STREAMS);
 +
 +      signal_changed (CONTENT);
  }
  
  void
 -Film::set_source_frame_rate (float f)
 +Film::move_content_earlier (shared_ptr<Content> c)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _source_frame_rate = f;
 +              ContentList::iterator i = find (_content.begin(), _content.end(), c);
 +              if (i == _content.begin () || i == _content.end()) {
 +                      return;
 +              }
 +
 +              ContentList::iterator j = i;
 +              --j;
 +
 +              swap (*i, *j);
        }
 -      signal_changed (SOURCE_FRAME_RATE);
 +
 +      signal_changed (CONTENT);
  }
 -      
 +
  void
 -Film::signal_changed (Property p)
 +Film::move_content_later (shared_ptr<Content> c)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _dirty = true;
 -      }
 +              ContentList::iterator i = find (_content.begin(), _content.end(), c);
 +              if (i == _content.end()) {
 +                      return;
 +              }
  
 -      if (ui_signaller) {
 -              ui_signaller->emit (boost::bind (boost::ref (Changed), p));
 +              ContentList::iterator j = i;
 +              ++j;
 +              if (j == _content.end ()) {
 +                      return;
 +              }
 +
 +              swap (*i, *j);
        }
 +
 +      signal_changed (CONTENT);
 +
 +}
 +
 +ContentAudioFrame
 +Film::audio_length () const
 +{
 +      return _playlist->audio_length ();
  }
  
  int
  Film::audio_channels () const
  {
 -      shared_ptr<AudioStream> s = audio_stream ();
 -      if (!s) {
 -              return 0;
 -      }
 -
 -      return s->channels ();
 +      return _playlist->audio_channels ();
  }
  
 -void
 -Film::set_dci_date_today ()
 +int
 +Film::audio_frame_rate () const
  {
 -      _dci_date = boost::gregorian::day_clock::local_day ();
 +      return _playlist->audio_frame_rate ();
  }
  
 -boost::shared_ptr<AudioStream>
 -Film::audio_stream () const
 +bool
 +Film::has_audio () const
  {
 -      if (use_content_audio()) {
 -              return _content_audio_stream;
 -      }
 +      return _playlist->has_audio ();
 +}
  
 -      return _sndfile_stream;
 +float
 +Film::video_frame_rate () const
 +{
 +      return _playlist->video_frame_rate ();
  }
  
 -string
 -Film::info_path (int f) const
 +libdcp::Size
 +Film::video_size () const
  {
 -      boost::filesystem::path p;
 -      p /= info_dir ();
 +      return _playlist->video_size ();
 +}
  
 -      stringstream s;
 -      s.width (8);
 -      s << setfill('0') << f << ".md5";
 +ContentVideoFrame
 +Film::video_length () const
 +{
 +      return _playlist->video_length ();
 +}
  
 -      p /= s.str();
 +/** Unfortunately this is needed as the GUI has FFmpeg-specific controls */
 +shared_ptr<FFmpegContent>
 +Film::ffmpeg () const
 +{
 +      boost::mutex::scoped_lock lm (_state_mutex);
 +      
 +      for (ContentList::const_iterator i = _content.begin (); i != _content.end(); ++i) {
 +              shared_ptr<FFmpegContent> f = boost::dynamic_pointer_cast<FFmpegContent> (*i);
 +              if (f) {
 +                      return f;
 +              }
 +      }
  
 -      /* info_dir() will already have added any initial bit of the path,
 -         so don't call file() on this.
 -      */
 -      return p.string ();
 +      return shared_ptr<FFmpegContent> ();
  }
  
 -string
 -Film::j2c_path (int f, bool t) const
 +vector<FFmpegSubtitleStream>
 +Film::ffmpeg_subtitle_streams () const
  {
 -      boost::filesystem::path p;
 -      p /= "j2c";
 -      p /= video_state_identifier ();
 +      shared_ptr<FFmpegContent> f = ffmpeg ();
 +      if (f) {
 +              return f->subtitle_streams ();
 +      }
  
 -      stringstream s;
 -      s.width (8);
 -      s << setfill('0') << f << ".j2c";
 +      return vector<FFmpegSubtitleStream> ();
 +}
  
 -      if (t) {
 -              s << ".tmp";
 +boost::optional<FFmpegSubtitleStream>
 +Film::ffmpeg_subtitle_stream () const
 +{
 +      shared_ptr<FFmpegContent> f = ffmpeg ();
 +      if (f) {
 +              return f->subtitle_stream ();
        }
  
 -      p /= s.str();
 -      return file (p.string ());
 +      return boost::none;
  }
  
 -/** Make an educated guess as to whether we have a complete DCP
 - *  or not.
 - *  @return true if we do.
 - */
 +vector<FFmpegAudioStream>
 +Film::ffmpeg_audio_streams () const
 +{
 +      shared_ptr<FFmpegContent> f = ffmpeg ();
 +      if (f) {
 +              return f->audio_streams ();
 +      }
  
 -bool
 -Film::have_dcp () const
 +      return vector<FFmpegAudioStream> ();
 +}
 +
 +boost::optional<FFmpegAudioStream>
 +Film::ffmpeg_audio_stream () const
  {
 -      try {
 -              libdcp::DCP dcp (dir (dcp_name()));
 -              dcp.read ();
 -      } catch (...) {
 -              return false;
 +      shared_ptr<FFmpegContent> f = ffmpeg ();
 +      if (f) {
 +              return f->audio_stream ();
        }
  
 -      return true;
 +      return boost::none;
  }
  
 -bool
 -Film::has_audio () const
 +void
 +Film::set_ffmpeg_subtitle_stream (FFmpegSubtitleStream s)
  {
 -      if (use_content_audio()) {
 -              return audio_stream();
 +      shared_ptr<FFmpegContent> f = ffmpeg ();
 +      if (f) {
 +              f->set_subtitle_stream (s);
        }
 +}
  
 -      vector<string> const e = external_audio ();
 -      for (vector<string>::const_iterator i = e.begin(); i != e.end(); ++i) {
 -              if (!i->empty ()) {
 -                      return true;
 -              }
 +void
 +Film::set_ffmpeg_audio_stream (FFmpegAudioStream s)
 +{
 +      shared_ptr<FFmpegContent> f = ffmpeg ();
 +      if (f) {
 +              f->set_audio_stream (s);
 +      }
 +}
 +
 +void
 +Film::set_audio_mapping (AudioMapping m)
 +{
 +      {
 +              boost::mutex::scoped_lock lm (_state_mutex);
 +              _audio_mapping = m;
        }
  
 -      return false;
 +      signal_changed (AUDIO_MAPPING);
  }
  
 +void
 +Film::content_changed (boost::weak_ptr<Content> c, int p)
 +{
 +      if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
 +              set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ()));
 +      } else if (p == AudioContentProperty::AUDIO_CHANNELS) {
 +              set_audio_mapping (_playlist->default_audio_mapping ());
 +      }               
 +
 +      if (ui_signaller) {
 +              ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
 +      }
 +}
diff --combined src/lib/film.h
index ffa5d0690ef108a23a78773d660be03b62599bf8,dd0a83d94e473f5da6d5b9e98d1378e247c0c3eb..071f474aca6f645a097dee2360e88e0a05cc6141
  */
  
  /** @file  src/film.h
 - *  @brief A representation of a piece of video (with sound), including naming,
 - *  the source content file, and how it should be presented in a DCP.
 + *  @brief A representation of some audio and video content, and details of
 + *  how they should be presented in a DCP.
   */
  
 -#ifndef DVDOMATIC_FILM_H
 -#define DVDOMATIC_FILM_H
 +#ifndef DCPOMATIC_FILM_H
 +#define DCPOMATIC_FILM_H
  
  #include <string>
  #include <vector>
  #include <boost/thread.hpp>
  #include <boost/signals2.hpp>
  #include <boost/enable_shared_from_this.hpp>
 -extern "C" {
 -#include <libavcodec/avcodec.h>
 -}
 -#include "dcp_content_type.h"
  #include "util.h"
 -#include "stream.h"
  #include "dci_metadata.h"
 +#include "types.h"
 +#include "ffmpeg_content.h"
 +#include "audio_mapping.h"
  
 +class DCPContentType;
  class Format;
  class Job;
  class Filter;
@@@ -46,30 -47,34 +46,33 @@@ class Log
  class ExamineContentJob;
  class AnalyseAudioJob;
  class ExternalAudioStream;
 +class Content;
 +class Player;
 +class Playlist;
  
  /** @class Film
 - *  @brief A representation of a video, maybe with sound.
 - *
 - *  A representation of a piece of video (maybe with sound), including naming,
 - *  the source content file, and how it should be presented in a DCP.
 + *  @brief A representation of some audio and video content, and details of
 + *  how they should be presented in a DCP.
   */
  class Film : public boost::enable_shared_from_this<Film>
  {
  public:
        Film (std::string d, bool must_exist = true);
        Film (Film const &);
 -      ~Film ();
  
        std::string info_dir () const;
        std::string j2c_path (int f, bool t) const;
        std::string info_path (int f) const;
-       std::string video_mxf_dir () const;
-       std::string video_mxf_filename () const;
+       std::string internal_video_mxf_dir () const;
+       std::string internal_video_mxf_filename () const;
        std::string audio_analysis_path () const;
  
 -      void examine_content ();
 +      void examine_content (boost::shared_ptr<Content>);
+       std::string dcp_video_mxf_filename () const;
+       std::string dcp_audio_mxf_filename () const;
        void analyse_audio ();
        void send_dcp_to_tms ();
 -
        void make_dcp ();
  
        /** @return Logger.
        std::string file (std::string f) const;
        std::string dir (std::string d) const;
  
 -      std::string content_path () const;
 -      ContentType content_type () const;
 -      
        int target_audio_sample_rate () const;
        
        void write_metadata () const;
 -      void read_metadata ();
  
        libdcp::Size cropped_size (libdcp::Size) const;
        std::string dci_name (bool if_created_now) const;
                return _dirty;
        }
  
 +      bool have_dcp () const;
 +
 +      boost::shared_ptr<Player> player () const;
 +
 +      /* Proxies for some Playlist methods */
 +
 +      ContentAudioFrame audio_length () const;
        int audio_channels () const;
 +      int audio_frame_rate () const;
 +      bool has_audio () const;
 +      
 +      float video_frame_rate () const;
 +      libdcp::Size video_size () const;
 +      ContentVideoFrame video_length () const;        
  
 -      void set_dci_date_today ();
 +      std::vector<FFmpegSubtitleStream> ffmpeg_subtitle_streams () const;
 +      boost::optional<FFmpegSubtitleStream> ffmpeg_subtitle_stream () const;
 +      std::vector<FFmpegAudioStream> ffmpeg_audio_streams () const;
 +      boost::optional<FFmpegAudioStream> ffmpeg_audio_stream () const;
  
 -      bool have_dcp () const;
 +      void set_ffmpeg_subtitle_stream (FFmpegSubtitleStream);
 +      void set_ffmpeg_audio_stream (FFmpegAudioStream);
  
+       enum TrimType {
+               CPL,
+               ENCODE
+       };
        /** Identifiers for the parts of our state;
            used for signalling changes.
        */
                NONE,
                NAME,
                USE_DCI_NAME,
 +              TRUST_CONTENT_HEADERS,
 +              /** The content list has changed (i.e. content has been added, moved around or removed) */
                CONTENT,
 -              TRUST_CONTENT_HEADER,
                DCP_CONTENT_TYPE,
                FORMAT,
                CROP,
                SCALER,
                TRIM_START,
                TRIM_END,
 -              DCP_AB,
 -              CONTENT_AUDIO_STREAM,
 -              EXTERNAL_AUDIO,
 -              USE_CONTENT_AUDIO,
 +              AB,
+               TRIM_TYPE,
                AUDIO_GAIN,
                AUDIO_DELAY,
 -              STILL_DURATION,
 -              SUBTITLE_STREAM,
                WITH_SUBTITLES,
                SUBTITLE_OFFSET,
                SUBTITLE_SCALE,
                COLOUR_LUT,
                J2K_BANDWIDTH,
                DCI_METADATA,
 -              SIZE,
 -              LENGTH,
 -              CONTENT_AUDIO_STREAMS,
 -              SUBTITLE_STREAMS,
 -              SOURCE_FRAME_RATE,
 -              DCP_FRAME_RATE
 +              DCP_FRAME_RATE,
 +              AUDIO_MAPPING
        };
  
  
                return _use_dci_name;
        }
  
 -      std::string content () const {
 +      bool trust_content_headers () const {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              return _content;
 +              return _trust_content_headers;
        }
  
 -      bool trust_content_header () const {
 +      ContentList content () const {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              return _trust_content_header;
 +              return _content;
        }
  
        DCPContentType const * dcp_content_type () const {
                return _trim_end;
        }
  
 -      bool dcp_ab () const {
+       TrimType trim_type () const {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               return _trim_type;
+       }
 +      bool ab () const {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              return _dcp_ab;
 +              return _ab;
        }
  
 -      boost::shared_ptr<AudioStream> content_audio_stream () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _content_audio_stream;
 -      }
 -
 -      std::vector<std::string> external_audio () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _external_audio;
 -      }
 -
 -      bool use_content_audio () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _use_content_audio;
 -      }
 -      
        float audio_gain () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _audio_gain;
                return _audio_delay;
        }
  
 -      int still_duration () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              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;
 -      }
 -
        bool with_subtitles () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _with_subtitles;
                boost::mutex::scoped_lock lm (_state_mutex);
                return _dcp_frame_rate;
        }
 -      
 -      libdcp::Size size () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _size;
 -      }
 -
 -      boost::optional<SourceFrame> length () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _length;
 -      }
 -      
 -      std::string content_digest () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _content_digest;
 -      }
 -      
 -      std::vector<boost::shared_ptr<AudioStream> > content_audio_streams () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _content_audio_streams;
 -      }
  
 -      std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _subtitle_streams;
 -      }
 -      
 -      float source_frame_rate () const {
 +      AudioMapping audio_mapping () const {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              if (content_type() == STILL) {
 -                      return 24;
 -              }
 -              
 -              return _source_frame_rate;
 +              return _audio_mapping;
        }
  
 -      boost::shared_ptr<AudioStream> audio_stream () const;
 -      bool has_audio () const;
 -      
        /* SET */
  
        void set_directory (std::string);
        void set_name (std::string);
        void set_use_dci_name (bool);
 -      void set_content (std::string);
 -      void set_trust_content_header (bool);
 +      void set_trust_content_headers (bool);
 +      void add_content (boost::shared_ptr<Content>);
 +      void remove_content (boost::shared_ptr<Content>);
 +      void move_content_earlier (boost::shared_ptr<Content>);
 +      void move_content_later (boost::shared_ptr<Content>);
        void set_dcp_content_type (DCPContentType const *);
        void set_format (Format const *);
        void set_crop (Crop);
        void set_scaler (Scaler const *);
        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_use_content_audio (bool);
 +      void set_ab (bool);
+       void set_trim_type (TrimType);
        void set_audio_gain (float);
        void set_audio_delay (int);
 -      void set_still_duration (int);
 -      void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
        void set_with_subtitles (bool);
        void set_subtitle_offset (int);
        void set_subtitle_scale (float);
        void set_j2k_bandwidth (int);
        void set_dci_metadata (DCIMetadata);
        void set_dcp_frame_rate (int);
 -      void set_size (libdcp::Size);
 -      void set_length (SourceFrame);
 -      void unset_length ();
 -      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> >);
 -      void set_source_frame_rate (float);
 -
 -      /** Emitted when some property has changed */
 +      void set_dci_date_today ();
 +      void set_audio_mapping (AudioMapping);
 +
 +      /** Emitted when some property has of the Film has changed */
        mutable boost::signals2::signal<void (Property)> Changed;
  
 +      /** Emitted when some property of our content has changed */
 +      mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
 +
        boost::signals2::signal<void ()> AudioAnalysisSucceeded;
  
        /** Current version number of the state file */
  
  private:
        
 -      /** Log to write to */
 -      boost::shared_ptr<Log> _log;
 -
 -      /** Any running ExamineContentJob, or 0 */
 -      boost::shared_ptr<ExamineContentJob> _examine_content_job;
 -      /** Any running AnalyseAudioJob, or 0 */
 -      boost::shared_ptr<AnalyseAudioJob> _analyse_audio_job;
 -
        void signal_changed (Property);
 -      void examine_content_finished ();
        void analyse_audio_finished ();
        std::string video_state_identifier () const;
 +      void read_metadata ();
 +      void content_changed (boost::weak_ptr<Content>, int);
 +      boost::shared_ptr<FFmpegContent> ffmpeg () const;
 +      void setup_default_audio_mapping ();
+       std::string filename_safe_name () const;
  
 +      /** Log to write to */
 +      boost::shared_ptr<Log> _log;
 +      /** Any running AnalyseAudioJob, or 0 */
 +      boost::shared_ptr<AnalyseAudioJob> _analyse_audio_job;
 +      boost::shared_ptr<Playlist> _playlist;
 +
        /** Complete path to directory containing the film metadata;
         *  must not be relative.
         */
        /** Mutex for _directory */
        mutable boost::mutex _directory_mutex;
        
 -      /** Name for DVD-o-matic */
 +      /** Name for DCP-o-matic */
        std::string _name;
        /** True if a auto-generated DCI-compliant name should be used for our DCP */
        bool _use_dci_name;
 -      /** File or directory containing content; may be relative to our directory
 -       *  or an absolute path.
 -       */
 -      std::string _content;
 -      /** If this is true, we will believe the length specified by the content
 -       *  file's header; if false, we will run through the whole content file
 -       *  the first time we see it in order to obtain the length.
 -       */
 -      bool _trust_content_header;
 +      bool _trust_content_headers;
 +      ContentList _content;
        /** The type of content that this Film represents (feature, trailer etc.) */
        DCPContentType const * _dcp_content_type;
        /** The format to present this Film in (flat, scope, etc.) */
        int _trim_start;
        /** Frames to trim off the end of the DCP */
        int _trim_end;
+       TrimType _trim_type;
        /** 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.
        */
 -      bool _dcp_ab;
 -      /** The audio stream to use from our content */
 -      boost::shared_ptr<AudioStream> _content_audio_stream;
 -      /** List of filenames of external audio files, in channel order
 -          (L, R, C, Lfe, Ls, Rs)
 -      */
 -      std::vector<std::string> _external_audio;
 -      /** true to use audio from our content file; false to use external audio */
 -      bool _use_content_audio;
 +      bool _ab;
        /** Gain to apply to audio in dB */
        float _audio_gain;
        /** Delay to apply to audio (positive moves audio later) in milliseconds */
        int _audio_delay;
 -      /** Duration to make still-sourced films (in seconds) */
 -      int _still_duration;
 -      boost::shared_ptr<SubtitleStream> _subtitle_stream;
        /** True if subtitles should be shown for this film */
        bool _with_subtitles;
        /** y offset for placing subtitles, in source pixels; +ve is further down
        int _colour_lut;
        /** bandwidth for J2K files in bits per second */
        int _j2k_bandwidth;
 -
        /** DCI naming stuff */
        DCIMetadata _dci_metadata;
 -      /** The date that we should use in a DCI name */
 -      boost::gregorian::date _dci_date;
        /** Frames per second to run our DCP at */
        int _dcp_frame_rate;
 -
 -      /* Data which are cached to speed things up */
 -
 -      /** 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;
 -      /** MD5 digest of our content file */
 -      std::string _content_digest;
 -      /** The audio streams in our content */
 -      std::vector<boost::shared_ptr<AudioStream> > _content_audio_streams;
 -      /** A stream to represent possible external audio (will always exist) */
 -      boost::shared_ptr<AudioStream> _sndfile_stream;
 -      /** the subtitle streams that we can use */
 -      std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams;
 -      /** Frames per second of the source */
 -      float _source_frame_rate;
 +      /** The date that we should use in a DCI name */
 +      boost::gregorian::date _dci_date;
 +      AudioMapping _audio_mapping;
  
        /** true if our state has changed since we last saved it */
        mutable bool _dirty;
diff --combined src/lib/po/sv_SE.po
index c8695ce4dc24423fe05676e05d14a86e7d203ed8,ef8109dfa6ada42f22cf0485af3f677707e6cb31..f89874e35e9b952399a227c4f8999a2bbec2d025
@@@ -5,13 -5,12 +5,12 @@@
  #
  msgid ""
  msgstr ""
 -"Project-Id-Version: DVD-o-matic\n"
 +"Project-Id-Version: DCP-o-matic\n"
  "Report-Msgid-Bugs-To: \n"
  "POT-Creation-Date: 2013-04-09 11:14+0100\n"
- "PO-Revision-Date: 2013-04-09 10:13+0100\n"
+ "PO-Revision-Date: 2013-04-10 15:35+0100\n"
  "Last-Translator: Adam Klotblixt <adam.klotblixt@gmail.com>\n"
  "Language-Team: \n"
- "Language: \n"
  "MIME-Version: 1.0\n"
  "Content-Type: text/plain; charset=UTF-8\n"
  "Content-Transfer-Encoding: 8bit\n"
@@@ -50,9 -49,8 +49,8 @@@ msgid "16:9 within Flat
  msgstr "16:9 innanför Flat"
  
  #: src/lib/format.cc:115
- #, fuzzy
  msgid "16:9 within Scope"
- msgstr "16:9 innanför Flat"
+ msgstr "16:9 innanför Scope"
  
  #: src/lib/filter.cc:88
  msgid "3D denoiser"
@@@ -76,7 -74,7 +74,7 @@@ msgstr "Reklam
  
  #: src/lib/job.cc:72
  msgid "An error occurred whilst handling the file %1."
- msgstr "Ett fel inträffade vid hantering av filen (%1)"
+ msgstr "Ett fel inträffade vid hantering av filen %1"
  
  #: src/lib/analyse_audio_job.cc:49
  msgid "Analyse audio of %1"
@@@ -249,10 -247,10 +247,10 @@@ msgstr "Filter för horisontal kantighe
  #: src/lib/job.cc:92 src/lib/job.cc:101
  msgid ""
  "It is not known what caused this error.  The best idea is to report the "
 -"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
 +"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
  msgstr ""
  "Det Ã¤r inte känt vad som orsakade detta fel. Bästa sättet att rapportera "
 -"problemet Ã¤r till DVD-o-matics mejl-lista (dvdomatic@carlh.net)"
 +"problemet Ã¤r till DCP-o-matics mejl-lista (dcpomatic@carlh.net)"
  
  #: src/lib/filter.cc:82
  msgid "Kernel deinterlacer"
diff --combined src/lib/writer.cc
index 7258826ba1774f5a9fd3e32656bb32a2a761edd3,ad81686d108a9e4ea3603a90250b4a2046383925..8e771a5e239e73044de2456310e754d042d0ff90
  #include <libdcp/sound_asset.h>
  #include <libdcp/picture_frame.h>
  #include <libdcp/reel.h>
 +#include <libdcp/dcp.h>
  #include "writer.h"
  #include "compose.hpp"
  #include "film.h"
  #include "format.h"
  #include "log.h"
  #include "dcp_video_frame.h"
 +#include "dcp_content_type.h"
 +#include "player.h"
 +#include "audio_mapping.h"
  
  #include "i18n.h"
  
@@@ -69,8 -65,8 +69,8 @@@ Writer::Writer (shared_ptr<Film> f
        
        _picture_asset.reset (
                new libdcp::MonoPictureAsset (
-                       _film->video_mxf_dir (),
-                       _film->video_mxf_filename (),
+                       _film->internal_video_mxf_dir (),
+                       _film->internal_video_mxf_filename (),
                        _film->dcp_frame_rate (),
                        _film->format()->dcp_size ()
                        )
  
        _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
  
 -      AudioMapping m (_film->audio_channels ());
 -      
 -      if (m.dcp_channels() > 0) {
 +      if (_film->audio_channels() > 0) {
                _sound_asset.reset (
                        new libdcp::SoundAsset (
                                _film->dir (_film->dcp_name()),
-                               N_("audio.mxf"),
+                               _film->dcp_audio_mxf_filename (),
                                _film->dcp_frame_rate (),
 -                              m.dcp_channels (),
 -                              dcp_audio_sample_rate (_film->audio_stream()->sample_rate())
 +                              _film->audio_mapping().dcp_channels (),
 +                              dcp_audio_sample_rate (_film->audio_frame_rate())
                                )
                        );
  
@@@ -261,20 -259,25 +261,25 @@@ Writer::finish (
        }
  
        int const frames = _last_written_frame + 1;
-       int const duration = frames - _film->trim_start() - _film->trim_end();
+       int duration = 0;
+       if (_film->trim_type() == Film::CPL) {
+               duration = frames - _film->trim_start() - _film->trim_end();
+               _picture_asset->set_entry_point (_film->trim_start ());
+       } else {
+               duration = frames;
+       }
        
-       _picture_asset->set_entry_point (_film->trim_start ());
        _picture_asset->set_duration (duration);
  
        /* Hard-link the video MXF into the DCP */
  
        boost::filesystem::path from;
-       from /= _film->video_mxf_dir();
-       from /= _film->video_mxf_filename();
+       from /= _film->internal_video_mxf_dir();
+       from /= _film->internal_video_mxf_filename();
        
        boost::filesystem::path to;
        to /= _film->dir (_film->dcp_name());
-       to /= N_("video.mxf");
+       to /= _film->dcp_video_mxf_filename ();
  
        boost::system::error_code ec;
        boost::filesystem::create_hard_link (from, to, ec);
        /* And update the asset */
  
        _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
-       _picture_asset->set_file_name (N_("video.mxf"));
+       _picture_asset->set_file_name (_film->dcp_video_mxf_filename ());
  
        if (_sound_asset) {
-               _sound_asset->set_entry_point (_film->trim_start ());
+               if (_film->trim_type() == Film::CPL) {
+                       _sound_asset->set_entry_point (_film->trim_start ());
+               }
                _sound_asset->set_duration (duration);
        }
        
@@@ -341,8 -346,8 +348,8 @@@ Writer::check_existing_picture_mxf (
  {
        /* Try to open the existing MXF */
        boost::filesystem::path p;
-       p /= _film->video_mxf_dir ();
-       p /= _film->video_mxf_filename ();
+       p /= _film->internal_video_mxf_dir ();
+       p /= _film->internal_video_mxf_filename ();
        FILE* mxf = fopen (p.string().c_str(), N_("rb"));
        if (!mxf) {
                return;
diff --combined src/lib/wscript
index fddebe8e648180a2744631a80ed88b6cd891ed0a,51b103afdc3e639fb357a8e09551551e6f3864b9..e53ac5a840d705381fafa16955a592f9cc68ff1a
@@@ -6,18 -6,16 +6,18 @@@ sources = ""
          ab_transcoder.cc
            analyse_audio_job.cc
            audio_analysis.cc
 +          audio_content.cc
            audio_decoder.cc
 +          audio_mapping.cc
            audio_source.cc
            config.cc
            combiner.cc
 +          content.cc
            cross.cc
            dci_metadata.cc
            dcp_content_type.cc
            dcp_video_frame.cc
            decoder.cc
 -          decoder_factory.cc
            delay_line.cc
            dolby_cp750.cc
            encoder.cc
            exceptions.cc
            filter_graph.cc
            ffmpeg_compatibility.cc
 +          ffmpeg_content.cc
            ffmpeg_decoder.cc
            film.cc
            filter.cc
            format.cc
            gain.cc
            image.cc
 +          imagemagick_content.cc
            imagemagick_decoder.cc
            job.cc
            job_manager.cc
            log.cc
            lut.cc
            matcher.cc
 +          player.cc
 +          playlist.cc
            scp_dcp_job.cc
            scaler.cc
            server.cc
 +          sndfile_content.cc
            sndfile_decoder.cc
            sound_processor.cc
 -          stream.cc
            subtitle.cc
            timer.cc
            transcode_job.cc
            transcoder.cc
 +          types.cc
+           trimmer.cc
            ui_signaller.cc
            util.cc
 +          video_content.cc
            video_decoder.cc
            video_source.cc
            writer.cc
@@@ -66,24 -59,22 +67,24 @@@ def build(bld)
      else:
          obj = bld(features = 'cxx cxxshlib')
  
 -    obj.name = 'libdvdomatic'
 +    obj.name = 'libdcpomatic'
      obj.export_includes = ['.']
      obj.uselib = """
                   AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE 
                   BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 
 -                 SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP GLIB LZMA
 +                 SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA
                   """
      if bld.env.TARGET_WINDOWS:
          obj.uselib += ' WINSOCK2'
 +    if bld.env.STATIC:
 +        obj.uselib += ' XML++'
      obj.source = sources + " version.cc"
 -    obj.target = 'dvdomatic'
 +    obj.target = 'dcpomatic'
  
 -    i18n.po_to_mo(os.path.join('src', 'lib'), 'libdvdomatic', bld)
 +    i18n.po_to_mo(os.path.join('src', 'lib'), 'libdcpomatic', bld)
  
  def pot(bld):
 -    i18n.pot(os.path.join('src', 'lib'), sources, 'libdvdomatic')
 +    i18n.pot(os.path.join('src', 'lib'), sources, 'libdcpomatic')
  
  def pot_merge(bld):
 -    i18n.pot_merge(os.path.join('src', 'lib'), 'libdvdomatic')
 +    i18n.pot_merge(os.path.join('src', 'lib'), 'libdcpomatic')
diff --combined src/tools/dcpomatic.cc
index ee9f9977cb93ee1fef332fbff0728816e6f91f38,0000000000000000000000000000000000000000..e053da5340d8fc2e45335c610e5b155f3c7cd1d4
mode 100644,000000..100644
--- /dev/null
@@@ -1,578 -1,0 +1,589 @@@
-       if (Config::instance()->language()) {
-               wxLanguageInfo const * li = wxLocale::FindLanguageInfo (std_to_wx (Config::instance()->language().get()));
 +/*
 +    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +*/
 +
 +#include <iostream>
++#include <fstream>
 +#include <boost/filesystem.hpp>
 +#ifdef __WXMSW__
 +#include <shellapi.h>
 +#endif
 +#include <wx/aboutdlg.h>
 +#include <wx/stdpaths.h>
 +#include <wx/cmdline.h>
 +#include "wx/film_viewer.h"
 +#include "wx/film_editor.h"
 +#include "wx/job_manager_view.h"
 +#include "wx/config_dialog.h"
 +#include "wx/job_wrapper.h"
 +#include "wx/wx_util.h"
 +#include "wx/new_film_dialog.h"
 +#include "wx/properties_dialog.h"
 +#include "wx/wx_ui_signaller.h"
 +#include "lib/film.h"
 +#include "lib/format.h"
 +#include "lib/config.h"
 +#include "lib/filter.h"
 +#include "lib/util.h"
 +#include "lib/scaler.h"
 +#include "lib/exceptions.h"
 +#include "lib/version.h"
 +#include "lib/ui_signaller.h"
 +#include "lib/log.h"
 +
 +using std::cout;
 +using std::string;
 +using std::wstring;
 +using std::stringstream;
 +using std::map;
 +using std::make_pair;
 +using std::exception;
++using std::ofstream;
 +using boost::shared_ptr;
 +
 +static FilmEditor* film_editor = 0;
 +static FilmViewer* film_viewer = 0;
 +static shared_ptr<Film> film;
 +static std::string log_level;
 +static std::string film_to_load;
 +static std::string film_to_create;
 +static wxMenu* jobs_menu = 0;
 +static wxLocale* locale = 0;
 +
 +static void set_menu_sensitivity ();
 +
 +class FilmChangedDialog
 +{
 +public:
 +      FilmChangedDialog ()
 +      {
 +              _dialog = new wxMessageDialog (
 +                      0,
 +                      wxString::Format (_("Save changes to film \"%s\" before closing?"), std_to_wx (film->name ()).data()),
 +                      _("Film changed"),
 +                      wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION
 +                      );
 +      }
 +
 +      ~FilmChangedDialog ()
 +      {
 +              _dialog->Destroy ();
 +      }
 +
 +      int run ()
 +      {
 +              return _dialog->ShowModal ();
 +      }
 +
 +private:      
 +      wxMessageDialog* _dialog;
 +};
 +
 +
 +void
 +maybe_save_then_delete_film ()
 +{
 +      if (!film) {
 +              return;
 +      }
 +                      
 +      if (film->dirty ()) {
 +              FilmChangedDialog d;
 +              switch (d.run ()) {
 +              case wxID_NO:
 +                      break;
 +              case wxID_YES:
 +                      film->write_metadata ();
 +                      break;
 +              }
 +      }
 +      
 +      film.reset ();
 +}
 +
 +enum Sensitivity {
 +      ALWAYS,
 +      NEEDS_FILM
 +};
 +
 +map<wxMenuItem*, Sensitivity> menu_items;
 +      
 +void
 +add_item (wxMenu* menu, wxString text, int id, Sensitivity sens)
 +{
 +      wxMenuItem* item = menu->Append (id, text);
 +      menu_items.insert (make_pair (item, sens));
 +}
 +
 +void
 +set_menu_sensitivity ()
 +{
 +      for (map<wxMenuItem*, Sensitivity>::iterator i = menu_items.begin(); i != menu_items.end(); ++i) {
 +              if (i->second == NEEDS_FILM) {
 +                      i->first->Enable (film != 0);
 +              } else {
 +                      i->first->Enable (true);
 +              }
 +      }
 +}
 +
 +enum {
 +      ID_file_new = 1,
 +      ID_file_open,
 +      ID_file_save,
 +      ID_file_properties,
 +      ID_file_quit,
 +      ID_edit_preferences,
 +      ID_jobs_make_dcp,
 +      ID_jobs_send_dcp_to_tms,
 +      ID_jobs_show_dcp,
 +      ID_jobs_analyse_audio,
 +      ID_help_about
 +};
 +
 +void
 +setup_menu (wxMenuBar* m)
 +{
 +      wxMenu* file = new wxMenu;
 +      add_item (file, _("New..."), ID_file_new, ALWAYS);
 +      add_item (file, _("&Open..."), ID_file_open, ALWAYS);
 +      file->AppendSeparator ();
 +      add_item (file, _("&Save"), ID_file_save, NEEDS_FILM);
 +      file->AppendSeparator ();
 +      add_item (file, _("&Properties..."), ID_file_properties, NEEDS_FILM);
 +      file->AppendSeparator ();
 +      add_item (file, _("&Quit"), ID_file_quit, ALWAYS);
 +
 +      wxMenu* edit = new wxMenu;
 +      add_item (edit, _("&Preferences..."), ID_edit_preferences, ALWAYS);
 +
 +      jobs_menu = new wxMenu;
 +      add_item (jobs_menu, _("&Make DCP"), ID_jobs_make_dcp, NEEDS_FILM);
 +      add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM);
 +      add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM);
 +      jobs_menu->AppendSeparator ();
 +      add_item (jobs_menu, _("&Analyse audio"), ID_jobs_analyse_audio, NEEDS_FILM);
 +
 +      wxMenu* help = new wxMenu;
 +      add_item (help, _("About"), ID_help_about, ALWAYS);
 +
 +      m->Append (file, _("&File"));
 +      m->Append (edit, _("&Edit"));
 +      m->Append (jobs_menu, _("&Jobs"));
 +      m->Append (help, _("&Help"));
 +}
 +
 +bool
 +window_closed (wxCommandEvent &)
 +{
 +      maybe_save_then_delete_film ();
 +      return false;
 +}
 +
 +class Frame : public wxFrame
 +{
 +public:
 +      Frame (wxString const & title)
 +              : wxFrame (NULL, -1, title)
 +      {
 +              wxMenuBar* bar = new wxMenuBar;
 +              setup_menu (bar);
 +              SetMenuBar (bar);
 +
 +              Connect (ID_file_new, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_new));
 +              Connect (ID_file_open, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_open));
 +              Connect (ID_file_save, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_save));
 +              Connect (ID_file_properties, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_properties));
 +              Connect (ID_file_quit, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_quit));
 +              Connect (ID_edit_preferences, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::edit_preferences));
 +              Connect (ID_jobs_make_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_make_dcp));
 +              Connect (ID_jobs_send_dcp_to_tms, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_send_dcp_to_tms));
 +              Connect (ID_jobs_show_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_show_dcp));
 +              Connect (ID_jobs_analyse_audio, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_analyse_audio));
 +              Connect (ID_help_about, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::help_about));
 +
 +              Connect (wxID_ANY, wxEVT_MENU_OPEN, wxMenuEventHandler (Frame::menu_opened));
 +
 +              wxPanel* panel = new wxPanel (this);
 +              wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 +              s->Add (panel, 1, wxEXPAND);
 +              SetSizer (s);
 +
 +              film_editor = new FilmEditor (film, panel);
 +              film_viewer = new FilmViewer (film, panel);
 +              JobManagerView* job_manager_view = new JobManagerView (panel);
 +
 +              _top_sizer = new wxBoxSizer (wxHORIZONTAL);
 +              _top_sizer->Add (film_editor, 0, wxALL, 6);
 +              _top_sizer->Add (film_viewer, 1, wxEXPAND | wxALL, 6);
 +
 +              wxBoxSizer* main_sizer = new wxBoxSizer (wxVERTICAL);
 +              main_sizer->Add (_top_sizer, 2, wxEXPAND | wxALL, 6);
 +              main_sizer->Add (job_manager_view, 1, wxEXPAND | wxALL, 6);
 +              panel->SetSizer (main_sizer);
 +
 +              set_menu_sensitivity ();
 +
 +              film_editor->FileChanged.connect (bind (&Frame::file_changed, this, _1));
 +              if (film) {
 +                      file_changed (film->directory ());
 +              } else {
 +                      file_changed ("");
 +              }
 +
 +              set_film ();
 +
 +              film_editor->Connect (wxID_ANY, wxEVT_SIZE, wxSizeEventHandler (Frame::film_editor_sized), 0, this);
 +      }
 +
 +private:
 +
 +      void film_editor_sized (wxSizeEvent &)
 +      {
 +              static bool in_layout = false;
 +              if (!in_layout) {
 +                      in_layout = true;
 +                      _top_sizer->Layout ();
 +                      in_layout = false;
 +              }
 +      }
 +
 +      void menu_opened (wxMenuEvent& ev)
 +      {
 +              if (ev.GetMenu() != jobs_menu) {
 +                      return;
 +              }
 +
 +              bool const have_dcp = film && film->have_dcp();
 +              jobs_menu->Enable (ID_jobs_send_dcp_to_tms, have_dcp);
 +              jobs_menu->Enable (ID_jobs_show_dcp, have_dcp);
 +      }
 +
 +      void set_film ()
 +      {
 +              film_viewer->set_film (film);
 +              film_editor->set_film (film);
 +              set_menu_sensitivity ();
 +      }
 +
 +      void file_changed (string f)
 +      {
 +              stringstream s;
 +              s << wx_to_std (_("DCP-o-matic"));
 +              if (!f.empty ()) {
 +                      s << " - " << f;
 +              }
 +              
 +              SetTitle (std_to_wx (s.str()));
 +      }
 +      
 +      void file_new (wxCommandEvent &)
 +      {
 +              NewFilmDialog* d = new NewFilmDialog (this);
 +              int const r = d->ShowModal ();
 +              
 +              if (r == wxID_OK) {
 +
 +                      if (boost::filesystem::exists (d->get_path())) {
 +                              error_dialog (this, std_to_wx (String::compose (wx_to_std (_("The directory %1 already exists.")), d->get_path().c_str())));
 +                              return;
 +                      }
 +                      
 +                      maybe_save_then_delete_film ();
 +                      film.reset (new Film (d->get_path (), false));
 +                      film->log()->set_level (log_level);
 +                      film->set_name (boost::filesystem::path (d->get_path()).filename().generic_string());
 +                      set_film ();
 +              }
 +              
 +              d->Destroy ();
 +      }
 +
 +      void file_open (wxCommandEvent &)
 +      {
 +              wxDirDialog* c = new wxDirDialog (this, _("Select film to open"), wxStandardPaths::Get().GetDocumentsDir(), wxDEFAULT_DIALOG_STYLE | wxDD_DIR_MUST_EXIST);
 +              int r;
 +              while (1) {
 +                      r = c->ShowModal ();
 +                      if (r == wxID_OK && c->GetPath() == wxStandardPaths::Get().GetDocumentsDir()) {
 +                              error_dialog (this, _("You did not select a folder.  Make sure that you select a folder before clicking Open."));
 +                      } else {
 +                              break;
 +                      }
 +              }
 +                      
 +              if (r == wxID_OK) {
 +                      maybe_save_then_delete_film ();
 +                      try {
 +                              film.reset (new Film (wx_to_std (c->GetPath ())));
 +                              film->log()->set_level (log_level);
 +                              set_film ();
 +                      } catch (std::exception& e) {
 +                              wxString p = c->GetPath ();
 +                              wxCharBuffer b = p.ToUTF8 ();
 +                              error_dialog (this, wxString::Format (_("Could not open film at %s (%s)"), p.data(), std_to_wx (e.what()).data()));
 +                      }
 +              }
 +
 +              c->Destroy ();
 +      }
 +
 +      void file_save (wxCommandEvent &)
 +      {
 +              film->write_metadata ();
 +      }
 +
 +      void file_properties (wxCommandEvent &)
 +      {
 +              PropertiesDialog* d = new PropertiesDialog (this, film);
 +              d->ShowModal ();
 +              d->Destroy ();
 +      }
 +      
 +      void file_quit (wxCommandEvent &)
 +      {
 +              maybe_save_then_delete_film ();
 +              Close (true);
 +      }
 +
 +      void edit_preferences (wxCommandEvent &)
 +      {
 +              ConfigDialog* d = new ConfigDialog (this);
 +              d->ShowModal ();
 +              d->Destroy ();
 +              Config::instance()->write ();
 +      }
 +
 +      void jobs_make_dcp (wxCommandEvent &)
 +      {
 +              JobWrapper::make_dcp (this, film);
 +      }
 +      
 +      void jobs_send_dcp_to_tms (wxCommandEvent &)
 +      {
 +              film->send_dcp_to_tms ();
 +      }
 +
 +      void jobs_show_dcp (wxCommandEvent &)
 +      {
 +#ifdef __WXMSW__
 +              string d = film->directory();
 +              wstring w;
 +              w.assign (d.begin(), d.end());
 +              ShellExecute (0, L"open", w.c_str(), 0, 0, SW_SHOWDEFAULT);
 +#else
 +              int r = system ("which nautilus");
 +              if (WEXITSTATUS (r) == 0) {
 +                      system (string ("nautilus " + film->directory()).c_str ());
 +              } else {
 +                      int r = system ("which konqueror");
 +                      if (WEXITSTATUS (r) == 0) {
 +                              system (string ("konqueror " + film->directory()).c_str ());
 +                      }
 +              }
 +#endif                
 +      }
 +
 +      void jobs_analyse_audio (wxCommandEvent &)
 +      {
 +              film->analyse_audio ();
 +      }
 +      
 +      void help_about (wxCommandEvent &)
 +      {
 +              wxAboutDialogInfo info;
 +              info.SetName (_("DCP-o-matic"));
 +              if (strcmp (dcpomatic_git_commit, "release") == 0) {
 +                      info.SetVersion (std_to_wx (String::compose ("version %1", dcpomatic_version)));
 +              } else {
 +                      info.SetVersion (std_to_wx (String::compose ("version %1 git %2", dcpomatic_version, dcpomatic_git_commit)));
 +              }
 +              info.SetDescription (_("Free, open-source DCP generation from almost anything."));
 +              info.SetCopyright (_("(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen"));
 +
 +              wxArrayString authors;
 +              authors.Add (wxT ("Carl Hetherington"));
 +              authors.Add (wxT ("Terrence Meiczinger"));
 +              authors.Add (wxT ("Paul Davis"));
 +              authors.Add (wxT ("Ole Laursen"));
 +              info.SetDevelopers (authors);
 +
 +              wxArrayString translators;
 +              translators.Add (wxT ("Olivier Perriere"));
 +              translators.Add (wxT ("Lilian Lefranc"));
 +              translators.Add (wxT ("Thierry Journet"));
 +              translators.Add (wxT ("Massimiliano Broggi"));
 +              translators.Add (wxT ("Manuel AC"));
 +              translators.Add (wxT ("Adam Klotblixt"));
 +              info.SetTranslators (translators);
 +              
 +              info.SetWebSite (wxT ("http://carlh.net/software/dcpomatic"));
 +              wxAboutBox (info);
 +      }
 +
 +      wxSizer* _top_sizer;
 +};
 +
 +#if wxMINOR_VERSION == 9
 +static const wxCmdLineEntryDesc command_line_description[] = {
 +      { wxCMD_LINE_OPTION, "l", "log", "set log level (silent, verbose or timing)", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
 +      { wxCMD_LINE_SWITCH, "n", "new", "create new film", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
 +        { wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
 +      { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
 +};
 +#else
 +static const wxCmdLineEntryDesc command_line_description[] = {
 +      { wxCMD_LINE_OPTION, wxT("l"), wxT("log"), wxT("set log level (silent, verbose or timing)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
 +      { wxCMD_LINE_SWITCH, wxT("n"), wxT("new"), wxT("create new film"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
 +        { wxCMD_LINE_PARAM, 0, 0, wxT("film to load or create"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
 +      { wxCMD_LINE_NONE, wxT(""), wxT(""), wxT(""), wxCmdLineParamType (0), 0 }
 +};
 +#endif
 +
 +void
 +setup_i18n ()
 +{
 +      int language = wxLANGUAGE_DEFAULT;
 +
++      ofstream f ("c:/users/carl hetherington/foo", std::ios::app);
++      f << "Hello.\n";
++
++      boost::optional<string> config_lang = Config::instance()->language ();
++      if (config_lang && !config_lang->empty ()) {
++              f << "Configured language " << config_lang.get() << "\n";
++              wxLanguageInfo const * li = wxLocale::FindLanguageInfo (std_to_wx (config_lang.get ()));
++              f << "LanguageInfo " << li << "\n";
 +              if (li) {
 +                      language = li->Language;
++                      f << "language=" << language << " cf " << wxLANGUAGE_DEFAULT << " " << wxLANGUAGE_ENGLISH << "\n";
 +              }
 +      }
 + 
 +      if (wxLocale::IsAvailable (language)) {
++              f << "Language is available.\n";
 +              locale = new wxLocale (language, wxLOCALE_LOAD_DEFAULT);
 +
 +#ifdef DCPOMATIC_WINDOWS
 +              locale->AddCatalogLookupPathPrefix (std_to_wx (mo_path().string()));
 +#endif                
 +
 +              locale->AddCatalog (wxT ("libdcpomatic-wx"));
 +              locale->AddCatalog (wxT ("dcpomatic"));
 +              
 +              if (!locale->IsOk()) {
++                      f << "Locale is not ok.\n";
 +                      delete locale;
 +                      locale = new wxLocale (wxLANGUAGE_ENGLISH);
 +                      language = wxLANGUAGE_ENGLISH;
 +              }
 +      }
 +
 +      if (locale) {
 +              dcpomatic_setup_i18n (wx_to_std (locale->GetCanonicalName ()));
 +      }
 +}
 +
 +class App : public wxApp
 +{
 +      bool OnInit ()
 +      {
 +              if (!wxApp::OnInit()) {
 +                      return false;
 +              }
 +              
 +#ifdef DCPOMATIC_POSIX                
 +              unsetenv ("UBUNTU_MENUPROXY");
 +#endif                
 +
 +              wxInitAllImageHandlers ();
 +
 +              /* Enable i18n; this will create a Config object
 +                 to look for a force-configured language.  This Config
 +                 object will be wrong, however, because dcpomatic_setup
 +                 hasn't yet been called and there aren't any scalers, filters etc.
 +                 set up yet.
 +              */
 +              setup_i18n ();
 +
 +              /* Set things up, including scalers / filters etc.
 +                 which will now be internationalised correctly.
 +              */
 +              dcpomatic_setup ();
 +
 +              /* Force the configuration to be re-loaded correctly next
 +                 time it is needed.
 +              */
 +              Config::drop ();
 +
 +              if (!film_to_load.empty() && boost::filesystem::is_directory (film_to_load)) {
 +                      try {
 +                              film.reset (new Film (film_to_load));
 +                              film->log()->set_level (log_level);
 +                      } catch (exception& e) {
 +                              error_dialog (0, std_to_wx (String::compose (wx_to_std (_("Could not load film %1 (%2)")), film_to_load, e.what())));
 +                      }
 +              }
 +
 +              if (!film_to_create.empty ()) {
 +                      film.reset (new Film (film_to_create, false));
 +                      film->log()->set_level (log_level);
 +                      film->set_name (boost::filesystem::path (film_to_create).filename().generic_string ());
 +              }
 +
 +              Frame* f = new Frame (_("DCP-o-matic"));
 +              SetTopWindow (f);
 +              f->Maximize ();
 +              f->Show ();
 +
 +              ui_signaller = new wxUISignaller (this);
 +              this->Connect (-1, wxEVT_IDLE, wxIdleEventHandler (App::idle));
 +
 +              return true;
 +      }
 +
 +      void OnInitCmdLine (wxCmdLineParser& parser)
 +      {
 +              parser.SetDesc (command_line_description);
 +              parser.SetSwitchChars (wxT ("-"));
 +      }
 +
 +      bool OnCmdLineParsed (wxCmdLineParser& parser)
 +      {
 +              if (parser.GetParamCount() > 0) {
 +                      if (parser.Found (wxT ("new"))) {
 +                              film_to_create = wx_to_std (parser.GetParam (0));
 +                      } else {
 +                              film_to_load = wx_to_std (parser.GetParam(0));
 +                      }
 +              }
 +
 +              wxString log;
 +              if (parser.Found (wxT ("log"), &log)) {
 +                      log_level = wx_to_std (log);
 +              }
 +
 +              return true;
 +      }
 +
 +      void idle (wxIdleEvent &)
 +      {
 +              ui_signaller->ui_idle ();
 +      }
 +};
 +
 +IMPLEMENT_APP (App)
diff --combined src/wx/config_dialog.cc
index 4ae8f1eb4bd53f2f6fa56b0ebea4ab468ca57d7f,c32b03ec0478b81661a72427b4d86e3cb86b1150..c3eebc01515451d5a33529926bc6f039c6cbd166
@@@ -18,7 -18,7 +18,7 @@@
  */
  
  /** @file src/config_dialog.cc
 - *  @brief A dialogue to edit DVD-o-matic configuration.
 + *  @brief A dialogue to edit DCP-o-matic configuration.
   */
  
  #include <iostream>
@@@ -41,7 -41,7 +41,7 @@@ using namespace std
  using boost::bind;
  
  ConfigDialog::ConfigDialog (wxWindow* parent)
 -      : wxDialog (parent, wxID_ANY, _("DVD-o-matic Preferences"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
 +      : wxDialog (parent, wxID_ANY, _("DCP-o-matic Preferences"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
  {
        wxFlexGridSizer* table = new wxFlexGridSizer (3, 6, 6);
        table->AddGrowableCol (1, 1);
@@@ -58,7 -58,7 +58,7 @@@
        table->AddSpacer (0);
  
        table->AddSpacer (0);
 -      wxStaticText* restart = add_label_to_sizer (table, this, _("(restart DVD-o-matic to see language changes)"));
 +      wxStaticText* restart = add_label_to_sizer (table, this, _("(restart DCP-o-matic to see language changes)"));
        wxFont font = restart->GetFont();
        font.SetStyle (wxFONTSTYLE_ITALIC);
        font.SetPointSize (font.GetPointSize() - 1);
@@@ -226,7 -226,7 +226,7 @@@ ConfigDialog::language_changed (wxComma
  {
        switch (_language->GetSelection ()) {
        case 0:
-               Config::instance()->set_language ("");
+               Config::instance()->set_language ("en");
                break;
        case 1:
                Config::instance()->set_language ("fr");
diff --combined src/wx/film_editor.cc
index f960abcfa02f616ef289742cfdc4b0affeae54da,a21782a6f9da188530d9e4b472461e8cf363f19e..ad028c930c52510b86036a2988a1a03128222c2f
@@@ -25,7 -25,6 +25,7 @@@
  #include <iomanip>
  #include <wx/wx.h>
  #include <wx/notebook.h>
 +#include <wx/listctrl.h>
  #include <boost/thread.hpp>
  #include <boost/filesystem.hpp>
  #include <boost/lexical_cast.hpp>
@@@ -38,9 -37,6 +38,9 @@@
  #include "lib/filter.h"
  #include "lib/config.h"
  #include "lib/ffmpeg_decoder.h"
 +#include "lib/imagemagick_content.h"
 +#include "lib/sndfile_content.h"
 +#include "lib/dcp_content_type.h"
  #include "filter_dialog.h"
  #include "wx_util.h"
  #include "film_editor.h"
@@@ -49,8 -45,6 +49,8 @@@
  #include "dci_metadata_dialog.h"
  #include "scaler.h"
  #include "audio_dialog.h"
 +#include "imagemagick_content_dialog.h"
 +#include "audio_mapping_view.h"
  
  using std::string;
  using std::cout;
@@@ -61,24 -55,22 +61,24 @@@ using std::setprecision
  using std::list;
  using std::vector;
  using boost::shared_ptr;
 +using boost::weak_ptr;
  using boost::dynamic_pointer_cast;
 +using boost::lexical_cast;
  
  /** @param f Film to edit */
  FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
        : wxPanel (parent)
 -      , _film (f)
        , _generally_sensitive (true)
        , _audio_dialog (0)
  {
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
 -      SetSizer (s);
        _notebook = new wxNotebook (this, wxID_ANY);
        s->Add (_notebook, 1);
  
        make_film_panel ();
        _notebook->AddPage (_film_panel, _("Film"), true);
 +      make_content_panel ();
 +      _notebook->AddPage (_content_panel, _("Content"), false);
        make_video_panel ();
        _notebook->AddPage (_video_panel, _("Video"), false);
        make_audio_panel ();
        make_subtitle_panel ();
        _notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
  
 -      set_film (_film);
 +      set_film (f);
        connect_to_widgets ();
  
        JobManager::instance()->ActiveJobsChanged.connect (
                bind (&FilmEditor::active_jobs_changed, this, _1)
                );
        
 -      setup_visibility ();
        setup_formats ();
 +
 +      SetSizerAndFit (s);
  }
  
  void
@@@ -126,8 -117,14 +126,8 @@@ FilmEditor::make_film_panel (
        grid->Add (_edit_dci_button, wxGBPosition (r, 1), wxDefaultSpan);
        ++r;
  
 -      add_label_to_grid_bag_sizer (grid, _film_panel, _("Content"), wxGBPosition (r, 0));
 -      _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), _("Select Content File"), wxT("*.*"));
 -      grid->Add (_content, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
 -      ++r;
 -
 -      _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, _("Trust content's header"));
 -      video_control (_trust_content_header);
 -      grid->Add (_trust_content_header, wxGBPosition (r, 0), wxGBSpan(1, 2));
 +      _trust_content_headers = new wxCheckBox (_film_panel, wxID_ANY, _("Trust content's header"));
 +      grid->Add (_trust_content_headers, wxGBPosition (r, 0), wxGBSpan(1, 2));
        ++r;
  
        add_label_to_grid_bag_sizer (grid, _film_panel, _("Content Type"), wxGBPosition (r, 0));
        grid->Add (_dcp_content_type, wxGBPosition (r, 1));
        ++r;
  
 -      video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Original Frame Rate"), wxGBPosition (r, 0)));
 -      _source_frame_rate = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
 -      grid->Add (video_control (_source_frame_rate), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
 -      ++r;
 -
        {
                add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Frame Rate"), wxGBPosition (r, 0));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
        ++r;
  
        _frame_rate_description = new wxStaticText (_film_panel, wxID_ANY, wxT ("\n \n "), wxDefaultPosition, wxDefaultSize);
 -      grid->Add (video_control (_frame_rate_description), wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
 +      grid->Add (_frame_rate_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
        wxFont font = _frame_rate_description->GetFont();
        font.SetStyle(wxFONTSTYLE_ITALIC);
        font.SetPointSize(font.GetPointSize() - 1);
        _frame_rate_description->SetFont(font);
        ++r;
        
 -      video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Length"), wxGBPosition (r, 0)));
 +      add_label_to_grid_bag_sizer (grid, _film_panel, _("Length"), wxGBPosition (r, 0));
        _length = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
 -      grid->Add (video_control (_length), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
 +      grid->Add (_length, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
        ++r;
  
  
        {
 -              video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim frames"), wxGBPosition (r, 0)));
 +              add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim frames"), wxGBPosition (r, 0));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 -              video_control (add_label_to_sizer (s, _film_panel, _("Start")));
 +              add_label_to_sizer (s, _film_panel, _("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")));
 +              s->Add (_trim_start);
 +              add_label_to_sizer (s, _film_panel, _("End"));
                _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
 -              s->Add (video_control (_trim_end));
 +              s->Add (_trim_end);
  
                grid->Add (s, wxGBPosition (r, 1));
        }
        ++r;
  
 -      video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim method"), wxGBPosition (r, 0)));
++      add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim method"), wxGBPosition (r, 0));
+       _trim_type = new wxChoice (_film_panel, wxID_ANY);
 -      grid->Add (video_control (_trim_type), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
++      grid->Add (_trim_type, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
+       ++r;
 -      _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B"));
 -      video_control (_dcp_ab);
 -      grid->Add (_dcp_ab, wxGBPosition (r, 0));
 -      ++r;
 -
 -      /* STILL-only stuff */
 -      {
 -              still_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Duration"), wxGBPosition (r, 0)));
 -              wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 -              _still_duration = new wxSpinCtrl (_film_panel);
 -              still_control (_still_duration);
 -              s->Add (_still_duration, 1, wxEXPAND);
 -              /// TRANSLATORS: `s' here is an abbreviation for seconds, the unit of time
 -              still_control (add_label_to_sizer (s, _film_panel, _("s")));
 -              grid->Add (s, wxGBPosition (r, 1));
 -      }
 +      _ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B"));
 +      grid->Add (_ab, wxGBPosition (r, 0));
        ++r;
  
        vector<DCPContentType const *> const ct = DCPContentType::all ();
        for (list<int>::const_iterator i = dfr.begin(); i != dfr.end(); ++i) {
                _dcp_frame_rate->Append (std_to_wx (boost::lexical_cast<string> (*i)));
        }
+       _trim_type->Append (_("encode all frames and play the subset"));
+       _trim_type->Append (_("encode only the subset"));
  }
  
  void
  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_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this);
-       _trust_content_headers->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::trust_content_headers_changed), 0, this);
-       _content->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler (FilmEditor::content_selection_changed), 0, this);
-       _content->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler (FilmEditor::content_selection_changed), 0, this);
-       _content->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxListEventHandler (FilmEditor::content_activated), 0, this);
-       _content_add->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_add_clicked), 0, this);
-       _content_remove->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_remove_clicked), 0, this);
-       _content_edit->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_edit_clicked), 0, this);
-       _content_earlier->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_earlier_clicked), 0, this);
-       _content_later->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_later_clicked), 0, this);
-       _left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
-       _right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_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_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_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_frame_rate_changed), 0, this);
-       _best_dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::best_dcp_frame_rate_clicked), 0, this);
-       _ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::ab_toggled), 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_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);
-       _ffmpeg_subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::ffmpeg_subtitle_stream_changed), 0, this);
-       _ffmpeg_audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::ffmpeg_audio_stream_changed), 0, this);
-       _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
 -      _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_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);
 -      _right_crop->Connect           (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::right_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_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_frame_rate->Connect       (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::dcp_frame_rate_changed), 0, this);
 -      _best_dcp_frame_rate->Connect  (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::best_dcp_frame_rate_clicked), 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);
 -      _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);
 -      _trim_type->Connect            (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::trim_type_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_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_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);
++      _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_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::format_changed), 0, this);
++      _trust_content_headers->Connect  (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::trust_content_headers_changed), 0, this);
++      _content->Connect                (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_SELECTED,   wxListEventHandler    (FilmEditor::content_selection_changed), 0, this);
++      _content->Connect                (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler    (FilmEditor::content_selection_changed), 0, this);
++      _content->Connect                (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_ACTIVATED,  wxListEventHandler    (FilmEditor::content_activated), 0, this);
++      _content_add->Connect            (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_add_clicked), 0, this);
++      _content_remove->Connect         (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_remove_clicked), 0, this);
++      _content_edit->Connect           (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_edit_clicked), 0, this);
++      _content_earlier->Connect        (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_earlier_clicked), 0, this);
++      _content_later->Connect          (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_later_clicked), 0, this);
++      _left_crop->Connect              (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
++      _right_crop->Connect             (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::right_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_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_frame_rate->Connect         (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::dcp_frame_rate_changed), 0, this);
++      _best_dcp_frame_rate->Connect    (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::best_dcp_frame_rate_clicked), 0, this);
++      _ab->Connect                     (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::ab_toggled), 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);
++      _trim_type->Connect              (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::trim_type_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_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);
++      _ffmpeg_subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::ffmpeg_subtitle_stream_changed), 0, this);
++      _ffmpeg_audio_stream->Connect    (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::ffmpeg_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
                );
-       _show_audio->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this);
-       _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
 -      _show_audio->Connect           (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this);
 -      _audio_delay->Connect          (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
 -      _use_content_audio->Connect    (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
 -      _use_external_audio->Connect   (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
 -      for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
 -              _external_audio[i]->Connect (
 -                      wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::external_audio_changed), 0, this
 -                      );
 -      }
++      _show_audio->Connect             (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this);
++      _audio_delay->Connect            (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
  }
  
  void
@@@ -278,19 -304,21 +287,19 @@@ FilmEditor::make_video_panel (
  
        /* VIDEO-only stuff */
        {
 -              video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), wxGBPosition (r, 0)));
 +              add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), wxGBPosition (r, 0));
                wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _filters = new wxStaticText (_video_panel, wxID_ANY, _("None"));
 -              video_control (_filters);
                s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
                _filters_button = new wxButton (_video_panel, wxID_ANY, _("Edit..."));
 -              video_control (_filters_button);
                s->Add (_filters_button, 0);
                grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
        }
        ++r;
  
 -      video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Scaler"), wxGBPosition (r, 0)));
 +      add_label_to_grid_bag_sizer (grid, _video_panel, _("Scaler"), wxGBPosition (r, 0));
        _scaler = new wxChoice (_video_panel, wxID_ANY);
 -      grid->Add (video_control (_scaler), wxGBPosition (r, 1));
 +      grid->Add (_scaler, wxGBPosition (r, 1));
        ++r;
  
        vector<Scaler const *> const sc = Scaler::all ();
        _top_crop->SetRange (0, 1024);
        _right_crop->SetRange (0, 1024);
        _bottom_crop->SetRange (0, 1024);
 -      _still_duration->SetRange (1, 60 * 60);
        _trim_start->SetRange (0, 100);
        _trim_end->SetRange (0, 100);
        _j2k_bandwidth->SetRange (50, 250);
  }
  
 +void
 +FilmEditor::make_content_panel ()
 +{
 +      _content_panel = new wxPanel (_notebook);
 +      _content_sizer = new wxBoxSizer (wxVERTICAL);
 +      _content_panel->SetSizer (_content_sizer);
 +      
 +        {
 +                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 +                
 +                _content = new wxListCtrl (_content_panel, wxID_ANY, wxDefaultPosition, wxSize (320, 160), wxLC_REPORT | wxLC_NO_HEADER | wxLC_SINGLE_SEL);
 +                s->Add (_content, 1, wxEXPAND | wxTOP | wxBOTTOM, 6);
 +
 +                _content->InsertColumn (0, wxT(""));
 +              _content->SetColumnWidth (0, 512);
 +
 +                wxBoxSizer* b = new wxBoxSizer (wxVERTICAL);
 +                _content_add = new wxButton (_content_panel, wxID_ANY, _("Add..."));
 +                b->Add (_content_add);
 +                _content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove"));
 +                b->Add (_content_remove);
 +                _content_edit = new wxButton (_content_panel, wxID_ANY, _("Edit..."));
 +                b->Add (_content_edit);
 +                _content_earlier = new wxButton (_content_panel, wxID_ANY, _("Earlier"));
 +                b->Add (_content_earlier);
 +                _content_later = new wxButton (_content_panel, wxID_ANY, _("Later"));
 +                b->Add (_content_later);
 +
 +                s->Add (b, 0, wxALL, 4);
 +
 +                _content_sizer->Add (s, 1, wxEXPAND | wxALL, 6);
 +        }
 +
 +      _content_information = new wxTextCtrl (_content_panel, wxID_ANY, wxT ("\n\n\n\n"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxTE_MULTILINE);
 +      _content_sizer->Add (_content_information, 1, wxEXPAND | wxALL, 6);
 +}
 +
  void
  FilmEditor::make_audio_panel ()
  {
        grid->AddSpacer (0);
  
        {
 -              video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Gain")));
 +              add_label_to_sizer (grid, _audio_panel, _("Audio Gain"));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _audio_gain = new wxSpinCtrl (_audio_panel);
 -              s->Add (video_control (_audio_gain), 1);
 -              video_control (add_label_to_sizer (s, _audio_panel, _("dB")));
 +              s->Add (_audio_gain, 1);
 +              add_label_to_sizer (s, _audio_panel, _("dB"));
                _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate..."));
 -              video_control (_audio_gain_calculate_button);
                s->Add (_audio_gain_calculate_button, 1, wxEXPAND);
                grid->Add (s);
        }
  
        {
 -              video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Delay")));
 +              add_label_to_sizer (grid, _audio_panel, _("Audio Delay"));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _audio_delay = new wxSpinCtrl (_audio_panel);
 -              s->Add (video_control (_audio_delay), 1);
 +              s->Add (_audio_delay, 1);
                /// TRANSLATORS: this is an abbreviation for milliseconds, the unit of time
 -              video_control (add_label_to_sizer (s, _audio_panel, _("ms")));
 +              add_label_to_sizer (s, _audio_panel, _("ms"));
                grid->Add (s);
        }
  
 -      {
 -              _use_content_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use content's audio"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
 -              grid->Add (video_control (_use_content_audio));
 -              wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 -              _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);
 -              grid->Add (s, 1, wxEXPAND);
 -      }
 -
 -      _use_external_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use external audio"));
 -      grid->Add (_use_external_audio);
 -      grid->AddSpacer (0);
 -
 -      for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
 -              add_label_to_sizer (grid, _audio_panel, std_to_wx (audio_channel_name (i)));
 -              _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), _("Select Audio File"), wxT ("*.wav"));
 -              grid->Add (_external_audio[i], 1, wxEXPAND);
 -      }
 +        {
 +                add_label_to_sizer (grid, _audio_panel, _("Audio Stream"));
 +                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 +                _ffmpeg_audio_stream = new wxChoice (_audio_panel, wxID_ANY);
 +                s->Add (_ffmpeg_audio_stream, 1);
 +                _audio = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
 +                s->Add (_audio, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
 +                grid->Add (s, 1, wxEXPAND);
 +        }
  
 +      _audio_mapping = new AudioMappingView (_audio_panel);
 +      _audio_sizer->Add (_audio_mapping, 1, wxEXPAND | wxALL, 6);
 +      
        _audio_gain->SetRange (-60, 60);
        _audio_delay->SetRange (-1000, 1000);
  }
@@@ -425,26 -426,27 +434,26 @@@ FilmEditor::make_subtitle_panel (
        _subtitle_sizer->Add (grid, 0, wxALL, 8);
  
        _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, _("With Subtitles"));
 -      video_control (_with_subtitles);
        grid->Add (_with_subtitles, 1);
        
 -      _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
 -      grid->Add (video_control (_subtitle_stream));
 +      _ffmpeg_subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
 +      grid->Add (_ffmpeg_subtitle_stream);
  
        {
 -              video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset")));
 +              add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _subtitle_offset = new wxSpinCtrl (_subtitle_panel);
                s->Add (_subtitle_offset);
 -              video_control (add_label_to_sizer (s, _subtitle_panel, _("pixels")));
 +              add_label_to_sizer (s, _subtitle_panel, _("pixels"));
                grid->Add (s);
        }
  
        {
 -              video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale")));
 +              add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _subtitle_scale = new wxSpinCtrl (_subtitle_panel);
 -              s->Add (video_control (_subtitle_scale));
 -              video_control (add_label_to_sizer (s, _subtitle_panel, _("%")));
 +              s->Add (_subtitle_scale);
 +              add_label_to_sizer (s, _subtitle_panel, _("%"));
                grid->Add (s);
        }
  
@@@ -496,25 -498,41 +505,25 @@@ FilmEditor::bottom_crop_changed (wxComm
        _film->set_bottom_crop (_bottom_crop->GetValue ());
  }
  
 -/** Called when the content filename has been changed */
  void
 -FilmEditor::content_changed (wxCommandEvent &)
 +FilmEditor::trust_content_headers_changed (wxCommandEvent &)
  {
        if (!_film) {
                return;
        }
  
 -      try {
 -              _film->set_content (wx_to_std (_content->GetPath ()));
 -      } catch (std::exception& e) {
 -              _content->SetPath (std_to_wx (_film->directory ()));
 -              error_dialog (this, wxString::Format (_("Could not set content: %s"), std_to_wx (e.what()).data()));
 -      }
 -}
 -
 -void
 -FilmEditor::trust_content_header_changed (wxCommandEvent &)
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_trust_content_header (_trust_content_header->GetValue ());
 +      _film->set_trust_content_headers (_trust_content_headers->GetValue ());
  }
  
  /** Called when the DCP A/B switch has been toggled */
  void
 -FilmEditor::dcp_ab_toggled (wxCommandEvent &)
 +FilmEditor::ab_toggled (wxCommandEvent &)
  {
        if (!_film) {
                return;
        }
        
 -      _film->set_dcp_ab (_dcp_ab->GetValue ());
 +      _film->set_ab (_ab->GetValue ());
  }
  
  /** Called when the name widget has been changed */
@@@ -602,19 -620,43 +611,19 @@@ FilmEditor::film_changed (Film::Propert
        case Film::NONE:
                break;
        case Film::CONTENT:
 -              checked_set (_content, _film->content ());
 -              setup_visibility ();
 +              setup_content ();
                setup_formats ();
 +              setup_format ();
                setup_subtitle_control_sensitivity ();
                setup_streams ();
                setup_show_audio_sensitivity ();
 -              setup_frame_rate_description ();
 -              break;
 -      case Film::TRUST_CONTENT_HEADER:
 -              checked_set (_trust_content_header, _film->trust_content_header ());
                break;
 -      case Film::SUBTITLE_STREAMS:
 -              setup_subtitle_control_sensitivity ();
 -              setup_streams ();
 -              break;
 -      case Film::CONTENT_AUDIO_STREAMS:
 -              setup_streams ();
 -              setup_show_audio_sensitivity ();
 -              setup_frame_rate_description ();
 +      case Film::TRUST_CONTENT_HEADERS:
 +              checked_set (_trust_content_headers, _film->trust_content_headers ());
                break;
        case Film::FORMAT:
 -      {
 -              int n = 0;
 -              vector<Format const *>::iterator i = _formats.begin ();
 -              while (i != _formats.end() && *i != _film->format ()) {
 -                      ++i;
 -                      ++n;
 -              }
 -              if (i == _formats.end()) {
 -                      checked_set (_format, -1);
 -              } else {
 -                      checked_set (_format, n);
 -              }
 -              setup_dcp_name ();
 -              setup_scaling_description ();
 +              setup_format ();
                break;
 -      }
        case Film::CROP:
                checked_set (_left_crop, _film->crop().left);
                checked_set (_right_crop, _film->crop().right);
                checked_set (_name, _film->name());
                setup_dcp_name ();
                break;
 -      case Film::SOURCE_FRAME_RATE:
 -              s << fixed << setprecision(2) << _film->source_frame_rate();
 -              _source_frame_rate->SetLabel (std_to_wx (s.str ()));
 -              setup_frame_rate_description ();
 -              break;
 -      case Film::SIZE:
 -              setup_scaling_description ();
 -              break;
 -      case Film::LENGTH:
 -              if (_film->source_frame_rate() > 0 && _film->length()) {
 -                      s << _film->length().get() << " "
 -                        << wx_to_std (_("frames")) << "; " << seconds_to_hms (_film->length().get() / _film->source_frame_rate());
 -              } else if (_film->length()) {
 -                      s << _film->length().get() << " "
 -                        << wx_to_std (_("frames"));
 -              } 
 -              _length->SetLabel (std_to_wx (s.str ()));
 -              if (_film->length()) {
 -                      _trim_start->SetRange (0, _film->length().get());
 -                      _trim_end->SetRange (0, _film->length().get());
 -              }
 -              break;
        case Film::DCP_CONTENT_TYPE:
                checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
                setup_dcp_name ();
                break;
 -      case Film::DCP_AB:
 -              checked_set (_dcp_ab, _film->dcp_ab ());
 +      case Film::AB:
 +              checked_set (_ab, _film->ab ());
                break;
        case Film::SCALER:
                checked_set (_scaler, Scaler::as_index (_film->scaler ()));
        case Film::TRIM_END:
                checked_set (_trim_end, _film->trim_end());
                break;
+       case Film::TRIM_TYPE:
+               checked_set (_trim_type, _film->trim_type() == Film::CPL ? 0 : 1);
+               break;
        case Film::AUDIO_GAIN:
                checked_set (_audio_gain, _film->audio_gain ());
                break;
        case Film::AUDIO_DELAY:
                checked_set (_audio_delay, _film->audio_delay ());
                break;
 -      case Film::STILL_DURATION:
 -              checked_set (_still_duration, _film->still_duration ());
 -              break;
        case Film::WITH_SUBTITLES:
                checked_set (_with_subtitles, _film->with_subtitles ());
                setup_subtitle_control_sensitivity ();
        case Film::DCI_METADATA:
                setup_dcp_name ();
                break;
 -      case Film::CONTENT_AUDIO_STREAM:
 -              if (_film->content_audio_stream()) {
 -                      checked_set (_audio_stream, _film->content_audio_stream()->to_string());
 -              }
 -              setup_dcp_name ();
 -              setup_audio_details ();
 -              setup_audio_control_sensitivity ();
 -              setup_show_audio_sensitivity ();
 -              setup_frame_rate_description ();
 -              break;
 -      case Film::USE_CONTENT_AUDIO:
 -              checked_set (_use_content_audio, _film->use_content_audio());
 -              checked_set (_use_external_audio, !_film->use_content_audio());
 -              setup_dcp_name ();
 -              setup_audio_details ();
 -              setup_audio_control_sensitivity ();
 -              setup_show_audio_sensitivity ();
 -              setup_frame_rate_description ();
 -              break;
 -      case Film::SUBTITLE_STREAM:
 -              if (_film->subtitle_stream()) {
 -                      checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
 -              }
 -              break;
 -      case Film::EXTERNAL_AUDIO:
 -      {
 -              vector<string> a = _film->external_audio ();
 -              for (size_t i = 0; i < a.size() && i < MAX_AUDIO_CHANNELS; ++i) {
 -                      checked_set (_external_audio[i], a[i]);
 -              }
 -              setup_audio_details ();
 -              setup_show_audio_sensitivity ();
 -              setup_frame_rate_description ();
 -              break;
 -      }
        case Film::DCP_FRAME_RATE:
                for (unsigned int i = 0; i < _dcp_frame_rate->GetCount(); ++i) {
                        if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_frame_rate())) {
                        }
                }
  
 -              if (_film->source_frame_rate()) {
 -                      _best_dcp_frame_rate->Enable (best_dcp_frame_rate (_film->source_frame_rate ()) != _film->dcp_frame_rate ());
 +              if (_film->video_frame_rate()) {
 +                      _best_dcp_frame_rate->Enable (best_dcp_frame_rate (_film->video_frame_rate ()) != _film->dcp_frame_rate ());
                } else {
                        _best_dcp_frame_rate->Disable ();
                }
 -
                setup_frame_rate_description ();
 +              break;
 +      case Film::AUDIO_MAPPING:
 +              _audio_mapping->set_mapping (_film->audio_mapping ());
 +              break;
        }
  }
  
 +void
 +FilmEditor::film_content_changed (weak_ptr<Content> content, int property)
 +{
 +      if (!_film) {
 +              /* We call this method ourselves (as well as using it as a signal handler)
 +                 so _film can be 0.
 +              */
 +              return;
 +      }
 +              
 +      if (property == FFmpegContentProperty::SUBTITLE_STREAMS) {
 +              setup_subtitle_control_sensitivity ();
 +              setup_streams ();
 +      } else if (property == FFmpegContentProperty::AUDIO_STREAMS) {
 +              setup_streams ();
 +              setup_show_audio_sensitivity ();
 +      } else if (property == VideoContentProperty::VIDEO_LENGTH) {
 +              setup_length ();
 +              boost::shared_ptr<Content> c = content.lock ();
 +              if (c && c == selected_content()) {
 +                      setup_content_information ();
 +              }
 +      } else if (property == FFmpegContentProperty::AUDIO_STREAM) {
 +              if (_film->ffmpeg_audio_stream()) {
 +                      checked_set (_ffmpeg_audio_stream, boost::lexical_cast<string> (_film->ffmpeg_audio_stream()->id));
 +              }
 +              setup_dcp_name ();
 +              setup_audio_details ();
 +              setup_show_audio_sensitivity ();
 +      } else if (property == FFmpegContentProperty::SUBTITLE_STREAM) {
 +              if (_film->ffmpeg_subtitle_stream()) {
 +                      checked_set (_ffmpeg_subtitle_stream, boost::lexical_cast<string> (_film->ffmpeg_subtitle_stream()->id));
 +              }
 +      }
 +}
 +
 +void
 +FilmEditor::setup_format ()
 +{
 +      int n = 0;
 +      vector<Format const *>::iterator i = _formats.begin ();
 +      while (i != _formats.end() && *i != _film->format ()) {
 +              ++i;
 +              ++n;
 +      }
 +      
 +      if (i == _formats.end()) {
 +              checked_set (_format, -1);
 +      } else {
 +              checked_set (_format, n);
 +      }
 +      
 +      setup_dcp_name ();
 +      setup_scaling_description ();
 +}     
 +
 +void
 +FilmEditor::setup_length ()
 +{
 +      stringstream s;
 +      if (_film->video_frame_rate() > 0 && _film->video_length()) {
 +              s << _film->video_length() << " "
 +                << wx_to_std (_("frames")) << "; " << seconds_to_hms (_film->video_length() / _film->video_frame_rate());
 +      } else if (_film->video_length()) {
 +              s << _film->video_length() << " "
 +                << wx_to_std (_("frames"));
 +      } 
 +      _length->SetLabel (std_to_wx (s.str ()));
 +      if (_film->video_length()) {
 +              _trim_start->SetRange (0, _film->video_length());
 +              _trim_end->SetRange (0, _film->video_length());
 +      }
 +}     
 +
  void
  FilmEditor::setup_frame_rate_description ()
  {
        wxString d;
        int lines = 0;
        
 -      if (_film->source_frame_rate()) {
 -              d << std_to_wx (FrameRateConversion (_film->source_frame_rate(), _film->dcp_frame_rate()).description);
 +      if (_film->video_frame_rate()) {
 +              d << std_to_wx (FrameRateConversion (_film->video_frame_rate(), _film->dcp_frame_rate()).description);
                ++lines;
  #ifdef HAVE_SWRESAMPLE
 -              if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate ()) {
 +              if (_film->audio_frame_rate() && _film->audio_frame_rate() != _film->target_audio_sample_rate ()) {
                        d << wxString::Format (
                                _("Audio will be resampled from %dHz to %dHz\n"),
 -                              _film->audio_stream()->sample_rate(),
 +                              _film->audio_frame_rate(),
                                _film->target_audio_sample_rate()
                                );
                        ++lines;
@@@ -842,17 -870,12 +854,17 @@@ FilmEditor::dcp_content_type_changed (w
  void
  FilmEditor::set_film (shared_ptr<Film> f)
  {
 -      _film = f;
 +      set_things_sensitive (f != 0);
  
 -      set_things_sensitive (_film != 0);
 +      if (_film == f) {
 +              return;
 +      }
 +      
 +      _film = f;
  
        if (_film) {
                _film->Changed.connect (bind (&FilmEditor::film_changed, this, _1));
 +              _film->ContentChanged.connect (bind (&FilmEditor::film_content_changed, this, _1, _2));
        }
  
        if (_film) {
        film_changed (Film::NAME);
        film_changed (Film::USE_DCI_NAME);
        film_changed (Film::CONTENT);
 -      film_changed (Film::TRUST_CONTENT_HEADER);
 +      film_changed (Film::TRUST_CONTENT_HEADERS);
        film_changed (Film::DCP_CONTENT_TYPE);
        film_changed (Film::FORMAT);
        film_changed (Film::CROP);
        film_changed (Film::SCALER);
        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);
 -      film_changed (Film::USE_CONTENT_AUDIO);
 +      film_changed (Film::AB);
+       film_changed (Film::TRIM_TYPE);
        film_changed (Film::AUDIO_GAIN);
        film_changed (Film::AUDIO_DELAY);
 -      film_changed (Film::STILL_DURATION);
        film_changed (Film::WITH_SUBTITLES);
        film_changed (Film::SUBTITLE_OFFSET);
        film_changed (Film::SUBTITLE_SCALE);
        film_changed (Film::COLOUR_LUT);
        film_changed (Film::J2K_BANDWIDTH);
        film_changed (Film::DCI_METADATA);
 -      film_changed (Film::SIZE);
 -      film_changed (Film::LENGTH);
 -      film_changed (Film::CONTENT_AUDIO_STREAMS);
 -      film_changed (Film::SUBTITLE_STREAMS);
 -      film_changed (Film::SOURCE_FRAME_RATE);
        film_changed (Film::DCP_FRAME_RATE);
 +      film_changed (Film::AUDIO_MAPPING);
 +
 +      film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::SUBTITLE_STREAMS);
 +      film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::SUBTITLE_STREAM);
 +      film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::AUDIO_STREAMS);
 +      film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::AUDIO_STREAM);
  }
  
  /** Updates the sensitivity of lots of widgets to a given value.
@@@ -907,30 -934,31 +920,31 @@@ FilmEditor::set_things_sensitive (bool 
        _edit_dci_button->Enable (s);
        _format->Enable (s);
        _content->Enable (s);
 -      _trust_content_header->Enable (s);
 +      _trust_content_headers->Enable (s);
 +      _content->Enable (s);
        _left_crop->Enable (s);
        _right_crop->Enable (s);
        _top_crop->Enable (s);
        _bottom_crop->Enable (s);
        _filters_button->Enable (s);
        _scaler->Enable (s);
 -      _audio_stream->Enable (s);
 +      _ffmpeg_audio_stream->Enable (s);
        _dcp_content_type->Enable (s);
        _dcp_frame_rate->Enable (s);
        _trim_start->Enable (s);
        _trim_end->Enable (s);
 -      _dcp_ab->Enable (s);
 +      _ab->Enable (s);
+       _trim_type->Enable (s);
        _colour_lut->Enable (s);
        _j2k_bandwidth->Enable (s);
        _audio_gain->Enable (s);
        _audio_gain_calculate_button->Enable (s);
        _show_audio->Enable (s);
        _audio_delay->Enable (s);
 -      _still_duration->Enable (s);
  
        setup_subtitle_control_sensitivity ();
 -      setup_audio_control_sensitivity ();
        setup_show_audio_sensitivity ();
 +      setup_content_button_sensitivity ();
  }
  
  /** Called when the `Edit filters' button has been clicked */
@@@ -977,6 -1005,40 +991,6 @@@ FilmEditor::audio_delay_changed (wxComm
        _film->set_audio_delay (_audio_delay->GetValue ());
  }
  
 -wxControl *
 -FilmEditor::video_control (wxControl* c)
 -{
 -      _video_controls.push_back (c);
 -      return c;
 -}
 -
 -wxControl *
 -FilmEditor::still_control (wxControl* c)
 -{
 -      _still_controls.push_back (c);
 -      return c;
 -}
 -
 -void
 -FilmEditor::setup_visibility ()
 -{
 -      ContentType c = VIDEO;
 -
 -      if (_film) {
 -              c = _film->content_type ();
 -      }
 -
 -      for (list<wxControl*>::iterator i = _video_controls.begin(); i != _video_controls.end(); ++i) {
 -              (*i)->Show (c == VIDEO);
 -      }
 -
 -      for (list<wxControl*>::iterator i = _still_controls.begin(); i != _still_controls.end(); ++i) {
 -              (*i)->Show (c == STILL);
 -      }
 -
 -      setup_notebook_size ();
 -}
 -
  void
  FilmEditor::setup_notebook_size ()
  {
        Fit ();
  }
  
 -void
 -FilmEditor::still_duration_changed (wxCommandEvent &)
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_still_duration (_still_duration->GetValue ());
 -}
 -
  void
  FilmEditor::trim_start_changed (wxCommandEvent &)
  {
@@@ -1045,7 -1117,20 +1059,7 @@@ FilmEditor::audio_gain_calculate_button
  void
  FilmEditor::setup_formats ()
  {
 -      ContentType c = VIDEO;
 -
 -      if (_film) {
 -              c = _film->content_type ();
 -      }
 -      
 -      _formats.clear ();
 -
 -      vector<Format const *> fmt = Format::all ();
 -      for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
 -              if (c == VIDEO || (c == STILL && dynamic_cast<VariableFormat const *> (*i))) {
 -                      _formats.push_back (*i);
 -              }
 -      }
 +      _formats = Format::all ();
  
        _format->Clear ();
        for (vector<Format const *>::iterator i = _formats.begin(); i != _formats.end(); ++i) {
@@@ -1070,7 -1155,7 +1084,7 @@@ FilmEditor::setup_subtitle_control_sens
  {
        bool h = false;
        if (_generally_sensitive && _film) {
 -              h = !_film->subtitle_streams().empty();
 +              h = !_film->ffmpeg_subtitle_streams().empty();
        }
        
        _with_subtitles->Enable (h);
                j = _film->with_subtitles ();
        }
        
 -      _subtitle_stream->Enable (j);
 +      _ffmpeg_subtitle_stream->Enable (j);
        _subtitle_offset->Enable (j);
        _subtitle_scale->Enable (j);
  }
  
 -void
 -FilmEditor::setup_audio_control_sensitivity ()
 -{
 -      _use_content_audio->Enable (_generally_sensitive && _film && !_film->content_audio_streams().empty());
 -      _use_external_audio->Enable (_generally_sensitive);
 -      
 -      bool const source = _generally_sensitive && _use_content_audio->GetValue();
 -      bool const external = _generally_sensitive && _use_external_audio->GetValue();
 -
 -      _audio_stream->Enable (source);
 -      for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
 -              _external_audio[i]->Enable (external);
 -      }
 -}
 -
  void
  FilmEditor::use_dci_name_toggled (wxCommandEvent &)
  {
@@@ -1111,84 -1211,73 +1125,84 @@@ FilmEditor::edit_dci_button_clicked (wx
  void
  FilmEditor::setup_streams ()
  {
 -      _audio_stream->Clear ();
 -      vector<shared_ptr<AudioStream> > a = _film->content_audio_streams ();
 -      for (vector<shared_ptr<AudioStream> >::iterator i = a.begin(); i != a.end(); ++i) {
 -              shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (*i);
 -              assert (ffa);
 -              _audio_stream->Append (std_to_wx (ffa->name()), new wxStringClientData (std_to_wx (ffa->to_string ())));
 +      if (!_film) {
 +              return;
 +      }
 +      
 +      _ffmpeg_audio_stream->Clear ();
 +      vector<FFmpegAudioStream> a = _film->ffmpeg_audio_streams ();
 +      for (vector<FFmpegAudioStream>::iterator i = a.begin(); i != a.end(); ++i) {
 +              _ffmpeg_audio_stream->Append (std_to_wx (i->name), new wxStringClientData (std_to_wx (boost::lexical_cast<string> (i->id))));
        }
        
 -      if (_film->use_content_audio() && _film->audio_stream()) {
 -              checked_set (_audio_stream, _film->audio_stream()->to_string());
 +      if (_film->ffmpeg_audio_stream()) {
 +              checked_set (_ffmpeg_audio_stream, boost::lexical_cast<string> (_film->ffmpeg_audio_stream()->id));
        }
  
 -      _subtitle_stream->Clear ();
 -      vector<shared_ptr<SubtitleStream> > s = _film->subtitle_streams ();
 -      for (vector<shared_ptr<SubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
 -              _subtitle_stream->Append (std_to_wx ((*i)->name()), new wxStringClientData (std_to_wx ((*i)->to_string ())));
 +      _ffmpeg_subtitle_stream->Clear ();
 +      vector<FFmpegSubtitleStream> s = _film->ffmpeg_subtitle_streams ();
 +      for (vector<FFmpegSubtitleStream>::iterator i = s.begin(); i != s.end(); ++i) {
 +              _ffmpeg_subtitle_stream->Append (std_to_wx (i->name), new wxStringClientData (std_to_wx (boost::lexical_cast<string> (i->id))));
        }
 -      if (_film->subtitle_stream()) {
 -              checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
 +      
 +      if (_film->ffmpeg_subtitle_stream()) {
 +              checked_set (_ffmpeg_subtitle_stream, boost::lexical_cast<string> (_film->ffmpeg_subtitle_stream()->id));
        } else {
 -              _subtitle_stream->SetSelection (wxNOT_FOUND);
 +              _ffmpeg_subtitle_stream->SetSelection (wxNOT_FOUND);
        }
  }
  
  void
 -FilmEditor::audio_stream_changed (wxCommandEvent &)
 +FilmEditor::ffmpeg_audio_stream_changed (wxCommandEvent &)
  {
        if (!_film) {
                return;
        }
  
 -      _film->set_content_audio_stream (
 -              audio_stream_factory (
 -                      string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ())),
 -                      Film::state_version
 -                      )
 -              );
 +      vector<FFmpegAudioStream> a = _film->ffmpeg_audio_streams ();
 +      vector<FFmpegAudioStream>::iterator i = a.begin ();
 +      string const s = string_client_data (_ffmpeg_audio_stream->GetClientObject (_ffmpeg_audio_stream->GetSelection ()));
 +      while (i != a.end() && lexical_cast<string> (i->id) != s) {
 +              ++i;
 +      }
 +
 +      if (i != a.end ()) {
 +              _film->set_ffmpeg_audio_stream (*i);
 +      }
  }
  
  void
 -FilmEditor::subtitle_stream_changed (wxCommandEvent &)
 +FilmEditor::ffmpeg_subtitle_stream_changed (wxCommandEvent &)
  {
        if (!_film) {
                return;
        }
  
 -      _film->set_subtitle_stream (
 -              subtitle_stream_factory (
 -                      string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ())),
 -                      Film::state_version
 -                      )
 -              );
 +      vector<FFmpegSubtitleStream> a = _film->ffmpeg_subtitle_streams ();
 +      vector<FFmpegSubtitleStream>::iterator i = a.begin ();
 +      string const s = string_client_data (_ffmpeg_subtitle_stream->GetClientObject (_ffmpeg_subtitle_stream->GetSelection ()));
 +      while (i != a.end() && lexical_cast<string> (i->id) != s) {
 +              ++i;
 +      }
 +
 +      if (i != a.end ()) {
 +              _film->set_ffmpeg_subtitle_stream (*i);
 +      }
  }
  
  void
  FilmEditor::setup_audio_details ()
  {
 -      if (!_film->content_audio_stream()) {
 +      if (!_film->ffmpeg_audio_stream()) {
                _audio->SetLabel (wxT (""));
        } else {
                wxString s;
 -              if (_film->audio_stream()->channels() == 1) {
 +              if (_film->audio_channels() == 1) {
                        s << _("1 channel");
                } else {
 -                      s << _film->audio_stream()->channels () << wxT (" ") << _("channels");
 +                      s << _film->audio_channels() << wxT (" ") << _("channels");
                }
 -              s << wxT (", ") << _film->audio_stream()->sample_rate() << _("Hz");
 +              s << wxT (", ") << _film->audio_frame_rate() << _("Hz");
                _audio->SetLabel (s);
        }
  
@@@ -1201,6 -1290,23 +1215,6 @@@ FilmEditor::active_jobs_changed (bool a
        set_things_sensitive (!a);
  }
  
 -void
 -FilmEditor::use_audio_changed (wxCommandEvent &)
 -{
 -      _film->set_use_content_audio (_use_content_audio->GetValue());
 -}
 -
 -void
 -FilmEditor::external_audio_changed (wxCommandEvent &)
 -{
 -      vector<string> a;
 -      for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
 -              a.push_back (wx_to_std (_external_audio[i]->GetPath()));
 -      }
 -
 -      _film->set_external_audio (a);
 -}
 -
  void
  FilmEditor::setup_dcp_name ()
  {
@@@ -1233,7 -1339,7 +1247,7 @@@ FilmEditor::best_dcp_frame_rate_clicke
                return;
        }
        
 -      _film->set_dcp_frame_rate (best_dcp_frame_rate (_film->source_frame_rate ()));
 +      _film->set_dcp_frame_rate (best_dcp_frame_rate (_film->video_frame_rate ()));
  }
  
  void
@@@ -1242,163 -1348,6 +1256,163 @@@ FilmEditor::setup_show_audio_sensitivit
        _show_audio->Enable (_film && _film->has_audio ());
  }
  
 +void
 +FilmEditor::setup_content ()
 +{
 +      string selected_summary;
 +      int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
 +      if (s != -1) {
 +              selected_summary = wx_to_std (_content->GetItemText (s));
 +      }
 +      
 +      _content->DeleteAllItems ();
 +
 +      ContentList content = _film->content ();
 +      for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
 +              int const t = _content->GetItemCount ();
 +              _content->InsertItem (t, std_to_wx ((*i)->summary ()));
 +              if ((*i)->summary() == selected_summary) {
 +                      _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
 +              }
 +      }
 +
 +      if (selected_summary.empty () && !content.empty ()) {
 +              /* Select the first item of content if non was selected before */
 +              _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
 +      }
 +}
 +
 +void
 +FilmEditor::content_add_clicked (wxCommandEvent &)
 +{
 +      wxFileDialog* d = new wxFileDialog (this);
 +      int const r = d->ShowModal ();
 +      d->Destroy ();
 +
 +      if (r != wxID_OK) {
 +              return;
 +      }
 +
 +      boost::filesystem::path p (wx_to_std (d->GetPath()));
 +
 +      if (ImageMagickContent::valid_file (p)) {
 +              _film->add_content (shared_ptr<ImageMagickContent> (new ImageMagickContent (p)));
 +      } else if (SndfileContent::valid_file (p)) {
 +              _film->add_content (shared_ptr<SndfileContent> (new SndfileContent (p)));
 +      } else {
 +              _film->add_content (shared_ptr<FFmpegContent> (new FFmpegContent (p)));
 +      }
 +      
 +}
 +
 +void
 +FilmEditor::content_remove_clicked (wxCommandEvent &)
 +{
 +      shared_ptr<Content> c = selected_content ();
 +      if (c) {
 +              _film->remove_content (c);
 +      }
 +}
 +
 +void
 +FilmEditor::content_activated (wxListEvent& ev)
 +{
 +      ContentList c = _film->content ();
 +      assert (ev.GetIndex() >= 0 && size_t (ev.GetIndex()) < c.size ());
 +
 +      edit_content (c[ev.GetIndex()]);
 +}
 +
 +void
 +FilmEditor::content_edit_clicked (wxCommandEvent &)
 +{
 +      shared_ptr<Content> c = selected_content ();
 +      if (!c) {
 +              return;
 +      }
 +
 +      edit_content (c);
 +}
 +
 +void
 +FilmEditor::edit_content (shared_ptr<Content> c)
 +{
 +      shared_ptr<ImageMagickContent> im = dynamic_pointer_cast<ImageMagickContent> (c);
 +      if (im) {
 +              ImageMagickContentDialog* d = new ImageMagickContentDialog (this, im);
 +              d->ShowModal ();
 +              d->Destroy ();
 +
 +              im->set_video_length (d->video_length() * 24);
 +      }
 +}
 +
 +void
 +FilmEditor::content_earlier_clicked (wxCommandEvent &)
 +{
 +      shared_ptr<Content> c = selected_content ();
 +      if (c) {
 +              _film->move_content_earlier (c);
 +      }
 +}
 +
 +void
 +FilmEditor::content_later_clicked (wxCommandEvent &)
 +{
 +      shared_ptr<Content> c = selected_content ();
 +      if (c) {
 +              _film->move_content_later (c);
 +      }
 +}
 +
 +void
 +FilmEditor::content_selection_changed (wxListEvent &)
 +{
 +        setup_content_button_sensitivity ();
 +      setup_content_information ();
 +}
 +
 +void
 +FilmEditor::setup_content_information ()
 +{
 +      shared_ptr<Content> c = selected_content ();
 +      if (!c) {
 +              _content_information->SetValue (wxT (""));
 +              return;
 +      }
 +
 +      _content_information->SetValue (std_to_wx (c->information ()));
 +}
 +
 +void
 +FilmEditor::setup_content_button_sensitivity ()
 +{
 +        _content_add->Enable (_generally_sensitive);
 +
 +      shared_ptr<Content> selection = selected_content ();
 +
 +        _content_edit->Enable (selection && _generally_sensitive && dynamic_pointer_cast<ImageMagickContent> (selection));
 +        _content_remove->Enable (selection && _generally_sensitive);
 +        _content_earlier->Enable (selection && _generally_sensitive);
 +        _content_later->Enable (selection && _generally_sensitive);
 +}
 +
 +shared_ptr<Content>
 +FilmEditor::selected_content ()
 +{
 +      int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
 +      if (s == -1) {
 +              return shared_ptr<Content> ();
 +      }
 +
 +      ContentList c = _film->content ();
 +      if (s < 0 || size_t (s) >= c.size ()) {
 +              return shared_ptr<Content> ();
 +      }
 +      
 +      return c[s];
 +}
 +
  void
  FilmEditor::setup_scaling_description ()
  {
  
        int lines = 0;
  
 -      if (_film->size().width && _film->size().height) {
 +      if (_film->video_size().width && _film->video_size().height) {
                d << wxString::Format (
                        _("Original video is %dx%d (%.2f:1)\n"),
 -                      _film->size().width, _film->size().height,
 -                      float (_film->size().width) / _film->size().height
 +                      _film->video_size().width, _film->video_size().height,
 +                      float (_film->video_size().width) / _film->video_size().height
                        );
                ++lines;
        }
  
        Crop const crop = _film->crop ();
        if (crop.left || crop.right || crop.top || crop.bottom) {
 -              libdcp::Size const cropped = _film->cropped_size (_film->size ());
 +              libdcp::Size const cropped = _film->cropped_size (_film->video_size ());
                d << wxString::Format (
                        _("Cropped to %dx%d (%.2f:1)\n"),
                        cropped.width, cropped.height,
  
        _scaling_description->SetLabel (d);
  }
+ void
+ FilmEditor::trim_type_changed (wxCommandEvent &)
+ {
+       _film->set_trim_type (_trim_type->GetSelection () == 0 ? Film::CPL : Film::ENCODE);
+ }
diff --combined src/wx/film_editor.h
index 0f3d8eb507a5eabc0c6e316f81fd7b03c4f6924c,e2a4d583641eb9ae23ea5d76afc0262b602be20e..ffffc1e76de853ffb03b045ff2c84d96517bb34e
@@@ -16,7 -16,7 +16,7 @@@
      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  
  */
 -
 + 
  /** @file src/film_editor.h
   *  @brief A wx widget to edit a film's metadata, and perform various functions.
   */
  #include "lib/film.h"
  
  class wxNotebook;
 +class wxListCtrl;
 +class wxListEvent;
  class Film;
  class AudioDialog;
 +class AudioMappingView;
  
  /** @class FilmEditor
   *  @brief A wx widget to edit a film's metadata, and perform various functions.
@@@ -44,12 -41,12 +44,12 @@@ public
        FilmEditor (boost::shared_ptr<Film>, wxWindow *);
  
        void set_film (boost::shared_ptr<Film>);
 -      void setup_visibility ();
  
        boost::signals2::signal<void (std::string)> FileChanged;
  
  private:
        void make_film_panel ();
 +      void make_content_panel ();
        void make_video_panel ();
        void make_audio_panel ();
        void make_subtitle_panel ();
        void right_crop_changed (wxCommandEvent &);
        void top_crop_changed (wxCommandEvent &);
        void bottom_crop_changed (wxCommandEvent &);
 -      void content_changed (wxCommandEvent &);
 -      void trust_content_header_changed (wxCommandEvent &);
 +      void trust_content_headers_changed (wxCommandEvent &);
 +      void content_selection_changed (wxListEvent &);
 +      void content_activated (wxListEvent &);
 +      void content_add_clicked (wxCommandEvent &);
 +      void content_remove_clicked (wxCommandEvent &);
 +      void content_edit_clicked (wxCommandEvent &);
 +      void content_earlier_clicked (wxCommandEvent &);
 +      void content_later_clicked (wxCommandEvent &);
 +      void imagemagick_video_length_changed (wxCommandEvent &);
        void format_changed (wxCommandEvent &);
        void trim_start_changed (wxCommandEvent &);
        void trim_end_changed (wxCommandEvent &);
+       void trim_type_changed (wxCommandEvent &);
        void dcp_content_type_changed (wxCommandEvent &);
 -      void dcp_ab_toggled (wxCommandEvent &);
 +      void ab_toggled (wxCommandEvent &);
        void scaler_changed (wxCommandEvent &);
        void audio_gain_changed (wxCommandEvent &);
        void audio_gain_calculate_button_clicked (wxCommandEvent &);
        void subtitle_scale_changed (wxCommandEvent &);
        void colour_lut_changed (wxCommandEvent &);
        void j2k_bandwidth_changed (wxCommandEvent &);
 -      void still_duration_changed (wxCommandEvent &);
 -      void audio_stream_changed (wxCommandEvent &);
 -      void subtitle_stream_changed (wxCommandEvent &);
 -      void use_audio_changed (wxCommandEvent &);
 -      void external_audio_changed (wxCommandEvent &);
 +      void ffmpeg_audio_stream_changed (wxCommandEvent &);
 +      void ffmpeg_subtitle_stream_changed (wxCommandEvent &);
        void dcp_frame_rate_changed (wxCommandEvent &);
        void best_dcp_frame_rate_clicked (wxCommandEvent &);
 +      void edit_filters_clicked (wxCommandEvent &);
  
        /* Handle changes to the model */
        void film_changed (Film::Property);
 -
 -      /* Button clicks */
 -      void edit_filters_clicked (wxCommandEvent &);
 +      void film_content_changed (boost::weak_ptr<Content>, int);
  
        void set_things_sensitive (bool);
        void setup_formats ();
        void setup_subtitle_control_sensitivity ();
 -      void setup_audio_control_sensitivity ();
        void setup_streams ();
        void setup_audio_details ();
        void setup_dcp_name ();
        void setup_scaling_description ();
        void setup_notebook_size ();
        void setup_frame_rate_description ();
 +      void setup_content ();
 +      void setup_format ();
 +      void setup_length ();
 +      void setup_content_information ();
 +      void setup_content_button_sensitivity ();
        
 -      wxControl* video_control (wxControl *);
 -      wxControl* still_control (wxControl *);
 -
        void active_jobs_changed (bool);
 +      boost::shared_ptr<Content> selected_content ();
 +      void edit_content (boost::shared_ptr<Content>);
  
        wxNotebook* _notebook;
        wxPanel* _film_panel;
        wxSizer* _film_sizer;
 +      wxPanel* _content_panel;
 +      wxSizer* _content_sizer;
        wxPanel* _video_panel;
        wxSizer* _video_sizer;
        wxPanel* _audio_panel;
        wxTextCtrl* _name;
        wxStaticText* _dcp_name;
        wxCheckBox* _use_dci_name;
 +      wxListCtrl* _content;
 +      wxButton* _content_add;
 +      wxButton* _content_remove;
 +      wxButton* _content_edit;
 +      wxButton* _content_earlier;
 +      wxButton* _content_later;
 +      wxTextCtrl* _content_information;
        wxButton* _edit_dci_button;
 -      /** The Film's format */
        wxChoice* _format;
 +      wxStaticText* _format_description;
 +      wxCheckBox* _trust_content_headers;
        wxStaticText* _scaling_description;
 -      /** The Film's content file */
 -      wxFilePickerCtrl* _content;
 -      wxCheckBox* _trust_content_header;
 -      /** The Film's left crop */
        wxSpinCtrl* _left_crop;
 -      /** The Film's right crop */
        wxSpinCtrl* _right_crop;
 -      /** The Film's top crop */
        wxSpinCtrl* _top_crop;
 -      /** The Film's bottom crop */
        wxSpinCtrl* _bottom_crop;
 -      /** Currently-applied filters */
        wxStaticText* _filters;
 -      /** Button to open the filters dialogue */
        wxButton* _filters_button;
 -      /** The Film's scaler */
        wxChoice* _scaler;
 -      wxRadioButton* _use_content_audio;
 -      wxChoice* _audio_stream;
 -      wxRadioButton* _use_external_audio;
 -      wxFilePickerCtrl* _external_audio[MAX_AUDIO_CHANNELS];
 -      /** The Film's audio gain */
        wxSpinCtrl* _audio_gain;
 -      /** A button to open the gain calculation dialogue */
        wxButton* _audio_gain_calculate_button;
        wxButton* _show_audio;
 -      /** The Film's audio delay */
        wxSpinCtrl* _audio_delay;
 +      wxChoice* _ffmpeg_audio_stream;
 +      AudioMappingView* _audio_mapping;
        wxCheckBox* _with_subtitles;
 -      wxChoice* _subtitle_stream;
 +      wxChoice* _ffmpeg_subtitle_stream;
        wxSpinCtrl* _subtitle_offset;
        wxSpinCtrl* _subtitle_scale;
        wxChoice* _colour_lut;
        wxSpinCtrl* _j2k_bandwidth;
 -      /** The Film's DCP content type */
        wxChoice* _dcp_content_type;
 -      /** The Film's source frame rate */
 -      wxStaticText* _source_frame_rate;
        wxChoice* _dcp_frame_rate;
        wxButton* _best_dcp_frame_rate;
        wxStaticText* _frame_rate_description;
 -      /** The Film's length */
        wxStaticText* _length;
        /** The Film's audio details */
        wxStaticText* _audio;
 -      /** The Film's duration for still sources */
 -      wxSpinCtrl* _still_duration;
  
        wxSpinCtrl* _trim_start;
        wxSpinCtrl* _trim_end;
+       wxChoice* _trim_type;
        /** Selector to generate an A/B comparison DCP */
 -      wxCheckBox* _dcp_ab;
 -
 -      std::list<wxControl*> _video_controls;
 -      std::list<wxControl*> _still_controls;
 +      wxCheckBox* _ab;
  
        std::vector<Format const *> _formats;
  
diff --combined test/test.cc
index 39a921d3682149a88f7485c5cc584b5688512b53,ac7ab85fe5bd2df8a205181dcfe778c722b7553c..a2c0de25048443675eb61dc55c4a6a80c8fdb953
  #include "scaler.h"
  #include "ffmpeg_decoder.h"
  #include "sndfile_decoder.h"
 +#include "dcp_content_type.h"
+ #include "trimmer.h"
  #define BOOST_TEST_DYN_LINK
 -#define BOOST_TEST_MODULE dvdomatic_test
 +#define BOOST_TEST_MODULE dcpomatic_test
  #include <boost/test/unit_test.hpp>
  
  using std::string;
@@@ -90,7 -90,7 +91,7 @@@ new_test_film (string name
  BOOST_AUTO_TEST_CASE (make_black_test)
  {
        /* This needs to happen in the first test */
 -      dvdomatic_setup ();
 +      dcpomatic_setup ();
  
        libdcp::Size in_size (512, 512);
        libdcp::Size out_size (1024, 1024);
        }
  }
  
+ shared_ptr<AudioBuffers> trimmer_test_last;
+ void
+ trimmer_test_helper (shared_ptr<AudioBuffers> audio)
+ {
+       trimmer_test_last = audio;
+ }
+ /** Test the audio handling of the Trimmer */
+ BOOST_AUTO_TEST_CASE (trimmer_test)
+ {
+       Trimmer trimmer (shared_ptr<Log> (), 25, 75, 200, 48000, 25, 25);
+       trimmer.Audio.connect (bind (&trimmer_test_helper, _1));
+       /* 21 video frames-worth of audio frames; should be completely stripped */
+       trimmer_test_last.reset ();
+       shared_ptr<AudioBuffers> audio (new AudioBuffers (6, 21 * 1920));
+       trimmer.process_audio (audio);
+       BOOST_CHECK (trimmer_test_last == 0);
+       /* 42 more video frames-worth, 4 should be stripped from the start */
+       audio.reset (new AudioBuffers (6, 42 * 1920));
+       trimmer.process_audio (audio);
+       BOOST_CHECK (trimmer_test_last);
+       BOOST_CHECK_EQUAL (trimmer_test_last->frames(), 38 * 1920);
+       /* 42 more video frames-worth, should be kept as-is */
+       trimmer_test_last.reset ();
+       audio.reset (new AudioBuffers (6, 42 * 1920));
+       trimmer.process_audio (audio);
+       BOOST_CHECK (trimmer_test_last);
+       BOOST_CHECK_EQUAL (trimmer_test_last->frames(), 42 * 1920);
+       /* 25 more video frames-worth, 5 should be trimmed from the end */
+       trimmer_test_last.reset ();
+       audio.reset (new AudioBuffers (6, 25 * 1920));
+       trimmer.process_audio (audio);
+       BOOST_CHECK (trimmer_test_last);
+       BOOST_CHECK_EQUAL (trimmer_test_last->frames(), 20 * 1920);
+       /* Now some more; all should be trimmed */
+       trimmer_test_last.reset ();
+       audio.reset (new AudioBuffers (6, 100 * 1920));
+       trimmer.process_audio (audio);
+       BOOST_CHECK (trimmer_test_last == 0);
+ }
  BOOST_AUTO_TEST_CASE (film_metadata_test)
  {
        setup_test_config ();
        BOOST_CHECK (f->filters ().empty());
  
        f->set_name ("fred");
 -      BOOST_CHECK_THROW (f->set_content ("jim"), OpenFileError);
 +//    BOOST_CHECK_THROW (f->set_content ("jim"), OpenFileError);
        f->set_dcp_content_type (DCPContentType::from_pretty_name ("Short"));
        f->set_format (Format::from_nickname ("Flat"));
        f->set_left_crop (1);
        f->set_filters (f_filters);
        f->set_trim_start (42);
        f->set_trim_end (99);
 -      f->set_dcp_ab (true);
 +      f->set_ab (true);
        f->write_metadata ();
  
        stringstream s;
        BOOST_CHECK_EQUAL (g_filters.back(), Filter::from_id ("unsharp"));
        BOOST_CHECK_EQUAL (g->trim_start(), 42);
        BOOST_CHECK_EQUAL (g->trim_end(), 99);
 -      BOOST_CHECK_EQUAL (g->dcp_ab(), true);
 +      BOOST_CHECK_EQUAL (g->ab(), true);
        
        g->write_metadata ();
        BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0);
  }
  
 -BOOST_AUTO_TEST_CASE (stream_test)
 -{
 -      FFmpegAudioStream a ("ffmpeg 4 44100 1 hello there world", boost::optional<int> (1));
 -      BOOST_CHECK_EQUAL (a.id(), 4);
 -      BOOST_CHECK_EQUAL (a.sample_rate(), 44100);
 -      BOOST_CHECK_EQUAL (a.channel_layout(), 1);
 -      BOOST_CHECK_EQUAL (a.name(), "hello there world");
 -      BOOST_CHECK_EQUAL (a.to_string(), "ffmpeg 4 44100 1 hello there world");
 -
 -      SndfileStream e ("external 44100 1", boost::optional<int> (1));
 -      BOOST_CHECK_EQUAL (e.sample_rate(), 44100);
 -      BOOST_CHECK_EQUAL (e.channel_layout(), 1);
 -      BOOST_CHECK_EQUAL (e.to_string(), "external 44100 1");
 -
 -      SubtitleStream s ("5 a b c", boost::optional<int> (1));
 -      BOOST_CHECK_EQUAL (s.id(), 5);
 -      BOOST_CHECK_EQUAL (s.name(), "a b c");
 -
 -      shared_ptr<AudioStream> ff = audio_stream_factory ("ffmpeg 4 44100 1 hello there world", boost::optional<int> (1));
 -      shared_ptr<FFmpegAudioStream> cff = dynamic_pointer_cast<FFmpegAudioStream> (ff);
 -      BOOST_CHECK (cff);
 -      BOOST_CHECK_EQUAL (cff->id(), 4);
 -      BOOST_CHECK_EQUAL (cff->sample_rate(), 44100);
 -      BOOST_CHECK_EQUAL (cff->channel_layout(), 1);
 -      BOOST_CHECK_EQUAL (cff->name(), "hello there world");
 -      BOOST_CHECK_EQUAL (cff->to_string(), "ffmpeg 4 44100 1 hello there world");
 -
 -      shared_ptr<AudioStream> fe = audio_stream_factory ("external 44100 1", boost::optional<int> (1));
 -      BOOST_CHECK_EQUAL (fe->sample_rate(), 44100);
 -      BOOST_CHECK_EQUAL (fe->channel_layout(), 1);
 -      BOOST_CHECK_EQUAL (fe->to_string(), "external 44100 1");
 -}
 -
  BOOST_AUTO_TEST_CASE (format_test)
  {
        Format::setup_formats ();
        
        Format const * f = Format::from_nickname ("Flat");
        BOOST_CHECK (f);
 -      BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 185);
 +//    BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 185);
        
        f = Format::from_nickname ("Scope");
        BOOST_CHECK (f);
 -      BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 239);
 -}
 -
 -/* Test VariableFormat-based scaling of content */
 -BOOST_AUTO_TEST_CASE (scaling_test)
 -{
 -      shared_ptr<Film> film (new Film (test_film_dir ("scaling_test").string(), false));
 -
 -      /* 4:3 ratio */
 -      film->set_size (libdcp::Size (320, 240));
 -
 -      /* This format should preserve aspect ratio of the source */
 -      Format const * format = Format::from_id ("var-185");
 -
 -      /* We should have enough padding that the result is 4:3,
 -         which would be 1440 pixels.
 -      */
 -      BOOST_CHECK_EQUAL (format->dcp_padding (film), (1998 - 1440) / 2);
 -      
 -      /* This crops it to 1.291666667 */
 -      film->set_left_crop (5);
 -      film->set_right_crop (5);
 -
 -      /* We should now have enough padding that the result is 1.29166667,
 -         which would be 1395 pixels.
 -      */
 -      BOOST_CHECK_EQUAL (format->dcp_padding (film), rint ((1998 - 1395) / 2.0));
 +//    BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 239);
  }
  
  BOOST_AUTO_TEST_CASE (util_test)
@@@ -323,6 -431,17 +373,6 @@@ BOOST_AUTO_TEST_CASE (md5_digest_test
        BOOST_CHECK_THROW (md5_digest ("foobar"), OpenFileError);
  }
  
 -BOOST_AUTO_TEST_CASE (paths_test)
 -{
 -      shared_ptr<Film> f = new_test_film ("paths_test");
 -      f->set_directory ("build/test/a/b/c/d/e");
 -
 -      f->_content = "/foo/bar/baz";
 -      BOOST_CHECK_EQUAL (f->content_path(), "/foo/bar/baz");
 -      f->_content = "foo/bar/baz";
 -      BOOST_CHECK_EQUAL (f->content_path(), "build/test/a/b/c/d/e/foo/bar/baz");
 -}
 -
  void
  do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* description, shared_ptr<EncodedData> locally_encoded)
  {
@@@ -392,7 -511,7 +442,7 @@@ BOOST_AUTO_TEST_CASE (client_server_tes
        new thread (boost::bind (&Server::run, server, 2));
  
        /* Let the server get itself ready */
 -      dvdomatic_sleep (1);
 +      dcpomatic_sleep (1);
  
        ServerDescription description ("localhost", 2);
  
@@@ -414,14 -533,14 +464,14 @@@ BOOST_AUTO_TEST_CASE (make_dcp_test
  {
        shared_ptr<Film> film = new_test_film ("make_dcp_test");
        film->set_name ("test_film2");
 -      film->set_content ("../../../test/test.mp4");
 +//    film->set_content ("../../../test/test.mp4");
        film->set_format (Format::from_nickname ("Flat"));
        film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
        film->make_dcp ();
        film->write_metadata ();
  
        while (JobManager::instance()->work_to_do ()) {
 -              dvdomatic_sleep (1);
 +              dcpomatic_sleep (1);
        }
        
        BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
@@@ -435,7 -554,7 +485,7 @@@ BOOST_AUTO_TEST_CASE (have_dcp_test
        BOOST_CHECK (f.have_dcp());
  
        p /= f.dcp_name();
-       p /= "video.mxf";
+       p /= f.dcp_video_mxf_filename();
        boost::filesystem::remove (p);
        BOOST_CHECK (!f.have_dcp ());
  }
@@@ -444,15 -563,15 +494,15 @@@ BOOST_AUTO_TEST_CASE (make_dcp_with_ran
  {
        shared_ptr<Film> film = new_test_film ("make_dcp_with_range_test");
        film->set_name ("test_film3");
 -      film->set_content ("../../../test/test.mp4");
 -      film->examine_content ();
 +//    film->set_content ("../../../test/test.mp4");
 +//    film->examine_content ();
        film->set_format (Format::from_nickname ("Flat"));
        film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
        film->set_trim_end (42);
        film->make_dcp ();
  
        while (JobManager::instance()->work_to_do() && !JobManager::instance()->errors()) {
 -              dvdomatic_sleep (1);
 +              dcpomatic_sleep (1);
        }
  
        BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
@@@ -606,44 -725,44 +656,44 @@@ BOOST_AUTO_TEST_CASE (audio_sampling_ra
        Config::instance()->set_allowed_dcp_frame_rates (afr);
  
        shared_ptr<Film> f = new_test_film ("audio_sampling_rate_test");
 -      f->set_source_frame_rate (24);
 +//    f->set_source_frame_rate (24);
        f->set_dcp_frame_rate (24);
  
 -      f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
 +//    f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000);
  
 -      f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
 +//    f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000);
  
 -      f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0)));
 +//    f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 96000);
  
 -      f->set_source_frame_rate (23.976);
 +//    f->set_source_frame_rate (23.976);
        f->set_dcp_frame_rate (best_dcp_frame_rate (23.976));
 -      f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
 +//    f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952);
  
 -      f->set_source_frame_rate (29.97);
 +//    f->set_source_frame_rate (29.97);
        f->set_dcp_frame_rate (best_dcp_frame_rate (29.97));
        BOOST_CHECK_EQUAL (f->dcp_frame_rate (), 30);
 -      f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
 +//    f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952);
  
 -      f->set_source_frame_rate (25);
 +//    f->set_source_frame_rate (25);
        f->set_dcp_frame_rate (24);
 -      f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
 +//    f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 50000);
  
 -      f->set_source_frame_rate (25);
 +//    f->set_source_frame_rate (25);
        f->set_dcp_frame_rate (24);
 -      f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
 +//    f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 50000);
  
        /* Check some out-there conversions (not the best) */
        
 -      f->set_source_frame_rate (14.99);
 +//    f->set_source_frame_rate (14.99);
        f->set_dcp_frame_rate (25);
 -      f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
 +//    f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
        /* The FrameRateConversion within target_audio_sample_rate should choose to double-up
           the 14.99 fps video to 30 and then run it slow at 25.
        */
@@@ -689,10 -808,10 +739,10 @@@ BOOST_AUTO_TEST_CASE (job_manager_test
        shared_ptr<TestJob> a (new TestJob (f));
  
        JobManager::instance()->add (a);
 -      dvdomatic_sleep (1);
 +      dcpomatic_sleep (1);
        BOOST_CHECK_EQUAL (a->running (), true);
        a->set_finished_ok ();
 -      dvdomatic_sleep (2);
 +      dcpomatic_sleep (2);
        BOOST_CHECK_EQUAL (a->finished_ok(), true);
  }
  
@@@ -794,3 -913,4 +844,4 @@@ BOOST_AUTO_TEST_CASE (aligned_image_tes
        delete t;
        delete u;
  }