Merge master and multifarious hackery.
authorCarl Hetherington <cth@carlh.net>
Sat, 25 May 2013 00:07:35 +0000 (01:07 +0100)
committerCarl Hetherington <cth@carlh.net>
Sat, 25 May 2013 00:07:35 +0000 (01:07 +0100)
33 files changed:
1  2 
cscript
src/lib/ab_transcode_job.cc
src/lib/audio_decoder.cc
src/lib/dcp_video_frame.cc
src/lib/dcp_video_frame.h
src/lib/encoder.cc
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_decoder.h
src/lib/film.cc
src/lib/film.h
src/lib/filter_graph.cc
src/lib/filter_graph.h
src/lib/image.h
src/lib/player.cc
src/lib/po/fr_FR.po
src/lib/server.cc
src/lib/sndfile_decoder.cc
src/lib/wscript
src/tools/dcpomatic_cli.cc
src/wx/film_editor.cc
src/wx/film_viewer.cc
test/client_server_test.cc
test/dcp_test.cc
test/film_metadata_test.cc
test/format_test.cc
test/frame_rate_test.cc
test/job_test.cc
test/pixel_formats_test.cc
test/stream_test.cc
test/test.cc
wscript

diff --combined cscript
index 17f267628c3e1550698d6b174400028e77dda8d4,521bc54f9c20691ab214c0ba5fed3741a6a1eead..ffbca416880d87703525b4aa4c759b1f5d672c6d
+++ b/cscript
@@@ -7,9 -7,8 +7,9 @@@ def dependencies(target)
          return ()
      else:
          return (('openjpeg-cdist', None),
-                 ('ffmpeg-cdist', '488d5d4496af5e3a3b9d31d6b221e8eeada6b77e'),
 +                ('libcxml', None),
 -                ('libdcp', 'v0.49'))
+                 ('ffmpeg-cdist', '7a23ec9c771184ab563cfe24ad9b427f38368961'),
 +                ('libdcp', None))
  
  def build(env, target):
      cmd = './waf configure --prefix=%s' % env.work_dir_cscript()
@@@ -43,14 -42,14 +43,14 @@@ def package(env, target, version)
          shutil.copyfile('builds/control-%s-%d' % (target.version, target.bits), 'debian/control')
          env.command('./waf dist')
          f = open('debian/files', 'w')
 -        print >>f,'dvdomatic_%s-1_%s.deb video extra' % (version, cpu)
 +        print >>f,'dcpomatic_%s-1_%s.deb video extra' % (version, cpu)
          shutil.rmtree('build/deb', ignore_errors=True)
  
          os.makedirs('build/deb')
          os.chdir('build/deb')
 -        shutil.move('../../dvdomatic-%s.tar.bz2' % version, 'dvdomatic_%s.orig.tar.bz2' % version)
 -        env.command('tar xjf dvdomatic_%s.orig.tar.bz2' % version)
 -        os.chdir('dvdomatic-%s' % version)
 +        shutil.move('../../dcpomatic-%s.tar.bz2' % version, 'dcpomatic_%s.orig.tar.bz2' % version)
 +        env.command('tar xjf dcpomatic_%s.orig.tar.bz2' % version)
 +        os.chdir('dcpomatic-%s' % version)
          env.command('dch -b -v %s-1 "New upstream release."' % version)
          env.set('CDIST_LINKFLAGS', env.get('LINKFLAGS'))
          env.set('CDIST_CXXFLAGS', env.get('CXXFLAGS'))
@@@ -65,9 -64,9 +65,9 @@@
  
  def make_pot(env):
      env.command('./waf pot')
 -    return [os.path.abspath('build/src/lib/libdvdomatic.pot'),
 -            os.path.abspath('build/src/wx/libdvdomatic-wx.pot'),
 -          os.path.abspath('build/src/tools/dvdomatic.pot')]
 +    return [os.path.abspath('build/src/lib/libdcpomatic.pot'),
 +            os.path.abspath('build/src/wx/libdcpomatic-wx.pot'),
 +          os.path.abspath('build/src/tools/dcpomatic.pot')]
  
  def make_manual(env):
      os.chdir('doc/manual')
index 2bdff47de684827ab0e0ec15664737cb624609ed,4ffdd9af6b815cbe6c572d1e72a336e82ce0445b..9a883fdd93773a87516e47b95c4448b8943f9602
@@@ -32,13 -32,15 +32,14 @@@ using std::string
  using boost::shared_ptr;
  
  /** @param f Film to compare.
 - *  @param o Decode options.
   */
 -ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, DecodeOptions o)
 +ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f)
        : Job (f)
 -      , _decode_opt (o)
  {
        _film_b.reset (new Film (*_film));
        _film_b->set_scaler (Config::instance()->reference_scaler ());
--      _film_b->set_filters (Config::instance()->reference_filters ());
++      /* XXX */
++//    _film_b->set_filters (Config::instance()->reference_filters ());
  }
  
  string
@@@ -52,7 -54,7 +53,7 @@@ ABTranscodeJob::run (
  {
        try {
                /* _film_b is the one with reference filters */
 -              ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film)));
 +              ABTranscoder w (_film_b, _film, shared_from_this ());
                w.go ();
                set_progress (1);
                set_state (FINISHED_OK);
diff --combined src/lib/audio_decoder.cc
index 8950e15465d3602673ff75bcae43f5f5e129a49c,a54c14843927449b4a62f00665ea83020a53bf70..9b8d15bf163927c69cbe201e5c7427825013db76
  */
  
  #include "audio_decoder.h"
 -#include "stream.h"
 +#include "audio_buffers.h"
 +#include "exceptions.h"
 +#include "log.h"
  
 +#include "i18n.h"
 +
 +using std::stringstream;
++using std::list;
++using std::pair;
  using boost::optional;
  using boost::shared_ptr;
  
 -AudioDecoder::AudioDecoder (shared_ptr<Film> f, DecodeOptions o)
 -      : Decoder (f, o)
 +AudioDecoder::AudioDecoder (shared_ptr<const Film> f, shared_ptr<const AudioContent> c)
 +      : Decoder (f)
 +      , _next_audio (0)
 +      , _audio_content (c)
  {
 +      if (_audio_content->content_audio_frame_rate() != _audio_content->output_audio_frame_rate()) {
 +
 +              shared_ptr<const Film> film = _film.lock ();
 +              assert (film);
 +
 +              stringstream s;
 +              s << String::compose (
 +                      "Will resample audio from %1 to %2",
 +                      _audio_content->content_audio_frame_rate(), _audio_content->output_audio_frame_rate()
 +                      );
 +              
 +              film->log()->log (s.str ());
 +
 +              /* We will be using planar float data when we call the
 +                 resampler.  As far as I can see, the audio channel
 +                 layout is not necessary for our purposes; it seems
 +                 only to be used get the number of channels and
 +                 decide if rematrixing is needed.  It won't be, since
 +                 input and output layouts are the same.
 +              */
 +
 +              _swr_context = swr_alloc_set_opts (
 +                      0,
 +                      av_get_default_channel_layout (MAX_AUDIO_CHANNELS),
 +                      AV_SAMPLE_FMT_FLTP,
 +                      _audio_content->output_audio_frame_rate(),
 +                      av_get_default_channel_layout (MAX_AUDIO_CHANNELS),
 +                      AV_SAMPLE_FMT_FLTP,
 +                      _audio_content->content_audio_frame_rate(),
 +                      0, 0
 +                      );
 +              
 +              swr_init (_swr_context);
 +      } else {
 +              _swr_context = 0;
 +      }
 +}
  
 +AudioDecoder::~AudioDecoder ()
 +{
 +      if (_swr_context) {
 +              swr_free (&_swr_context);
 +      }
  }
 +      
  
 +#if 0
  void
 -AudioDecoder::set_audio_stream (shared_ptr<AudioStream> s)
 +AudioDecoder::process_end ()
  {
 -      _audio_stream = s;
 +      if (_swr_context) {
 +
 +              shared_ptr<const Film> film = _film.lock ();
 +              assert (film);
 +              
 +              shared_ptr<AudioBuffers> out (new AudioBuffers (film->audio_mapping().dcp_channels(), 256));
 +                      
 +              while (1) {
 +                      int const frames = swr_convert (_swr_context, (uint8_t **) out->data(), 256, 0, 0);
 +
 +                      if (frames < 0) {
 +                              throw EncodeError (_("could not run sample-rate converter"));
 +                      }
 +
 +                      if (frames == 0) {
 +                              break;
 +                      }
 +
 +                      out->set_frames (frames);
 +                      _writer->write (out);
 +              }
 +
 +      }
  }
-       shared_ptr<AudioBuffers> dcp_mapped (film->dcp_audio_channels(), data->frames());
 +#endif
 +
 +void
 +AudioDecoder::audio (shared_ptr<const AudioBuffers> data, Time time)
 +{
 +      /* Maybe resample */
 +      if (_swr_context) {
 +
 +              /* Compute the resampled frames count and add 32 for luck */
 +              int const max_resampled_frames = ceil (
 +                      (int64_t) data->frames() * _audio_content->output_audio_frame_rate() / _audio_content->content_audio_frame_rate()
 +                      ) + 32;
 +
 +              shared_ptr<AudioBuffers> resampled (new AudioBuffers (data->channels(), max_resampled_frames));
 +
 +              /* Resample audio */
 +              int const resampled_frames = swr_convert (
 +                      _swr_context, (uint8_t **) resampled->data(), max_resampled_frames, (uint8_t const **) data->data(), data->frames()
 +                      );
 +              
 +              if (resampled_frames < 0) {
 +                      throw EncodeError (_("could not run sample-rate converter"));
 +              }
 +
 +              resampled->set_frames (resampled_frames);
 +              
 +              /* And point our variables at the resampled audio */
 +              data = resampled;
 +      }
 +
 +      shared_ptr<const Film> film = _film.lock ();
 +      assert (film);
 +      
 +      /* Remap channels */
-               dcp_mapped->accumulate (data, i->first, i->second);
++      shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (film->dcp_audio_channels(), data->frames()));
 +      dcp_mapped->make_silent ();
 +      list<pair<int, libdcp::Channel> > map = _audio_content->audio_mapping().content_to_dcp ();
 +      for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
++              dcp_mapped->accumulate_channel (data.get(), i->first, i->second);
 +      }
 +
 +      Audio (dcp_mapped, time);
 +      _next_audio = time + film->audio_frames_to_time (data->frames());
 +}
 +
 +              
index da51665d170809972547d227fa2735f51f18bd38,d674393a98023e2e02459c1bc6b8ec6c966d2a42..1c1838df79403f046f62511ec84bbe04273e4897
@@@ -47,6 -47,7 +47,6 @@@
  #include "dcp_video_frame.h"
  #include "lut.h"
  #include "config.h"
 -#include "options.h"
  #include "exceptions.h"
  #include "server.h"
  #include "util.h"
@@@ -79,7 -80,7 +79,7 @@@ using libdcp::Size
  DCPVideoFrame::DCPVideoFrame (
        shared_ptr<const Image> yuv, shared_ptr<Subtitle> sub,
        Size out, int p, int subtitle_offset, float subtitle_scale,
--      Scaler const * s, int f, int dcp_fps, string pp, int clut, int bw, shared_ptr<Log> l
++      Scaler const * s, int f, int dcp_fps, int clut, int bw, shared_ptr<Log> l
        )
        : _input (yuv)
        , _subtitle (sub)
@@@ -90,7 -91,7 +90,6 @@@
        , _scaler (s)
        , _frame (f)
        , _frames_per_second (dcp_fps)
--      , _post_process (pp)
        , _colour_lut (clut)
        , _j2k_bandwidth (bw)
        , _log (l)
@@@ -156,10 -157,10 +155,6 @@@ DCPVideoFrame::~DCPVideoFrame (
  shared_ptr<EncodedData>
  DCPVideoFrame::encode_locally ()
  {
--      if (!_post_process.empty ()) {
--              _input = _input->post_process (_post_process, true);
--      }
--      
        shared_ptr<Image> prepared = _input->scale_and_convert_to_rgb (_out_size, _padding, _scaler, true);
  
        if (_subtitle) {
        _parameters->tcp_numlayers++;
        _parameters->cp_disto_alloc = 1;
        _parameters->cp_rsiz = CINEMA2K;
 -      _parameters->cp_comment = strdup (N_("DVD-o-matic"));
 +      _parameters->cp_comment = strdup (N_("DCP-o-matic"));
        _parameters->cp_cinema = CINEMA2K_24;
  
        /* 3 components, so use MCT */
@@@ -333,10 -334,10 +328,6 @@@ DCPVideoFrame::encode_remotely (ServerD
          << N_("frame ") << _frame << N_("\n")
          << N_("frames_per_second ") << _frames_per_second << N_("\n");
  
--      if (!_post_process.empty()) {
--              s << N_("post_process ") << _post_process << N_("\n");
--      }
--      
        s << N_("colour_lut ") << _colour_lut << N_("\n")
          << N_("j2k_bandwidth ") << _j2k_bandwidth << N_("\n");
  
index 4ceb07d2649905a87a80ebf9daa6712e97fc8d96,4ceb07d2649905a87a80ebf9daa6712e97fc8d96..ba49c95a43364a125452c8e515aa33c7d8737dda
@@@ -107,7 -107,7 +107,7 @@@ class DCPVideoFram
  public:
        DCPVideoFrame (
                boost::shared_ptr<const Image>, boost::shared_ptr<Subtitle>, libdcp::Size,
--              int, int, float, Scaler const *, int, int, std::string, int, int, boost::shared_ptr<Log>
++              int, int, float, Scaler const *, int, int, int, int, boost::shared_ptr<Log>
                );
        
        virtual ~DCPVideoFrame ();
@@@ -131,7 -131,7 +131,6 @@@ private
        Scaler const * _scaler;          ///< scaler to use
        int _frame;                      ///< frame index within the DCP's intrinsic duration
        int _frames_per_second;          ///< Frames per second that we will use for the DCP
--      std::string _post_process;       ///< FFmpeg post-processing string to use
        int _colour_lut;                 ///< Colour look-up table to use
        int _j2k_bandwidth;              ///< J2K bandwidth to use
  
diff --combined src/lib/encoder.cc
index 52927c5d324f054250e939f96d9e4b841153e2a1,8549962ff01807505683266e778d16c4c8eaef43..270bf3d43b736fe888f23a537cdf1c1c68d0dfa5
@@@ -27,6 -27,7 +27,6 @@@
  #include <libdcp/picture_asset.h>
  #include "encoder.h"
  #include "util.h"
 -#include "options.h"
  #include "film.h"
  #include "log.h"
  #include "exceptions.h"
@@@ -37,9 -38,6 +37,9 @@@
  #include "format.h"
  #include "cross.h"
  #include "writer.h"
 +#include "player.h"
 +#include "audio_mapping.h"
 +#include "container.h"
  
  #include "i18n.h"
  
@@@ -50,16 -48,18 +50,16 @@@ using std::vector
  using std::list;
  using std::cout;
  using std::make_pair;
 -using namespace boost;
 +using boost::shared_ptr;
 +using boost::optional;
  
  int const Encoder::_history_size = 25;
  
  /** @param f Film that we are encoding */
 -Encoder::Encoder (shared_ptr<Film> f)
 +Encoder::Encoder (shared_ptr<Film> f, shared_ptr<Job> j)
        : _film (f)
 -      , _video_frames_in (0)
 +      , _job (j)
        , _video_frames_out (0)
 -#ifdef HAVE_SWRESAMPLE          
 -      , _swr_context (0)
 -#endif
        , _have_a_real_frame (false)
        , _terminate (false)
  {
@@@ -77,6 -77,35 +77,6 @@@ Encoder::~Encoder (
  void
  Encoder::process_begin ()
  {
 -      if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate()) {
 -#ifdef HAVE_SWRESAMPLE
 -
 -              stringstream s;
 -              s << String::compose (N_("Will resample audio from %1 to %2"), _film->audio_stream()->sample_rate(), _film->target_audio_sample_rate());
 -              _film->log()->log (s.str ());
 -
 -              /* We will be using planar float data when we call the resampler */
 -              _swr_context = swr_alloc_set_opts (
 -                      0,
 -                      _film->audio_stream()->channel_layout(),
 -                      AV_SAMPLE_FMT_FLTP,
 -                      _film->target_audio_sample_rate(),
 -                      _film->audio_stream()->channel_layout(),
 -                      AV_SAMPLE_FMT_FLTP,
 -                      _film->audio_stream()->sample_rate(),
 -                      0, 0
 -                      );
 -              
 -              swr_init (_swr_context);
 -#else
 -              throw EncodeError (_("Cannot resample audio as libswresample is not present"));
 -#endif
 -      } else {
 -#ifdef HAVE_SWRESAMPLE
 -              _swr_context = 0;
 -#endif                
 -      }
 -
        for (int i = 0; i < Config::instance()->num_local_encoding_threads (); ++i) {
                _threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, (ServerDescription *) 0)));
        }
                }
        }
  
 -      _writer.reset (new Writer (_film));
 +      _writer.reset (new Writer (_film, _job));
  }
  
  
  void
  Encoder::process_end ()
  {
 -#if HAVE_SWRESAMPLE   
 -      if (_film->audio_stream() && _film->audio_stream()->channels() && _swr_context) {
 -
 -              shared_ptr<AudioBuffers> out (new AudioBuffers (_film->audio_stream()->channels(), 256));
 -                      
 -              while (1) {
 -                      int const frames = swr_convert (_swr_context, (uint8_t **) out->data(), 256, 0, 0);
 -
 -                      if (frames < 0) {
 -                              throw EncodeError (_("could not run sample-rate converter"));
 -                      }
 -
 -                      if (frames == 0) {
 -                              break;
 -                      }
 -
 -                      out->set_frames (frames);
 -                      write_audio (out);
 -              }
 -
 -              swr_free (&_swr_context);
 -      }
 -#endif
 -
        boost::mutex::scoped_lock lock (_mutex);
  
        _film->log()->log (String::compose (N_("Clearing queue of %1"), _queue.size ()));
   *  or 0 if not known.
   */
  float
 -Encoder::current_frames_per_second () const
 +Encoder::current_encoding_rate () const
  {
        boost::mutex::scoped_lock lock (_history_mutex);
        if (int (_time_history.size()) < _history_size) {
@@@ -178,8 -231,15 +178,8 @@@ Encoder::frame_done (
  }
  
  void
 -Encoder::process_video (shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub)
 +Encoder::process_video (shared_ptr<const Image> image, bool same, shared_ptr<Subtitle> sub, Time)
  {
 -      FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate());
 -      
 -      if (frc.skip && (_video_frames_in % 2)) {
 -              ++_video_frames_in;
 -              return;
 -      }
 -
        boost::mutex::scoped_lock lock (_mutex);
  
        /* Wait until the queue has gone down a bit */
                frame_done ();
        } else {
                /* Queue this new frame for encoding */
--              pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
                TIMING ("adding to queue of %1", _queue.size ());
 -              _queue.push_back (boost::shared_ptr<DCPVideoFrame> (
 +              /* XXX: padding */
 +              _queue.push_back (shared_ptr<DCPVideoFrame> (
                                          new DCPVideoFrame (
 -                                                image, sub, _film->format()->dcp_size(), _film->format()->dcp_padding (_film),
 +                                                image, sub, _film->container()->dcp_size(), 0,
                                                  _film->subtitle_offset(), _film->subtitle_scale(),
-                                                 _film->scaler(), _video_frames_out, _film->dcp_video_frame_rate(), s.second,
 -                                                _film->scaler(), _video_frames_out, _film->dcp_frame_rate(), s.second,
++                                                _film->scaler(), _video_frames_out, _film->dcp_video_frame_rate(),
                                                  _film->colour_lut(), _film->j2k_bandwidth(),
                                                  _film->log()
                                                  )
                _have_a_real_frame = true;
        }
  
 -      ++_video_frames_in;
        ++_video_frames_out;
 -
 -      if (frc.repeat) {
 -              _writer->repeat (_video_frames_out);
 -              ++_video_frames_out;
 -              frame_done ();
 -      }
  }
  
  void
 -Encoder::process_audio (shared_ptr<const AudioBuffers> data)
 +Encoder::process_audio (shared_ptr<const AudioBuffers> data, Time)
  {
 -      if (!data->frames ()) {
 -              return;
 -      }
 -      
 -#if HAVE_SWRESAMPLE
 -      /* Maybe sample-rate convert */
 -      if (_swr_context) {
 -
 -              /* Compute the resampled frames count and add 32 for luck */
 -              int const max_resampled_frames = ceil ((int64_t) data->frames() * _film->target_audio_sample_rate() / _film->audio_stream()->sample_rate()) + 32;
 -
 -              shared_ptr<AudioBuffers> resampled (new AudioBuffers (_film->audio_stream()->channels(), max_resampled_frames));
 -
 -              /* Resample audio */
 -              int const resampled_frames = swr_convert (
 -                      _swr_context, (uint8_t **) resampled->data(), max_resampled_frames, (uint8_t const **) data->data(), data->frames()
 -                      );
 -              
 -              if (resampled_frames < 0) {
 -                      throw EncodeError (_("could not run sample-rate converter"));
 -              }
 -
 -              resampled->set_frames (resampled_frames);
 -              
 -              /* And point our variables at the resampled audio */
 -              data = resampled;
 -      }
 -#endif
 -
 -      write_audio (data);
 +      _writer->write (data);
  }
  
  void
@@@ -271,7 -366,7 +270,7 @@@ Encoder::encoder_thread (ServerDescript
                }
  
                TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size());
 -              boost::shared_ptr<DCPVideoFrame> vf = _queue.front ();
 +              shared_ptr<DCPVideoFrame> vf = _queue.front ();
                _film->log()->log (String::compose (N_("Encoder thread %1 pops frame %2 from queue"), boost::this_thread::get_id(), vf->frame()), Log::VERBOSE);
                _queue.pop_front ();
                
                }
  
                if (remote_backoff > 0) {
 -                      dvdomatic_sleep (remote_backoff);
 +                      dcpomatic_sleep (remote_backoff);
                }
  
                lock.lock ();
                _condition.notify_all ();
        }
  }
 -
 -void
 -Encoder::write_audio (shared_ptr<const AudioBuffers> data)
 -{
 -      AudioMapping m (_film->audio_channels ());
 -      if (m.dcp_channels() != _film->audio_channels()) {
 -
 -              /* Remap (currently just for mono -> 5.1) */
 -
 -              shared_ptr<AudioBuffers> b (new AudioBuffers (m.dcp_channels(), data->frames ()));
 -              for (int i = 0; i < m.dcp_channels(); ++i) {
 -                      optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (i));
 -                      if (!s) {
 -                              b->make_silent (i);
 -                      } else {
 -                              memcpy (b->data()[i], data->data()[s.get()], data->frames() * sizeof(float));
 -                      }
 -              }
 -
 -              data = b;
 -      }
 -
 -      _writer->write (data);
 -}
index ad7af07d864479123513384ecf93241e49176af7,0000000000000000000000000000000000000000..55139ca56cd8b380265a957cb45402088d6bbabb
mode 100644,000000..100644
--- /dev/null
@@@ -1,337 -1,0 +1,359 @@@
 +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
 +
 +/*
 +    Copyright (C) 2013 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 <libcxml/cxml.h>
 +#include "ffmpeg_content.h"
 +#include "ffmpeg_decoder.h"
 +#include "compose.hpp"
 +#include "job.h"
 +#include "util.h"
++#include "filter.h"
 +#include "log.h"
 +
 +#include "i18n.h"
 +
 +using std::string;
 +using std::stringstream;
 +using std::vector;
 +using std::list;
 +using std::cout;
 +using boost::shared_ptr;
 +using boost::lexical_cast;
 +
 +int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
 +int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
 +int const FFmpegContentProperty::AUDIO_STREAMS = 102;
 +int const FFmpegContentProperty::AUDIO_STREAM = 103;
++int const FFmpegContentProperty::FILTERS = 104;
 +
 +FFmpegContent::FFmpegContent (shared_ptr<const Film> f, boost::filesystem::path p)
 +      : Content (f, p)
 +      , VideoContent (f, p)
 +      , AudioContent (f, p)
 +{
 +
 +}
 +
 +FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
 +      : Content (f, node)
 +      , VideoContent (f, node)
 +      , AudioContent (f, node)
 +{
 +      list<shared_ptr<cxml::Node> > c = node->node_children ("SubtitleStream");
 +      for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
 +              _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (*i)));
 +              if ((*i)->optional_number_child<int> ("Selected")) {
 +                      _subtitle_stream = _subtitle_streams.back ();
 +              }
 +      }
 +
 +      c = node->node_children ("AudioStream");
 +      for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
 +              _audio_streams.push_back (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (*i)));
 +              if ((*i)->optional_number_child<int> ("Selected")) {
 +                      _audio_stream = _audio_streams.back ();
 +              }
 +      }
++
++      c = node->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 ()));
++      }
 +}
 +
 +FFmpegContent::FFmpegContent (FFmpegContent const & o)
 +      : Content (o)
 +      , VideoContent (o)
 +      , AudioContent (o)
 +      , _subtitle_streams (o._subtitle_streams)
 +      , _subtitle_stream (o._subtitle_stream)
 +      , _audio_streams (o._audio_streams)
 +      , _audio_stream (o._audio_stream)
 +{
 +
 +}
 +
 +void
 +FFmpegContent::as_xml (xmlpp::Node* node) const
 +{
 +      node->add_child("Type")->add_child_text ("FFmpeg");
 +      Content::as_xml (node);
 +      VideoContent::as_xml (node);
 +      AudioContent::as_xml (node);
 +
 +      boost::mutex::scoped_lock lm (_mutex);
 +
 +      for (vector<shared_ptr<FFmpegSubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
 +              xmlpp::Node* t = node->add_child("SubtitleStream");
 +              if (_subtitle_stream && *i == _subtitle_stream) {
 +                      t->add_child("Selected")->add_child_text("1");
 +              }
 +              (*i)->as_xml (t);
 +      }
 +
 +      for (vector<shared_ptr<FFmpegAudioStream> >::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
 +              xmlpp::Node* t = node->add_child("AudioStream");
 +              if (_audio_stream && *i == _audio_stream) {
 +                      t->add_child("Selected")->add_child_text("1");
 +              }
 +              (*i)->as_xml (t);
 +      }
++
++      for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
++              node->add_child("Filter")->add_child_text ((*i)->id ());
++      }
 +}
 +
 +void
 +FFmpegContent::examine (shared_ptr<Job> job)
 +{
 +      job->set_progress_unknown ();
 +
 +      Content::examine (job);
 +
 +      shared_ptr<const Film> film = _film.lock ();
 +      assert (film);
 +
 +      shared_ptr<FFmpegDecoder> decoder (new FFmpegDecoder (film, shared_from_this (), true, false, false));
 +
 +      ContentVideoFrame video_length = 0;
 +      video_length = decoder->video_length ();
 +      film->log()->log (String::compose ("Video length obtained from header as %1 frames", decoder->video_length ()));
 +
 +        {
 +                boost::mutex::scoped_lock lm (_mutex);
 +
 +                _video_length = video_length;
 +
 +                _subtitle_streams = decoder->subtitle_streams ();
 +                if (!_subtitle_streams.empty ()) {
 +                        _subtitle_stream = _subtitle_streams.front ();
 +                }
 +                
 +                _audio_streams = decoder->audio_streams ();
 +                if (!_audio_streams.empty ()) {
 +                        _audio_stream = _audio_streams.front ();
 +                }
 +        }
 +
 +        take_from_video_decoder (decoder);
 +
 +        signal_changed (VideoContentProperty::VIDEO_LENGTH);
 +        signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
 +        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
 +        signal_changed (FFmpegContentProperty::AUDIO_STREAMS);
 +        signal_changed (FFmpegContentProperty::AUDIO_STREAM);
 +        signal_changed (AudioContentProperty::AUDIO_CHANNELS);
 +}
 +
 +string
 +FFmpegContent::summary () const
 +{
 +      return String::compose (_("Movie: %1"), file().filename().string());
 +}
 +
 +string
 +FFmpegContent::information () const
 +{
 +      if (video_length() == 0 || video_frame_rate() == 0) {
 +              return "";
 +      }
 +      
 +      stringstream s;
 +      
 +      s << String::compose (_("%1 frames; %2 frames per second"), video_length(), video_frame_rate()) << "\n";
 +      s << VideoContent::information ();
 +
 +      return s.str ();
 +}
 +
 +void
 +FFmpegContent::set_subtitle_stream (shared_ptr<FFmpegSubtitleStream> s)
 +{
 +        {
 +                boost::mutex::scoped_lock lm (_mutex);
 +                _subtitle_stream = s;
 +        }
 +
 +        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
 +}
 +
 +void
 +FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s)
 +{
 +        {
 +                boost::mutex::scoped_lock lm (_mutex);
 +                _audio_stream = s;
 +        }
 +
 +        signal_changed (FFmpegContentProperty::AUDIO_STREAM);
 +}
 +
 +ContentAudioFrame
 +FFmpegContent::audio_length () const
 +{
 +      int const cafr = content_audio_frame_rate ();
 +      int const vfr  = video_frame_rate ();
 +      ContentVideoFrame const vl = video_length ();
 +
 +      boost::mutex::scoped_lock lm (_mutex);
 +        if (!_audio_stream) {
 +                return 0;
 +        }
 +        
 +        return video_frames_to_audio_frames (vl, cafr, vfr);
 +}
 +
 +int
 +FFmpegContent::audio_channels () const
 +{
 +      boost::mutex::scoped_lock lm (_mutex);
 +      
 +        if (!_audio_stream) {
 +                return 0;
 +        }
 +
 +        return _audio_stream->channels;
 +}
 +
 +int
 +FFmpegContent::content_audio_frame_rate () const
 +{
 +      boost::mutex::scoped_lock lm (_mutex);
 +
 +        if (!_audio_stream) {
 +                return 0;
 +        }
 +
 +        return _audio_stream->frame_rate;
 +}
 +
 +int
 +FFmpegContent::output_audio_frame_rate () const
 +{
 +      shared_ptr<const Film> film = _film.lock ();
 +      assert (film);
 +      
 +      /* Resample to a DCI-approved sample rate */
 +      double t = dcp_audio_frame_rate (content_audio_frame_rate ());
 +
 +      FrameRateConversion frc (video_frame_rate(), film->dcp_video_frame_rate());
 +
 +      /* Compensate if the DCP is being run at a different frame rate
 +         to the source; that is, if the video is run such that it will
 +         look different in the DCP compared to the source (slower or faster).
 +         skip/repeat doesn't come into effect here.
 +      */
 +
 +      if (frc.change_speed) {
 +              t *= video_frame_rate() * frc.factor() / film->dcp_video_frame_rate();
 +      }
 +
 +      return rint (t);
 +}
 +
 +bool
 +operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
 +{
 +        return a.id == b.id;
 +}
 +
 +bool
 +operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
 +{
 +        return a.id == b.id;
 +}
 +
 +FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node)
 +{
 +      name = node->string_child ("Name");
 +      id = node->number_child<int> ("Id");
 +      frame_rate = node->number_child<int> ("FrameRate");
 +      channels = node->number_child<int64_t> ("Channels");
 +      mapping = AudioMapping (node->node_child ("Mapping"));
 +}
 +
 +void
 +FFmpegAudioStream::as_xml (xmlpp::Node* root) const
 +{
 +      root->add_child("Name")->add_child_text (name);
 +      root->add_child("Id")->add_child_text (lexical_cast<string> (id));
 +      root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
 +      root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
 +      mapping.as_xml (root->add_child("Mapping"));
 +}
 +
 +/** Construct a SubtitleStream from a value returned from to_string().
 + *  @param t String returned from to_string().
 + *  @param v State file version.
 + */
 +FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
 +{
 +      name = node->string_child ("Name");
 +      id = node->number_child<int> ("Id");
 +}
 +
 +void
 +FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
 +{
 +      root->add_child("Name")->add_child_text (name);
 +      root->add_child("Id")->add_child_text (lexical_cast<string> (id));
 +}
 +
 +shared_ptr<Content>
 +FFmpegContent::clone () const
 +{
 +      return shared_ptr<Content> (new FFmpegContent (*this));
 +}
 +
 +Time
 +FFmpegContent::length () const
 +{
 +      shared_ptr<const Film> film = _film.lock ();
 +      assert (film);
 +      
 +      FrameRateConversion frc (video_frame_rate (), film->dcp_video_frame_rate ());
 +      return video_length() * frc.factor() * TIME_HZ / film->dcp_video_frame_rate ();
 +}
 +
 +AudioMapping
 +FFmpegContent::audio_mapping () const
 +{
 +      boost::mutex::scoped_lock lm (_mutex);
 +
 +      if (!_audio_stream) {
 +              return AudioMapping ();
 +      }
 +
 +      return _audio_stream->mapping;
 +}
 +
++void
++FFmpegContent::set_filters (vector<Filter const *> const & filters)
++{
++      {
++              boost::mutex::scoped_lock lm (_mutex);
++              _filters = filters;
++      }
++
++      signal_changed (FFmpegContentProperty::FILTERS);
++}
++
diff --combined src/lib/ffmpeg_content.h
index d5b9869967074de0347244ebaec3c6ebbab17f3e,0000000000000000000000000000000000000000..8f5c773eeb853ba7f7530268bef65d4188621969
mode 100644,000000..100644
--- /dev/null
@@@ -1,135 -1,0 +1,147 @@@
 +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
 +
 +/*
 +    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +*/
 +
 +#ifndef DCPOMATIC_FFMPEG_CONTENT_H
 +#define DCPOMATIC_FFMPEG_CONTENT_H
 +
 +#include <boost/enable_shared_from_this.hpp>
 +#include "video_content.h"
 +#include "audio_content.h"
 +
++class Filter;
++
 +class FFmpegAudioStream
 +{
 +public:
 +        FFmpegAudioStream (std::string n, int i, int f, int c)
 +                : name (n)
 +                , id (i)
 +                , frame_rate (f)
 +              , channels (c)
 +              , mapping (c)
 +        {}
 +
 +      FFmpegAudioStream (boost::shared_ptr<const cxml::Node>);
 +
 +      void as_xml (xmlpp::Node *) const;
 +      
 +        std::string name;
 +        int id;
 +        int frame_rate;
 +      int channels;
 +      AudioMapping mapping;
 +};
 +
 +extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
 +
 +class FFmpegSubtitleStream
 +{
 +public:
 +        FFmpegSubtitleStream (std::string n, int i)
 +                : name (n)
 +                , id (i)
 +        {}
 +        
 +      FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>);
 +
 +      void as_xml (xmlpp::Node *) const;
 +      
 +        std::string name;
 +        int id;
 +};
 +
 +extern bool operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
 +
 +class FFmpegContentProperty : public VideoContentProperty
 +{
 +public:
 +        static int const SUBTITLE_STREAMS;
 +        static int const SUBTITLE_STREAM;
 +        static int const AUDIO_STREAMS;
 +        static int const AUDIO_STREAM;
++        static int const FILTERS;
 +};
 +
 +class FFmpegContent : public VideoContent, public AudioContent
 +{
 +public:
 +      FFmpegContent (boost::shared_ptr<const Film>, boost::filesystem::path);
 +      FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
 +      FFmpegContent (FFmpegContent const &);
 +
 +      boost::shared_ptr<FFmpegContent> shared_from_this () {
 +              return boost::dynamic_pointer_cast<FFmpegContent> (Content::shared_from_this ());
 +      }
 +      
 +      void examine (boost::shared_ptr<Job>);
 +      std::string summary () const;
 +      std::string information () const;
 +      void as_xml (xmlpp::Node *) const;
 +      boost::shared_ptr<Content> clone () const;
 +      Time length () const;
 +
 +        /* AudioContent */
 +        int audio_channels () const;
 +        ContentAudioFrame audio_length () const;
 +        int content_audio_frame_rate () const;
 +        int output_audio_frame_rate () const;
 +      AudioMapping audio_mapping () const;
++
++      void set_filters (std::vector<Filter const *> const &);
 +      
 +        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
 +                boost::mutex::scoped_lock lm (_mutex);
 +                return _subtitle_streams;
 +        }
 +
 +        boost::shared_ptr<FFmpegSubtitleStream> subtitle_stream () const {
 +                boost::mutex::scoped_lock lm (_mutex);
 +                return _subtitle_stream;
 +        }
 +
 +        std::vector<boost::shared_ptr<FFmpegAudioStream> > audio_streams () const {
 +                boost::mutex::scoped_lock lm (_mutex);
 +                return _audio_streams;
 +        }
 +        
 +        boost::shared_ptr<FFmpegAudioStream> audio_stream () const {
 +                boost::mutex::scoped_lock lm (_mutex);
 +                return _audio_stream;
 +        }
 +
++      std::vector<Filter const *> filters () const {
++              boost::mutex::scoped_lock lm (_mutex);
++              return _filters;
++      }
++
 +        void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
 +        void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>);
 +      
 +private:
 +      std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
 +      boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
 +      std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
 +      boost::shared_ptr<FFmpegAudioStream> _audio_stream;
++      /** Video filters that should be used when generating DCPs */
++      std::vector<Filter const *> _filters;
 +};
 +
 +#endif
index 047829d456395e8abd039dbe9aecf9616c256b31,982139515b0af588eeb9c8180e5accd74cef444f..119e82851ec5de9433d119e2409b1b951067104f
@@@ -1,5 -1,3 +1,5 @@@
 +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
 +
  /*
      Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
  
@@@ -43,6 -41,7 +43,6 @@@ extern "C" 
  #include "transcoder.h"
  #include "job.h"
  #include "filter.h"
 -#include "options.h"
  #include "exceptions.h"
  #include "image.h"
  #include "util.h"
@@@ -50,7 -49,6 +50,7 @@@
  #include "ffmpeg_decoder.h"
  #include "filter_graph.h"
  #include "subtitle.h"
 +#include "audio_buffers.h"
  
  #include "i18n.h"
  
@@@ -59,19 -57,15 +59,19 @@@ using std::string
  using std::vector;
  using std::stringstream;
  using std::list;
 +using std::min;
  using boost::shared_ptr;
  using boost::optional;
  using boost::dynamic_pointer_cast;
  using libdcp::Size;
  
 -FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o)
 -      : Decoder (f, o)
 -      , VideoDecoder (f, o)
 -      , AudioDecoder (f, o)
 +boost::mutex FFmpegDecoder::_mutex;
 +
 +FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio, bool subtitles)
 +      : Decoder (f)
 +      , VideoDecoder (f, c)
 +      , AudioDecoder (f, c)
 +      , _ffmpeg_content (c)
        , _format_context (0)
        , _video_stream (-1)
        , _frame (0)
@@@ -81,9 -75,6 +81,9 @@@
        , _audio_codec (0)
        , _subtitle_codec_context (0)
        , _subtitle_codec (0)
 +      , _decode_video (video)
 +      , _decode_audio (audio)
 +      , _decode_subtitles (subtitles)
  {
        setup_general ();
        setup_video ();
  
  FFmpegDecoder::~FFmpegDecoder ()
  {
 +      boost::mutex::scoped_lock lm (_mutex);
 +      
        if (_audio_codec_context) {
                avcodec_close (_audio_codec_context);
        }
 -      
 +
        if (_video_codec_context) {
                avcodec_close (_video_codec_context);
        }
@@@ -117,15 -106,15 +117,15 @@@ FFmpegDecoder::setup_general (
  {
        av_register_all ();
  
 -      if (avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0) < 0) {
 -              throw OpenFileError (_film->content_path ());
 +      if (avformat_open_input (&_format_context, _ffmpeg_content->file().string().c_str(), 0, 0) < 0) {
 +              throw OpenFileError (_ffmpeg_content->file().string ());
        }
  
        if (avformat_find_stream_info (_format_context, 0) < 0) {
                throw DecodeError (_("could not find stream information"));
        }
  
 -      /* Find video, audio and subtitle streams and choose the first of each */
 +      /* Find video, audio and subtitle streams */
  
        for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
                AVStream* s = _format_context->streams[i];
                        }
                        
                        _audio_streams.push_back (
 -                              shared_ptr<AudioStream> (
 -                                      new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channel_layout)
 +                              shared_ptr<FFmpegAudioStream> (
 +                                      new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channels)
                                        )
                                );
 -                      
 +
                } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
 -                      _subtitle_streams.push_back (
 -                              shared_ptr<SubtitleStream> (
 -                                      new SubtitleStream (stream_name (s), i)
 -                                      )
 -                              );
 +                      _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (stream_name (s), i)));
                }
        }
  
  void
  FFmpegDecoder::setup_video ()
  {
 +      boost::mutex::scoped_lock lm (_mutex);
 +      
        _video_codec_context = _format_context->streams[_video_stream]->codec;
        _video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
  
  void
  FFmpegDecoder::setup_audio ()
  {
 -      if (!_audio_stream) {
 +      boost::mutex::scoped_lock lm (_mutex);
 +      
 +      if (!_ffmpeg_content->audio_stream ()) {
                return;
        }
  
 -      shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
 -      assert (ffa);
 -      
 -      _audio_codec_context = _format_context->streams[ffa->id()]->codec;
 +      _audio_codec_context = _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec;
        _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id);
  
        if (_audio_codec == 0) {
  void
  FFmpegDecoder::setup_subtitle ()
  {
 -      if (!_subtitle_stream || _subtitle_stream->id() >= int (_format_context->nb_streams)) {
 +      boost::mutex::scoped_lock lm (_mutex);
 +      
 +      if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->id >= int (_format_context->nb_streams)) {
                return;
        }
  
 -      _subtitle_codec_context = _format_context->streams[_subtitle_stream->id()]->codec;
 +      _subtitle_codec_context = _format_context->streams[_ffmpeg_content->subtitle_stream()->id]->codec;
        _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
  
        if (_subtitle_codec == 0) {
  }
  
  
 -bool
 +void
  FFmpegDecoder::pass ()
  {
        int r = av_read_frame (_format_context, &_packet);
 -      
 +
        if (r < 0) {
                if (r != AVERROR_EOF) {
                        /* Maybe we should fail here, but for now we'll just finish off instead */
                        char buf[256];
                        av_strerror (r, buf, sizeof(buf));
 -                      _film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
 +                      shared_ptr<const Film> film = _film.lock ();
 +                      assert (film);
 +                      film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
                }
  
                /* Get any remaining frames */
                
                /* XXX: should we reset _packet.data and size after each *_decode_* call? */
                
 -              int frame_finished;
 -              
 -              if (_opt.decode_video) {
 -                      while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
 -                              filter_and_emit_video ();
 -                      }
 +              if (_decode_video) {
 +                      while (decode_video_packet ());
                }
 -              
 -              if (_audio_stream && _opt.decode_audio) {
 +
 +              if (_ffmpeg_content->audio_stream() && _decode_audio) {
                        decode_audio_packet ();
                }
                        
 -              return true;
 +              return;
        }
  
        avcodec_get_frame_defaults (_frame);
  
 -      shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
 -
 -      if (_packet.stream_index == _video_stream && _opt.decode_video) {
 -
 -              int frame_finished;
 -              int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet);
 -              if (r >= 0 && frame_finished) {
 -
 -                      if (r != _packet.size) {
 -                              _film->log()->log (String::compose (N_("Used only %1 bytes of %2 in packet"), r, _packet.size));
 -                      }
 -
 -                      filter_and_emit_video ();
 -              }
 -
 -      } else if (ffa && _packet.stream_index == ffa->id() && _opt.decode_audio) {
 +      if (_packet.stream_index == _video_stream && _decode_video) {
 +              decode_video_packet ();
 +      } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->id && _decode_audio) {
                decode_audio_packet ();
 -      } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles) {
 +      } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id && _decode_subtitles) {
  
                int got_subtitle;
                AVSubtitle sub;
                        if (sub.num_rects > 0) {
                                shared_ptr<TimedSubtitle> ts;
                                try {
 -                                      emit_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub)));
 +                                      subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub)));
                                } catch (...) {
                                        /* some problem with the subtitle; we probably didn't understand it */
                                }
                        } else {
 -                              emit_subtitle (shared_ptr<TimedSubtitle> ());
 +                              subtitle (shared_ptr<TimedSubtitle> ());
                        }
                        avsubtitle_free (&sub);
                }
        }
 -      
 +
        av_free_packet (&_packet);
 -      return false;
  }
  
  /** @param data pointer to array of pointers to buffers.
  shared_ptr<AudioBuffers>
  FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
  {
 -      assert (_film->audio_channels());
 +      assert (_ffmpeg_content->audio_channels());
        assert (bytes_per_audio_sample());
  
 -      shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
 -      assert (ffa);
 -      
        /* Deinterleave and convert to float */
  
 -      assert ((size % (bytes_per_audio_sample() * ffa->channels())) == 0);
 +      assert ((size % (bytes_per_audio_sample() * _ffmpeg_content->audio_channels())) == 0);
  
        int const total_samples = size / bytes_per_audio_sample();
 -      int const frames = total_samples / _film->audio_channels();
 -      shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), frames));
 +      int const frames = total_samples / _ffmpeg_content->audio_channels();
 +      shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames));
  
        switch (audio_sample_format()) {
        case AV_SAMPLE_FMT_S16:
                        audio->data(channel)[sample] = float(*p++) / (1 << 15);
  
                        ++channel;
 -                      if (channel == _film->audio_channels()) {
 +                      if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
        case AV_SAMPLE_FMT_S16P:
        {
                int16_t** p = reinterpret_cast<int16_t **> (data);
 -              for (int i = 0; i < _film->audio_channels(); ++i) {
 +              for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
                        for (int j = 0; j < frames; ++j) {
                                audio->data(i)[j] = static_cast<float>(p[i][j]) / (1 << 15);
                        }
                        audio->data(channel)[sample] = static_cast<float>(*p++) / (1 << 31);
  
                        ++channel;
 -                      if (channel == _film->audio_channels()) {
 +                      if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
                        audio->data(channel)[sample] = *p++;
  
                        ++channel;
 -                      if (channel == _film->audio_channels()) {
 +                      if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
        case AV_SAMPLE_FMT_FLTP:
        {
                float** p = reinterpret_cast<float**> (data);
 -              for (int i = 0; i < _film->audio_channels(); ++i) {
 +              for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
                        memcpy (audio->data(i), p[i], frames * sizeof(float));
                }
        }
  }
  
  float
 -FFmpegDecoder::frames_per_second () const
 +FFmpegDecoder::video_frame_rate () const
  {
        AVStream* s = _format_context->streams[_video_stream];
  
@@@ -405,11 -414,41 +405,11 @@@ FFmpegDecoder::audio_sample_format () c
  }
  
  libdcp::Size
 -FFmpegDecoder::native_size () const
 +FFmpegDecoder::video_size () const
  {
        return libdcp::Size (_video_codec_context->width, _video_codec_context->height);
  }
  
 -PixelFormat
 -FFmpegDecoder::pixel_format () const
 -{
 -      return _video_codec_context->pix_fmt;
 -}
 -
 -int
 -FFmpegDecoder::time_base_numerator () const
 -{
 -      return _video_codec_context->time_base.num;
 -}
 -
 -int
 -FFmpegDecoder::time_base_denominator () const
 -{
 -      return _video_codec_context->time_base.den;
 -}
 -
 -int
 -FFmpegDecoder::sample_aspect_ratio_numerator () const
 -{
 -      return _video_codec_context->sample_aspect_ratio.num;
 -}
 -
 -int
 -FFmpegDecoder::sample_aspect_ratio_denominator () const
 -{
 -      return _video_codec_context->sample_aspect_ratio.den;
 -}
 -
  string
  FFmpegDecoder::stream_name (AVStream* s) const
  {
@@@ -444,36 -483,89 +444,36 @@@ FFmpegDecoder::bytes_per_audio_sample (
  }
  
  void
 -FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s)
 -{
 -      AudioDecoder::set_audio_stream (s);
 -      setup_audio ();
 -}
 -
 -void
 -FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
 +FFmpegDecoder::seek (Time t)
  {
 -      VideoDecoder::set_subtitle_stream (s);
 -      setup_subtitle ();
 -      OutputChanged ();
 +      do_seek (t, false, false);
  }
  
  void
 -FFmpegDecoder::filter_and_emit_video ()
 +FFmpegDecoder::seek_back ()
  {
 -      int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
 -      if (bet == AV_NOPTS_VALUE) {
 -              _film->log()->log ("Dropping frame without PTS");
 +      if (next() < (2.5 * TIME_HZ / video_frame_rate())) {
                return;
        }
        
 -      shared_ptr<FilterGraph> graph;
 -
 -      {
 -              boost::mutex::scoped_lock lm (_filter_graphs_mutex);
 -              
 -              list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
 -              while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
 -                      ++i;
 -              }
 -              
 -              if (i == _filter_graphs.end ()) {
 -                      graph = filter_graph_factory (_film, this, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format);
 -                      _filter_graphs.push_back (graph);
 -                      _film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
 -              } else {
 -                      graph = *i;
 -              }
 -      }
 -
 -      list<shared_ptr<Image> > images = graph->process (_frame);
 -
 -      for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
 -              emit_video (*i, false, bet * av_q2d (_format_context->streams[_video_stream]->time_base));
 -      }
 -}
 -
 -bool
 -FFmpegDecoder::seek (double p)
 -{
 -      return do_seek (p, false, false);
 -}
 -
 -bool
 -FFmpegDecoder::seek_to_last ()
 -{
 -      /* This AVSEEK_FLAG_BACKWARD in do_seek is a bit of a hack; without it, if we ask for a seek to the same place as last time
 -         (used when we change decoder parameters and want to re-fetch the frame) we end up going forwards rather than
 -         staying in the same place.
 -      */
 -      return do_seek (last_source_time(), true, false);
 -}
 -
 -void
 -FFmpegDecoder::seek_back ()
 -{
 -      do_seek (last_source_time() - 2.5 / frames_per_second (), true, true);
 +      do_seek (next() - 2.5 * TIME_HZ / video_frame_rate(), true, true);
  }
  
  void
  FFmpegDecoder::seek_forward ()
  {
 -      do_seek (last_source_time() - 0.5 / frames_per_second(), true, true);
 +      if (next() >= (_ffmpeg_content->length() - 0.5 * TIME_HZ / video_frame_rate())) {
 +              return;
 +      }
 +      
 +      do_seek (next() - 0.5 * TIME_HZ / video_frame_rate(), true, true);
  }
  
 -bool
 -FFmpegDecoder::do_seek (double p, bool backwards, bool accurate)
 +void
 +FFmpegDecoder::do_seek (Time t, bool backwards, bool accurate)
  {
 -      int64_t const vt = p / av_q2d (_format_context->streams[_video_stream]->time_base);
 -
 -      int const r = av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
 +      int64_t const vt = t / (av_q2d (_format_context->streams[_video_stream]->time_base) * TIME_HZ);
 +      av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
  
        avcodec_flush_buffers (_video_codec_context);
        if (_subtitle_codec_context) {
                while (1) {
                        int r = av_read_frame (_format_context, &_packet);
                        if (r < 0) {
 -                              return true;
 +                              return;
                        }
                        
                        avcodec_get_frame_defaults (_frame);
                        av_free_packet (&_packet);
                }
        }
 -              
 -      return r < 0;
 -}
 -
 -shared_ptr<FFmpegAudioStream>
 -FFmpegAudioStream::create (string t, optional<int> v)
 -{
 -      if (!v) {
 -              /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
 -              return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
 -      }
 -
 -      stringstream s (t);
 -      string type;
 -      s >> type;
 -      if (type != N_("ffmpeg")) {
 -              return shared_ptr<FFmpegAudioStream> ();
 -      }
 -
 -      return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
 -}
 -
 -FFmpegAudioStream::FFmpegAudioStream (string t, optional<int> version)
 -{
 -      stringstream n (t);
 -      
 -      int name_index = 4;
 -      if (!version) {
 -              name_index = 2;
 -              int channels;
 -              n >> _id >> channels;
 -              _channel_layout = av_get_default_channel_layout (channels);
 -              _sample_rate = 0;
 -      } else {
 -              string type;
 -              /* Current (marked version 1) */
 -              n >> type >> _id >> _sample_rate >> _channel_layout;
 -              assert (type == N_("ffmpeg"));
 -      }
 -
 -      for (int i = 0; i < name_index; ++i) {
 -              size_t const s = t.find (' ');
 -              if (s != string::npos) {
 -                      t = t.substr (s + 1);
 -              }
 -      }
 -
 -      _name = t;
 -}
 -
 -string
 -FFmpegAudioStream::to_string () const
 -{
 -      return String::compose (N_("ffmpeg %1 %2 %3 %4"), _id, _sample_rate, _channel_layout, _name);
 -}
 -
 -void
 -FFmpegDecoder::film_changed (Film::Property p)
 -{
 -      switch (p) {
 -      case Film::CROP:
 -      case Film::FILTERS:
 -      {
 -              boost::mutex::scoped_lock lm (_filter_graphs_mutex);
 -              _filter_graphs.clear ();
 -      }
 -      OutputChanged ();
 -      break;
  
 -      default:
 -              break;
 -      }
 +      return;
  }
  
- void
- FFmpegDecoder::film_changed (Film::Property p)
- {
-       switch (p) {
-       case Film::FILTERS:
-       {
-               boost::mutex::scoped_lock lm (_filter_graphs_mutex);
-               _filter_graphs.clear ();
-       }
-       break;
-       default:
-               break;
-       }
- }
  /** @return Length (in video frames) according to our content's header */
 -SourceFrame
 -FFmpegDecoder::length () const
 +ContentVideoFrame
 +FFmpegDecoder::video_length () const
  {
 -      return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second();
 +      return (double(_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
  }
  
  void
  FFmpegDecoder::decode_audio_packet ()
  {
 -      shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
 -      assert (ffa);
 -
        /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
           several times.
        */
                                        0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
                                        );
                                
 -                              assert (_audio_codec_context->channels == _film->audio_channels());
 +                              assert (_audio_codec_context->channels == _ffmpeg_content->audio_channels());
                                Audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds);
                        }
                        
                }
        }
  }
-               graph.reset (new FilterGraph (_film, this, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
-               _filter_graphs.push_back (graph);
 +
 +bool
 +FFmpegDecoder::decode_video_packet ()
 +{
 +      int frame_finished;
 +      if (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) < 0 || !frame_finished) {
 +              return false;
 +      }
 +              
 +      boost::mutex::scoped_lock lm (_filter_graphs_mutex);
 +
 +      shared_ptr<FilterGraph> graph;
 +      
 +      list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
 +      while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
 +              ++i;
 +      }
 +
 +      if (i == _filter_graphs.end ()) {
 +              shared_ptr<const Film> film = _film.lock ();
 +              assert (film);
++
++              graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
++              _filter_graphs.push_back (graph);
++
 +              film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
 +      } else {
 +              graph = *i;
 +      }
 +
-                       video (*i, false, t);
 +      list<shared_ptr<Image> > images = graph->process (_frame);
++
++      string post_process = Filter::ffmpeg_strings (_ffmpeg_content->filters()).second;
 +      
 +      for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
++
++              shared_ptr<Image> image = *i;
++              if (!post_process.empty ()) {
++                      image = image->post_process (post_process, true);
++              }
++              
 +              int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
 +              if (bet != AV_NOPTS_VALUE) {
 +                      /* XXX: may need to insert extra frames / remove frames here ...
 +                         (as per old Matcher)
 +                      */
 +                      Time const t = bet * av_q2d (_format_context->streams[_video_stream]->time_base) * TIME_HZ;
++                      video (image, false, t);
 +              } else {
 +                      shared_ptr<const Film> film = _film.lock ();
 +                      assert (film);
 +                      film->log()->log ("Dropping frame without PTS");
 +              }
 +      }
 +
 +      return true;
 +}
 +
 +Time
 +FFmpegDecoder::next () const
 +{
 +      if (_decode_video && _decode_audio) {
 +              return min (_next_video, _next_audio);
 +      }
 +
 +      if (_decode_audio) {
 +              return _next_audio;
 +      }
 +
 +      return _next_video;
 +}
diff --combined src/lib/ffmpeg_decoder.h
index dbcfe3be01017816205934e2bd8c72a3a1aa2ab4,0c89b973dfbb9b47c237cd2e9ade391327758fd9..c3747961266789a8b2da5447d15dc9c578d3cc1b
@@@ -1,5 -1,3 +1,5 @@@
 +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
 +
  /*
      Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
  
@@@ -38,7 -36,6 +38,7 @@@ extern "C" 
  #include "video_decoder.h"
  #include "audio_decoder.h"
  #include "film.h"
 +#include "ffmpeg_content.h"
  
  struct AVFilterGraph;
  struct AVCodecContext;
@@@ -53,71 -50,85 +53,69 @@@ class Options
  class Image;
  class Log;
  
 -class FFmpegAudioStream : public AudioStream
 -{
 -public:
 -      FFmpegAudioStream (std::string n, int i, int s, int64_t c)
 -              : AudioStream (s, c)
 -              , _name (n)
 -              , _id (i)
 -      {}
 -
 -      std::string to_string () const;
 -
 -      std::string name () const {
 -              return _name;
 -      }
 -
 -      int id () const {
 -              return _id;
 -      }
 -
 -      static boost::shared_ptr<FFmpegAudioStream> create (std::string t, boost::optional<int> v);
 -
 -private:
 -      friend class stream_test;
 -      
 -      FFmpegAudioStream (std::string t, boost::optional<int> v);
 -      
 -      std::string _name;
 -      int _id;
 -};
 -
  /** @class FFmpegDecoder
   *  @brief A decoder using FFmpeg to decode content.
   */
  class FFmpegDecoder : public VideoDecoder, public AudioDecoder
  {
  public:
 -      FFmpegDecoder (boost::shared_ptr<Film>, DecodeOptions);
 +      FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio, bool subtitles);
        ~FFmpegDecoder ();
  
 -      float frames_per_second () const;
 -      libdcp::Size native_size () const;
 -      SourceFrame length () const;
 -      int time_base_numerator () const;
 -      int time_base_denominator () const;
 -      int sample_aspect_ratio_numerator () const;
 -      int sample_aspect_ratio_denominator () const;
 +      /* Decoder */
  
 -      void set_audio_stream (boost::shared_ptr<AudioStream>);
 -      void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
 -
 -      bool seek (double);
 -      bool seek_to_last ();
 -      void seek_forward ();
 +      void pass ();
 +      void seek (Time);
        void seek_back ();
 +      void seek_forward ();
 +      Time next () const;
 +
 +      /* VideoDecoder */
 +
 +      float video_frame_rate () const;
 +      libdcp::Size video_size () const;
 +      ContentVideoFrame video_length () const;
 +
 +      /* FFmpegDecoder */
 +
 +      std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
 +              return _subtitle_streams;
 +      }
 +      
 +      std::vector<boost::shared_ptr<FFmpegAudioStream> > audio_streams () const {
 +              return _audio_streams;
 +      }
 +
 +      boost::shared_ptr<const FFmpegContent> ffmpeg_content () const {
 +              return _ffmpeg_content;
 +      }
  
  private:
  
 -      bool pass ();
 -      bool do_seek (double p, bool, bool);
 +      /* No copy construction */
 +      FFmpegDecoder (FFmpegDecoder const &);
 +      FFmpegDecoder& operator= (FFmpegDecoder const &);
 +
        PixelFormat pixel_format () const;
        AVSampleFormat audio_sample_format () const;
        int bytes_per_audio_sample () const;
 -
 -      void filter_and_emit_video ();
 +      void do_seek (Time, bool, bool);
  
        void setup_general ();
        void setup_video ();
        void setup_audio ();
        void setup_subtitle ();
  
 +      bool decode_video_packet ();
        void decode_audio_packet ();
  
        void maybe_add_subtitle ();
        boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
  
--      void film_changed (Film::Property);
--
        std::string stream_name (AVStream* s) const;
  
 +      boost::shared_ptr<const FFmpegContent> _ffmpeg_content;
 +
        AVFormatContext* _format_context;
        int _video_stream;
        
  
        std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
        boost::mutex _filter_graphs_mutex;
 +
 +        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
 +        std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
 +
 +      bool _decode_video;
 +      bool _decode_audio;
 +      bool _decode_subtitles;
 +
 +      /* It would appear (though not completely verified) that one must have
 +         a mutex around calls to avcodec_open* and avcodec_close... and here
 +         it is.
 +      */
 +      static boost::mutex _mutex;
  };
diff --combined src/lib/film.cc
index a61a0d53daaf5b140cc9c816d79a6232e2256f56,81c7de77f72bb0b3a04f1e7d906011c8777bcca8..fc1d2d8a4ee2fa7c98ee7d34ad06a639cf42b38e
@@@ -1,5 -1,3 +1,5 @@@
 +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
 +
  /*
      Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
  
  #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 "container.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"
  
@@@ -71,8 -67,6 +71,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;
@@@ -83,32 -77,39 +83,32 @@@ using libdcp::Size
  
  int const Film::state_version = 4;
  
 -/** Construct a Film object in a given directory, reading any metadata
 - *  file that exists in that directory.  An exception will be thrown if
 - *  must_exist is true and the specified directory does not exist.
 +/** Construct a Film object in a given directory.
   *
   *  @param d Film directory.
 - *  @param must_exist true to throw an exception if does not exist.
   */
  
 -Film::Film (string d, bool must_exist)
 -      : _use_dci_name (true)
 -      , _trust_content_header (true)
 +Film::Film (string d)
 +      : _playlist (new Playlist)
 +      , _use_dci_name (true)
        , _dcp_content_type (Config::instance()->default_dcp_content_type ())
 -      , _format (Config::instance()->default_format ())
 +      , _container (Config::instance()->default_container ())
        , _scaler (Scaler::from_id ("bicubic"))
 -      , _trim_start (0)
 -      , _trim_end (0)
 -      , _trim_type (CPL)
 -      , _dcp_ab (false)
 -      , _use_content_audio (true)
 -      , _audio_gain (0)
 -      , _audio_delay (0)
 -      , _still_duration (10)
 +      , _ab (false)
        , _with_subtitles (false)
        , _subtitle_offset (0)
        , _subtitle_scale (1)
        , _colour_lut (0)
        , _j2k_bandwidth (200000000)
        , _dci_metadata (Config::instance()->default_dci_metadata ())
 -      , _dcp_frame_rate (0)
 -      , _source_frame_rate (0)
 +      , _dcp_video_frame_rate (0)
 +      , _dcp_audio_channels (MAX_AUDIO_CHANNELS)
        , _dirty (false)
  {
        set_dci_date_today ();
 +
 +      _playlist->Changed.connect (bind (&Film::playlist_changed, this));
 +      _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
        
        /* Make state.directory a complete path without ..s (where possible)
           (Code swiped from Adam Bowen on stackoverflow)
        }
  
        set_directory (result.string ());
 -      
 -      if (!boost::filesystem::exists (directory())) {
 -              if (must_exist) {
 -                      throw OpenFileError (directory());
 -              } else {
 -                      boost::filesystem::create_directory (directory());
 -              }
 -      }
 -
 -      _sndfile_stream = SndfileStream::create ();
 -      
 -      if (must_exist) {
 -              read_metadata ();
 -      } else {
 -              write_metadata ();
 -      }
 -
        _log.reset (new FileLog (file ("log")));
  }
  
@@@ -136,46 -154,71 +136,42 @@@ 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 (o._playlist))
        , _directory         (o._directory)
        , _name              (o._name)
        , _use_dci_name      (o._use_dci_name)
 -      , _content           (o._content)
 -      , _trust_content_header (o._trust_content_header)
        , _dcp_content_type  (o._dcp_content_type)
 -      , _format            (o._format)
 -      , _crop              (o._crop)
 -      , _filters           (o._filters)
 +      , _container         (o._container)
-       , _filters           (o._filters)
        , _scaler            (o._scaler)
 -      , _trim_start        (o._trim_start)
 -      , _trim_end          (o._trim_end)
 -      , _trim_type         (o._trim_type)
 -      , _dcp_ab            (o._dcp_ab)
 -      , _content_audio_stream (o._content_audio_stream)
 -      , _external_audio    (o._external_audio)
 -      , _use_content_audio (o._use_content_audio)
 -      , _audio_gain        (o._audio_gain)
 -      , _audio_delay       (o._audio_delay)
 -      , _still_duration    (o._still_duration)
 -      , _subtitle_stream   (o._subtitle_stream)
 +      , _ab                (o._ab)
        , _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)
 +      , _dcp_video_frame_rate (o._dcp_video_frame_rate)
        , _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)
        , _dirty             (o._dirty)
  {
 -      
 -}
 -
 -Film::~Film ()
 -{
 -
 +      _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
  }
  
  string
  Film::video_state_identifier () const
  {
 -      assert (format ());
 +      assert (container ());
        LocaleGuard lg;
  
--      pair<string, string> f = Filter::ffmpeg_strings (filters());
--
        stringstream s;
 -      s << format()->id()
 -        << "_" << content_digest()
 -        << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
 -        << "_" << _dcp_frame_rate
 -        << "_" << f.first << "_" << f.second
 +      s << container()->id()
 +        << "_" << _playlist->video_digest()
 +        << "_" << _dcp_video_frame_rate
-         << "_" << f.first << "_" << f.second
          << "_" << scaler()->id()
          << "_" << j2k_bandwidth()
 -        << "_" << boost::lexical_cast<int> (colour_lut());
 +        << "_" << 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;
        }
@@@ -239,7 -282,7 +235,7 @@@ Film::audio_analysis_path () cons
  {
        boost::filesystem::path p;
        p /= "analysis";
 -      p /= content_digest();
 +      p /= _playlist->audio_digest();
        return file (p.string ());
  }
  
@@@ -253,7 -296,7 +249,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.");
        pair<string, int> const c = cpu_info ();
        log()->log (String::compose ("CPU: %1, %2 processors", c.first, c.second));
        
 -      if (format() == 0) {
 -              throw MissingSettingError (_("format"));
 +      if (container() == 0) {
 +              throw MissingSettingError (_("container"));
        }
  
 -      if (content().empty ()) {
 -              throw MissingSettingError (_("content"));
 +      if (_playlist->content().empty ()) {
 +              throw StringError (_("You must add some content to the DCP before creating it"));
        }
  
        if (dcp_content_type() == 0) {
                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));
 +      JobManager::instance()->add (j);
  }
  
  void
@@@ -340,6 -391,12 +336,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 ()
  int
  Film::encoded_frames () const
  {
 -      if (format() == 0) {
 +      if (container() == 0) {
                return 0;
        }
  
  void
  Film::write_metadata () const
  {
 +      if (!boost::filesystem::exists (directory())) {
 +              boost::filesystem::create_directory (directory());
 +      }
 +      
        boost::mutex::scoped_lock lm (_state_mutex);
        LocaleGuard lg;
  
        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 (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");
  
 -      /* 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;
 -      }
 -      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;
 -      }
 -      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";
 -              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;
 -      }
 -      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;
 +              root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
        }
  
 -      f << "external_audio_stream " << _sndfile_stream->to_string() << endl;
 -
 -      for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
 -              f << "subtitle_stream " << (*i)->to_string () << endl;
 +      if (_container) {
 +              root->add_child("Container")->add_child_text (_container->id ());
        }
  
-       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("AB")->add_child_text (_ab ? "1" : "0");
 +      root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
 +      root->add_child("SubtitleOffset")->add_child_text (lexical_cast<string> (_subtitle_offset));
 +      root->add_child("SubtitleScale")->add_child_text (lexical_cast<string> (_subtitle_scale));
 +      root->add_child("ColourLUT")->add_child_text (lexical_cast<string> (_colour_lut));
 +      root->add_child("J2KBandwidth")->add_child_text (lexical_cast<string> (_j2k_bandwidth));
 +      _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
 +      root->add_child("DCPVideoFrameRate")->add_child_text (lexical_cast<string> (_dcp_video_frame_rate));
 +      root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
 +      root->add_child("DCPAudioChannels")->add_child_text (lexical_cast<string> (_dcp_audio_channels));
 +      _playlist->as_xml (root->add_child ("Playlist"));
 +
 +      doc.write_to_file_formatted (file ("metadata.xml"));
        
        _dirty = false;
  }
@@@ -424,53 -519,177 +416,46 @@@ Film::read_metadata (
        boost::mutex::scoped_lock lm (_state_mutex);
        LocaleGuard lg;
  
 -      _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;
 -
 -              if (k == "audio_sample_rate") {
 -                      audio_sample_rate = atoi (v.c_str());
 -              }
 +      _name = f.string_child ("Name");
 +      _use_dci_name = f.bool_child ("UseDCIName");
  
 -              /* 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 ("DCPContentType");
 +              if (c) {
 +                      _dcp_content_type = DCPContentType::from_dci_name (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 ("Container");
 +              if (c) {
 +                      _container = Container::from_id (c.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 ()));
 -      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());
 -                      }
--              }
-       }
 +      _scaler = Scaler::from_id (f.string_child ("Scaler"));
 +      _ab = f.bool_child ("AB");
 +      _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_video_frame_rate = f.number_child<int> ("DCPVideoFrameRate");
 +      _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
 +      _dcp_audio_channels = f.number_child<int> ("DCPAudioChannels");
  
 -              /* 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()];
 -              }
 +      _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"));
  
 -              /* 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()];
 -              }
 -      }
 -              
        _dirty = false;
  }
  
 -libdcp::Size
 -Film::cropped_size (libdcp::Size s) const
 -{
 -      boost::mutex::scoped_lock lm (_state_mutex);
 -      s.width -= _crop.left + _crop.right;
 -      s.height -= _crop.top + _crop.bottom;
 -      return s;
 -}
 -
  /** Given a directory name, return its full path within the Film's directory.
   *  The directory (and its parents) will be created if they do not exist.
   */
@@@ -506,6 -725,67 +491,6 @@@ 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()) {
 -              return 0;
 -      }
 -      
 -      /* Resample to a DCI-approved sample rate */
 -      double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
 -
 -      FrameRateConversion frc (source_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
 -         look different in the DCP compared to the source (slower or faster).
 -         skip/repeat doesn't come into effect here.
 -      */
 -
 -      if (frc.change_speed) {
 -              t *= source_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
                d << "_" << dcp_content_type()->dci_name();
        }
  
 -      if (format()) {
 -              d << "_" << format()->dci_name();
 +      if (container()) {
 +              d << "_" << container()->dci_name();
        }
  
        DCIMetadata const dm = dci_metadata ();
                }
        }
  
 -      switch (audio_channels()) {
 -      case 1:
 -              d << "_10";
 -              break;
 -      case 2:
 -              d << "_20";
 -              break;
 -      case 6:
 -              d << "_51";
 -              break;
 -      case 8:
 -              d << "_71";
 -              break;
 -      }
 -
 -      d << "_2K";
 +      d << "_51_2K";
  
        if (!dm.studio.empty ()) {
                d << "_" << dm.studio;
@@@ -615,6 -910,107 +600,6 @@@ Film::set_use_dci_name (bool u
        signal_changed (USE_DCI_NAME);
  }
  
 -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 */
 -      set_format (Config::instance()->default_format ());
 -
 -      /* Still image DCPs must use external audio */
 -      if (content_type() == STILL) {
 -              set_use_content_audio (false);
 -      }
 -}
 -
 -void
 -Film::set_trust_content_header (bool t)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _trust_content_header = t;
 -      }
 -      
 -      signal_changed (TRUST_CONTENT_HEADER);
 -
 -      if (!_trust_content_header && !content().empty()) {
 -              /* We just said that we don't trust the content's header */
 -              examine_content ();
 -      }
 -}
 -             
  void
  Film::set_dcp_content_type (DCPContentType const * t)
  {
  }
  
  void
 -Film::set_format (Format const * f)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _format = f;
 -      }
 -      signal_changed (FORMAT);
 -}
 -
 -void
 -Film::set_crop (Crop c)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _crop = c;
 -      }
 -      signal_changed (CROP);
 -}
 -
 -void
 -Film::set_left_crop (int c)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              
 -              if (_crop.left == c) {
 -                      return;
 -              }
 -              
 -              _crop.left = c;
 -      }
 -      signal_changed (CROP);
 -}
 -
 -void
 -Film::set_right_crop (int c)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              if (_crop.right == c) {
 -                      return;
 -              }
 -              
 -              _crop.right = c;
 -      }
 -      signal_changed (CROP);
 -}
 -
 -void
 -Film::set_top_crop (int c)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              if (_crop.top == c) {
 -                      return;
 -              }
 -              
 -              _crop.top = c;
 -      }
 -      signal_changed (CROP);
 -}
 -
 -void
 -Film::set_bottom_crop (int c)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              if (_crop.bottom == c) {
 -                      return;
 -              }
 -              
 -              _crop.bottom = c;
 -      }
 -      signal_changed (CROP);
 -}
 -
 -void
 -Film::set_filters (vector<Filter const *> f)
 +Film::set_container (Container const * c)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _filters = f;
 +              _container = c;
        }
 -      signal_changed (FILTERS);
 +      signal_changed (CONTAINER);
  }
  
- void
- Film::set_filters (vector<Filter const *> f)
- {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _filters = f;
-       }
-       signal_changed (FILTERS);
- }
  void
  Film::set_scaler (Scaler const * s)
  {
  }
  
  void
 -Film::set_trim_start (int t)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _trim_start = t;
 -      }
 -      signal_changed (TRIM_START);
 -}
 -
 -void
 -Film::set_trim_end (int t)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _trim_end = 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)
 +Film::set_ab (bool a)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_audio_stream = s;
 +              _ab = a;
        }
 -      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)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _use_content_audio = e;
 -      }
 -
 -      signal_changed (USE_CONTENT_AUDIO);
 -}
 -
 -void
 -Film::set_audio_gain (float g)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _audio_gain = g;
 -      }
 -      signal_changed (AUDIO_GAIN);
 -}
 -
 -void
 -Film::set_audio_delay (int d)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _audio_delay = 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);
 +      signal_changed (AB);
  }
  
  void
@@@ -727,15 -1297,85 +702,15 @@@ Film::set_dci_metadata (DCIMetadata m
  
  
  void
 -Film::set_dcp_frame_rate (int f)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _dcp_frame_rate = f;
 -      }
 -      signal_changed (DCP_FRAME_RATE);
 -}
 -
 -void
 -Film::set_size (libdcp::Size s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _size = s;
 -      }
 -      signal_changed (SIZE);
 -}
 -
 -void
 -Film::set_length (SourceFrame l)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _length = l;
 -      }
 -      signal_changed (LENGTH);
 -}
 -
 -void
 -Film::unset_length ()
 +Film::set_dcp_video_frame_rate (int f)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _length = boost::none;
 +              _dcp_video_frame_rate = f;
        }
 -      signal_changed (LENGTH);
 +      signal_changed (DCP_VIDEO_FRAME_RATE);
  }
  
 -void
 -Film::set_content_digest (string d)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_digest = d;
 -      }
 -      _dirty = true;
 -}
 -
 -void
 -Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_audio_streams = s;
 -      }
 -      signal_changed (CONTENT_AUDIO_STREAMS);
 -}
 -
 -void
 -Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _subtitle_streams = s;
 -      }
 -      signal_changed (SUBTITLE_STREAMS);
 -}
 -
 -void
 -Film::set_source_frame_rate (float f)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _source_frame_rate = f;
 -      }
 -      signal_changed (SOURCE_FRAME_RATE);
 -}
 -      
  void
  Film::signal_changed (Property p)
  {
                _dirty = true;
        }
  
 -      if (ui_signaller) {
 -              ui_signaller->emit (boost::bind (boost::ref (Changed), p));
 +      switch (p) {
 +      case Film::CONTENT:
 +              set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ());
 +              break;
 +      default:
 +              break;
        }
 -}
  
 -int
 -Film::audio_channels () const
 -{
 -      shared_ptr<AudioStream> s = audio_stream ();
 -      if (!s) {
 -              return 0;
 +      if (ui_signaller) {
 +              ui_signaller->emit (boost::bind (boost::ref (Changed), p));
        }
 -
 -      return s->channels ();
  }
  
  void
@@@ -763,6 -1406,16 +738,6 @@@ Film::set_dci_date_today (
        _dci_date = boost::gregorian::day_clock::local_day ();
  }
  
 -boost::shared_ptr<AudioStream>
 -Film::audio_stream () const
 -{
 -      if (use_content_audio()) {
 -              return _content_audio_stream;
 -      }
 -
 -      return _sndfile_stream;
 -}
 -
  string
  Film::info_path (int f) const
  {
@@@ -818,114 -1471,20 +793,113 @@@ Film::have_dcp () cons
        return true;
  }
  
-       boost::mutex::scoped_lock lm (_state_mutex);
 +shared_ptr<Player>
 +Film::player () const
 +{
 +      return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
 +}
 +
 +shared_ptr<Playlist>
 +Film::playlist () const
 +{
 +      boost::mutex::scoped_lock lm (_state_mutex);
 +      return _playlist;
 +}
 +
 +Playlist::ContentList
 +Film::content () const
 +{
 +      return _playlist->content ();
 +}
 +
 +void
 +Film::add_content (shared_ptr<Content> c)
 +{
 +      _playlist->add (c);
 +      examine_content (c);
 +}
 +
 +void
 +Film::remove_content (shared_ptr<Content> c)
 +{
 +      _playlist->remove (c);
 +}
 +
 +Time
 +Film::length () const
 +{
 +      return _playlist->length ();
 +}
 +
  bool
 -Film::has_audio () const
 +Film::has_subtitles () const
  {
 -      if (use_content_audio()) {
 -              return audio_stream();
 -      }
 +      return _playlist->has_subtitles ();
 +}
  
 -      vector<string> const e = external_audio ();
 -      for (vector<string>::const_iterator i = e.begin(); i != e.end(); ++i) {
 -              if (!i->empty ()) {
 -                      return true;
 -              }
 +OutputVideoFrame
 +Film::best_dcp_video_frame_rate () const
 +{
 +      return _playlist->best_dcp_frame_rate ();
 +}
 +
 +void
 +Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
 +{
 +      if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
 +              set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ());
 +      } 
 +
 +      if (ui_signaller) {
 +              ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
        }
 +}
 +
 +void
 +Film::playlist_changed ()
 +{
 +      signal_changed (CONTENT);
 +}     
 +
 +int
 +Film::loop () const
 +{
 +      return _playlist->loop ();
 +}
 +
 +void
 +Film::set_loop (int c)
 +{
 +      _playlist->set_loop (c);
 +}
  
 -      return false;
 +OutputAudioFrame
 +Film::time_to_audio_frames (Time t) const
 +{
 +      return t * dcp_audio_frame_rate () / TIME_HZ;
 +}
 +
 +OutputVideoFrame
 +Film::time_to_video_frames (Time t) const
 +{
 +      return t * dcp_video_frame_rate () / TIME_HZ;
 +}
 +
 +Time
 +Film::audio_frames_to_time (OutputAudioFrame f) const
 +{
 +      return f * TIME_HZ / dcp_audio_frame_rate ();
 +}
 +
 +Time
 +Film::video_frames_to_time (OutputVideoFrame f) const
 +{
 +      return f * TIME_HZ / dcp_video_frame_rate ();
  }
  
 +OutputAudioFrame
 +Film::dcp_audio_frame_rate () const
 +{
 +      /* XXX */
 +      return 48000;
 +}
diff --combined src/lib/film.h
index f0ccd99e77b33139b95ad23bc6d5678e18c4da9a,dd0a83d94e473f5da6d5b9e98d1378e247c0c3eb..84f0b0233ac6247634a9499b94b2c0cc30f8a934
  */
  
  /** @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 "playlist.h"
  
 -class Format;
 +class DCPContentType;
 +class Container;
  class Job;
  class Filter;
  class Log;
  class ExamineContentJob;
  class AnalyseAudioJob;
  class ExternalAudioStream;
 +class Content;
 +class Player;
  
  /** @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 (std::string d);
        Film (Film const &);
 -      ~Film ();
  
        std::string info_dir () const;
        std::string j2c_path (int f, bool t) const;
        std::string internal_video_mxf_filename () const;
        std::string audio_analysis_path () const;
  
 +      void examine_content (boost::shared_ptr<Content>);
        std::string dcp_video_mxf_filename () const;
        std::string dcp_audio_mxf_filename () const;
  
 -      void examine_content ();
        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 ();
 +      void write_metadata () const;
  
 -      libdcp::Size cropped_size (libdcp::Size) const;
        std::string dci_name (bool if_created_now) const;
        std::string dcp_name (bool if_created_now = false) const;
  
                return _dirty;
        }
  
 -      int audio_channels () const;
 +      bool have_dcp () const;
 +
 +      boost::shared_ptr<Player> player () const;
 +      boost::shared_ptr<Playlist> playlist () const;
  
 -      void set_dci_date_today ();
 +      OutputAudioFrame dcp_audio_frame_rate () const;
-       int dcp_audio_channels () const;
  
 -      bool have_dcp () const;
 +      OutputAudioFrame time_to_audio_frames (Time) const;
 +      OutputVideoFrame time_to_video_frames (Time) const;
 +      Time video_frames_to_time (OutputVideoFrame) const;
 +      Time audio_frames_to_time (OutputAudioFrame) const;
  
 -      enum TrimType {
 -              CPL,
 -              ENCODE
 -      };
 +      /* Proxies for some Playlist methods */
 +
 +      Playlist::ContentList content () const;
 +
 +      Time length () const;
 +      bool has_subtitles () const;
 +      OutputVideoFrame best_dcp_video_frame_rate () const;
 +
 +      void set_loop (int);
 +      int loop () const;
  
        /** Identifiers for the parts of our state;
            used for signalling changes.
                NONE,
                NAME,
                USE_DCI_NAME,
 +              /** The playlist's content list has changed (i.e. content has been added, moved around or removed) */
                CONTENT,
 -              TRUST_CONTENT_HEADER,
 +              LOOP,
                DCP_CONTENT_TYPE,
 -              FORMAT,
 -              CROP,
 -              FILTERS,
 +              CONTAINER,
-               FILTERS,
                SCALER,
 -              TRIM_START,
 -              TRIM_END,
 -              TRIM_TYPE,
 -              DCP_AB,
 -              CONTENT_AUDIO_STREAM,
 -              EXTERNAL_AUDIO,
 -              USE_CONTENT_AUDIO,
 -              AUDIO_GAIN,
 -              AUDIO_DELAY,
 -              STILL_DURATION,
 -              SUBTITLE_STREAM,
 +              AB,
                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_VIDEO_FRAME_RATE,
        };
  
  
                return _use_dci_name;
        }
  
 -      std::string content () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _content;
 -      }
 -
 -      bool trust_content_header () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _trust_content_header;
 -      }
 -
        DCPContentType const * dcp_content_type () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _dcp_content_type;
        }
  
 -      Format const * format () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _format;
 -      }
 -
 -      Crop crop () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _crop;
 -      }
 -
 -      std::vector<Filter const *> filters () const {
 +      Container const * container () const {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              return _filters;
 +              return _container;
        }
  
-       std::vector<Filter const *> filters () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _filters;
-       }
        Scaler const * scaler () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _scaler;
        }
  
 -      int trim_start () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _trim_start;
 -      }
 -
 -      int trim_end () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _trim_end;
 -      }
 -
 -      TrimType trim_type () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _trim_type;
 -      }
 -
 -      bool dcp_ab () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _dcp_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;
 -      }
 -
 -      int audio_delay () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _audio_delay;
 -      }
 -
 -      int still_duration () const {
 +      bool ab () 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;
 +              return _ab;
        }
  
        bool with_subtitles () const {
                return _dci_metadata;
        }
  
 -      int dcp_frame_rate () const {
 +      /* XXX: -> "video_frame_rate" */
 +      int dcp_video_frame_rate () const {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              return _dcp_frame_rate;
 -      }
 -      
 -      libdcp::Size size () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _size;
 +              return _dcp_video_frame_rate;
        }
  
 -      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 {
 +      int dcp_audio_channels () const {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              if (content_type() == STILL) {
 -                      return 24;
 -              }
 -              
 -              return _source_frame_rate;
 +              return _dcp_audio_channels;
        }
  
 -      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 add_content (boost::shared_ptr<Content>);
 +      void remove_content (boost::shared_ptr<Content>);
        void set_dcp_content_type (DCPContentType const *);
 -      void set_format (Format const *);
 -      void set_crop (Crop);
 -      void set_left_crop (int);
 -      void set_right_crop (int);
 -      void set_top_crop (int);
 -      void set_bottom_crop (int);
 -      void set_filters (std::vector<Filter const *>);
 +      void set_container (Container const *);
-       void set_filters (std::vector<Filter const *>);
        void set_scaler (Scaler const *);
 -      void set_trim_start (int);
 -      void set_trim_end (int);
 -      void set_trim_type (TrimType);
 -      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_audio_gain (float);
 -      void set_audio_delay (int);
 -      void set_still_duration (int);
 -      void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
 +      void set_ab (bool);
        void set_with_subtitles (bool);
        void set_subtitle_offset (int);
        void set_subtitle_scale (float);
        void set_colour_lut (int);
        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_dcp_video_frame_rate (int);
 +      void set_dci_date_today ();
 +
 +      /** 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 playlist_changed ();
 +      void playlist_content_changed (boost::weak_ptr<Content>, int);
        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;
        /** 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.) */
 -      Format const * _format;
 -      /** The crop to apply to the source */
 -      Crop _crop;
 -      /** Video filters that should be used when generating DCPs */
 -      std::vector<Filter const *> _filters;
 +      /** The container to put this Film in (flat, scope, etc.) */
 +      Container const * _container;
-       /** Video filters that should be used when generating DCPs */
-       std::vector<Filter const *> _filters;
        /** Scaler algorithm to use */
        Scaler const * _scaler;
 -      /** Frames to trim off the start of the DCP */
 -      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;
 -      /** 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;
 +      bool _ab;
        /** 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;
 +      /** Frames per second to run our DCP at */
 +      int _dcp_video_frame_rate;
        /** 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;
 +      int _dcp_audio_channels;
  
        /** true if our state has changed since we last saved it */
        mutable bool _dirty;
diff --combined src/lib/filter_graph.cc
index df8f1e9ddef2c14956633dbc6dbb2e010eb77b82,b0427a23d0566166921fd6e95eceb5b86c879505..4564033d5379dd5d6770addfdac826cff447237f
  
  extern "C" {
  #include <libavfilter/avfiltergraph.h>
- #ifdef HAVE_BUFFERSRC_H       
  #include <libavfilter/buffersrc.h>
- #endif        
- #if (LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 53 && LIBAVFILTER_VERSION_MINOR <= 77) || LIBAVFILTER_VERSION_MAJOR == 3
  #include <libavfilter/avcodec.h>
  #include <libavfilter/buffersink.h>
- #elif LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
- #include <libavfilter/vsrc_buffer.h>
- #endif
  #include <libavformat/avio.h>
  }
  #include "decoder.h"
  #include "filter_graph.h"
- #include "ffmpeg_compatibility.h"
  #include "filter.h"
  #include "exceptions.h"
  #include "image.h"
--#include "film.h"
  #include "ffmpeg_decoder.h"
  
  #include "i18n.h"
@@@ -49,34 -42,28 +41,32 @@@ using std::stringstream
  using std::string;
  using std::list;
  using boost::shared_ptr;
 +using boost::weak_ptr;
  using libdcp::Size;
  
- /** Construct a FilterGraph for the settings in a film.
 -/** Construct a FFmpegFilterGraph for the settings in a film.
-- *  @param film Film.
-- *  @param decoder Decoder that we are using.
++/** Construct a FilterGraph for the settings in a piece of content.
++ *  @param content Content.
   *  @param s Size of the images to process.
   *  @param p Pixel format of the images to process.
   */
- FilterGraph::FilterGraph (weak_ptr<const Film> weak_film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p)
 -FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p)
++FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p)
        : _buffer_src_context (0)
        , _buffer_sink_context (0)
        , _size (s)
        , _pixel_format (p)
  {
-       shared_ptr<const Film> film = weak_film.lock ();
-       assert (film);
+       _frame = av_frame_alloc ();
        
--      string filters = Filter::ffmpeg_strings (film->filters()).first;
++      string filters = Filter::ffmpeg_strings (content->filters()).first;
        if (!filters.empty ()) {
--              filters += N_(",");
++              filters += ",";
        }
  
-       Crop crop = decoder->ffmpeg_content()->crop ();
-       libdcp::Size cropped_size = decoder->video_size ();
 -      filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size()));
++      Crop crop = content->crop ();
++      libdcp::Size cropped_size = _size;
 +      cropped_size.width -= crop.left + crop.right;
 +      cropped_size.height -= crop.top + crop.bottom;
 +      filters += crop_string (Position (crop.left, crop.top), cropped_size);
  
        AVFilterGraph* graph = avfilter_graph_alloc();
        if (graph == 0) {
                throw DecodeError (N_("could not find buffer src filter"));
        }
  
-       AVFilter* buffer_sink = get_sink ();
+       AVFilter* buffer_sink = avfilter_get_by_name(N_("buffersink"));
+       if (buffer_sink == 0) {
+               throw DecodeError (N_("Could not create buffer sink filter"));
+       }
  
        stringstream a;
-       a << _size.width << N_(":")
-         << _size.height << N_(":")
-         << _pixel_format << N_(":")
-         << "0:1:0:1";
+       a << "video_size=" << _size.width << "x" << _size.height << ":"
+         << "pix_fmt=" << _pixel_format << ":"
 -        << "time_base=" << decoder->time_base_numerator() << "/" << decoder->time_base_denominator() << ":"
 -        << "pixel_aspect=" << decoder->sample_aspect_ratio_numerator() << "/" << decoder->sample_aspect_ratio_denominator();
++        << "time_base=0/1:"
++        << "pixel_aspect=0/1";
  
        int r;
  
-       if ((r = avfilter_graph_create_filter (&_buffer_src_context, buffer_src, N_("in"), a.str().c_str(), 0, graph)) < 0) {
+       if ((r = avfilter_graph_create_filter (&_buffer_src_context, buffer_src, "in", a.str().c_str(), 0, graph)) < 0) {
                throw DecodeError (N_("could not create buffer source"));
        }
  
                throw DecodeError (N_("could not create buffer sink."));
        }
  
+       av_free (sink_params);
        AVFilterInOut* outputs = avfilter_inout_alloc ();
        outputs->name = av_strdup(N_("in"));
        outputs->filter_ctx = _buffer_src_context;
        inputs->pad_idx = 0;
        inputs->next = 0;
  
- #if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-       if (avfilter_graph_parse (graph, filters.c_str(), inputs, outputs, 0) < 0) {
-               throw DecodeError (N_("could not set up filter graph."));
-       }
- #else 
        if (avfilter_graph_parse (graph, filters.c_str(), &inputs, &outputs, 0) < 0) {
                throw DecodeError (N_("could not set up filter graph."));
        }
- #endif        
        
        if (avfilter_graph_config (graph, 0) < 0) {
                throw DecodeError (N_("could not configure filter graph."));
        /* XXX: leaking `inputs' / `outputs' ? */
  }
  
 -FFmpegFilterGraph::~FFmpegFilterGraph ()
++FilterGraph::~FilterGraph ()
+ {
+       av_frame_free (&_frame);
+ }
  /** Take an AVFrame and process it using our configured filters, returning a
-  *  set of Images.
+  *  set of Images.  Caller handles memory management of the input frame.
   */
  list<shared_ptr<Image> >
- FilterGraph::process (AVFrame const * frame)
 -FFmpegFilterGraph::process (AVFrame* frame)
++FilterGraph::process (AVFrame* frame)
  {
        list<shared_ptr<Image> > images;
-       
- #if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 53 && LIBAVFILTER_VERSION_MINOR <= 61
-       if (av_vsrc_buffer_add_frame (_buffer_src_context, frame, 0) < 0) {
-               throw DecodeError (N_("could not push buffer into filter chain."));
-       }
- #elif LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-       AVRational par;
-       par.num = sample_aspect_ratio_numerator ();
-       par.den = sample_aspect_ratio_denominator ();
-       if (av_vsrc_buffer_add_frame (_buffer_src_context, frame, 0, par) < 0) {
-               throw DecodeError (N_("could not push buffer into filter chain."));
-       }
- #else
  
        if (av_buffersrc_write_frame (_buffer_src_context, frame) < 0) {
                throw DecodeError (N_("could not push buffer into filter chain."));
        }
  
- #endif        
-       
- #if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 15 && LIBAVFILTER_VERSION_MINOR <= 61      
-       while (avfilter_poll_frame (_buffer_sink_context->inputs[0])) {
- #else
-       while (av_buffersink_read (_buffer_sink_context, 0)) {
- #endif                
- #if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 15
-               
-               int r = avfilter_request_frame (_buffer_sink_context->inputs[0]);
-               if (r < 0) {
-                       throw DecodeError (N_("could not request filtered frame"));
-               }
-               
-               AVFilterBufferRef* filter_buffer = _buffer_sink_context->inputs[0]->cur_buf;
-               
- #else
-               AVFilterBufferRef* filter_buffer;
-               if (av_buffersink_get_buffer_ref (_buffer_sink_context, &filter_buffer, 0) < 0) {
-                       filter_buffer = 0;
+       while (1) {
+               if (av_buffersink_get_frame (_buffer_sink_context, _frame) < 0) {
+                       break;
                }
  
- #endif                
-               
-               if (filter_buffer) {
-                       /* This takes ownership of filter_buffer */
-                       images.push_back (shared_ptr<Image> (new FilterBufferImage ((PixelFormat) frame->format, filter_buffer)));
-               }
+               images.push_back (shared_ptr<Image> (new SimpleImage (_frame)));
        }
        
        return images;
   *  @return true if this chain can process images with `s' and `p', otherwise false.
   */
  bool
 -FFmpegFilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
 +FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
  {
        return (_size == s && _pixel_format == p);
  }
 -
 -list<shared_ptr<Image> >
 -EmptyFilterGraph::process (AVFrame* frame)
 -{
 -      list<shared_ptr<Image> > im;
 -      im.push_back (shared_ptr<Image> (new SimpleImage (frame)));
 -      return im;
 -}
 -
 -shared_ptr<FilterGraph>
 -filter_graph_factory (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size size, AVPixelFormat pixel_format)
 -{
 -      if (film->filters().empty() && film->crop() == Crop()) {
 -              return shared_ptr<FilterGraph> (new EmptyFilterGraph);
 -      }
 -
 -      return shared_ptr<FilterGraph> (new FFmpegFilterGraph (film, decoder, size, pixel_format));
 -}
diff --combined src/lib/filter_graph.h
index c7a01f58e1f66c446c3c1ce21390d200bb182790,2138943e42b6e3e6ef11ba4967f0c85f0755d618..e294812c2baa9836f5aeff99711057bae611148e
   *  @brief A graph of FFmpeg filters.
   */
  
 -#ifndef DVDOMATIC_FILTER_GRAPH_H
 -#define DVDOMATIC_FILTER_GRAPH_H
 +#ifndef DCPOMATIC_FILTER_GRAPH_H
 +#define DCPOMATIC_FILTER_GRAPH_H
  
  #include "util.h"
- #include "ffmpeg_compatibility.h"
  
  class Image;
  class VideoFilter;
--class FFmpegDecoder;
  
 -class FilterGraph
 -{
 -public:
 -      virtual bool can_process (libdcp::Size, AVPixelFormat) const = 0;
 -      virtual std::list<boost::shared_ptr<Image> > process (AVFrame *) = 0;
 -};
 -
 -class EmptyFilterGraph : public FilterGraph
 -{
 -public:
 -      bool can_process (libdcp::Size, AVPixelFormat) const {
 -              return true;
 -      }
 -
 -      std::list<boost::shared_ptr<Image> > process (AVFrame *);
 -};
 -
 -/** @class FFmpegFilterGraph
 +/** @class FilterGraph
   *  @brief A graph of FFmpeg filters.
   */
 -class FFmpegFilterGraph : public FilterGraph
 +class FilterGraph
  {
  public:
-       FilterGraph (boost::weak_ptr<const Film>, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p);
 -      FFmpegFilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p);
 -      ~FFmpegFilterGraph ();
++      FilterGraph (boost::shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p);
++      ~FilterGraph ();
  
        bool can_process (libdcp::Size s, AVPixelFormat p) const;
-       std::list<boost::shared_ptr<Image> > process (AVFrame const * frame);
+       std::list<boost::shared_ptr<Image> > process (AVFrame * frame);
  
  private:
        AVFilterContext* _buffer_src_context;
        AVFilterContext* _buffer_sink_context;
        libdcp::Size _size; ///< size of the images that this chain can process
        AVPixelFormat _pixel_format; ///< pixel format of the images that this chain can process
+       AVFrame* _frame;
  };
  
 -boost::shared_ptr<FilterGraph> filter_graph_factory (boost::shared_ptr<Film>, FFmpegDecoder *, libdcp::Size, AVPixelFormat);
 -
  #endif
diff --combined src/lib/image.h
index de03d0e3f60485a8d316deb00cef21ad80bc92f7,70dacfaee4bd9e4bcd24bdf8ba4241916b1941e4..34f87b18852966c39a7b446a59ee1809a0ef35e5
@@@ -21,8 -21,8 +21,8 @@@
   *  @brief A set of classes to describe video images.
   */
  
 -#ifndef DVDOMATIC_IMAGE_H
 -#define DVDOMATIC_IMAGE_H
 +#ifndef DCPOMATIC_IMAGE_H
 +#define DCPOMATIC_IMAGE_H
  
  #include <string>
  #include <boost/shared_ptr.hpp>
@@@ -32,10 -32,8 +32,8 @@@ extern "C" 
  #include <libavfilter/avfilter.h>
  }
  #include "util.h"
- #include "ffmpeg_compatibility.h"
  
  class Scaler;
- class RGBFrameImage;
  class SimpleImage;
  
  /** @class Image
@@@ -92,6 -90,8 +90,8 @@@ protected
        virtual void swap (Image &);
        float bytes_per_pixel (int) const;
  
+       friend class pixel_formats_test;
  private:
        void yuv_16_black (uint16_t);
        static uint16_t swap_16 (uint16_t);
        AVPixelFormat _pixel_format; ///< FFmpeg's way of describing the pixel format of this Image
  };
  
- /** @class FilterBufferImage
-  *  @brief An Image that is held in an AVFilterBufferRef.
-  */
- class FilterBufferImage : public Image
- {
- public:
-       FilterBufferImage (AVPixelFormat, AVFilterBufferRef *);
-       ~FilterBufferImage ();
-       uint8_t ** data () const;
-       int * line_size () const;
-       int * stride () const;
-       libdcp::Size size () const;
-       bool aligned () const;
- private:
-       /* Not allowed */
-       FilterBufferImage (FilterBufferImage const &);
-       FilterBufferImage& operator= (FilterBufferImage const &);
-       
-       AVFilterBufferRef* _buffer;
-       int* _line_size;
- };
  /** @class SimpleImage
   *  @brief An Image for which memory is allocated using a `simple' av_malloc().
   */
@@@ -130,6 -106,7 +106,7 @@@ class SimpleImage : public Imag
  {
  public:
        SimpleImage (AVPixelFormat, libdcp::Size, bool);
+       SimpleImage (AVFrame *);
        SimpleImage (SimpleImage const &);
        SimpleImage (boost::shared_ptr<const Image>);
        SimpleImage& operator= (SimpleImage const &);
diff --combined src/lib/player.cc
index 032b3d49bcc897129d99d5ddd7b6c53448908880,0000000000000000000000000000000000000000..ff13f95dbd2197be7be681ff520b29ab1ef4422b
mode 100644,000000..100644
--- /dev/null
@@@ -1,351 -1,0 +1,355 @@@
-         _audio_buffers.accumulate_frames (audio, 0, 0, audio->frames ());
 +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
 +
 +/*
 +    Copyright (C) 2013 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 "player.h"
 +#include "film.h"
 +#include "ffmpeg_decoder.h"
 +#include "ffmpeg_content.h"
 +#include "imagemagick_decoder.h"
 +#include "imagemagick_content.h"
 +#include "sndfile_decoder.h"
 +#include "sndfile_content.h"
 +#include "playlist.h"
 +#include "job.h"
 +#include "image.h"
 +#include "null_content.h"
 +#include "black_decoder.h"
 +#include "silence_decoder.h"
 +
 +using std::list;
 +using std::cout;
 +using std::min;
 +using std::max;
 +using std::vector;
 +using boost::shared_ptr;
 +using boost::weak_ptr;
 +using boost::dynamic_pointer_cast;
 +
 +struct Piece
 +{
 +      Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
 +              : content (c)
 +              , decoder (d)
 +      {}
 +      
 +      shared_ptr<Content> content;
 +      shared_ptr<Decoder> decoder;
 +};
 +
 +Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
 +      : _film (f)
 +      , _playlist (p)
 +      , _video (true)
 +      , _audio (true)
 +      , _subtitles (true)
 +      , _have_valid_pieces (false)
 +      , _position (0)
 +      , _audio_buffers (f->dcp_audio_channels(), 0)
 +      , _next_audio (0)
 +{
 +      _playlist->Changed.connect (bind (&Player::playlist_changed, this));
 +      _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2));
 +}
 +
 +void
 +Player::disable_video ()
 +{
 +      _video = false;
 +}
 +
 +void
 +Player::disable_audio ()
 +{
 +      _audio = false;
 +}
 +
 +void
 +Player::disable_subtitles ()
 +{
 +      _subtitles = false;
 +}
 +
 +bool
 +Player::pass ()
 +{
 +      if (!_have_valid_pieces) {
 +              setup_pieces ();
 +              _have_valid_pieces = true;
 +      }
 +
 +        /* Here we are just finding the active decoder with the earliest last emission time, then
 +           calling pass on it.
 +        */
 +
 +        Time earliest_t = TIME_MAX;
 +        shared_ptr<Piece> earliest;
 +
 +      for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
 +              cout << "check " << (*i)->content->file() << " start=" << (*i)->content->start() << ", next=" << (*i)->decoder->next() << ", end=" << (*i)->content->end() << "\n";
 +              if (((*i)->decoder->next() + (*i)->content->start()) >= (*i)->content->end()) {
 +                      continue;
 +              }
++
++              if (!_audio && dynamic_pointer_cast<SndfileContent> ((*i)->content)) {
++                      continue;
++              }
 +              
 +              Time const t = (*i)->content->start() + (*i)->decoder->next();
 +              if (t < earliest_t) {
 +                      cout << "\t candidate; " << t << " " << (t / TIME_HZ) << ".\n";
 +                      earliest_t = t;
 +                      earliest = *i;
 +              }
 +      }
 +
 +      if (!earliest) {
 +              return true;
 +      }
 +
 +      cout << "PASS:\n";
 +      cout << "\tpass " << earliest->content->file() << " ";
 +      if (dynamic_pointer_cast<FFmpegContent> (earliest->content)) {
 +              cout << " FFmpeg.\n";
 +      } else if (dynamic_pointer_cast<ImageMagickContent> (earliest->content)) {
 +              cout << " ImageMagickContent.\n";
 +      } else if (dynamic_pointer_cast<SndfileContent> (earliest->content)) {
 +              cout << " SndfileContent.\n";
 +      } else if (dynamic_pointer_cast<BlackDecoder> (earliest->decoder)) {
 +              cout << " Black.\n";
 +      } else if (dynamic_pointer_cast<SilenceDecoder> (earliest->decoder)) {
 +              cout << " Silence.\n";
 +      }
 +      
 +      earliest->decoder->pass ();
 +      _position = earliest->content->start() + earliest->decoder->next ();
 +      cout << "\tpassed to " << _position << " " << (_position / TIME_HZ) << "\n";
 +
 +        return false;
 +}
 +
 +void
 +Player::process_video (weak_ptr<Content> weak_content, shared_ptr<const Image> image, bool same, shared_ptr<Subtitle> sub, Time time)
 +{
 +      cout << "[V]\n";
 +      
 +      shared_ptr<Content> content = weak_content.lock ();
 +      if (!content) {
 +              return;
 +      }
 +      
 +      time += content->start ();
 +      
 +        Video (image, same, sub, time);
 +}
 +
 +void
 +Player::process_audio (weak_ptr<Content> weak_content, shared_ptr<const AudioBuffers> audio, Time time)
 +{
 +      shared_ptr<Content> content = weak_content.lock ();
 +      if (!content) {
 +              return;
 +      }
 +      
 +        /* The time of this audio may indicate that some of our buffered audio is not going to
 +           be added to any more, so it can be emitted.
 +        */
 +
 +      time += content->start ();
 +
 +        if (time > _next_audio) {
 +                /* We can emit some audio from our buffers */
 +              assert (_film->time_to_audio_frames (time - _next_audio) <= _audio_buffers.frames());
 +                OutputAudioFrame const N = _film->time_to_audio_frames (time - _next_audio);
 +                shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), N));
 +                emit->copy_from (&_audio_buffers, N, 0, 0);
 +                Audio (emit, _next_audio);
 +                _next_audio += _film->audio_frames_to_time (N);
 +
 +                /* And remove it from our buffers */
 +                if (_audio_buffers.frames() > N) {
 +                        _audio_buffers.move (N, 0, _audio_buffers.frames() - N);
 +                }
 +                _audio_buffers.set_frames (_audio_buffers.frames() - N);
 +        }
 +
 +        /* Now accumulate the new audio into our buffers */
 +        _audio_buffers.ensure_size (_audio_buffers.frames() + audio->frames());
++        _audio_buffers.accumulate_frames (audio.get(), 0, 0, audio->frames ());
 +}
 +
 +/** @return true on error */
 +void
 +Player::seek (Time t)
 +{
 +      if (!_have_valid_pieces) {
 +              setup_pieces ();
 +              _have_valid_pieces = true;
 +      }
 +
 +      if (_pieces.empty ()) {
 +              return;
 +      }
 +
 +      cout << "seek to " << t << " " << (t / TIME_HZ) << "\n";
 +
 +      for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
 +              Time s = t - (*i)->content->start ();
 +              s = max (static_cast<Time> (0), s);
 +              s = min ((*i)->content->length(), s);
 +              cout << "seek [" << (*i)->content->file() << "," << (*i)->content->start() << "," << (*i)->content->end() << "] to " << s << "\n";
 +              (*i)->decoder->seek (s);
 +      }
 +
 +      /* XXX: don't seek audio because we don't need to... */
 +}
 +
 +
 +void
 +Player::seek_back ()
 +{
 +
 +}
 +
 +void
 +Player::seek_forward ()
 +{
 +
 +}
 +
 +struct ContentSorter
 +{
 +      bool operator() (shared_ptr<Content> a, shared_ptr<Content> b)
 +      {
 +              return a->start() < b->start();
 +      }
 +};
 +
 +void
 +Player::setup_pieces ()
 +{
 +//    cout << "----- Player SETUP PIECES.\n";
 +
 +      list<shared_ptr<Piece> > old_pieces = _pieces;
 +
 +      _pieces.clear ();
 +
 +      Playlist::ContentList content = _playlist->content ();
 +      sort (content.begin(), content.end(), ContentSorter ());
 +      
 +      for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) {
 +
 +              shared_ptr<Decoder> decoder;
 +              
 +                /* XXX: into content? */
 +
 +              shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
 +              if (fc) {
 +                      shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio, _subtitles));
 +                      
 +                      fd->Video.connect (bind (&Player::process_video, this, *i, _1, _2, _3, _4));
 +                      fd->Audio.connect (bind (&Player::process_audio, this, *i, _1, _2));
 +
 +                      decoder = fd;
 +//                    cout << "\tFFmpeg @ " << fc->start() << " -- " << fc->end() << "\n";
 +              }
 +              
 +              shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i);
 +              if (ic) {
 +                      shared_ptr<ImageMagickDecoder> id;
 +                      
 +                      /* See if we can re-use an old ImageMagickDecoder */
 +                      for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
 +                              shared_ptr<ImageMagickDecoder> imd = dynamic_pointer_cast<ImageMagickDecoder> ((*j)->decoder);
 +                              if (imd && imd->content() == ic) {
 +                                      id = imd;
 +                              }
 +                      }
 +
 +                      if (!id) {
 +                              id.reset (new ImageMagickDecoder (_film, ic));
 +                              id->Video.connect (bind (&Player::process_video, this, *i, _1, _2, _3, _4));
 +                      }
 +
 +                      decoder = id;
 +//                    cout << "\tImageMagick @ " << ic->start() << " -- " << ic->end() << "\n";
 +              }
 +
 +              shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
 +              if (sc) {
 +                      shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
 +                      sd->Audio.connect (bind (&Player::process_audio, this, *i, _1, _2));
 +
 +                      decoder = sd;
 +//                    cout << "\tSndfile @ " << sc->start() << " -- " << sc->end() << "\n";
 +              }
 +
 +              _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder)));
 +      }
 +
 +      /* Fill in visual gaps with black and audio gaps with silence */
 +
 +      Time video_pos = 0;
 +      Time audio_pos = 0;
 +      list<shared_ptr<Piece> > pieces_copy = _pieces;
 +      for (list<shared_ptr<Piece> >::iterator i = pieces_copy.begin(); i != pieces_copy.end(); ++i) {
 +              if (dynamic_pointer_cast<VideoContent> ((*i)->content)) {
 +                      Time const diff = (*i)->content->start() - video_pos;
 +                      if (diff > 0) {
 +                              shared_ptr<NullContent> nc (new NullContent (_film, video_pos, diff));
 +                              shared_ptr<BlackDecoder> bd (new BlackDecoder (_film, nc));
 +                              bd->Video.connect (bind (&Player::process_video, this, nc, _1, _2, _3, _4));
 +                              _pieces.push_back (shared_ptr<Piece> (new Piece (nc, bd)));
 +//                            cout << "\tblack @ " << video_pos << " -- " << (video_pos + diff) << "\n";
 +                      }
 +                                              
 +                      video_pos = (*i)->content->end();
 +              } else {
 +                      Time const diff = (*i)->content->start() - audio_pos;
 +                      if (diff > 0) {
 +                              shared_ptr<NullContent> nc (new NullContent (_film, audio_pos, diff));
 +                              shared_ptr<SilenceDecoder> sd (new SilenceDecoder (_film, nc));
 +                              sd->Audio.connect (bind (&Player::process_audio, this, nc, _1, _2));
 +                              _pieces.push_back (shared_ptr<Piece> (new Piece (nc, sd)));
 +//                            cout << "\tsilence @ " << audio_pos << " -- " << (audio_pos + diff) << "\n";
 +                      }
 +                      audio_pos = (*i)->content->end();
 +              }
 +      }
 +}
 +
 +void
 +Player::content_changed (weak_ptr<Content> w, int p)
 +{
 +      shared_ptr<Content> c = w.lock ();
 +      if (!c) {
 +              return;
 +      }
 +
 +      if (p == ContentProperty::START || p == VideoContentProperty::VIDEO_LENGTH) {
 +              _have_valid_pieces = false;
 +      }
 +}
 +
 +void
 +Player::playlist_changed ()
 +{
 +      _have_valid_pieces = false;
 +}
diff --combined src/lib/po/fr_FR.po
index d69e2f7d5c31d3587e2b2c747426b25f38e5b029,81f61d8b8ba128f19f3628f30fef2a130815ad7f..f7e362edafbcd72ce55a19956c25f7a1689680ca
@@@ -5,10 -5,10 +5,10 @@@
  #
  msgid ""
  msgstr ""
 -"Project-Id-Version: DVD-o-matic FRENCH\n"
 +"Project-Id-Version: DCP-o-matic FRENCH\n"
  "Report-Msgid-Bugs-To: \n"
  "POT-Creation-Date: 2013-05-09 09:51+0100\n"
- "PO-Revision-Date: 2013-05-10 14:33+0100\n"
+ "PO-Revision-Date: 2013-05-21 10:30+0100\n"
  "Last-Translator: \n"
  "Language-Team: \n"
  "Language: \n"
@@@ -257,8 -257,8 +257,8 @@@ msgstr "Filtre dé-bloc horizontal
  
  #: src/lib/job.cc:97
  #: src/lib/job.cc:106
 -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)"
 -msgstr "Erreur indéterminée. Merci de rapporter le problème Ã  la liste DVD-o-matic (dvdomatic@carlh.net)"
 +msgid "It is not known what caused this error.  The best idea is to report the problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
 +msgstr "Erreur indéterminée. Merci de rapporter le problème Ã  la liste DCP-o-matic (dcpomatic@carlh.net)"
  
  #: src/lib/filter.cc:82
  msgid "Kernel deinterlacer"
@@@ -489,7 -489,7 +489,7 @@@ msgstr "lecture du fichier impossible
  
  #: src/lib/exceptions.cc:44
  msgid "could not read from file %1 (%2)"
- msgstr "création du dossier distant %1 impossible (%2)"
+ msgstr "lecture du fichier impossible %1 (%2)"
  
  #: src/lib/encoder.cc:137
  #: src/lib/encoder.cc:314
diff --combined src/lib/server.cc
index ca0bec580b62a4412c77e33677814153c9b8a378,9c5a77f681b2903390fec271f5610c62cd2d8b16..07b82694666c3146be87fbae45d13cf4722f19d4
@@@ -29,7 -29,6 +29,7 @@@
  #include <boost/algorithm/string.hpp>
  #include <boost/lexical_cast.hpp>
  #include <boost/scoped_array.hpp>
 +#include <libcxml/cxml.h>
  #include "server.h"
  #include "util.h"
  #include "scaler.h"
@@@ -52,19 -51,6 +52,19 @@@ using boost::bind
  using boost::scoped_array;
  using libdcp::Size;
  
 +ServerDescription::ServerDescription (shared_ptr<const cxml::Node> node)
 +{
 +      _host_name = node->string_child ("HostName");
 +      _threads = node->number_child<int> ("Threads");
 +}
 +
 +void
 +ServerDescription::as_xml (xmlpp::Node* root) const
 +{
 +      root->add_child("HostName")->add_child_text (_host_name);
 +      root->add_child("Threads")->add_child_text (boost::lexical_cast<string> (_threads));
 +}
 +
  /** Create a server description from a string of metadata returned from as_metadata().
   *  @param v Metadata.
   *  @return ServerDescription, or 0.
@@@ -82,6 -68,15 +82,6 @@@ ServerDescription::create_from_metadat
        return new ServerDescription (b[0], atoi (b[1].c_str ()));
  }
  
 -/** @return Description of this server as text */
 -string
 -ServerDescription::as_metadata () const
 -{
 -      stringstream s;
 -      s << _host_name << N_(" ") << _threads;
 -      return s.str ();
 -}
 -
  Server::Server (shared_ptr<Log> log)
        : _log (log)
  {
@@@ -111,7 -106,7 +111,6 @@@ Server::process (shared_ptr<Socket> soc
        string scaler_id = get_required_string (kv, N_("scaler"));
        int frame = get_required_int (kv, N_("frame"));
        int frames_per_second = get_required_int (kv, N_("frames_per_second"));
--      string post_process = get_optional_string (kv, N_("post_process"));
        int colour_lut_index = get_required_int (kv, N_("colour_lut"));
        int j2k_bandwidth = get_required_int (kv, N_("j2k_bandwidth"));
        Position subtitle_position (get_optional_int (kv, N_("subtitle_x")), get_optional_int (kv, N_("subtitle_y")));
  
        DCPVideoFrame dcp_video_frame (
                image, sub, out_size, padding, subtitle_offset, subtitle_scale,
--              scaler, frame, frames_per_second, post_process, colour_lut_index, j2k_bandwidth, _log
++              scaler, frame, frames_per_second, colour_lut_index, j2k_bandwidth, _log
                );
        
        shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally ();
index b6dac2e765bb89ec0fd17bd3c1ff0c6e91e7e3c1,7e9e67d0fa2e24fd0ede23561ac47bbe682187e8..c4c6e5f4ee065f6dca6145defbb293bedcb5b7d1
  
  #include <iostream>
  #include <sndfile.h>
 +#include "sndfile_content.h"
  #include "sndfile_decoder.h"
  #include "film.h"
  #include "exceptions.h"
 +#include "audio_buffers.h"
  
  #include "i18n.h"
  
  using std::vector;
  using std::string;
 -using std::stringstream;
  using std::min;
  using std::cout;
  using boost::shared_ptr;
 -using boost::optional;
  
 -SndfileDecoder::SndfileDecoder (shared_ptr<Film> f, DecodeOptions o)
 -      : Decoder (f, o)
 -      , AudioDecoder (f, o)
 -      , _done (0)
 -      , _frames (0)
 +SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c)
 +      : Decoder (f)
 +      , AudioDecoder (f, c)
 +      , _sndfile_content (c)
 +      , _deinterleave_buffer (0)
  {
 -      _done = 0;
 -      _frames = 0;
 -      
 -      vector<string> const files = _film->external_audio ();
 -
 -      int N = 0;
 -      for (size_t i = 0; i < files.size(); ++i) {
 -              if (!files[i].empty()) {
 -                      N = i + 1;
 -              }
 -      }
 -
 -      if (N == 0) {
 -              return;
 +      _sndfile = sf_open (_sndfile_content->file().string().c_str(), SFM_READ, &_info);
 +      if (!_sndfile) {
 +              throw DecodeError (_("could not open audio file for reading"));
        }
  
 -      bool first = true;
 -      
 -      for (size_t i = 0; i < (size_t) N; ++i) {
 -              if (files[i].empty ()) {
 -                      _sndfiles.push_back (0);
 -              } else {
 -                      SF_INFO info;
 -                      SNDFILE* s = sf_open (files[i].c_str(), SFM_READ, &info);
 -                      if (!s) {
 -                              throw DecodeError (_("could not open external audio file for reading"));
 -                      }
 +      _done = 0;
 +      _remaining = _info.frames;
 +}
  
 -                      if (info.channels != 1) {
 -                              throw DecodeError (_("external audio files must be mono"));
 -                      }
 -                      
 -                      _sndfiles.push_back (s);
 -
 -                      if (first) {
 -                              shared_ptr<SndfileStream> st (
 -                                      new SndfileStream (
 -                                              info.samplerate, av_get_default_channel_layout (N)
 -                                              )
 -                                      );
 -                              
 -                              _audio_streams.push_back (st);
 -                              _audio_stream = st;
 -                              _frames = info.frames;
 -                              first = false;
 -                      } else {
 -                              if (info.frames != _frames) {
 -                                      throw DecodeError (_("external audio files have differing lengths"));
 -                              }
 -                      }
 -              }
 -      }
 +SndfileDecoder::~SndfileDecoder ()
 +{
 +      sf_close (_sndfile);
 +      delete[] _deinterleave_buffer;
  }
  
 -bool
 +void
  SndfileDecoder::pass ()
  {
 -      if (_audio_streams.empty ()) {
 -              return true;
 -      }
 -      
        /* Do things in half second blocks as I think there may be limits
           to what FFmpeg (and in particular the resampler) can cope with.
        */
 -      sf_count_t const block = _audio_stream->sample_rate() / 2;
 -      shared_ptr<AudioBuffers> audio (new AudioBuffers (_audio_stream->channels(), block));
 -      sf_count_t const this_time = min (block, _frames - _done);
 -      for (size_t i = 0; i < _sndfiles.size(); ++i) {
 -              if (!_sndfiles[i]) {
 -                      audio->make_silent (i);
 -              } else {
 -                      sf_read_float (_sndfiles[i], audio->data(i), this_time);
 -              }
 -      }
 +      sf_count_t const block = _sndfile_content->content_audio_frame_rate() / 2;
 +      sf_count_t const this_time = min (block, _remaining);
  
 -      audio->set_frames (this_time);
 -      Audio (audio, double(_done) / _audio_stream->sample_rate());
 -      _done += this_time;
 -
 -      return (_done == _frames);
 -}
 -
 -SndfileDecoder::~SndfileDecoder ()
 -{
 -      for (size_t i = 0; i < _sndfiles.size(); ++i) {
 -              if (_sndfiles[i]) {
 -                      sf_close (_sndfiles[i]);
 +      int const channels = _sndfile_content->audio_channels ();
 +      
-       shared_ptr<AudioBuffers> audio (new AudioBuffers (channels, this_time));
++      shared_ptr<AudioBuffers> data (new AudioBuffers (channels, this_time));
 +
 +      if (_sndfile_content->audio_channels() == 1) {
 +              /* No de-interleaving required */
-               sf_read_float (_sndfile, audio->data(0), this_time);
++              sf_read_float (_sndfile, data->data(0), this_time);
 +      } else {
 +              /* Deinterleave */
 +              if (!_deinterleave_buffer) {
 +                      _deinterleave_buffer = new float[block * channels];
 +              }
 +              sf_readf_float (_sndfile, _deinterleave_buffer, this_time);
 +              vector<float*> out_ptr (channels);
 +              for (int i = 0; i < channels; ++i) {
-                       out_ptr[i] = audio->data(i);
++                      out_ptr[i] = data->data(i);
 +              }
 +              float* in_ptr = _deinterleave_buffer;
 +              for (int i = 0; i < this_time; ++i) {
 +                      for (int j = 0; j < channels; ++j) {
 +                              *out_ptr[j]++ = *in_ptr++;
 +                      }
                }
        }
-       audio->set_frames (this_time);
-       Audio (audio, double(_done) / audio_frame_rate());
 +              
++      data->set_frames (this_time);
++      audio (data, double(_done) / audio_frame_rate());
 +      _done += this_time;
 +      _remaining -= this_time;
  }
  
 -shared_ptr<SndfileStream>
 -SndfileStream::create ()
 -{
 -      return shared_ptr<SndfileStream> (new SndfileStream);
 -}
 -
 -shared_ptr<SndfileStream>
 -SndfileStream::create (string t, optional<int> v)
 +int
 +SndfileDecoder::audio_channels () const
  {
 -      if (!v) {
 -              /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
 -              return shared_ptr<SndfileStream> ();
 -      }
 -
 -      stringstream s (t);
 -      string type;
 -      s >> type;
 -      if (type != N_("external")) {
 -              return shared_ptr<SndfileStream> ();
 -      }
 -
 -      return shared_ptr<SndfileStream> (new SndfileStream (t, v));
 +      return _info.channels;
  }
  
 -SndfileStream::SndfileStream (string t, optional<int> v)
 +ContentAudioFrame
 +SndfileDecoder::audio_length () const
  {
 -      assert (v);
 -
 -      stringstream s (t);
 -      string type;
 -      s >> type >> _sample_rate >> _channel_layout;
 +      return _info.frames;
  }
  
 -SndfileStream::SndfileStream ()
 +int
 +SndfileDecoder::audio_frame_rate () const
  {
 -
 +      return _info.samplerate;
  }
  
 -string
 -SndfileStream::to_string () const
 +Time
 +SndfileDecoder::next () const
  {
 -      return String::compose (N_("external %1 %2"), _sample_rate, _channel_layout);
 +      return _next_audio;
  }
diff --combined src/lib/wscript
index e7349a9c4d71149588ab867b4b303955ca2214f5,66207b1e4468f700729e69b412fecb28650483a9..54941df420aac8bc445f4805372711acf94a8dfe
@@@ -6,57 -6,47 +6,56 @@@ sources = ""
          ab_transcoder.cc
            analyse_audio_job.cc
            audio_analysis.cc
 +          audio_buffers.cc
 +          audio_content.cc
            audio_decoder.cc
 +          audio_mapping.cc
            audio_source.cc
 +          black_decoder.cc
            config.cc
            combiner.cc
 +          container.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
            examine_content_job.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
 +          null_content.cc
 +          player.cc
 +          playlist.cc
            scp_dcp_job.cc
            scaler.cc
            server.cc
 +          silence_decoder.cc
 +          sndfile_content.cc
            sndfile_decoder.cc
            sound_processor.cc
 -          stream.cc
            subtitle.cc
            timer.cc
            transcode_job.cc
            transcoder.cc
 -          trimmer.cc
 +          types.cc
            ui_signaller.cc
            util.cc
 +          video_content.cc
            video_decoder.cc
            video_source.cc
            writer.cc
@@@ -68,12 -58,12 +67,12 @@@ 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
                   """
  
      obj.source = sources + ' version.cc'
      if bld.env.TARGET_WINDOWS:
          obj.uselib += ' WINSOCK2 BFD DBGHELP IBERTY'
          obj.source += ' stack.cpp'
 +    if bld.env.STATIC:
 +        obj.uselib += ' XML++'
 +    obj.source = sources + " version.cc"
 +    obj.target = 'dcpomatic'
  
 -    obj.target = 'dvdomatic'
 -
 -    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')
index bc95f622da36f6cbfd7fb286ec03ff6d4e5efb4e,0000000000000000000000000000000000000000..c295a011d76110f6396989e112220d53e7d54f40
mode 100644,000000..100644
--- /dev/null
@@@ -1,205 -1,0 +1,205 @@@
-       pair<string, string> const f = Filter::ffmpeg_strings (film->filters ());
-       cout << "Filters: " << f.first << " " << f.second << "\n";
 +/*
 +    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 <iomanip>
 +#include <getopt.h>
 +#include <libdcp/version.h>
 +#include "format.h"
 +#include "film.h"
 +#include "filter.h"
 +#include "transcode_job.h"
 +#include "job_manager.h"
 +#include "ab_transcode_job.h"
 +#include "util.h"
 +#include "scaler.h"
 +#include "version.h"
 +#include "cross.h"
 +#include "config.h"
 +#include "log.h"
 +
 +using std::string;
 +using std::cerr;
 +using std::cout;
 +using std::vector;
 +using std::pair;
 +using std::list;
 +using boost::shared_ptr;
 +
 +static void
 +help (string n)
 +{
 +      cerr << "Syntax: " << n << " [OPTION] <FILM>\n"
 +           << "  -v, --version      show DCP-o-matic version\n"
 +           << "  -h, --help         show this help\n"
 +           << "  -d, --deps         list DCP-o-matic dependency details and quit\n"
 +           << "  -n, --no-progress  do not print progress to stdout\n"
 +           << "  -r, --no-remote    do not use any remote servers\n"
 +           << "\n"
 +           << "<FILM> is the film directory.\n";
 +}
 +
 +int
 +main (int argc, char* argv[])
 +{
 +      string film_dir;
 +      bool progress = true;
 +      bool no_remote = false;
 +      int log_level = 0;
 +
 +      int option_index = 0;
 +      while (1) {
 +              static struct option long_options[] = {
 +                      { "version", no_argument, 0, 'v'},
 +                      { "help", no_argument, 0, 'h'},
 +                      { "deps", no_argument, 0, 'd'},
 +                      { "no-progress", no_argument, 0, 'n'},
 +                      { "no-remote", no_argument, 0, 'r'},
 +                      { "log-level", required_argument, 0, 'l' },
 +                      { 0, 0, 0, 0 }
 +              };
 +
 +              int c = getopt_long (argc, argv, "vhdnrl:", long_options, &option_index);
 +
 +              if (c == -1) {
 +                      break;
 +              }
 +
 +              switch (c) {
 +              case 'v':
 +                      cout << "dcpomatic version " << dcpomatic_version << " " << dcpomatic_git_commit << "\n";
 +                      exit (EXIT_SUCCESS);
 +              case 'h':
 +                      help (argv[0]);
 +                      exit (EXIT_SUCCESS);
 +              case 'd':
 +                      cout << dependency_version_summary () << "\n";
 +                      exit (EXIT_SUCCESS);
 +              case 'n':
 +                      progress = false;
 +                      break;
 +              case 'r':
 +                      no_remote = true;
 +                      break;
 +              case 'l':
 +                      log_level = atoi (optarg);
 +                      break;
 +              }
 +      }
 +
 +      if (optind >= argc) {
 +              help (argv[0]);
 +              exit (EXIT_FAILURE);
 +      }
 +
 +      film_dir = argv[optind];
 +                      
 +      dcpomatic_setup ();
 +
 +      if (no_remote) {
 +              Config::instance()->set_servers (vector<ServerDescription*> ());
 +      }
 +
 +      cout << "DCP-o-matic " << dcpomatic_version << " git " << dcpomatic_git_commit;
 +      char buf[256];
 +      if (gethostname (buf, 256) == 0) {
 +              cout << " on " << buf;
 +      }
 +      cout << "\n";
 +
 +      shared_ptr<Film> film;
 +      try {
 +              film.reset (new Film (film_dir));
 +              film->read_metadata ();
 +      } catch (std::exception& e) {
 +              cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n";
 +              exit (EXIT_FAILURE);
 +      }
 +
 +      film->log()->set_level ((Log::Level) log_level);
 +
 +      cout << "\nMaking ";
 +      if (film->ab()) {
 +              cout << "A/B ";
 +      }
 +      cout << "DCP for " << film->name() << "\n";
 +//    cout << "Content: " << film->content() << "\n";
++//    pair<string, string> const f = Filter::ffmpeg_strings (film->filters ());
++//    cout << "Filters: " << f.first << " " << f.second << "\n";
 +
 +      film->make_dcp ();
 +
 +      bool should_stop = false;
 +      bool first = true;
 +      bool error = false;
 +      while (!should_stop) {
 +
 +              dcpomatic_sleep (5);
 +
 +              list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
 +
 +              if (!first && progress) {
 +                      cout << "\033[" << jobs.size() << "A";
 +                      cout.flush ();
 +              }
 +
 +              first = false;
 +
 +              int unfinished = 0;
 +              int finished_in_error = 0;
 +
 +              for (list<shared_ptr<Job> >::iterator i = jobs.begin(); i != jobs.end(); ++i) {
 +                      if (progress) {
 +                              cout << (*i)->name() << ": ";
 +                              
 +                              float const p = (*i)->overall_progress ();
 +                              
 +                              if (p >= 0) {
 +                                      cout << (*i)->status() << "                         \n";
 +                              } else {
 +                                      cout << ": Running           \n";
 +                              }
 +                      }
 +
 +                      if (!(*i)->finished ()) {
 +                              ++unfinished;
 +                      }
 +
 +                      if ((*i)->finished_in_error ()) {
 +                              ++finished_in_error;
 +                              error = true;
 +                      }
 +
 +                      if (!progress && (*i)->finished_in_error ()) {
 +                              /* We won't see this error if we haven't been showing progress,
 +                                 so show it now.
 +                              */
 +                              cout << (*i)->status() << "\n";
 +                      }
 +              }
 +
 +              if (unfinished == 0 || finished_in_error != 0) {
 +                      should_stop = true;
 +              }
 +      }
 +
 +      return error ? EXIT_FAILURE : EXIT_SUCCESS;
 +}
 +
 +        
diff --combined src/wx/film_editor.cc
index 5cf0b789713a363da28d2dc494b416c93c5dff46,a21782a6f9da188530d9e4b472461e8cf363f19e..36d63b8055a4b7de144c8facdc1ac40f5d37cc8a
@@@ -1,5 -1,3 +1,5 @@@
 +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
 +
  /*
      Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
  
@@@ -27,7 -25,6 +27,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>
@@@ -40,9 -37,6 +40,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"
  #include "dci_metadata_dialog.h"
  #include "scaler.h"
  #include "audio_dialog.h"
 +#include "imagemagick_content_dialog.h"
 +#include "timeline_dialog.h"
 +#include "audio_mapping_view.h"
 +#include "container.h"
  
  using std::string;
  using std::cout;
@@@ -64,114 -54,150 +64,114 @@@ using std::fixed
  using std::setprecision;
  using std::list;
  using std::vector;
 +using std::max;
  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)
 +      , _timeline_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_video_panel ();
 -      _notebook->AddPage (_video_panel, _("Video"), false);
 -      make_audio_panel ();
 -      _notebook->AddPage (_audio_panel, _("Audio"), false);
 -      make_subtitle_panel ();
 -      _notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
 +      _main_notebook = new wxNotebook (this, wxID_ANY);
 +      s->Add (_main_notebook, 1);
 +
 +      make_content_panel ();
 +      _main_notebook->AddPage (_content_panel, _("Content"), true);
 +      make_dcp_panel ();
 +      _main_notebook->AddPage (_dcp_panel, _("DCP"), false);
 +      
 +      setup_formats ();
  
 -      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
 -FilmEditor::make_film_panel ()
 +FilmEditor::make_dcp_panel ()
  {
 -      _film_panel = new wxPanel (_notebook);
 -      _film_sizer = new wxBoxSizer (wxVERTICAL);
 -      _film_panel->SetSizer (_film_sizer);
 +      _dcp_panel = new wxPanel (_main_notebook);
 +      _dcp_sizer = new wxBoxSizer (wxVERTICAL);
 +      _dcp_panel->SetSizer (_dcp_sizer);
  
        wxGridBagSizer* grid = new wxGridBagSizer (4, 4);
 -      _film_sizer->Add (grid, 0, wxALL, 8);
 +      _dcp_sizer->Add (grid, 0, wxEXPAND | wxALL, 8);
  
        int r = 0;
        
 -      add_label_to_grid_bag_sizer (grid, _film_panel, _("Name"), wxGBPosition (r, 0));
 -      _name = new wxTextCtrl (_film_panel, wxID_ANY);
 +      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Name"), wxGBPosition (r, 0));
 +      _name = new wxTextCtrl (_dcp_panel, wxID_ANY);
        grid->Add (_name, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
        ++r;
        
 -      add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Name"), wxGBPosition (r, 0));
 -      _dcp_name = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
 +      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Name"), wxGBPosition (r, 0));
 +      _dcp_name = new wxStaticText (_dcp_panel, wxID_ANY, wxT (""));
        grid->Add (_dcp_name, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
        ++r;
  
 -      _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, _("Use DCI name"));
 +      _use_dci_name = new wxCheckBox (_dcp_panel, wxID_ANY, _("Use DCI name"));
        grid->Add (_use_dci_name, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
 -      _edit_dci_button = new wxButton (_film_panel, wxID_ANY, _("Details..."));
 +      _edit_dci_button = new wxButton (_dcp_panel, wxID_ANY, _("Details..."));
        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));
 +      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Container"), wxGBPosition (r, 0));
 +      _container = new wxChoice (_dcp_panel, wxID_ANY);
 +      grid->Add (_container, wxGBPosition (r, 1));
        ++r;
  
 -      add_label_to_grid_bag_sizer (grid, _film_panel, _("Content Type"), wxGBPosition (r, 0));
 -      _dcp_content_type = new wxChoice (_film_panel, wxID_ANY);
 +      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Content Type"), wxGBPosition (r, 0));
 +      _dcp_content_type = new wxChoice (_dcp_panel, wxID_ANY);
        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));
 +              add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Frame Rate"), wxGBPosition (r, 0));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 -              _dcp_frame_rate = new wxChoice (_film_panel, wxID_ANY);
 +              _dcp_frame_rate = new wxChoice (_dcp_panel, wxID_ANY);
                s->Add (_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL);
 -              _best_dcp_frame_rate = new wxButton (_film_panel, wxID_ANY, _("Use best"));
 -              s->Add (_best_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 6);
 +              _best_dcp_frame_rate = new wxButton (_dcp_panel, wxID_ANY, _("Use best"));
 +              s->Add (_best_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND);
                grid->Add (s, wxGBPosition (r, 1));
        }
        ++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);
 -      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)));
 -      _length = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
 -      grid->Add (video_control (_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)));
 -              wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 -              video_control (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")));
 -              _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
 -              s->Add (video_control (_trim_end));
 -
 +              add_label_to_grid_bag_sizer (grid, _dcp_panel, _("JPEG2000 bandwidth"), wxGBPosition (r, 0));
 +              wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 +              _j2k_bandwidth = new wxSpinCtrl (_dcp_panel, wxID_ANY);
 +              s->Add (_j2k_bandwidth, 1);
 +              add_label_to_sizer (s, _dcp_panel, _("MBps"));
                grid->Add (s, wxGBPosition (r, 1));
        }
        ++r;
  
 -      video_control (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);
 +      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Scaler"), wxGBPosition (r, 0));
 +      _scaler = new wxChoice (_dcp_panel, wxID_ANY);
 +      grid->Add (_scaler, wxGBPosition (r, 1));
        ++r;
  
 -      _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B"));
 -      video_control (_dcp_ab);
 -      grid->Add (_dcp_ab, wxGBPosition (r, 0));
 -      ++r;
 +      vector<Scaler const *> const sc = Scaler::all ();
 +      for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
 +              _scaler->Append (std_to_wx ((*i)->name()));
 +      }
  
 -      /* 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));
 +      vector<Container const *> const co = Container::all ();
 +      for (vector<Container const *>::const_iterator i = co.begin(); i != co.end(); ++i) {
 +              _container->Append (std_to_wx ((*i)->name ()));
        }
 -      ++r;
  
        vector<DCPContentType const *> const ct = DCPContentType::all ();
        for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.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"));
 +      _j2k_bandwidth->SetRange (50, 250);
  }
  
  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);
 -      _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);
 +      _container->Connect              (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::container_changed), 0, this);
 +//    _format->Connect                 (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::format_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_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_timeline->Connect       (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_timeline_clicked), 0, this);
 +      _loop_content->Connect           (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::loop_content_toggled), 0, this);
 +      _loop_count->Connect             (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::loop_count_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);
 +      _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);
 +      _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);
 -      _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);
 +      _audio_stream->Connect           (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this);
 +      _subtitle_stream->Connect        (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
 +      _audio_mapping->Changed.connect  (bind (&FilmEditor::audio_mapping_changed, this, _1));
  }
  
  void
  FilmEditor::make_video_panel ()
  {
 -      _video_panel = new wxPanel (_notebook);
 +      _video_panel = new wxPanel (_content_notebook);
        _video_sizer = new wxBoxSizer (wxVERTICAL);
        _video_panel->SetSizer (_video_sizer);
        
        _video_sizer->Add (grid, 0, wxALL, 8);
  
        int r = 0;
 -      add_label_to_grid_bag_sizer (grid, _video_panel, _("Format"), wxGBPosition (r, 0));
 -      _format = new wxChoice (_video_panel, wxID_ANY);
 -      grid->Add (_format, wxGBPosition (r, 1));
 -      ++r;
 -
        add_label_to_grid_bag_sizer (grid, _video_panel, _("Left crop"), wxGBPosition (r, 0));
        _left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
        grid->Add (_left_crop, wxGBPosition (r, 1));
        grid->Add (_bottom_crop, wxGBPosition (r, 1));
        ++r;
  
 +      add_label_to_grid_bag_sizer (grid, _video_panel, _("Scale to"), wxGBPosition (r, 0));
 +      _format = new wxChoice (_video_panel, wxID_ANY);
 +      grid->Add (_format, wxGBPosition (r, 1));
 +      ++r;
 +
        _scaling_description = new wxStaticText (_video_panel, wxID_ANY, wxT ("\n \n \n \n"), wxDefaultPosition, wxDefaultSize);
        grid->Add (_scaling_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
        wxFont font = _scaling_description->GetFont();
  
        /* 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)));
 -      _scaler = new wxChoice (_video_panel, wxID_ANY);
 -      grid->Add (video_control (_scaler), wxGBPosition (r, 1));
 -      ++r;
 -
 -      vector<Scaler const *> const sc = Scaler::all ();
 -      for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
 -              _scaler->Append (std_to_wx ((*i)->name()));
 -      }
 -
        add_label_to_grid_bag_sizer (grid, _video_panel, _("Colour look-up table"), wxGBPosition (r, 0));
        _colour_lut = new wxChoice (_video_panel, wxID_ANY);
        for (int i = 0; i < 2; ++i) {
        grid->Add (_colour_lut, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
        ++r;
  
 -      {
 -              add_label_to_grid_bag_sizer (grid, _video_panel, _("JPEG2000 bandwidth"), wxGBPosition (r, 0));
 -              wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 -              _j2k_bandwidth = new wxSpinCtrl (_video_panel, wxID_ANY);
 -              s->Add (_j2k_bandwidth, 1);
 -              add_label_to_sizer (s, _video_panel, _("MBps"));
 -              grid->Add (s, wxGBPosition (r, 1));
 -      }
 -      ++r;
 -
        _left_crop->SetRange (0, 1024);
        _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 (_main_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, 1, wxEXPAND | wxLEFT | wxRIGHT);
 +                _content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove"));
 +                b->Add (_content_remove, 1, wxEXPAND | wxLEFT | wxRIGHT);
 +              _content_timeline = new wxButton (_content_panel, wxID_ANY, _("Timeline..."));
 +              b->Add (_content_timeline, 1, wxEXPAND | wxLEFT | wxRIGHT);
 +
 +                s->Add (b, 0, wxALL, 4);
 +
 +                _content_sizer->Add (s, 0.75, wxEXPAND | wxALL, 6);
 +        }
 +
 +      wxBoxSizer* h = new wxBoxSizer (wxHORIZONTAL);
 +      _loop_content = new wxCheckBox (_content_panel, wxID_ANY, _("Loop everything"));
 +      h->Add (_loop_content, 0, wxALL, 6);
 +      _loop_count = new wxSpinCtrl (_content_panel, wxID_ANY);
 +      h->Add (_loop_count, 0, wxALL, 6);
 +      add_label_to_sizer (h, _content_panel, _("times"));
 +      _content_sizer->Add (h, 0, wxALL, 6);
 +
 +      _content_notebook = new wxNotebook (_content_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_LEFT);
 +      _content_sizer->Add (_content_notebook, 1, wxEXPAND | wxTOP, 6);
 +      
 +      make_video_panel ();
 +      _content_notebook->AddPage (_video_panel, _("Video"), false);
 +      make_audio_panel ();
 +      _content_notebook->AddPage (_audio_panel, _("Audio"), false);
 +      make_subtitle_panel ();
 +      _content_notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
 +
 +      _loop_count->SetRange (2, 1024);
  }
  
  void
  FilmEditor::make_audio_panel ()
  {
 -      _audio_panel = new wxPanel (_notebook);
 +      _audio_panel = new wxPanel (_content_notebook);
        _audio_sizer = new wxBoxSizer (wxVERTICAL);
        _audio_panel->SetSizer (_audio_sizer);
        
 -      wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
 +      wxFlexGridSizer* grid = new wxFlexGridSizer (3, 4, 4);
        _audio_sizer->Add (grid, 0, wxALL, 8);
  
        _show_audio = new wxButton (_audio_panel, wxID_ANY, _("Show Audio..."));
        grid->Add (_show_audio, 1);
        grid->AddSpacer (0);
 +      grid->AddSpacer (0);
  
 +      add_label_to_sizer (grid, _audio_panel, _("Audio Gain"));
        {
 -              video_control (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")));
 -              _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);
 +              s->Add (_audio_gain, 1);
 +              add_label_to_sizer (s, _audio_panel, _("dB"));
 +              grid->Add (s, 1);
        }
 +      
 +      _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate..."));
 +      grid->Add (_audio_gain_calculate_button);
  
 +      add_label_to_sizer (grid, _audio_panel, _("Audio Delay"));
        {
 -              video_control (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"));
 +      _audio_stream = new wxChoice (_audio_panel, wxID_ANY);
 +      grid->Add (_audio_stream, 1);
 +      _audio_description = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
 +      grid->AddSpacer (0);
 +      
 +      grid->Add (_audio_description, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
 +      grid->AddSpacer (0);
 +      grid->AddSpacer (0);
 +      
 +      _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);
  void
  FilmEditor::make_subtitle_panel ()
  {
 -      _subtitle_panel = new wxPanel (_notebook);
 +      _subtitle_panel = new wxPanel (_content_notebook);
        _subtitle_sizer = new wxBoxSizer (wxVERTICAL);
        _subtitle_panel->SetSizer (_subtitle_sizer);
        wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
        _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);
 +      grid->AddSpacer (0);
        
 -      _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
 -      grid->Add (video_control (_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);
        }
  
 +      add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Stream"));
 +      _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
 +      grid->Add (_subtitle_stream, 1, wxEXPAND | wxALL, 6);
 +      grid->AddSpacer (0);
 +      
        _subtitle_offset->SetRange (-1024, 1024);
        _subtitle_scale->SetRange (1, 1000);
  }
  void
  FilmEditor::left_crop_changed (wxCommandEvent &)
  {
 -      if (!_film) {
 +      shared_ptr<VideoContent> c = selected_video_content ();
 +      if (!c) {
                return;
        }
  
 -      _film->set_left_crop (_left_crop->GetValue ());
 +      c->set_left_crop (_left_crop->GetValue ());
  }
  
  /** Called when the right crop widget has been changed */
  void
  FilmEditor::right_crop_changed (wxCommandEvent &)
  {
 -      if (!_film) {
 +      shared_ptr<VideoContent> c = selected_video_content ();
 +      if (!c) {
                return;
        }
  
 -      _film->set_right_crop (_right_crop->GetValue ());
 +      c->set_right_crop (_right_crop->GetValue ());
  }
  
  /** Called when the top crop widget has been changed */
  void
  FilmEditor::top_crop_changed (wxCommandEvent &)
  {
 -      if (!_film) {
 +      shared_ptr<VideoContent> c = selected_video_content ();
 +      if (!c) {
                return;
        }
  
 -      _film->set_top_crop (_top_crop->GetValue ());
 +      c->set_top_crop (_top_crop->GetValue ());
  }
  
  /** Called when the bottom crop value has been changed */
  void
  FilmEditor::bottom_crop_changed (wxCommandEvent &)
  {
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_bottom_crop (_bottom_crop->GetValue ());
 -}
 -
 -/** Called when the content filename has been changed */
 -void
 -FilmEditor::content_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) {
 +      shared_ptr<VideoContent> c = selected_video_content ();
 +      if (!c) {
                return;
        }
  
 -      _film->set_trust_content_header (_trust_content_header->GetValue ());
 -}
 -
 -/** Called when the DCP A/B switch has been toggled */
 -void
 -FilmEditor::dcp_ab_toggled (wxCommandEvent &)
 -{
 -      if (!_film) {
 -              return;
 -      }
 -      
 -      _film->set_dcp_ab (_dcp_ab->GetValue ());
 +      c->set_bottom_crop (_bottom_crop->GetValue ());
  }
  
  /** Called when the name widget has been changed */
@@@ -549,7 -593,7 +549,7 @@@ FilmEditor::dcp_frame_rate_changed (wxC
                return;
        }
  
 -      _film->set_dcp_frame_rate (
 +      _film->set_dcp_video_frame_rate (
                boost::lexical_cast<int> (
                        wx_to_std (_dcp_frame_rate->GetString (_dcp_frame_rate->GetSelection ()))
                        )
@@@ -576,43 -620,116 +576,31 @@@ 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 ());
 +      case Film::LOOP:
 +              checked_set (_loop_content, _film->loop() > 1);
 +              checked_set (_loop_count, _film->loop());
 +              setup_loop_sensitivity ();
                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::CONTAINER:
 +              setup_container ();
                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 ();
 -              break;
 -      }
 -      case Film::CROP:
 -              checked_set (_left_crop, _film->crop().left);
 -              checked_set (_right_crop, _film->crop().right);
 -              checked_set (_top_crop, _film->crop().top);
 -              checked_set (_bottom_crop, _film->crop().bottom);
 -              setup_scaling_description ();
 -              break;
--      case Film::FILTERS:
--      {
--              pair<string, string> p = Filter::ffmpeg_strings (_film->filters ());
--              if (p.first.empty () && p.second.empty ()) {
--                      _filters->SetLabel (_("None"));
--              } else {
--                      string const b = p.first + " " + p.second;
--                      _filters->SetLabel (std_to_wx (b));
--              }
-               _dcp_sizer->Layout ();
 -              _film_sizer->Layout ();
--              break;
--      }
        case Film::NAME:
                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 ());
 -              break;
        case Film::SCALER:
                checked_set (_scaler, Scaler::as_index (_film->scaler ()));
                break;
 -      case Film::TRIM_START:
 -              checked_set (_trim_start, _film->trim_start());
 -              break;
 -      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:
 +      case Film::DCP_VIDEO_FRAME_RATE:
        {
 -              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:
 +              bool done = false;
                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 (_dcp_frame_rate->GetSelection() != int(i)) {
 -                                      _dcp_frame_rate->SetSelection (i);
 -                                      break;
 -                              }
 +                      if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_video_frame_rate())) {
 +                              checked_set (_dcp_frame_rate, i);
 +                              done = true;
 +                              break;
                        }
                }
  
 -              if (_film->source_frame_rate()) {
 -                      _best_dcp_frame_rate->Enable (best_dcp_frame_rate (_film->source_frame_rate ()) != _film->dcp_frame_rate ());
 -              } else {
 -                      _best_dcp_frame_rate->Disable ();
 +              if (!done) {
 +                      checked_set (_dcp_frame_rate, -1);
                }
  
 -              setup_frame_rate_description ();
 +              _best_dcp_frame_rate->Enable (_film->best_dcp_video_frame_rate () != _film->dcp_video_frame_rate ());
 +              break;
 +      }
        }
  }
  
  void
 -FilmEditor::setup_frame_rate_description ()
 +FilmEditor::film_content_changed (weak_ptr<Content> weak_content, int property)
  {
 -      wxString d;
 -      int lines = 0;
 -      
 -      if (_film->source_frame_rate()) {
 -              d << std_to_wx (FrameRateConversion (_film->source_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 ()) {
 -                      d << wxString::Format (
 -                              _("Audio will be resampled from %dHz to %dHz\n"),
 -                              _film->audio_stream()->sample_rate(),
 -                              _film->target_audio_sample_rate()
 -                              );
 -                      ++lines;
 -              }
 -#endif                
 +      if (!_film) {
 +              /* We call this method ourselves (as well as using it as a signal handler)
 +                 so _film can be 0.
 +              */
 +              return;
        }
  
 -      for (int i = lines; i < 2; ++i) {
 -              d << wxT ("\n ");
 +      shared_ptr<Content> content = weak_content.lock ();
 +      shared_ptr<VideoContent> video_content;
 +      shared_ptr<AudioContent> audio_content;
 +      shared_ptr<FFmpegContent> ffmpeg_content;
 +      if (content) {
 +              video_content = dynamic_pointer_cast<VideoContent> (content);
 +              audio_content = dynamic_pointer_cast<AudioContent> (content);
 +              ffmpeg_content = dynamic_pointer_cast<FFmpegContent> (content);
        }
  
 -      _frame_rate_description->SetLabel (d);
 +      if (property == VideoContentProperty::VIDEO_CROP) {
 +              checked_set (_left_crop,   video_content ? video_content->crop().left :   0);
 +              checked_set (_right_crop,  video_content ? video_content->crop().right :  0);
 +              checked_set (_top_crop,    video_content ? video_content->crop().top :    0);
 +              checked_set (_bottom_crop, video_content ? video_content->crop().bottom : 0);
 +              setup_scaling_description ();
 +      } else if (property == AudioContentProperty::AUDIO_GAIN) {
 +              checked_set (_audio_gain, audio_content ? audio_content->audio_gain() : 0);
 +      } else if (property == AudioContentProperty::AUDIO_DELAY) {
 +              checked_set (_audio_delay, audio_content ? audio_content->audio_delay() : 0);
 +      } else if (property == FFmpegContentProperty::SUBTITLE_STREAMS) {
 +              _subtitle_stream->Clear ();
 +              if (ffmpeg_content) {
 +                      vector<shared_ptr<FFmpegSubtitleStream> > s = ffmpeg_content->subtitle_streams ();
 +                      if (s.empty ()) {
 +                              _subtitle_stream->Enable (false);
 +                      }
 +                      for (vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
 +                              _subtitle_stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
 +                      }
 +                      
 +                      if (ffmpeg_content->subtitle_stream()) {
 +                              checked_set (_subtitle_stream, lexical_cast<string> (ffmpeg_content->subtitle_stream()->id));
 +                      } else {
 +                              _subtitle_stream->SetSelection (wxNOT_FOUND);
 +                      }
 +              }
 +              setup_subtitle_control_sensitivity ();
 +      } else if (property == FFmpegContentProperty::AUDIO_STREAMS) {
 +              _audio_stream->Clear ();
 +              if (ffmpeg_content) {
 +                      vector<shared_ptr<FFmpegAudioStream> > a = ffmpeg_content->audio_streams ();
 +                      for (vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin(); i != a.end(); ++i) {
 +                              _audio_stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
 +                      }
 +                      
 +                      if (ffmpeg_content->audio_stream()) {
 +                              checked_set (_audio_stream, lexical_cast<string> (ffmpeg_content->audio_stream()->id));
 +                      }
 +              }
 +              setup_show_audio_sensitivity ();
 +      } else if (property == FFmpegContentProperty::AUDIO_STREAM) {
 +              setup_dcp_name ();
 +              setup_show_audio_sensitivity ();
++      } else if (property == FFmpegContentProperty::FILTERS) {
++              if (ffmpeg_content) {
++                      pair<string, string> p = Filter::ffmpeg_strings (ffmpeg_content->filters ());
++                      if (p.first.empty () && p.second.empty ()) {
++                              _filters->SetLabel (_("None"));
++                      } else {
++                              string const b = p.first + " " + p.second;
++                              _filters->SetLabel (std_to_wx (b));
++                      }
++                      _dcp_sizer->Layout ();
++              }
 +      }
  }
  
 -/** Called when the format widget has been changed */
  void
 -FilmEditor::format_changed (wxCommandEvent &)
 +FilmEditor::setup_container ()
 +{
 +      int n = 0;
 +      vector<Container const *> containers = Container::all ();
 +      vector<Container const *>::iterator i = containers.begin ();
 +      while (i != containers.end() && *i != _film->container ()) {
 +              ++i;
 +              ++n;
 +      }
 +      
 +      if (i == containers.end()) {
 +              checked_set (_container, -1);
 +      } else {
 +              checked_set (_container, n);
 +      }
 +      
 +      setup_dcp_name ();
 +      setup_scaling_description ();
 +}     
 +
 +/** Called when the container widget has been changed */
 +void
 +FilmEditor::container_changed (wxCommandEvent &)
  {
        if (!_film) {
                return;
        }
  
 -      int const n = _format->GetSelection ();
 +      int const n = _container->GetSelection ();
        if (n >= 0) {
 -              assert (n < int (_formats.size()));
 -              _film->set_format (_formats[n]);
 +              vector<Container const *> containers = Container::all ();
 +              assert (n < int (containers.size()));
 +              _film->set_container (containers[n]);
        }
  }
  
@@@ -780,17 -870,12 +779,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::LOOP);
        film_changed (Film::DCP_CONTENT_TYPE);
 -      film_changed (Film::FORMAT);
 -      film_changed (Film::CROP);
 -      film_changed (Film::FILTERS);
 +      film_changed (Film::CONTAINER);
-       film_changed (Film::FILTERS);
        film_changed (Film::SCALER);
 -      film_changed (Film::TRIM_START);
 -      film_changed (Film::TRIM_END);
 -      film_changed (Film::TRIM_TYPE);
 -      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::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::DCP_VIDEO_FRAME_RATE);
 +
 +      film_content_changed (boost::shared_ptr<Content> (), VideoContentProperty::VIDEO_CROP);
 +      film_content_changed (boost::shared_ptr<Content> (), AudioContentProperty::AUDIO_GAIN);
 +      film_content_changed (boost::shared_ptr<Content> (), AudioContentProperty::AUDIO_DELAY);
 +      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);
++      film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::FILTERS);
  }
  
  /** Updates the sensitivity of lots of widgets to a given value.
@@@ -841,34 -934,39 +840,44 @@@ FilmEditor::set_things_sensitive (bool 
        _edit_dci_button->Enable (s);
        _format->Enable (s);
        _content->Enable (s);
 -      _trust_content_header->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);
        _dcp_content_type->Enable (s);
 +      _best_dcp_frame_rate->Enable (s);
        _dcp_frame_rate->Enable (s);
 -      _trim_start->Enable (s);
 -      _trim_end->Enable (s);
 -      _trim_type->Enable (s);
 -      _dcp_ab->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_sensitivity ();
  }
  
  /** Called when the `Edit filters' button has been clicked */
  void
  FilmEditor::edit_filters_clicked (wxCommandEvent &)
  {
--      FilterDialog* d = new FilterDialog (this, _film->filters());
--      d->ActiveChanged.connect (bind (&Film::set_filters, _film, _1));
++      shared_ptr<Content> c = selected_content ();
++      if (!c) {
++              return;
++      }
++
++      shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
++      if (!fc) {
++              return;
++      }
++      
++      FilterDialog* d = new FilterDialog (this, fc->filters());
++      d->ActiveChanged.connect (bind (&FFmpegContent::set_filters, fc, _1));
        d->ShowModal ();
        d->Destroy ();
  }
@@@ -890,39 -988,105 +899,39 @@@ FilmEditor::scaler_changed (wxCommandEv
  void
  FilmEditor::audio_gain_changed (wxCommandEvent &)
  {
 -      if (!_film) {
 +      shared_ptr<AudioContent> ac = selected_audio_content ();
 +      if (!ac) {
                return;
        }
  
 -      _film->set_audio_gain (_audio_gain->GetValue ());
 +      ac->set_audio_gain (_audio_gain->GetValue ());
  }
  
  void
  FilmEditor::audio_delay_changed (wxCommandEvent &)
  {
 -      if (!_film) {
 +      shared_ptr<AudioContent> ac = selected_audio_content ();
 +      if (!ac) {
                return;
        }
  
 -      _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;
 +      ac->set_audio_delay (_audio_delay->GetValue ());
  }
  
  void
 -FilmEditor::setup_visibility ()
 +FilmEditor::setup_main_notebook_size ()
  {
 -      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);
 -      }
 +      _main_notebook->InvalidateBestSize ();
  
 -      for (list<wxControl*>::iterator i = _still_controls.begin(); i != _still_controls.end(); ++i) {
 -              (*i)->Show (c == STILL);
 -      }
 -
 -      setup_notebook_size ();
 -}
 +      _content_sizer->Layout ();
 +      _content_sizer->SetSizeHints (_content_panel);
 +      _dcp_sizer->Layout ();
 +      _dcp_sizer->SetSizeHints (_dcp_panel);
  
 -void
 -FilmEditor::setup_notebook_size ()
 -{
 -      _notebook->InvalidateBestSize ();
 -      
 -      _film_sizer->Layout ();
 -      _film_sizer->SetSizeHints (_film_panel);
 -      _video_sizer->Layout ();
 -      _video_sizer->SetSizeHints (_video_panel);
 -      _audio_sizer->Layout ();
 -      _audio_sizer->SetSizeHints (_audio_panel);
 -      _subtitle_sizer->Layout ();
 -      _subtitle_sizer->SetSizeHints (_subtitle_panel);
 -
 -      _notebook->Fit ();
 +      _main_notebook->Fit ();
        Fit ();
  }
  
 -void
 -FilmEditor::still_duration_changed (wxCommandEvent &)
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_still_duration (_still_duration->GetValue ());
 -}
 -
 -void
 -FilmEditor::trim_start_changed (wxCommandEvent &)
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_trim_start (_trim_start->GetValue ());
 -}
 -
 -void
 -FilmEditor::trim_end_changed (wxCommandEvent &)
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_trim_end (_trim_end->GetValue ());
 -}
 -
  void
  FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &)
  {
  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) {
                _format->Append (std_to_wx ((*i)->name ()));
        }
  
 -      _film_sizer->Layout ();
 +      _dcp_sizer->Layout ();
  }
  
  void
@@@ -978,7 -1155,7 +987,7 @@@ FilmEditor::setup_subtitle_control_sens
  {
        bool h = false;
        if (_generally_sensitive && _film) {
 -              h = !_film->subtitle_streams().empty();
 +              h = _film->has_subtitles ();
        }
        
        _with_subtitles->Enable (h);
                j = _film->with_subtitles ();
        }
        
 -      _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 &)
  {
@@@ -1016,178 -1209,143 +1025,178 @@@ FilmEditor::edit_dci_button_clicked (wx
  }
  
  void
 -FilmEditor::setup_streams ()
 +FilmEditor::active_jobs_changed (bool a)
  {
 -      _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->use_content_audio() && _film->audio_stream()) {
 -              checked_set (_audio_stream, _film->audio_stream()->to_string());
 -      }
 +      set_things_sensitive (!a);
 +}
  
 -      _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 ())));
 -      }
 -      if (_film->subtitle_stream()) {
 -              checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
 +void
 +FilmEditor::setup_dcp_name ()
 +{
 +      string s = _film->dcp_name (true);
 +      if (s.length() > 28) {
 +              _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("..."));
 +              _dcp_name->SetToolTip (std_to_wx (s));
        } else {
 -              _subtitle_stream->SetSelection (wxNOT_FOUND);
 +              _dcp_name->SetLabel (std_to_wx (s));
        }
  }
  
  void
 -FilmEditor::audio_stream_changed (wxCommandEvent &)
 +FilmEditor::show_audio_clicked (wxCommandEvent &)
  {
 -      if (!_film) {
 -              return;
 +      if (_audio_dialog) {
 +              _audio_dialog->Destroy ();
 +              _audio_dialog = 0;
        }
 -
 -      _film->set_content_audio_stream (
 -              audio_stream_factory (
 -                      string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ())),
 -                      Film::state_version
 -                      )
 -              );
 +      
 +      _audio_dialog = new AudioDialog (this);
 +      _audio_dialog->Show ();
 +      _audio_dialog->set_film (_film);
  }
  
  void
 -FilmEditor::subtitle_stream_changed (wxCommandEvent &)
 +FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &)
  {
        if (!_film) {
                return;
        }
 +      
 +      _film->set_dcp_video_frame_rate (_film->best_dcp_video_frame_rate ());
 +}
  
 -      _film->set_subtitle_stream (
 -              subtitle_stream_factory (
 -                      string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ())),
 -                      Film::state_version
 -                      )
 -              );
 +void
 +FilmEditor::setup_show_audio_sensitivity ()
 +{
 +      _show_audio->Enable (_film);
  }
  
  void
 -FilmEditor::setup_audio_details ()
 +FilmEditor::setup_content ()
  {
 -      if (!_film->content_audio_stream()) {
 -              _audio->SetLabel (wxT (""));
 -      } else {
 -              wxString s;
 -              if (_film->audio_stream()->channels() == 1) {
 -                      s << _("1 channel");
 -              } else {
 -                      s << _film->audio_stream()->channels () << wxT (" ") << _("channels");
 +      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 ();
 +
 +      Playlist::ContentList content = _film->content ();
 +      for (Playlist::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);
                }
 -              s << wxT (", ") << _film->audio_stream()->sample_rate() << _("Hz");
 -              _audio->SetLabel (s);
        }
  
 -      setup_notebook_size ();
 +      if (selected_summary.empty () && !content.empty ()) {
 +              /* Select the item of content if non was selected before */
 +              _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
 +      }
  }
  
  void
 -FilmEditor::active_jobs_changed (bool a)
 +FilmEditor::content_add_clicked (wxCommandEvent &)
  {
 -      set_things_sensitive (!a);
 +      wxFileDialog* d = new wxFileDialog (this, _("Choose a file or files"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE);
 +      int const r = d->ShowModal ();
 +      d->Destroy ();
 +
 +      if (r != wxID_OK) {
 +              return;
 +      }
 +
 +      wxArrayString paths;
 +      d->GetPaths (paths);
 +
 +      for (unsigned int i = 0; i < paths.GetCount(); ++i) {
 +              boost::filesystem::path p (wx_to_std (paths[i]));
 +
 +              if (ImageMagickContent::valid_file (p)) {
 +                      _film->add_content (shared_ptr<ImageMagickContent> (new ImageMagickContent (_film, p)));
 +              } else if (SndfileContent::valid_file (p)) {
 +                      _film->add_content (shared_ptr<SndfileContent> (new SndfileContent (_film, p)));
 +              } else {
 +                      _film->add_content (shared_ptr<FFmpegContent> (new FFmpegContent (_film, p)));
 +              }
 +      }
  }
  
  void
 -FilmEditor::use_audio_changed (wxCommandEvent &)
 +FilmEditor::content_remove_clicked (wxCommandEvent &)
  {
 -      _film->set_use_content_audio (_use_content_audio->GetValue());
 +      shared_ptr<Content> c = selected_content ();
 +      if (c) {
 +              _film->remove_content (c);
 +      }
  }
  
  void
 -FilmEditor::external_audio_changed (wxCommandEvent &)
 +FilmEditor::content_selection_changed (wxListEvent &)
  {
 -      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);
 +        setup_content_sensitivity ();
 +      shared_ptr<Content> s = selected_content ();
 +      film_content_changed (s, VideoContentProperty::VIDEO_CROP);
 +      film_content_changed (s, AudioContentProperty::AUDIO_GAIN);
 +      film_content_changed (s, AudioContentProperty::AUDIO_DELAY);
 +      film_content_changed (s, FFmpegContentProperty::AUDIO_STREAM);
 +      film_content_changed (s, FFmpegContentProperty::AUDIO_STREAMS);
 +      film_content_changed (s, FFmpegContentProperty::SUBTITLE_STREAM);
  }
  
  void
 -FilmEditor::setup_dcp_name ()
 +FilmEditor::setup_content_sensitivity ()
  {
 -      string s = _film->dcp_name (true);
 -      if (s.length() > 28) {
 -              _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("..."));
 -              _dcp_name->SetToolTip (std_to_wx (s));
 -      } else {
 -              _dcp_name->SetLabel (std_to_wx (s));
 -      }
 +        _content_add->Enable (_generally_sensitive);
 +
 +      shared_ptr<Content> selection = selected_content ();
 +
 +        _content_remove->Enable (selection && _generally_sensitive);
 +      _content_timeline->Enable (_generally_sensitive);
 +
 +      _video_panel->Enable    (selection && dynamic_pointer_cast<VideoContent>  (selection) && _generally_sensitive);
 +      _audio_panel->Enable    (selection && dynamic_pointer_cast<AudioContent>  (selection) && _generally_sensitive);
 +      _subtitle_panel->Enable (selection && dynamic_pointer_cast<FFmpegContent> (selection) && _generally_sensitive);
  }
  
 -void
 -FilmEditor::show_audio_clicked (wxCommandEvent &)
 +shared_ptr<Content>
 +FilmEditor::selected_content ()
  {
 -      if (_audio_dialog) {
 -              _audio_dialog->Destroy ();
 -              _audio_dialog = 0;
 +      int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
 +      if (s == -1) {
 +              return shared_ptr<Content> ();
 +      }
 +
 +      Playlist::ContentList c = _film->content ();
 +      if (s < 0 || size_t (s) >= c.size ()) {
 +              return shared_ptr<Content> ();
        }
        
 -      _audio_dialog = new AudioDialog (this);
 -      _audio_dialog->Show ();
 -      _audio_dialog->set_film (_film);
 +      return c[s];
  }
  
 -void
 -FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &)
 +shared_ptr<VideoContent>
 +FilmEditor::selected_video_content ()
  {
 -      if (!_film) {
 -              return;
 +      shared_ptr<Content> c = selected_content ();
 +      if (!c) {
 +              return shared_ptr<VideoContent> ();
        }
 -      
 -      _film->set_dcp_frame_rate (best_dcp_frame_rate (_film->source_frame_rate ()));
 +
 +      return dynamic_pointer_cast<VideoContent> (c);
  }
  
 -void
 -FilmEditor::setup_show_audio_sensitivity ()
 +shared_ptr<AudioContent>
 +FilmEditor::selected_audio_content ()
  {
 -      _show_audio->Enable (_film && _film->has_audio ());
 +      shared_ptr<Content> c = selected_content ();
 +      if (!c) {
 +              return shared_ptr<AudioContent> ();
 +      }
 +
 +      return dynamic_pointer_cast<AudioContent> (c);
  }
  
  void
@@@ -1195,22 -1353,20 +1204,22 @@@ FilmEditor::setup_scaling_description (
  {
        wxString d;
  
 +#if 0 
 +XXX
        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,
                d << wxT ("\n ");
        }
  
 +#endif        
        _scaling_description->SetLabel (d);
  }
  
  void
 -FilmEditor::trim_type_changed (wxCommandEvent &)
 +FilmEditor::loop_content_toggled (wxCommandEvent &)
  {
 -      _film->set_trim_type (_trim_type->GetSelection () == 0 ? Film::CPL : Film::ENCODE);
 +      if (_loop_content->GetValue ()) {
 +              _film->set_loop (_loop_count->GetValue ());
 +      } else {
 +              _film->set_loop (1);
 +      }
 +              
 +      setup_loop_sensitivity ();
 +}
 +
 +void
 +FilmEditor::loop_count_changed (wxCommandEvent &)
 +{
 +      _film->set_loop (_loop_count->GetValue ());
 +}
 +
 +void
 +FilmEditor::setup_loop_sensitivity ()
 +{
 +      _loop_count->Enable (_loop_content->GetValue ());
 +}
 +
 +void
 +FilmEditor::content_timeline_clicked (wxCommandEvent &)
 +{
 +      if (_timeline_dialog) {
 +              _timeline_dialog->Destroy ();
 +              _timeline_dialog = 0;
 +      }
 +      
 +      _timeline_dialog = new TimelineDialog (this, _film);
 +      _timeline_dialog->Show ();
 +}
 +
 +void
 +FilmEditor::audio_stream_changed (wxCommandEvent &)
 +{
 +      shared_ptr<Content> c = selected_content ();
 +      if (!c) {
 +              return;
 +      }
 +      
 +      shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
 +      if (!fc) {
 +              return;
 +      }
 +      
 +      vector<shared_ptr<FFmpegAudioStream> > a = fc->audio_streams ();
 +      vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin ();
 +      string const s = string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ()));
 +      while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
 +              ++i;
 +      }
 +
 +      if (i != a.end ()) {
 +              fc->set_audio_stream (*i);
 +      }
 +
 +      if (!fc->audio_stream ()) {
 +              _audio_description->SetLabel (wxT (""));
 +      } else {
 +              wxString s;
 +              if (fc->audio_channels() == 1) {
 +                      s << _("1 channel");
 +              } else {
 +                      s << fc->audio_channels() << wxT (" ") << _("channels");
 +              }
 +              s << wxT (", ") << fc->content_audio_frame_rate() << _("Hz");
 +              _audio_description->SetLabel (s);
 +      }
 +}
 +
 +
 +
 +void
 +FilmEditor::subtitle_stream_changed (wxCommandEvent &)
 +{
 +      shared_ptr<Content> c = selected_content ();
 +      if (!c) {
 +              return;
 +      }
 +      
 +      shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
 +      if (!fc) {
 +              return;
 +      }
 +      
 +      vector<shared_ptr<FFmpegSubtitleStream> > a = fc->subtitle_streams ();
 +      vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = a.begin ();
 +      string const s = string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ()));
 +      while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
 +              ++i;
 +      }
 +
 +      if (i != a.end ()) {
 +              fc->set_subtitle_stream (*i);
 +      }
 +}
 +
 +void
 +FilmEditor::audio_mapping_changed (AudioMapping m)
 +{
 +      shared_ptr<Content> c = selected_content ();
 +      if (!c) {
 +              return;
 +      }
 +      
 +      shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
 +      if (!fc) {
 +              return;
 +      }
 +
 +      /* XXX: should be general to audiocontent */
 +      fc->audio_stream()->mapping = m;
  }
diff --combined src/wx/film_viewer.cc
index d08afe7a472912f0c7e86420996ec85a51c9d25e,4f2985a061db3677ebfb6d0ed5c89bf2fd25c9f8..4b1fb442e12ba802e42eb32a70affcd81dfd68c6
@@@ -1,5 -1,3 +1,5 @@@
 +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
 +
  /*
      Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
  
  #include <iomanip>
  #include <wx/tglbtn.h>
  #include "lib/film.h"
 +#include "lib/container.h"
  #include "lib/format.h"
  #include "lib/util.h"
  #include "lib/job_manager.h"
 -#include "lib/options.h"
  #include "lib/subtitle.h"
  #include "lib/image.h"
  #include "lib/scaler.h"
  #include "lib/exceptions.h"
  #include "lib/examine_content_job.h"
  #include "lib/filter.h"
 +#include "lib/player.h"
 +#include "lib/video_content.h"
 +#include "lib/ffmpeg_content.h"
 +#include "lib/imagemagick_content.h"
  #include "film_viewer.h"
  #include "wx_util.h"
  #include "video_decoder.h"
@@@ -51,8 -45,6 +51,8 @@@ using std::max
  using std::cout;
  using std::list;
  using boost::shared_ptr;
 +using boost::dynamic_pointer_cast;
 +using boost::weak_ptr;
  using libdcp::Size;
  
  FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
@@@ -116,27 -108,49 +116,26 @@@ voi
  FilmViewer::film_changed (Film::Property p)
  {
        switch (p) {
 -      case Film::FORMAT:
 +      case Film::CONTAINER:
                calculate_sizes ();
                update_from_raw ();
                break;
        case Film::CONTENT:
        {
 -              DecodeOptions o;
 -              o.decode_audio = false;
 -              o.decode_subtitles = true;
 -              o.video_sync = false;
 -
 -              try {
 -                      _decoders = decoder_factory (_film, o);
 -              } catch (StringError& e) {
 -                      error_dialog (this, wxString::Format (_("Could not open content file (%s)"), std_to_wx(e.what()).data()));
 -                      return;
 -              }
 -              
 -              if (_decoders.video == 0) {
 -                      break;
 -              }
 -              _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3, _4));
 -              _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this));
 -              _decoders.video->set_subtitle_stream (_film->subtitle_stream());
                calculate_sizes ();
 -              get_frame ();
 -              _panel->Refresh ();
 -              _slider->Show (_film->content_type() == VIDEO);
 -              _play_button->Show (_film->content_type() == VIDEO);
 -              _v_sizer->Layout ();
 +              wxScrollEvent ev;
 +              slider_moved (ev);
                break;
        }
        case Film::WITH_SUBTITLES:
        case Film::SUBTITLE_OFFSET:
        case Film::SUBTITLE_SCALE:
 -      case Film::SCALER:
 -      case Film::FILTERS:
 -              update_from_raw ();
 +              raw_to_display ();
 +              _panel->Refresh ();
 +              _panel->Update ();
                break;
 -      case Film::SUBTITLE_STREAM:
 -              if (_decoders.video) {
 -                      _decoders.video->set_subtitle_stream (_film->subtitle_stream ());
 -              }
 +      case Film::SCALER:
-       case Film::FILTERS:
 +              update_from_decoder ();
                break;
        default:
                break;
@@@ -149,7 -163,7 +148,7 @@@ FilmViewer::set_film (shared_ptr<Film> 
        if (_film == f) {
                return;
        }
 -      
 +
        _film = f;
  
        _raw_frame.reset ();
                return;
        }
  
 +      _player = f->player ();
 +      _player->disable_audio ();
 +      /* Don't disable subtitles here as we may need them, and it's nice to be able to turn them
 +         on and off without needing obtain a new Player.
 +      */
 +      
 +      _player->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3, _4));
 +      
        _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
 +      _film->ContentChanged.connect (boost::bind (&FilmViewer::film_content_changed, this, _1, _2));
  
        film_changed (Film::CONTENT);
 -      film_changed (Film::FORMAT);
 +      film_changed (Film::CONTAINER);
        film_changed (Film::WITH_SUBTITLES);
        film_changed (Film::SUBTITLE_OFFSET);
        film_changed (Film::SUBTITLE_SCALE);
 -      film_changed (Film::SUBTITLE_STREAM);
  }
  
  void
 -FilmViewer::decoder_changed ()
 +FilmViewer::update_from_decoder ()
  {
 -      if (_decoders.video == 0 || _decoders.video->seek_to_last ()) {
 +      if (!_player) {
                return;
        }
  
 +      _player->seek (_player->position ());
        get_frame ();
        _panel->Refresh ();
        _panel->Update ();
  void
  FilmViewer::timer (wxTimerEvent &)
  {
 -      if (!_film || !_decoders.video) {
 +      if (!_player) {
                return;
        }
        
        get_frame ();
  
        if (_film->length()) {
 -              int const new_slider_position = 4096 * _decoders.video->last_source_time() / (_film->length().get() / _film->source_frame_rate());
 +              int const new_slider_position = 4096 * _player->position() / _film->length();
                if (new_slider_position != _slider->GetValue()) {
                        _slider->SetValue (new_slider_position);
                }
@@@ -261,10 -266,12 +260,10 @@@ FilmViewer::paint_panel (wxPaintEvent &
  void
  FilmViewer::slider_moved (wxScrollEvent &)
  {
 -      if (!_film || !_film->length() || !_decoders.video) {
 -              return;
 -      }
 +      cout << "slider " << _slider->GetValue() << " " << _film->length() << "\n";
        
 -      if (_decoders.video->seek (_slider->GetValue() * _film->length().get() / (4096 * _film->source_frame_rate()))) {
 -              return;
 +      if (_film && _player) {
 +              _player->seek (_slider->GetValue() * _film->length() / 4096);
        }
        
        get_frame ();
@@@ -301,15 -308,15 +300,8 @@@ FilmViewer::raw_to_display (
                return;
        }
  
-       shared_ptr<const Image> input = _raw_frame;
 -      boost::shared_ptr<const Image> input = _raw_frame;
--
--      pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
--      if (!s.second.empty ()) {
--              input = input->post_process (s.second, true);
--      }
--      
        /* Get a compacted image as we have to feed it to wxWidgets */
--      _display_frame = input->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false);
++      _display_frame = _raw_frame->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false);
  
        if (_raw_sub) {
  
                   when working out the scale that we are applying.
                */
  
 -              Size const cropped_size = _film->cropped_size (_film->size ());
 +              /* XXX */
 +              Size const cropped_size = _raw_frame->size ();//_film->cropped_size (_raw_frame->size ());
  
                Rect tx = subtitle_transformed_area (
                        float (_film_size.width) / cropped_size.width,
  void
  FilmViewer::calculate_sizes ()
  {
 -      if (!_film) {
 +      if (!_film || !_player) {
                return;
        }
  
 -      Format const * format = _film->format ();
 +      Container const * container = _film->container ();
        
        float const panel_ratio = static_cast<float> (_panel_size.width) / _panel_size.height;
 -      float const film_ratio = format ? format->container_ratio () : 1.78;
 +      float const film_ratio = container ? container->ratio () : 1.78;
                        
        if (panel_ratio < film_ratio) {
                /* panel is less widscreen than the film; clamp width */
           of our _display_frame.
        */
        _display_frame_x = 0;
 -      if (format) {
 -              _display_frame_x = static_cast<float> (format->dcp_padding (_film)) * _out_size.width / format->dcp_size().width;
 -      }
 +//    if (format) {
 +//            _display_frame_x = static_cast<float> (format->dcp_padding (_film)) * _out_size.width / format->dcp_size().width;
 +//    }
  
        _film_size = _out_size;
        _film_size.width -= _display_frame_x * 2;
@@@ -382,19 -388,19 +374,19 @@@ FilmViewer::play_clicked (wxCommandEven
  void
  FilmViewer::check_play_state ()
  {
 -      if (!_film) {
 +      if (!_film || _film->dcp_video_frame_rate() == 0) {
                return;
        }
        
        if (_play_button->GetValue()) {
 -              _timer.Start (1000 / _film->source_frame_rate());
 +              _timer.Start (1000 / _film->dcp_video_frame_rate());
        } else {
                _timer.Stop ();
        }
  }
  
  void
 -FilmViewer::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subtitle> sub, double t)
 +FilmViewer::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subtitle> sub, Time t)
  {
        _raw_frame = image;
        _raw_sub = sub;
  
        _got_frame = true;
  
 -      double const fps = _decoders.video->frames_per_second ();
 -      _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps))));
 +      double const fps = _film->dcp_video_frame_rate ();
 +      _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ))));
  
 -      double w = t;
 +      double w = static_cast<double>(t) / TIME_HZ;
        int const h = (w / 3600);
        w -= h * 3600;
        int const m = (w / 60);
        _timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d:%02d"), h, m, s, f));
  }
  
 +/** Get a new _raw_frame from the decoder and then do
 + *  raw_to_display ().
 + */
  void
  FilmViewer::get_frame ()
  {
        /* Clear our raw frame in case we don't get a new one */
        _raw_frame.reset ();
  
 -      if (_decoders.video == 0) {
 +      if (!_player) {
                _display_frame.reset ();
                return;
        }
 -      
 +
        try {
                _got_frame = false;
                while (!_got_frame) {
 -                      if (_decoders.video->pass ()) {
 +                      if (_player->pass ()) {
                                /* We didn't get a frame before the decoder gave up,
                                   so clear our display frame.
                                */
@@@ -469,26 -472,14 +461,26 @@@ FilmViewer::active_jobs_changed (bool a
        _play_button->Enable (!a);
  }
  
 +void
 +FilmViewer::film_content_changed (weak_ptr<Content>, int p)
 +{
 +      if (p == VideoContentProperty::VIDEO_LENGTH) {
 +              /* Force an update to our frame */
 +              wxScrollEvent ev;
 +              slider_moved (ev);
 +      } else if (p == VideoContentProperty::VIDEO_CROP) {
 +              update_from_decoder ();
 +      }               
 +}
 +
  void
  FilmViewer::back_clicked (wxCommandEvent &)
  {
 -      if (!_decoders.video) {
 +      if (!_player) {
                return;
        }
        
 -      _decoders.video->seek_back ();
 +      _player->seek_back ();
        get_frame ();
        _panel->Refresh ();
        _panel->Update ();
  void
  FilmViewer::forward_clicked (wxCommandEvent &)
  {
 -      if (!_decoders.video) {
 +      if (!_player) {
                return;
        }
  
 -      _decoders.video->seek_forward ();
 +      _player->seek_forward ();
        get_frame ();
        _panel->Refresh ();
        _panel->Update ();
index 0000000000000000000000000000000000000000,e5229b5ffa1794485d21d7b221507846c54c5d5f..9c4482d3c0957fcdb001ec6327cccf6d1eb964a7
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,106 +1,105 @@@
 -                      "",
+ /*
+     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.
+ */
+ void
+ do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* description, shared_ptr<EncodedData> locally_encoded)
+ {
+       shared_ptr<EncodedData> remotely_encoded;
+       BOOST_CHECK_NO_THROW (remotely_encoded = frame->encode_remotely (description));
+       BOOST_CHECK (remotely_encoded);
+       
+       BOOST_CHECK_EQUAL (locally_encoded->size(), remotely_encoded->size());
+       BOOST_CHECK (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()) == 0);
+ }
+ BOOST_AUTO_TEST_CASE (client_server_test)
+ {
+       shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, libdcp::Size (1998, 1080), true));
+       uint8_t* p = image->data()[0];
+       
+       for (int y = 0; y < 1080; ++y) {
+               uint8_t* q = p;
+               for (int x = 0; x < 1998; ++x) {
+                       *q++ = x % 256;
+                       *q++ = y % 256;
+                       *q++ = (x + y) % 256;
+               }
+               p += image->stride()[0];
+       }
+       shared_ptr<Image> sub_image (new SimpleImage (PIX_FMT_RGBA, libdcp::Size (100, 200), true));
+       p = sub_image->data()[0];
+       for (int y = 0; y < 200; ++y) {
+               uint8_t* q = p;
+               for (int x = 0; x < 100; ++x) {
+                       *q++ = y % 256;
+                       *q++ = x % 256;
+                       *q++ = (x + y) % 256;
+                       *q++ = 1;
+               }
+               p += sub_image->stride()[0];
+       }
+       shared_ptr<Subtitle> subtitle (new Subtitle (Position (50, 60), sub_image));
+       shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test.log"));
+       shared_ptr<DCPVideoFrame> frame (
+               new DCPVideoFrame (
+                       image,
+                       subtitle,
+                       libdcp::Size (1998, 1080),
+                       0,
+                       0,
+                       1,
+                       Scaler::from_id ("bicubic"),
+                       0,
+                       24,
 -      dvdomatic_sleep (1);
+                       0,
+                       200000000,
+                       log
+                       )
+               );
+       shared_ptr<EncodedData> locally_encoded = frame->encode_locally ();
+       BOOST_ASSERT (locally_encoded);
+       
+       Server* server = new Server (log);
+       new thread (boost::bind (&Server::run, server, 2));
+       /* Let the server get itself ready */
++      dcpomatic_sleep (1);
+       ServerDescription description ("localhost", 2);
+       list<thread*> threads;
+       for (int i = 0; i < 8; ++i) {
+               threads.push_back (new thread (boost::bind (do_remote_encode, frame, &description, locally_encoded)));
+       }
+       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
+               (*i)->join ();
+       }
+       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
+               delete *i;
+       }
+ }
diff --combined test/dcp_test.cc
index 0000000000000000000000000000000000000000,9312a2067dd7ec115767c6f92f4c5356c0b25001..5a698684beb2f6824deaf6dcb6eaca65741029d1
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,67 +1,66 @@@
 -      film->set_content ("../../../test/test.mp4");
 -      film->set_format (Format::from_nickname ("Flat"));
+ /*
+     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.
+ */
+ BOOST_AUTO_TEST_CASE (make_dcp_test)
+ {
+       shared_ptr<Film> film = new_test_film ("make_dcp_test");
+       film->set_name ("test_film2");
 -              dvdomatic_sleep (1);
++      film->add_content (shared_ptr<FFmpegContent> (new FFmpegContent (film, "../../../test/test.mp4")));
++      film->set_container (Container::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       film->make_dcp ();
+       film->write_metadata ();
+       while (JobManager::instance()->work_to_do ()) {
 -      film->set_content ("../../../test/test.mp4");
 -      film->examine_content ();
 -      film->set_format (Format::from_nickname ("Flat"));
++              dcpomatic_sleep (1);
+       }
+       
+       BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
+ }
+ /** Test Film::have_dcp().  Requires the output from make_dcp_test above */
+ BOOST_AUTO_TEST_CASE (have_dcp_test)
+ {
+       boost::filesystem::path p = test_film_dir ("make_dcp_test");
+       Film f (p.string ());
+       BOOST_CHECK (f.have_dcp());
+       p /= f.dcp_name();
+       p /= f.dcp_video_mxf_filename();
+       boost::filesystem::remove (p);
+       BOOST_CHECK (!f.have_dcp ());
+ }
+ BOOST_AUTO_TEST_CASE (make_dcp_with_range_test)
+ {
+       shared_ptr<Film> film = new_test_film ("make_dcp_with_range_test");
+       film->set_name ("test_film3");
 -      film->set_trim_end (42);
++      film->add_content (shared_ptr<Content> (new FFmpegContent (film, "../../../test/test.mp4")));
++//    film->examine_content ();
++      film->set_container (Container::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
 -              dvdomatic_sleep (1);
+       film->make_dcp ();
+       while (JobManager::instance()->work_to_do() && !JobManager::instance()->errors()) {
++              dcpomatic_sleep (1);
+       }
+       BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
+ }
index 0000000000000000000000000000000000000000,0b4495b486989f3dc8b0adde516a7d936b9bf151..8309de7f1d350037f650539bdd16a87b848624d9
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,76 +1,54 @@@
 -      BOOST_CHECK_THROW (new Film (test_film, true), OpenFileError);
 -      
 -      shared_ptr<Film> f (new Film (test_film, false));
+ /*
+     Copyright (C) 2013 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.
+ */
+ BOOST_AUTO_TEST_CASE (film_metadata_test)
+ {
+       string const test_film = "build/test/film_metadata_test";
+       
+       if (boost::filesystem::exists (test_film)) {
+               boost::filesystem::remove_all (test_film);
+       }
 -      BOOST_CHECK (f->format() == 0);
++      shared_ptr<Film> f (new Film (test_film));
+       f->_dci_date = boost::gregorian::from_undelimited_string ("20130211");
 -      BOOST_CHECK (f->filters ().empty());
++      BOOST_CHECK (f->container() == 0);
+       BOOST_CHECK (f->dcp_content_type() == 0);
 -      BOOST_CHECK_THROW (f->set_content ("jim"), OpenFileError);
+       f->set_name ("fred");
 -      f->set_format (Format::from_nickname ("Flat"));
 -      f->set_left_crop (1);
 -      f->set_right_crop (2);
 -      f->set_top_crop (3);
 -      f->set_bottom_crop (4);
 -      vector<Filter const *> f_filters;
 -      f_filters.push_back (Filter::from_id ("pphb"));
 -      f_filters.push_back (Filter::from_id ("unsharp"));
 -      f->set_filters (f_filters);
 -      f->set_trim_start (42);
 -      f->set_trim_end (99);
 -      f->set_dcp_ab (true);
++//    BOOST_CHECK_THROW (f->add_content ("jim"), OpenFileError);
+       f->set_dcp_content_type (DCPContentType::from_pretty_name ("Short"));
 -      shared_ptr<Film> g (new Film (test_film, true));
++      f->set_container (Container::from_id ("185"));
++      f->set_ab (true);
+       f->write_metadata ();
+       stringstream s;
+       s << "diff -u test/metadata.ref " << test_film << "/metadata";
+       BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0);
 -      BOOST_CHECK_EQUAL (g->format(), Format::from_nickname ("Flat"));
 -      BOOST_CHECK_EQUAL (g->crop().left, 1);
 -      BOOST_CHECK_EQUAL (g->crop().right, 2);
 -      BOOST_CHECK_EQUAL (g->crop().top, 3);
 -      BOOST_CHECK_EQUAL (g->crop().bottom, 4);
 -      vector<Filter const *> g_filters = g->filters ();
 -      BOOST_CHECK_EQUAL (g_filters.size(), 2);
 -      BOOST_CHECK_EQUAL (g_filters.front(), Filter::from_id ("pphb"));
 -      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);
++      shared_ptr<Film> g (new Film (test_film));
++      g->read_metadata ();
+       BOOST_CHECK_EQUAL (g->name(), "fred");
+       BOOST_CHECK_EQUAL (g->dcp_content_type(), DCPContentType::from_pretty_name ("Short"));
++      BOOST_CHECK_EQUAL (g->container(), Container::from_id ("185"));
++      BOOST_CHECK_EQUAL (g->ab(), true);
+       
+       g->write_metadata ();
+       BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0);
+ }
diff --combined test/format_test.cc
index 0000000000000000000000000000000000000000,b150738a4d7a57d41f906d082f0786afb62cdeab..71bc00359d830e3f50fa3ed5f7af8373bd42d1c6
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,61 +1,33 @@@
 -
 -
 -/* 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));
 -}
 -
+ /*
+     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.
+ */
+ BOOST_AUTO_TEST_CASE (format_test)
+ {
+       Format::setup_formats ();
+       
+       Format const * f = Format::from_nickname ("Flat");
+       BOOST_CHECK (f);
+       BOOST_CHECK_EQUAL (f->dcp_size().width, 1998);
+       BOOST_CHECK_EQUAL (f->dcp_size().height, 1080);
+       
+       f = Format::from_nickname ("Scope");
+       BOOST_CHECK (f);
+       BOOST_CHECK_EQUAL (f->dcp_size().width, 2048);
+       BOOST_CHECK_EQUAL (f->dcp_size().height, 858);
+ }
diff --combined test/frame_rate_test.cc
index 0000000000000000000000000000000000000000,00700656eeba77fc5b9c2833eef61fa83893217e..8b04d3763bb83dab152be8a97da7eeb70410db8b
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,211 +1,213 @@@
+ /*
+     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.
+ */
+ /* Test best_dcp_frame_rate and FrameRateConversion */
+ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test)
+ {
++#if 0 
+       /* Run some tests with a limited range of allowed rates */
+       
+       std::list<int> afr;
+       afr.push_back (24);
+       afr.push_back (25);
+       afr.push_back (30);
+       Config::instance()->set_allowed_dcp_frame_rates (afr);
+       int best = best_dcp_frame_rate (60);
+       FrameRateConversion frc = FrameRateConversion (60, best);
+       BOOST_CHECK_EQUAL (best, 30);
+       BOOST_CHECK_EQUAL (frc.skip, true);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+       
+       best = best_dcp_frame_rate (50);
+       frc = FrameRateConversion (50, best);
+       BOOST_CHECK_EQUAL (best, 25);
+       BOOST_CHECK_EQUAL (frc.skip, true);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+       best = best_dcp_frame_rate (48);
+       frc = FrameRateConversion (48, best);
+       BOOST_CHECK_EQUAL (best, 24);
+       BOOST_CHECK_EQUAL (frc.skip, true);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+       
+       best = best_dcp_frame_rate (30);
+       frc = FrameRateConversion (30, best);
+       BOOST_CHECK_EQUAL (best, 30);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+       best = best_dcp_frame_rate (29.97);
+       frc = FrameRateConversion (29.97, best);
+       BOOST_CHECK_EQUAL (best, 30);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, true);
+       
+       best = best_dcp_frame_rate (25);
+       frc = FrameRateConversion (25, best);
+       BOOST_CHECK_EQUAL (best, 25);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+       best = best_dcp_frame_rate (24);
+       frc = FrameRateConversion (24, best);
+       BOOST_CHECK_EQUAL (best, 24);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+       best = best_dcp_frame_rate (14.5);
+       frc = FrameRateConversion (14.5, best);
+       BOOST_CHECK_EQUAL (best, 30);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, true);
+       BOOST_CHECK_EQUAL (frc.change_speed, true);
+       best = best_dcp_frame_rate (12.6);
+       frc = FrameRateConversion (12.6, best);
+       BOOST_CHECK_EQUAL (best, 25);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, true);
+       BOOST_CHECK_EQUAL (frc.change_speed, true);
+       best = best_dcp_frame_rate (12.4);
+       frc = FrameRateConversion (12.4, best);
+       BOOST_CHECK_EQUAL (best, 25);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, true);
+       BOOST_CHECK_EQUAL (frc.change_speed, true);
+       best = best_dcp_frame_rate (12);
+       frc = FrameRateConversion (12, best);
+       BOOST_CHECK_EQUAL (best, 24);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, true);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+       /* Now add some more rates and see if it will use them
+          in preference to skip/repeat.
+       */
+       afr.push_back (48);
+       afr.push_back (50);
+       afr.push_back (60);
+       Config::instance()->set_allowed_dcp_frame_rates (afr);
+       best = best_dcp_frame_rate (60);
+       frc = FrameRateConversion (60, best);
+       BOOST_CHECK_EQUAL (best, 60);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+       
+       best = best_dcp_frame_rate (50);
+       frc = FrameRateConversion (50, best);
+       BOOST_CHECK_EQUAL (best, 50);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+       best = best_dcp_frame_rate (48);
+       frc = FrameRateConversion (48, best);
+       BOOST_CHECK_EQUAL (best, 48);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+       /* Check some out-there conversions (not the best) */
+       
+       frc = FrameRateConversion (14.99, 24);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, true);
+       BOOST_CHECK_EQUAL (frc.change_speed, true);
+       /* Check some conversions with limited DCP targets */
+       afr.clear ();
+       afr.push_back (24);
+       Config::instance()->set_allowed_dcp_frame_rates (afr);
+       best = best_dcp_frame_rate (25);
+       frc = FrameRateConversion (25, best);
+       BOOST_CHECK_EQUAL (best, 24);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, true);
+ }
+ BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
+ {
+       std::list<int> afr;
+       afr.push_back (24);
+       afr.push_back (25);
+       afr.push_back (30);
+       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_dcp_frame_rate (24);
+       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)));
+       BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000);
+       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_dcp_frame_rate (best_dcp_frame_rate (23.976));
+       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_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)));
+       BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952);
+       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)));
+       BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 50000);
+       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)));
+       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_dcp_frame_rate (25);
+       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.
+       */
+       BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), rint (48000 * 2 * 14.99 / 25));
++#endif        
+ }
diff --combined test/job_test.cc
index 0000000000000000000000000000000000000000,247d4f75665895d09f243cd7c6c76a73d6ed848b..86c6dc9e342d45648ae92793be10f029d9d07eea
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,64 +1,64 @@@
 -      dvdomatic_sleep (1);
+ /*
+     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.
+ */
+ class TestJob : public Job
+ {
+ public:
+       TestJob (shared_ptr<Film> f)
+               : Job (f)
+       {
+       }
+       void set_finished_ok () {
+               set_state (FINISHED_OK);
+       }
+       void set_finished_error () {
+               set_state (FINISHED_ERROR);
+       }
+       void run ()
+       {
+               while (1) {
+                       if (finished ()) {
+                               return;
+                       }
+               }
+       }
+       string name () const {
+               return "";
+       }
+ };
+ BOOST_AUTO_TEST_CASE (job_manager_test)
+ {
+       shared_ptr<Film> f;
+       /* Single job */
+       shared_ptr<TestJob> a (new TestJob (f));
+       JobManager::instance()->add (a);
 -      dvdomatic_sleep (2);
++      dcpomatic_sleep (1);
+       BOOST_CHECK_EQUAL (a->running (), true);
+       a->set_finished_ok ();
++      dcpomatic_sleep (2);
+       BOOST_CHECK_EQUAL (a->finished_ok(), true);
+ }
index 0000000000000000000000000000000000000000,84f2a33ce325209614ae519a36a5fc4113104dab..08c9f2d2def524e250692d67be7bcdc498ba5d35
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,77 +1,74 @@@
 -      /* This needs to happen in the first test */
 -      dvdomatic_setup ();
 -
+ /*
+     Copyright (C) 2013 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.
+ */
+ using std::list;
+ using std::cout;
+ struct Case
+ {
+       Case (AVPixelFormat f, int c, int l0, int l1, int l2, float b0, float b1, float b2)
+               : format(f)
+               , components(c)
+       {
+               lines[0] = l0;
+               lines[1] = l1;
+               lines[2] = l2;
+               bpp[0] = b0;
+               bpp[1] = b1;
+               bpp[2] = b2;
+       }
+       
+       AVPixelFormat format;
+       int components;
+       int lines[3];
+       float bpp[3];
+ };
+ BOOST_AUTO_TEST_CASE (pixel_formats_test)
+ {
+       list<Case> cases;
+       cases.push_back(Case(AV_PIX_FMT_RGB24,       1, 480, 480, 480, 3, 0,   0  ));
+       cases.push_back(Case(AV_PIX_FMT_RGBA,        1, 480, 480, 480, 4, 0,   0  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV420P,     3, 480, 240, 240, 1, 0.5, 0.5));
+       cases.push_back(Case(AV_PIX_FMT_YUV422P,     3, 480, 480, 480, 1, 0.5, 0.5));
+       cases.push_back(Case(AV_PIX_FMT_YUV422P10LE, 3, 480, 480, 480, 2, 1,   1  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV422P16LE, 3, 480, 480, 480, 2, 1,   1  ));
+       cases.push_back(Case(AV_PIX_FMT_UYVY422,     1, 480, 480, 480, 2, 0,   0  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV444P,     3, 480, 480, 480, 1, 1,   1  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV444P9BE,  3, 480, 480, 480, 2, 2,   2  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV444P9LE,  3, 480, 480, 480, 2, 2,   2  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV444P10BE, 3, 480, 480, 480, 2, 2,   2  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV444P10LE, 3, 480, 480, 480, 2, 2,   2  ));
+       for (list<Case>::iterator i = cases.begin(); i != cases.end(); ++i) {
+               AVFrame* f = av_frame_alloc ();
+               f->width = 640;
+               f->height = 480;
+               f->format = static_cast<int> (i->format);
+               SimpleImage t (f);
+               BOOST_CHECK_EQUAL(t.components(), i->components);
+               BOOST_CHECK_EQUAL(t.lines(0), i->lines[0]);
+               BOOST_CHECK_EQUAL(t.lines(1), i->lines[1]);
+               BOOST_CHECK_EQUAL(t.lines(2), i->lines[2]);
+               BOOST_CHECK_EQUAL(t.bytes_per_pixel(0), i->bpp[0]);
+               BOOST_CHECK_EQUAL(t.bytes_per_pixel(1), i->bpp[1]);
+               BOOST_CHECK_EQUAL(t.bytes_per_pixel(2), i->bpp[2]);
+       }
+ }
diff --combined test/stream_test.cc
index 0000000000000000000000000000000000000000,b467dc9a2efa144e94b51e9b7c5f02391e03665b..4d97e82afbebda8e7de70b050053df2175d5476c
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,52 +1,54 @@@
+ /*
+     Copyright (C) 2013 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.
+ */
+ BOOST_AUTO_TEST_CASE (stream_test)
+ {
++#if 0 
+       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");
++#endif        
+ }
diff --combined test/test.cc
index 269bb374b4c370ba2f5877257cde549d21e66d60,91c876412f74b15af3f6f37eea5fc932cb7d1293..cbbd0443dc8eae7df4a64b4194bf843a1f7fdc5e
  #include "scaler.h"
  #include "ffmpeg_decoder.h"
  #include "sndfile_decoder.h"
 -#include "trimmer.h"
 +#include "dcp_content_type.h"
 +#include "container.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;
@@@ -53,14 -52,20 +53,20 @@@ using boost::shared_ptr
  using boost::thread;
  using boost::dynamic_pointer_cast;
  
- void
- setup_test_config ()
+ struct TestConfig
  {
-       Config::instance()->set_num_local_encoding_threads (1);
-       Config::instance()->set_servers (vector<ServerDescription*> ());
-       Config::instance()->set_server_port (61920);
-       Config::instance()->set_default_dci_metadata (DCIMetadata ());
- }
+       TestConfig()
+       {
 -              dvdomatic_setup();
++              dcpomatic_setup();
+               Config::instance()->set_num_local_encoding_threads (1);
+               Config::instance()->set_servers (vector<ServerDescription*> ());
+               Config::instance()->set_server_port (61920);
+               Config::instance()->set_default_dci_metadata (DCIMetadata ());
+       }
+ };
+ BOOST_GLOBAL_FIXTURE (TestConfig);
  
  boost::filesystem::path
  test_film_dir (string name)
@@@ -80,616 -85,19 +86,19 @@@ new_test_film (string name
                boost::filesystem::remove_all (p);
        }
        
 -      return shared_ptr<Film> (new Film (p.string(), false));
 +      shared_ptr<Film> f = shared_ptr<Film> (new Film (p.string()));
 +      f->write_metadata ();
 +      return f;
  }
  
- /* Check that Image::make_black works, and doesn't use values which crash
-    sws_scale().
- */
- BOOST_AUTO_TEST_CASE (make_black_test)
- {
-       /* This needs to happen in the first test */
-       dcpomatic_setup ();
-       libdcp::Size in_size (512, 512);
-       libdcp::Size out_size (1024, 1024);
-       list<AVPixelFormat> pix_fmts;
-       pix_fmts.push_back (AV_PIX_FMT_RGB24);
-       pix_fmts.push_back (AV_PIX_FMT_YUV420P);
-       pix_fmts.push_back (AV_PIX_FMT_YUV422P10LE);
-       pix_fmts.push_back (AV_PIX_FMT_YUV444P9LE);
-       pix_fmts.push_back (AV_PIX_FMT_YUV444P9BE);
-       pix_fmts.push_back (AV_PIX_FMT_YUV444P10LE);
-       pix_fmts.push_back (AV_PIX_FMT_YUV444P10BE);
-       pix_fmts.push_back (AV_PIX_FMT_UYVY422);
-       int N = 0;
-       for (list<AVPixelFormat>::const_iterator i = pix_fmts.begin(); i != pix_fmts.end(); ++i) {
-               boost::shared_ptr<Image> foo (new SimpleImage (*i, in_size, true));
-               foo->make_black ();
-               boost::shared_ptr<Image> bar = foo->scale_and_convert_to_rgb (out_size, 0, Scaler::from_id ("bicubic"), true);
-               
-               uint8_t* p = bar->data()[0];
-               for (int y = 0; y < bar->size().height; ++y) {
-                       uint8_t* q = p;
-                       for (int x = 0; x < bar->line_size()[0]; ++x) {
-                               BOOST_CHECK_EQUAL (*q++, 0);
-                       }
-                       p += bar->stride()[0];
-               }
-               ++N;
-       }
- }
- BOOST_AUTO_TEST_CASE (film_metadata_test)
- {
-       setup_test_config ();
-       string const test_film = "build/test/film_metadata_test";
-       
-       if (boost::filesystem::exists (test_film)) {
-               boost::filesystem::remove_all (test_film);
-       }
-       shared_ptr<Film> f (new Film (test_film));
-       f->write_metadata ();
-       f->_dci_date = boost::gregorian::from_undelimited_string ("20130211");
-       BOOST_CHECK (f->container() == 0);
-       BOOST_CHECK (f->dcp_content_type() == 0);
-       BOOST_CHECK (f->filters ().empty());
-       f->set_name ("fred");
- //    BOOST_CHECK_THROW (f->set_content ("jim"), OpenFileError);
-       f->set_dcp_content_type (DCPContentType::from_pretty_name ("Short"));
-       f->set_container (Container::from_id ("185"));
-       vector<Filter const *> f_filters;
-       f_filters.push_back (Filter::from_id ("pphb"));
-       f_filters.push_back (Filter::from_id ("unsharp"));
-       f->set_filters (f_filters);
-       f->set_ab (true);
-       f->write_metadata ();
-       stringstream s;
-       s << "diff -u test/metadata.ref " << test_film << "/metadata";
-       BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0);
-       shared_ptr<Film> g (new Film (test_film));
-       g->read_metadata ();
-       BOOST_CHECK_EQUAL (g->name(), "fred");
-       BOOST_CHECK_EQUAL (g->dcp_content_type(), DCPContentType::from_pretty_name ("Short"));
-       BOOST_CHECK_EQUAL (g->container(), Container::from_id ("185"));
-       vector<Filter const *> g_filters = g->filters ();
-       BOOST_CHECK_EQUAL (g_filters.size(), 2);
-       BOOST_CHECK_EQUAL (g_filters.front(), Filter::from_id ("pphb"));
-       BOOST_CHECK_EQUAL (g_filters.back(), Filter::from_id ("unsharp"));
-       BOOST_CHECK_EQUAL (g->ab(), true);
-       
-       g->write_metadata ();
-       BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0);
- }
- BOOST_AUTO_TEST_CASE (format_test)
- {
-       Format::setup_formats ();
-       
-       Format const * f = Format::from_nickname ("Flat");
-       BOOST_CHECK (f);
-       BOOST_CHECK_EQUAL (f->dcp_size().width, 1998);
-       BOOST_CHECK_EQUAL (f->dcp_size().height, 1080);
-       
-       f = Format::from_nickname ("Scope");
-       BOOST_CHECK (f);
-       BOOST_CHECK_EQUAL (f->dcp_size().width, 2048);
-       BOOST_CHECK_EQUAL (f->dcp_size().height, 858);
- }
- BOOST_AUTO_TEST_CASE (util_test)
- {
-       string t = "Hello this is a string \"with quotes\" and indeed without them";
-       vector<string> b = split_at_spaces_considering_quotes (t);
-       vector<string>::iterator i = b.begin ();
-       BOOST_CHECK_EQUAL (*i++, "Hello");
-       BOOST_CHECK_EQUAL (*i++, "this");
-       BOOST_CHECK_EQUAL (*i++, "is");
-       BOOST_CHECK_EQUAL (*i++, "a");
-       BOOST_CHECK_EQUAL (*i++, "string");
-       BOOST_CHECK_EQUAL (*i++, "with quotes");
-       BOOST_CHECK_EQUAL (*i++, "and");
-       BOOST_CHECK_EQUAL (*i++, "indeed");
-       BOOST_CHECK_EQUAL (*i++, "without");
-       BOOST_CHECK_EQUAL (*i++, "them");
- }
- class NullLog : public Log
- {
- public:
-       void do_log (string) {}
- };
- BOOST_AUTO_TEST_CASE (md5_digest_test)
- {
-       string const t = md5_digest ("test/md5.test");
-       BOOST_CHECK_EQUAL (t, "15058685ba99decdc4398c7634796eb0");
-       BOOST_CHECK_THROW (md5_digest ("foobar"), OpenFileError);
- }
- void
- do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* description, shared_ptr<EncodedData> locally_encoded)
- {
-       shared_ptr<EncodedData> remotely_encoded;
-       BOOST_CHECK_NO_THROW (remotely_encoded = frame->encode_remotely (description));
-       BOOST_CHECK (remotely_encoded);
-       
-       BOOST_CHECK_EQUAL (locally_encoded->size(), remotely_encoded->size());
-       BOOST_CHECK (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()) == 0);
- }
- BOOST_AUTO_TEST_CASE (client_server_test)
- {
-       shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, libdcp::Size (1998, 1080), true));
-       uint8_t* p = image->data()[0];
-       
-       for (int y = 0; y < 1080; ++y) {
-               uint8_t* q = p;
-               for (int x = 0; x < 1998; ++x) {
-                       *q++ = x % 256;
-                       *q++ = y % 256;
-                       *q++ = (x + y) % 256;
-               }
-               p += image->stride()[0];
-       }
-       shared_ptr<Image> sub_image (new SimpleImage (PIX_FMT_RGBA, libdcp::Size (100, 200), true));
-       p = sub_image->data()[0];
-       for (int y = 0; y < 200; ++y) {
-               uint8_t* q = p;
-               for (int x = 0; x < 100; ++x) {
-                       *q++ = y % 256;
-                       *q++ = x % 256;
-                       *q++ = (x + y) % 256;
-                       *q++ = 1;
-               }
-               p += sub_image->stride()[0];
-       }
-       shared_ptr<Subtitle> subtitle (new Subtitle (Position (50, 60), sub_image));
-       shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test.log"));
-       shared_ptr<DCPVideoFrame> frame (
-               new DCPVideoFrame (
-                       image,
-                       subtitle,
-                       libdcp::Size (1998, 1080),
-                       0,
-                       0,
-                       1,
-                       Scaler::from_id ("bicubic"),
-                       0,
-                       24,
-                       "",
-                       0,
-                       200000000,
-                       log
-                       )
-               );
-       shared_ptr<EncodedData> locally_encoded = frame->encode_locally ();
-       BOOST_ASSERT (locally_encoded);
-       
-       Server* server = new Server (log);
-       new thread (boost::bind (&Server::run, server, 2));
-       /* Let the server get itself ready */
-       dcpomatic_sleep (1);
-       ServerDescription description ("localhost", 2);
-       list<thread*> threads;
-       for (int i = 0; i < 8; ++i) {
-               threads.push_back (new thread (boost::bind (do_remote_encode, frame, &description, locally_encoded)));
-       }
-       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
-               (*i)->join ();
-       }
-       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
-               delete *i;
-       }
- }
- BOOST_AUTO_TEST_CASE (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_container (Container::from_id ("185"));
-       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
-       film->make_dcp ();
-       film->write_metadata ();
-       while (JobManager::instance()->work_to_do ()) {
-               dcpomatic_sleep (1);
-       }
-       
-       BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
- }
- /** Test Film::have_dcp().  Requires the output from make_dcp_test above */
- BOOST_AUTO_TEST_CASE (have_dcp_test)
- {
-       boost::filesystem::path p = test_film_dir ("make_dcp_test");
-       Film f (p.string ());
-       BOOST_CHECK (f.have_dcp());
-       p /= f.dcp_name();
-       p /= f.dcp_video_mxf_filename();
-       boost::filesystem::remove (p);
-       BOOST_CHECK (!f.have_dcp ());
- }
- BOOST_AUTO_TEST_CASE (make_dcp_with_range_test)
- {
-       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_container (Container::from_id ("185"));
-       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
-       film->make_dcp ();
-       while (JobManager::instance()->work_to_do() && !JobManager::instance()->errors()) {
-               dcpomatic_sleep (1);
-       }
-       BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
- }
- #if 0
- /* Test best_dcp_frame_rate and FrameRateConversion */
- BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test)
- {
-       /* Run some tests with a limited range of allowed rates */
-       
-       std::list<int> afr;
-       afr.push_back (24);
-       afr.push_back (25);
-       afr.push_back (30);
-       Config::instance()->set_allowed_dcp_frame_rates (afr);
-       int best = best_dcp_frame_rate (60);
-       FrameRateConversion frc = FrameRateConversion (60, best);
-       BOOST_CHECK_EQUAL (best, 30);
-       BOOST_CHECK_EQUAL (frc.skip, true);
-       BOOST_CHECK_EQUAL (frc.repeat, false);
-       BOOST_CHECK_EQUAL (frc.change_speed, false);
-       
-       best = best_dcp_frame_rate (50);
-       frc = FrameRateConversion (50, best);
-       BOOST_CHECK_EQUAL (best, 25);
-       BOOST_CHECK_EQUAL (frc.skip, true);
-       BOOST_CHECK_EQUAL (frc.repeat, false);
-       BOOST_CHECK_EQUAL (frc.change_speed, false);
-       best = best_dcp_frame_rate (48);
-       frc = FrameRateConversion (48, best);
-       BOOST_CHECK_EQUAL (best, 24);
-       BOOST_CHECK_EQUAL (frc.skip, true);
-       BOOST_CHECK_EQUAL (frc.repeat, false);
-       BOOST_CHECK_EQUAL (frc.change_speed, false);
-       
-       best = best_dcp_frame_rate (30);
-       frc = FrameRateConversion (30, best);
-       BOOST_CHECK_EQUAL (best, 30);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, false);
-       BOOST_CHECK_EQUAL (frc.change_speed, false);
-       best = best_dcp_frame_rate (29.97);
-       frc = FrameRateConversion (29.97, best);
-       BOOST_CHECK_EQUAL (best, 30);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, false);
-       BOOST_CHECK_EQUAL (frc.change_speed, true);
-       
-       best = best_dcp_frame_rate (25);
-       frc = FrameRateConversion (25, best);
-       BOOST_CHECK_EQUAL (best, 25);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, false);
-       BOOST_CHECK_EQUAL (frc.change_speed, false);
-       best = best_dcp_frame_rate (24);
-       frc = FrameRateConversion (24, best);
-       BOOST_CHECK_EQUAL (best, 24);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, false);
-       BOOST_CHECK_EQUAL (frc.change_speed, false);
-       best = best_dcp_frame_rate (14.5);
-       frc = FrameRateConversion (14.5, best);
-       BOOST_CHECK_EQUAL (best, 30);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, true);
-       BOOST_CHECK_EQUAL (frc.change_speed, true);
-       best = best_dcp_frame_rate (12.6);
-       frc = FrameRateConversion (12.6, best);
-       BOOST_CHECK_EQUAL (best, 25);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, true);
-       BOOST_CHECK_EQUAL (frc.change_speed, true);
-       best = best_dcp_frame_rate (12.4);
-       frc = FrameRateConversion (12.4, best);
-       BOOST_CHECK_EQUAL (best, 25);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, true);
-       BOOST_CHECK_EQUAL (frc.change_speed, true);
-       best = best_dcp_frame_rate (12);
-       frc = FrameRateConversion (12, best);
-       BOOST_CHECK_EQUAL (best, 24);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, true);
-       BOOST_CHECK_EQUAL (frc.change_speed, false);
-       /* Now add some more rates and see if it will use them
-          in preference to skip/repeat.
-       */
-       afr.push_back (48);
-       afr.push_back (50);
-       afr.push_back (60);
-       Config::instance()->set_allowed_dcp_frame_rates (afr);
-       best = best_dcp_frame_rate (60);
-       frc = FrameRateConversion (60, best);
-       BOOST_CHECK_EQUAL (best, 60);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, false);
-       BOOST_CHECK_EQUAL (frc.change_speed, false);
-       
-       best = best_dcp_frame_rate (50);
-       frc = FrameRateConversion (50, best);
-       BOOST_CHECK_EQUAL (best, 50);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, false);
-       BOOST_CHECK_EQUAL (frc.change_speed, false);
-       best = best_dcp_frame_rate (48);
-       frc = FrameRateConversion (48, best);
-       BOOST_CHECK_EQUAL (best, 48);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, false);
-       BOOST_CHECK_EQUAL (frc.change_speed, false);
-       /* Check some out-there conversions (not the best) */
-       
-       frc = FrameRateConversion (14.99, 24);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, true);
-       BOOST_CHECK_EQUAL (frc.change_speed, true);
-       /* Check some conversions with limited DCP targets */
-       afr.clear ();
-       afr.push_back (24);
-       Config::instance()->set_allowed_dcp_frame_rates (afr);
-       best = best_dcp_frame_rate (25);
-       frc = FrameRateConversion (25, best);
-       BOOST_CHECK_EQUAL (best, 24);
-       BOOST_CHECK_EQUAL (frc.skip, false);
-       BOOST_CHECK_EQUAL (frc.repeat, false);
-       BOOST_CHECK_EQUAL (frc.change_speed, true);
- }
- #endif
- BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
- {
-       std::list<int> afr;
-       afr.push_back (24);
-       afr.push_back (25);
-       afr.push_back (30);
-       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_dcp_frame_rate (24);
- //    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)));
- //    BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000);
- //    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_dcp_frame_rate (best_dcp_frame_rate (23.976));
- //    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_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)));
- //    BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952);
- //    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)));
- //    BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 50000);
- //    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)));
- //    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_dcp_frame_rate (25);
- //    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.
-       */
- //    BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), rint (48000 * 2 * 14.99 / 25));
- }
- class TestJob : public Job
- {
- public:
-       TestJob (shared_ptr<Film> f)
-               : Job (f)
-       {
-       }
-       void set_finished_ok () {
-               set_state (FINISHED_OK);
-       }
-       void set_finished_error () {
-               set_state (FINISHED_ERROR);
-       }
-       void run ()
-       {
-               while (1) {
-                       if (finished ()) {
-                               return;
-                       }
-               }
-       }
-       string name () const {
-               return "";
-       }
- };
- BOOST_AUTO_TEST_CASE (job_manager_test)
- {
-       shared_ptr<Film> f;
-       /* Single job */
-       shared_ptr<TestJob> a (new TestJob (f));
-       JobManager::instance()->add (a);
-       dcpomatic_sleep (1);
-       BOOST_CHECK_EQUAL (a->running (), true);
-       a->set_finished_ok ();
-       dcpomatic_sleep (2);
-       BOOST_CHECK_EQUAL (a->finished_ok(), true);
- }
- BOOST_AUTO_TEST_CASE (compact_image_test)
- {
-       SimpleImage* s = new SimpleImage (PIX_FMT_RGB24, libdcp::Size (50, 50), false);
-       BOOST_CHECK_EQUAL (s->components(), 1);
-       BOOST_CHECK_EQUAL (s->stride()[0], 50 * 3);
-       BOOST_CHECK_EQUAL (s->line_size()[0], 50 * 3);
-       BOOST_CHECK (s->data()[0]);
-       BOOST_CHECK (!s->data()[1]);
-       BOOST_CHECK (!s->data()[2]);
-       BOOST_CHECK (!s->data()[3]);
-       /* copy constructor */
-       SimpleImage* t = new SimpleImage (*s);
-       BOOST_CHECK_EQUAL (t->components(), 1);
-       BOOST_CHECK_EQUAL (t->stride()[0], 50 * 3);
-       BOOST_CHECK_EQUAL (t->line_size()[0], 50 * 3);
-       BOOST_CHECK (t->data()[0]);
-       BOOST_CHECK (!t->data()[1]);
-       BOOST_CHECK (!t->data()[2]);
-       BOOST_CHECK (!t->data()[3]);
-       BOOST_CHECK (t->data() != s->data());
-       BOOST_CHECK (t->data()[0] != s->data()[0]);
-       BOOST_CHECK (t->line_size() != s->line_size());
-       BOOST_CHECK (t->line_size()[0] == s->line_size()[0]);
-       BOOST_CHECK (t->stride() != s->stride());
-       BOOST_CHECK (t->stride()[0] == s->stride()[0]);
-       /* assignment operator */
-       SimpleImage* u = new SimpleImage (PIX_FMT_YUV422P, libdcp::Size (150, 150), true);
-       *u = *s;
-       BOOST_CHECK_EQUAL (u->components(), 1);
-       BOOST_CHECK_EQUAL (u->stride()[0], 50 * 3);
-       BOOST_CHECK_EQUAL (u->line_size()[0], 50 * 3);
-       BOOST_CHECK (u->data()[0]);
-       BOOST_CHECK (!u->data()[1]);
-       BOOST_CHECK (!u->data()[2]);
-       BOOST_CHECK (!u->data()[3]);
-       BOOST_CHECK (u->data() != s->data());
-       BOOST_CHECK (u->data()[0] != s->data()[0]);
-       BOOST_CHECK (u->line_size() != s->line_size());
-       BOOST_CHECK (u->line_size()[0] == s->line_size()[0]);
-       BOOST_CHECK (u->stride() != s->stride());
-       BOOST_CHECK (u->stride()[0] == s->stride()[0]);
-       delete s;
-       delete t;
-       delete u;
- }
- BOOST_AUTO_TEST_CASE (aligned_image_test)
- {
-       SimpleImage* s = new SimpleImage (PIX_FMT_RGB24, libdcp::Size (50, 50), true);
-       BOOST_CHECK_EQUAL (s->components(), 1);
-       /* 160 is 150 aligned to the nearest 32 bytes */
-       BOOST_CHECK_EQUAL (s->stride()[0], 160);
-       BOOST_CHECK_EQUAL (s->line_size()[0], 150);
-       BOOST_CHECK (s->data()[0]);
-       BOOST_CHECK (!s->data()[1]);
-       BOOST_CHECK (!s->data()[2]);
-       BOOST_CHECK (!s->data()[3]);
-       /* copy constructor */
-       SimpleImage* t = new SimpleImage (*s);
-       BOOST_CHECK_EQUAL (t->components(), 1);
-       BOOST_CHECK_EQUAL (t->stride()[0], 160);
-       BOOST_CHECK_EQUAL (t->line_size()[0], 150);
-       BOOST_CHECK (t->data()[0]);
-       BOOST_CHECK (!t->data()[1]);
-       BOOST_CHECK (!t->data()[2]);
-       BOOST_CHECK (!t->data()[3]);
-       BOOST_CHECK (t->data() != s->data());
-       BOOST_CHECK (t->data()[0] != s->data()[0]);
-       BOOST_CHECK (t->line_size() != s->line_size());
-       BOOST_CHECK (t->line_size()[0] == s->line_size()[0]);
-       BOOST_CHECK (t->stride() != s->stride());
-       BOOST_CHECK (t->stride()[0] == s->stride()[0]);
-       /* assignment operator */
-       SimpleImage* u = new SimpleImage (PIX_FMT_YUV422P, libdcp::Size (150, 150), false);
-       *u = *s;
-       BOOST_CHECK_EQUAL (u->components(), 1);
-       BOOST_CHECK_EQUAL (u->stride()[0], 160);
-       BOOST_CHECK_EQUAL (u->line_size()[0], 150);
-       BOOST_CHECK (u->data()[0]);
-       BOOST_CHECK (!u->data()[1]);
-       BOOST_CHECK (!u->data()[2]);
-       BOOST_CHECK (!u->data()[3]);
-       BOOST_CHECK (u->data() != s->data());
-       BOOST_CHECK (u->data()[0] != s->data()[0]);
-       BOOST_CHECK (u->line_size() != s->line_size());
-       BOOST_CHECK (u->line_size()[0] == s->line_size()[0]);
-       BOOST_CHECK (u->stride() != s->stride());
-       BOOST_CHECK (u->stride()[0] == s->stride()[0]);
-       delete s;
-       delete t;
-       delete u;
- }
+ #include "pixel_formats_test.cc"
+ #include "make_black_test.cc"
 -#include "trimmer_test.cc"
+ #include "film_metadata_test.cc"
+ #include "stream_test.cc"
+ #include "format_test.cc"
+ #include "util_test.cc"
 -#include "film_test.cc"
+ #include "dcp_test.cc"
+ #include "frame_rate_test.cc"
+ #include "job_test.cc"
+ #include "client_server_test.cc"
+ #include "image_test.cc"
diff --combined wscript
index f2b21bde35462934858cfbfac42610ecec387c11,b5676ba5bdb30988d6db07e6d0f29f6a95e9ff39..b82af4c37438afa59591b3a8fd0154a370174f16
+++ b/wscript
@@@ -2,8 -2,8 +2,8 @@@ import subproces
  import os
  import sys
  
 -APPNAME = 'dvdomatic'
 -VERSION = '0.94pre'
 +APPNAME = 'dcpomatic'
 +VERSION = '1.00pre'
  
  def options(opt):
      opt.load('compiler_cxx')
@@@ -21,11 -21,11 +21,11 @@@ def configure(conf)
      if conf.options.target_windows:
          conf.load('winres')
  
 -    conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS', '-msse', '-mfpmath=sse', '-ffast-math', '-fno-strict-aliasing',
 +    conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS', '-D__STDC_LIMIT_MACROS', '-msse', '-mfpmath=sse', '-ffast-math', '-fno-strict-aliasing',
                                         '-Wall', '-Wno-attributes', '-Wextra'])
  
      if conf.options.target_windows:
 -        conf.env.append_value('CXXFLAGS', ['-DDVDOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE'])
 +        conf.env.append_value('CXXFLAGS', ['-DDCPOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE'])
          wxrc = os.popen('wx-config --rescomp').read().split()[1:]
          conf.env.append_value('WINRCFLAGS', wxrc)
          if conf.options.enable_debug:
@@@ -38,9 -38,9 +38,9 @@@
          boost_lib_suffix = '-mt'
          boost_thread = 'boost_thread_win32-mt'
      else:
 -        conf.env.append_value('CXXFLAGS', '-DDVDOMATIC_POSIX')
 +        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_POSIX')
          conf.env.append_value('CXXFLAGS', '-DPOSIX_LOCALE_PREFIX="%s/share/locale"' % conf.env['PREFIX'])
 -        conf.env.append_value('CXXFLAGS', '-DPOSIX_ICON_PREFIX="%s/share/dvdomatic"' % conf.env['PREFIX'])
 +        conf.env.append_value('CXXFLAGS', '-DPOSIX_ICON_PREFIX="%s/share/dcpomatic"' % conf.env['PREFIX'])
          boost_lib_suffix = ''
          boost_thread = 'boost_thread'
          conf.env.append_value('LINKFLAGS', '-pthread')
      conf.env.VERSION = VERSION
  
      if conf.options.enable_debug:
 -        conf.env.append_value('CXXFLAGS', ['-g', '-DDVDOMATIC_DEBUG'])
 +        conf.env.append_value('CXXFLAGS', ['-g', '-DDCPOMATIC_DEBUG'])
      else:
          conf.env.append_value('CXXFLAGS', '-O2')
  
      if not conf.options.static:
          conf.check_cfg(package = 'libdcp', atleast_version = '0.49', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
 +        conf.check_cfg(package = 'libcxml', atleast_version = '0.01', args = '--cflags --libs', uselib_store = 'CXML', mandatory = True)
          conf.check_cfg(package = 'libavformat', args = '--cflags --libs', uselib_store = 'AVFORMAT', mandatory = True)
          conf.check_cfg(package = 'libavfilter', args = '--cflags --libs', uselib_store = 'AVFILTER', mandatory = True)
          conf.check_cfg(package = 'libavcodec', args = '--cflags --libs', uselib_store = 'AVCODEC', mandatory = True)
          conf.check_cfg(package = 'libavutil', args = '--cflags --libs', uselib_store = 'AVUTIL', mandatory = True)
          conf.check_cfg(package = 'libswscale', args = '--cflags --libs', uselib_store = 'SWSCALE', mandatory = True)
 -        conf.check_cfg(package = 'libswresample', args = '--cflags --libs', uselib_store = 'SWRESAMPLE', mandatory = False)
 +        conf.check_cfg(package = 'libswresample', args = '--cflags --libs', uselib_store = 'SWRESAMPLE', mandatory = True)
          conf.check_cfg(package = 'libpostproc', args = '--cflags --libs', uselib_store = 'POSTPROC', mandatory = True)
      else:
          # This is hackio grotesquio for static builds (ie for .deb packages).  We need to link some things
@@@ -76,9 -75,6 +76,9 @@@
          conf.env.HAVE_DCP = 1
          conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
          conf.env.LIB_DCP = ['glibmm-2.4', 'xml++-2.6', 'ssl', 'crypto', 'bz2']
 +        conf.env.HAVE_CXML = 1
 +        conf.env.STLIB_CXML = ['cxml']
 +        conf.check_cfg(package = 'libxml++-2.6', args = '--cflags --libs', uselib_store = 'XML++', mandatory = True)
          conf.env.HAVE_AVFORMAT = 1
          conf.env.STLIB_AVFORMAT = ['avformat']
          conf.env.HAVE_AVFILTER = 1
          conf.env.STLIB_AVUTIL = ['avutil']
          conf.env.HAVE_SWSCALE = 1
          conf.env.STLIB_SWSCALE = ['swscale']
 -        conf.env.HAVE_SWRESAMPLE = 1
 -        conf.env.STLIB_SWRESAMPLE = ['swresample']
          conf.env.HAVE_POSTPROC = 1
          conf.env.STLIB_POSTPROC = ['postproc']
 -
 -        # This doesn't seem to be set up, and we need it otherwise resampling support
 -        # won't be included.  Hack upon a hack, obviously
 -        conf.env.append_value('CXXFLAGS', ['-DHAVE_SWRESAMPLE=1'])
 +        conf.env.HAVE_SWRESAMPLE = 1
 +        conv.env.STLIB_SWRESAMPLE = ['swresample']
  
      conf.check_cfg(package = 'sndfile', args = '--cflags --libs', uselib_store = 'SNDFILE', mandatory = True)
      conf.check_cfg(package = 'glib-2.0', args = '--cflags --libs', uselib_store = 'GLIB', mandatory = True)
                               define_name = 'HAVE_G_FORMAT_SIZE',
                               mandatory = False)
  
-     conf.check_cc(fragment = """
-                              extern "C" {
-                                #include <libavutil/avutil.h>
-                              }
-                              int main() { AVPixelFormat f; }
-                              """, msg = 'Checking for AVPixelFormat',
-                              uselib = 'AVUTIL',
-                              define_name = 'HAVE_AV_PIXEL_FORMAT',
-                              mandatory = False)
-     conf.check_cc(fragment = """
-                              extern "C" {
-                                #include <libavcodec/avcodec.h>
-                              }
-                              int main() { AVFrame* f; av_frame_get_best_effort_timestamp(f); }
-                              """, msg = 'Checking for av_frame_get_best_effort_timestamp',
-                              uselib = 'AVCODEC',
-                              define_name = 'HAVE_AV_FRAME_GET_BEST_EFFORT_TIMESTAMP',
-                              mandatory = False)
-     conf.check_cc(fragment = """
-                              extern "C" {
-                                #include <libavfilter/buffersrc.h>
-                              }
-                              int main() { } 
-                              """, msg = 'Checking for buffersrc.h',
-                              uselib = 'AVCODEC',
-                              define_name = 'HAVE_BUFFERSRC_H',
-                              mandatory = False)
      conf.find_program('msgfmt', var='MSGFMT')
      
      datadir = conf.env.DATADIR
@@@ -227,22 -197,21 +197,22 @@@ def build(bld)
      d = { 'PREFIX' : '${PREFIX' }
  
      obj = bld(features = 'subst')
 -    obj.source = 'dvdomatic.desktop.in'
 -    obj.target = 'dvdomatic.desktop'
 +    obj.source = 'dcpomatic.desktop.in'
 +    obj.target = 'dcpomatic.desktop'
      obj.dict = d
  
      obj = bld(features = 'subst')
 -    obj.source = 'dvdomatic_batch.desktop.in'
 -    obj.target = 'dvdomatic_batch.desktop'
 +    obj.source = 'dcpomatic_batch.desktop.in'
 +    obj.target = 'dcpomatic_batch.desktop'
      obj.dict = d
  
 -    bld.install_files('${PREFIX}/share/applications', ['dvdomatic.desktop', 'dvdomatic_batch.desktop'])
 +    bld.install_files('${PREFIX}/share/applications', ['dcpomatic.desktop', 'dcpomatic_batch.desktop'])
 +
      for r in ['22x22', '32x32', '48x48', '64x64', '128x128']:
 -        bld.install_files('${PREFIX}/share/icons/hicolor/%s/apps' % r, 'icons/%s/dvdomatic.png' % r)
 +        bld.install_files('${PREFIX}/share/icons/hicolor/%s/apps' % r, 'icons/%s/dcpomatic.png' % r)
  
      if not bld.env.TARGET_WINDOWS:
 -        bld.install_files('${PREFIX}/share/dvdomatic', 'icons/taskbar_icon.png')
 +        bld.install_files('${PREFIX}/share/dcpomatic', 'icons/taskbar_icon.png')
  
      bld.add_post_fun(post)
  
@@@ -264,8 -233,8 +234,8 @@@ def create_version_cc(version)
  
      try:
          text =  '#include "version.h"\n'
 -        text += 'char const * dvdomatic_git_commit = \"%s\";\n' % commit
 -        text += 'char const * dvdomatic_version = \"%s\";\n' % version
 +        text += 'char const * dcpomatic_git_commit = \"%s\";\n' % commit
 +        text += 'char const * dcpomatic_version = \"%s\";\n' % version
          print('Writing version information to src/lib/version.cc')
          o = open('src/lib/version.cc', 'w')
          o.write(text)