Merge master; fix crash on new film.
authorCarl Hetherington <cth@carlh.net>
Fri, 26 Apr 2013 22:41:02 +0000 (23:41 +0100)
committerCarl Hetherington <cth@carlh.net>
Fri, 26 Apr 2013 22:41:02 +0000 (23:41 +0100)
42 files changed:
1  2 
builds/control-12.04-32
builds/control-12.04-64
builds/control-12.10-32
builds/control-12.10-64
cscript
debian/rules
src/lib/ab_transcoder.cc
src/lib/ab_transcoder.h
src/lib/analyse_audio_job.cc
src/lib/audio_sink.h
src/lib/audio_source.cc
src/lib/audio_source.h
src/lib/encoder.cc
src/lib/encoder.h
src/lib/image.h
src/lib/matcher.cc
src/lib/player.cc
src/lib/player.h
src/lib/po/es_ES.po
src/lib/po/fr_FR.po
src/lib/po/it_IT.po
src/lib/po/sv_SE.po
src/lib/transcoder.cc
src/lib/transcoder.h
src/lib/util.cc
src/lib/util.h
src/lib/video_sink.h
src/lib/video_source.cc
src/lib/video_source.h
src/tools/dcpomatic_cli.cc
src/tools/po/es_ES.po
src/tools/po/fr_FR.po
src/tools/po/it_IT.po
src/tools/po/sv_SE.po
src/tools/servomatictest.cc
src/wx/film_viewer.cc
src/wx/film_viewer.h
src/wx/po/es_ES.po
src/wx/po/fr_FR.po
src/wx/po/it_IT.po
src/wx/po/sv_SE.po
test/test.cc

index d31a5e3844623f9343154bbd8a620fc24eef1e64,0f52d03ae4e984ba4297f2b5c0e765c92df10851..571beee510612921307009e578c4a7afe131b152
@@@ -10,6 -10,15 +10,15 @@@ Package: dcpomati
  Architecture: i386
  Depends: libc6 (>= 2.15), libwxgtk2.8-0 (>= 2.8.12.1), libssh-4 (>= 0.5.2), libboost-filesystem1.46.1 (>= 1.46.1), libboost-thread1.46.1 (>= 1.46.1), libsndfile1 (>= 1.0.25), libmagick++4 (>= 8:6.6.9.7), libxml++2.6-2 (>= 2.34.1)
  Description: Generator of Digital Cinema Packages (DCPs)
 -  DVD-o-matic generates Digital Cinema Packages (DCPs) from video and audio
 +  DCP-o-matic generates Digital Cinema Packages (DCPs) from video and audio
    files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant
    digital projectors.
+ Package: dvdomatic-dbg
+ Architecture: i386
+ Section: debug
+ Priority: extra
+ Depends: ${dvdomatic:Depends}, ${misc:Depends}
+ Description: debugging symbols for dvdomatic
+   This package contains the debugging symbols for dvdomatic.
index 5d41558b61f34dd9e208aa8b801c484d3131088e,fa4b4476e77773708487f52073177edf0df8851c..77fbd9e8f47e7f56cd86f877cf565a70b908bd40
@@@ -10,6 -10,15 +10,15 @@@ Package: dcpomati
  Architecture: amd64
  Depends: libc6 (>= 2.15), libwxgtk2.8-0 (>= 2.8.12.1), libssh-4 (>= 0.5.2), libboost-filesystem1.46.1 (>= 1.46.1), libboost-thread1.46.1 (>= 1.46.1), libsndfile1 (>= 1.0.25), libmagick++4 (>= 8:6.6.9.7), libxml++2.6-2 (>= 2.34.1)
  Description: Generator of Digital Cinema Packages (DCPs)
 -  DVD-o-matic generates Digital Cinema Packages (DCPs) from video and audio
 +  DCP-o-matic generates Digital Cinema Packages (DCPs) from video and audio
    files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant
    digital projectors.
+ Package: dvdomatic-dbg
+ Architecture: amd64
+ Section: debug
+ Priority: extra
+ Depends: ${dvdomatic:Depends}, ${misc:Depends}
+ Description: debugging symbols for dvdomatic
+   This package contains the debugging symbols for dvdomatic.
index 67933f2ed3e76b1743a29d1895901de758035d2b,0e5fc1f466ecfced9203d368b8343f0fc92b51fb..80b024e46a1e183017a2c5de6ddc6555c3ce1bdf
@@@ -10,6 -10,14 +10,14 @@@ Package: dcpomati
  Architecture: i386
  Depends: libc6 (>= 2.15), libwxgtk2.8-0 (>= 2.8.12.1), libssh-4 (>= 0.5.2), libboost-filesystem1.49.0 (>= 1.49.0), libboost-thread1.49.0 (>= 1.49.0), libsndfile1 (>= 1.0.25), libmagick++5 (>= 8:6.7.7.10), libxml++2.6-2 (>= 2.34.2)
  Description: Generator of Digital Cinema Packages (DCPs)
 -  DVD-o-matic generates Digital Cinema Packages (DCPs) from video and audio
 +  DCP-o-matic generates Digital Cinema Packages (DCPs) from video and audio
    files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant
    digital projectors.
+ Package: dvdomatic-dbg
+ Architecture: i386
+ Section: debug
+ Priority: extra
+ Depends: ${dvdomatic:Depends}, ${misc:Depends}
+ Description: debugging symbols for dvdomatic
+   This package contains the debugging symbols for dvdomatic.
index cddf80007628ac5d45f4a0291bcbf06190762436,24e16b4b5afe3c21d3abb531e2d431fef0a0943e..24e893c157e5e19340a4843aaac7a98b35e2c625
@@@ -10,6 -10,15 +10,15 @@@ Package: dcpomati
  Architecture: amd64
  Depends: libc6 (>= 2.15), libwxgtk2.8-0 (>= 2.8.12.1), libssh-4 (>= 0.5.2), libboost-filesystem1.49.0 (>= 1.49.0), libboost-thread1.49.0 (>= 1.49.0), libsndfile1 (>= 1.0.25), libmagick++5 (>= 8:6.7.7.10), libxml++2.6-2 (>= 2.34.2)
  Description: Generator of Digital Cinema Packages (DCPs)
 -  DVD-o-matic generates Digital Cinema Packages (DCPs) from video and audio
 +  DCP-o-matic generates Digital Cinema Packages (DCPs) from video and audio
    files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant
    digital projectors.
+ Package: dvdomatic-dbg
+ Architecture: amd64
+ Section: debug
+ Priority: extra
+ Depends: ${dvdomatic:Depends}, ${misc:Depends}
+ Description: debugging symbols for dvdomatic
+   This package contains the debugging symbols for dvdomatic.
diff --cc cscript
Simple merge
diff --cc debian/rules
index 7f7da5530eddbb8d128f2ba9035454482dbe7844,f2b2219beb7c56829e770256c1810a3a68db03a7..17594701970869bd061c1f082cfdeb531c5607b0
@@@ -20,5 -20,8 +20,8 @@@ override_dh_auto_build
        ./waf --nocache build
  
  override_dh_auto_install:
 -      ./waf --nocache install --destdir=debian/dvdomatic
 +      ./waf --nocache install --destdir=debian/dcpomatic
  
+ .PHONY: override_dh_strip
+ override_dh_strip:
+       dh_strip --dbg-package=dvdomatic-dbg
index c6ccfdc67e268492a9cae8eca5de06f53c7b3daf,d8f13dae4b7854afb0b1f4d97c70ab488f78ff2c..2e0d41e7de08f24c4bc83b6fb615cecf9cf9a0de
@@@ -46,30 -50,60 +47,40 @@@ using boost::dynamic_pointer_cast
   *  @param e Encoder to use.
   */
  
 -ABTranscoder::ABTranscoder (
 -      shared_ptr<Film> a, shared_ptr<Film> b, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
 +ABTranscoder::ABTranscoder (shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<Job> j)
        : _film_a (a)
        , _film_b (b)
 +      , _player_a (_film_a->player ())
 +      , _player_b (_film_b->player ())
        , _job (j)
 -      , _encoder (e)
 +      , _encoder (new Encoder (_film_a))
        , _combiner (new Combiner (a->log()))
  {
 -      _da = decoder_factory (_film_a, o);
 -      _db = decoder_factory (_film_b, o);
 -
 -      shared_ptr<AudioStream> st = _film_a->audio_stream();
 -      if (st) {
 -              _matcher.reset (new Matcher (_film_a->log(), st->sample_rate(), _film_a->source_frame_rate()));
 -      }
 -      _delay_line.reset (new DelayLine (_film_a->log(), _film_a->audio_delay() / 1000.0f));
 +      _matcher.reset (new Matcher (_film_a->log(), _film_a->audio_frame_rate(), _film_a->video_frame_rate()));
 +      _delay_line.reset (new DelayLine (_film_a->log(), _film_a->audio_delay() * _film_a->audio_frame_rate() / 1000));
        _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain()));
  
 -      int const sr = st ? st->sample_rate() : 0;
 +      _player_a->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3, _4));
 +      _player_b->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3, _4));
 +
+       int const trim_start = _film_a->trim_type() == Film::ENCODE ? _film_a->trim_start() : 0;
+       int const trim_end = _film_a->trim_type() == Film::ENCODE ? _film_a->trim_end() : 0;
+       _trimmer.reset (new Trimmer (
 -                              _film_a->log(), trim_start, trim_end, _film_a->length().get(),
 -                              sr, _film_a->source_frame_rate(), _film_a->dcp_frame_rate()
++                              _film_a->log(), trim_start, trim_end, _film_a->content_length(),
++                              _film_a->audio_frame_rate(), _film_a->video_frame_rate(), _film_a->dcp_frame_rate()
+                               ));
+       
 -      /* Set up the decoder to use the film's set streams */
 -      _da.video->set_subtitle_stream (_film_a->subtitle_stream ());
 -      _db.video->set_subtitle_stream (_film_a->subtitle_stream ());
 -      if (_film_a->audio_stream ()) {
 -              _da.audio->set_audio_stream (_film_a->audio_stream ());
 -      }
 -
 -      _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3, _4));
 -      _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3, _4));
        _combiner->connect_video (_delay_line);
 -      if (_matcher) {
 -              _delay_line->connect_video (_matcher);
 -              _matcher->connect_video (_trimmer);
 -      } else {
 -              _delay_line->connect_video (_trimmer);
 -      }
 +      _delay_line->connect_video (_matcher);
-       _matcher->connect_video (_encoder);
++      _matcher->connect_video (_trimmer);
+       _trimmer->connect_video (_encoder);
        
 -      _da.audio->connect_audio (_delay_line);
 -      if (_matcher) {
 -              _delay_line->connect_audio (_matcher);
 -              _matcher->connect_audio (_gain);
 -      } else {
 -              _delay_line->connect_audio (_gain);
 -      }
 +      _player_a->connect_audio (_delay_line);
 +      _delay_line->connect_audio (_matcher);
 +      _matcher->connect_audio (_gain);
-       _gain->connect_audio (_encoder);
+       _gain->connect_audio (_trimmer);
+       _trimmer->connect_audio (_encoder);
  }
  
  void
@@@ -91,16 -131,12 +102,11 @@@ ABTranscoder::go (
                        break;
                }
        }
-       if (_delay_line) {
-               _delay_line->process_end ();
-       }
-       if (_matcher) {
-               _matcher->process_end ();
-       }
-       if (_gain) {
-               _gain->process_end ();
-       }
+               
+       _delay_line->process_end ();
 -      if (_matcher) {
 -              _matcher->process_end ();
 -      }
++      _matcher->process_end ();
+       _gain->process_end ();
++      _trimmer->process_end ();
        _encoder->process_end ();
  }
                            
index b1b01d724f7b4818400122c7347aafe8d9f0bcad,4f1b14e48ef27bea3072510ae58088046c784ca0..1fef66b8870a3ae72de2d8a645e056d656239350
@@@ -35,7 -39,7 +35,8 @@@ class Matcher
  class DelayLine;
  class Gain;
  class Combiner;
 +class Player;
+ class Trimmer;
  
  /** @class ABTranscoder
   *  @brief A transcoder which uses one Film for the left half of the screen, and a different one
Simple merge
Simple merge
index 3dd3027ab4650bd23fd4ef4fc8fd958438e5a7f4,d77e89367b059de70606d953be86f9ca50dae6ca..32b3deccfa4a887384d7c834eb1d9d2bcf0c039b
  #include "audio_sink.h"
  
  using boost::shared_ptr;
 +using boost::weak_ptr;
  using boost::bind;
  
- process_audio_proxy (weak_ptr<AudioSink> sink, shared_ptr<AudioBuffers> audio)
 +static void
++process_audio_proxy (weak_ptr<AudioSink> sink, shared_ptr<const AudioBuffers> audio)
 +{
 +      shared_ptr<AudioSink> p = sink.lock ();
 +      if (p) {
 +              p->process_audio (audio);
 +      }
 +}
 +
  void
  AudioSource::connect_audio (shared_ptr<AudioSink> s)
  {
Simple merge
index f56440dd7c1026bfb97f564410bb8c36512beac3,cff9899acb6151cc19990f372c0a091c7d63968f..c1d1041ae539f9cdab1f087291eb2d8d7104abbb
@@@ -240,9 -231,9 +240,9 @@@ Encoder::frame_done (
  }
  
  void
- Encoder::process_video (shared_ptr<Image> image, bool same, shared_ptr<Subtitle> sub)
 -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)
  {
 -      FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate());
 +      FrameRateConversion frc (_film->video_frame_rate(), _film->dcp_frame_rate());
        
        if (frc.skip && (_video_frames_in % 2)) {
                ++_video_frames_in;
Simple merge
diff --cc src/lib/image.h
Simple merge
index edbb084de7ebd6e4a872cdaf3218a00ab5199d03,9924c003ae5b81298ef27653e5ba015d7d312307..c56a563015b6eb617bf38535ff81be81c7cffeb0
@@@ -41,7 -41,7 +41,7 @@@ Matcher::Matcher (shared_ptr<Log> log, 
  }
  
  void
- Matcher::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
 -Matcher::process_video (boost::shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
++Matcher::process_video (shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
  {
        _pixel_format = image->pixel_format ();
        _size = image->size ();
@@@ -90,7 -90,7 +90,7 @@@
  }
  
  void
- Matcher::process_audio (shared_ptr<AudioBuffers> b, double t)
 -Matcher::process_audio (boost::shared_ptr<const AudioBuffers> b, double t)
++Matcher::process_audio (shared_ptr<const AudioBuffers> b, double t)
  {
        _channels = b->channels ();
  
index 7c75597eac352ea77179c9d28625ef35e7cff7de,0000000000000000000000000000000000000000..09f1f55a32cdc3a909448d37642278d59b5961ea
mode 100644,000000..100644
--- /dev/null
@@@ -1,319 -1,0 +1,323 @@@
- Player::process_video (shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s, double t)
 +/*
 +    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"
 +
 +using std::list;
 +using std::cout;
 +using std::vector;
 +using boost::shared_ptr;
 +using boost::weak_ptr;
 +using boost::dynamic_pointer_cast;
 +
 +Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
 +      : _film (f)
 +      , _playlist (p)
 +      , _video (true)
 +      , _audio (true)
 +      , _subtitles (true)
 +      , _have_valid_decoders (false)
 +{
 +      _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_decoders) {
 +              setup_decoders ();
 +              _have_valid_decoders = true;
 +      }
 +      
 +      bool done = true;
 +      
 +      if (_video && _video_decoder < _video_decoders.size ()) {
 +
 +              /* Run video decoder; this may also produce audio */
 +              
 +              if (_video_decoders[_video_decoder]->pass ()) {
 +                      _video_decoder++;
 +              }
 +              
 +              if (_video_decoder < _video_decoders.size ()) {
 +                      done = false;
 +              }
 +              
 +      }
 +
 +      if (!_video && _audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG && _sequential_audio_decoder < _audio_decoders.size ()) {
 +
 +              /* We're not producing video, so we may need to run FFmpeg content to get the audio */
 +              
 +              if (_audio_decoders[_sequential_audio_decoder]->pass ()) {
 +                      _sequential_audio_decoder++;
 +              }
 +              
 +              if (_sequential_audio_decoder < _audio_decoders.size ()) {
 +                      done = false;
 +              }
 +              
 +      }
 +
 +      if (_audio && _playlist->audio_from() == Playlist::AUDIO_SNDFILE) {
 +              
 +              /* We're getting audio from SndfileContent */
 +              
 +              for (vector<shared_ptr<AudioDecoder> >::iterator i = _audio_decoders.begin(); i != _audio_decoders.end(); ++i) {
 +                      if (!(*i)->pass ()) {
 +                              done = false;
 +                      }
 +              }
 +
 +              Audio (_audio_buffers, _audio_time.get());
 +              _audio_buffers.reset ();
 +              _audio_time = boost::none;
 +      }
 +
 +      return done;
 +}
 +
 +void
 +Player::set_progress (shared_ptr<Job> job)
 +{
 +      /* Assume progress can be divined from how far through the video we are */
 +
 +      if (_video_decoder >= _video_decoders.size() || !_playlist->video_length()) {
 +              return;
 +      }
 +
 +      job->set_progress ((_video_start[_video_decoder] + _video_decoders[_video_decoder]->video_frame()) / _playlist->video_length ());
 +}
 +
 +void
- Player::process_audio (weak_ptr<const AudioContent> c, shared_ptr<AudioBuffers> b, double t)
++Player::process_video (shared_ptr<const Image> i, bool same, shared_ptr<Subtitle> s, double t)
 +{
 +      Video (i, same, s, _video_start[_video_decoder] + t);
 +}
 +
 +void
++Player::process_audio (weak_ptr<const AudioContent> c, shared_ptr<const AudioBuffers> b, double t)
 +{
 +      AudioMapping mapping = _film->audio_mapping ();
 +      if (!_audio_buffers) {
 +              _audio_buffers.reset (new AudioBuffers (mapping.dcp_channels(), b->frames ()));
 +              _audio_buffers->make_silent ();
 +              _audio_time = t;
 +              if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) {
 +                      _audio_time = _audio_time.get() + _audio_start[_sequential_audio_decoder];
 +              }
 +      }
 +
 +      for (int i = 0; i < b->channels(); ++i) {
 +              list<libdcp::Channel> dcp = mapping.content_to_dcp (AudioMapping::Channel (c, i));
 +              for (list<libdcp::Channel>::iterator j = dcp.begin(); j != dcp.end(); ++j) {
 +                      _audio_buffers->accumulate (b, i, static_cast<int> (*j));
 +              }
 +      }
 +
 +      if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) {
 +              /* We can just emit this audio now as it will all be here */
 +              Audio (_audio_buffers, t);
 +              _audio_buffers.reset ();
 +              _audio_time = boost::none;
 +      }
 +}
 +
 +/** @return true on error */
 +bool
 +Player::seek (double t)
 +{
 +      if (!_have_valid_decoders) {
 +              setup_decoders ();
 +              _have_valid_decoders = true;
 +      }
 +
++      if (_video_decoders.empty ()) {
++              return true;
++      }
++
 +      /* Find the decoder that contains this position */
 +      _video_decoder = 0;
 +      while (1) {
 +              ++_video_decoder;
 +              if (_video_decoder >= _video_decoders.size () || t < _video_start[_video_decoder]) {
 +                      --_video_decoder;
 +                      t -= _video_start[_video_decoder];
 +                      break;
 +              }
 +      }
 +
 +      if (_video_decoder < _video_decoders.size()) {
 +              _video_decoders[_video_decoder]->seek (t);
 +      } else {
 +              return true;
 +      }
 +
 +      /* XXX: don't seek audio because we don't need to... */
 +
 +      return false;
 +}
 +
 +
 +void
 +Player::seek_back ()
 +{
 +      /* XXX */
 +}
 +
 +void
 +Player::seek_forward ()
 +{
 +      /* XXX */
 +}
 +
 +
 +void
 +Player::setup_decoders ()
 +{
 +      _video_decoders.clear ();
 +      _video_decoder = 0;
 +      _audio_decoders.clear ();
 +      _sequential_audio_decoder = 0;
 +
 +      _video_start.clear();
 +      _audio_start.clear();
 +
 +      double video_so_far = 0;
 +      double audio_so_far = 0;
 +      
 +      list<shared_ptr<const VideoContent> > vc = _playlist->video ();
 +      for (list<shared_ptr<const VideoContent> >::iterator i = vc.begin(); i != vc.end(); ++i) {
 +              
 +              shared_ptr<const VideoContent> video_content;
 +              shared_ptr<const AudioContent> audio_content;
 +              shared_ptr<VideoDecoder> video_decoder;
 +              shared_ptr<AudioDecoder> audio_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 && _playlist->audio_from() == Playlist::AUDIO_FFMPEG,
 +                                      _subtitles
 +                                      )
 +                              );
 +                      
 +                      video_content = fc;
 +                      audio_content = fc;
 +                      video_decoder = fd;
 +                      audio_decoder = fd;
 +              }
 +              
 +              shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i);
 +              if (ic) {
 +                      video_content = ic;
 +                      video_decoder.reset (new ImageMagickDecoder (_film, ic));
 +              }
 +              
 +              video_decoder->connect_video (shared_from_this ());
 +              _video_decoders.push_back (video_decoder);
 +              _video_start.push_back (video_so_far);
 +              video_so_far += video_content->video_length() / video_content->video_frame_rate();
 +
 +              if (audio_decoder && _playlist->audio_from() == Playlist::AUDIO_FFMPEG) {
 +                      audio_decoder->Audio.connect (bind (&Player::process_audio, this, audio_content, _1, _2));
 +                      _audio_decoders.push_back (audio_decoder);
 +                      _audio_start.push_back (audio_so_far);
 +                      audio_so_far += double(audio_content->audio_length()) / audio_content->audio_frame_rate();
 +              }
 +      }
 +      
 +      _video_decoder = 0;
 +      _sequential_audio_decoder = 0;
 +
 +      if (_playlist->audio_from() == Playlist::AUDIO_SNDFILE) {
 +              
 +              list<shared_ptr<const AudioContent> > ac = _playlist->audio ();
 +              for (list<shared_ptr<const AudioContent> >::iterator i = ac.begin(); i != ac.end(); ++i) {
 +                      
 +                      shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
 +                      assert (sc);
 +                      
 +                      shared_ptr<AudioDecoder> d (new SndfileDecoder (_film, sc));
 +                      d->Audio.connect (bind (&Player::process_audio, this, sc, _1, _2));
 +                      _audio_decoders.push_back (d);
 +                      _audio_start.push_back (audio_so_far);
 +              }
 +      }
 +}
 +
 +double
 +Player::last_video_time () const
 +{
 +      return _video_start[_video_decoder] + _video_decoders[_video_decoder]->last_content_time ();
 +}
 +
 +void
 +Player::content_changed (weak_ptr<Content> w, int p)
 +{
 +      shared_ptr<Content> c = w.lock ();
 +      if (!c) {
 +              return;
 +      }
 +
 +      if (p == VideoContentProperty::VIDEO_LENGTH) {
 +              if (dynamic_pointer_cast<FFmpegContent> (c)) {
 +                      /* FFmpeg content length changes are serious; we need new decoders */
 +                      _have_valid_decoders = false;
 +              }
 +      }
 +}
 +
 +void
 +Player::playlist_changed ()
 +{
 +      _have_valid_decoders = false;
 +}
index 2069064d797d295c1a8dbaae09d2ae6061bbec5e,0000000000000000000000000000000000000000..20b83bfdb7c2468e702409fbc500286937553ec5
mode 100644,000000..100644
--- /dev/null
@@@ -1,92 -1,0 +1,92 @@@
-       void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s, double);
-       void process_audio (boost::weak_ptr<const AudioContent>, boost::shared_ptr<AudioBuffers>, double);
 +/*
 +    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_PLAYER_H
 +#define DCPOMATIC_PLAYER_H
 +
 +#include <list>
 +#include <boost/shared_ptr.hpp>
 +#include <boost/enable_shared_from_this.hpp>
 +#include "video_source.h"
 +#include "audio_source.h"
 +#include "video_sink.h"
 +#include "audio_sink.h"
 +
 +class VideoDecoder;
 +class AudioDecoder;
 +class Job;
 +class Film;
 +class Playlist;
 +class AudioContent;
 +
 +/** @class Player
 + *  @brief A class which can `play' a Playlist; emitting its audio and video.
 + */
 + 
 +class Player : public TimedVideoSource, public TimedAudioSource, public TimedVideoSink, public boost::enable_shared_from_this<Player>
 +{
 +public:
 +      Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
 +
 +      void disable_video ();
 +      void disable_audio ();
 +      void disable_subtitles ();
 +
 +      bool pass ();
 +      void set_progress (boost::shared_ptr<Job>);
 +      bool seek (double);
 +      void seek_back ();
 +      void seek_forward ();
 +
 +      double last_video_time () const;
 +
 +private:
++      void process_video (boost::shared_ptr<const Image> i, bool same, boost::shared_ptr<Subtitle> s, double);
++      void process_audio (boost::weak_ptr<const AudioContent>, boost::shared_ptr<const AudioBuffers>, double);
 +      void setup_decoders ();
 +      void playlist_changed ();
 +      void content_changed (boost::weak_ptr<Content>, int);
 +
 +      boost::shared_ptr<const Film> _film;
 +      boost::shared_ptr<const Playlist> _playlist;
 +      
 +      bool _video;
 +      bool _audio;
 +      bool _subtitles;
 +
 +      /** Our decoders are ready to go; if this is false the decoders must be (re-)created before they are used */
 +      bool _have_valid_decoders;
 +      /** Video decoders in order of presentation */
 +      std::vector<boost::shared_ptr<VideoDecoder> > _video_decoders;
 +      /** Start positions of each video decoder in seconds*/
 +      std::vector<double> _video_start;
 +        /** Index of current video decoder */
 +      size_t _video_decoder;
 +        /** Audio decoders in order of presentation (if they are from FFmpeg) */
 +      std::vector<boost::shared_ptr<AudioDecoder> > _audio_decoders;
 +      /** Start positions of each audio decoder (if they are from FFmpeg) in seconds */
 +      std::vector<double> _audio_start;
 +      /** Current audio decoder index if we are running them sequentially; otherwise undefined */
 +      size_t _sequential_audio_decoder;
 +
 +      boost::shared_ptr<AudioBuffers> _audio_buffers;
 +      boost::optional<double> _audio_time;
 +};
 +
 +#endif
index 5c8d642e3365cc5908971181a6b72b98831e2ad0,1608f3b0c8bc0efb661c6ce5aa459a5211a31f55..7d2f8511e002314ab866778bcdcd18ccffd2b1a9
@@@ -5,9 -5,9 +5,9 @@@
  #
  msgid ""
  msgstr ""
 -"Project-Id-Version: LIBDVDOMATIC\n"
 +"Project-Id-Version: LIBDCPOMATIC\n"
  "Report-Msgid-Bugs-To: \n"
- "POT-Creation-Date: 2013-04-09 11:14+0100\n"
+ "POT-Creation-Date: 2013-04-22 15:06+0100\n"
  "PO-Revision-Date: 2013-04-02 19:10-0500\n"
  "Last-Translator: Manuel AC <manuel.acevedo@civantos.>\n"
  "Language-Team: Manuel AC <manuel.acevedo@civantos.com>\n"
@@@ -247,13 -247,13 +247,13 @@@ msgstr "Horizontal deblocking filter
  msgid "Horizontal deblocking filter A"
  msgstr "Horizontal deblocking filter A"
  
- #: src/lib/job.cc:92 src/lib/job.cc:101
+ #: src/lib/job.cc:96 src/lib/job.cc:105
  msgid ""
  "It is not known what caused this error.  The best idea is to report the "
 -"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
 +"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
  msgstr ""
  "Error desconocido. La mejor idea es informar del problema a la lista de "
 -"correo de DVD-O-matic (dvdomatic@carlh.net)"
 +"correo de DCP-o-matic (dcpomatic@carlh.net)"
  
  #: src/lib/filter.cc:82
  msgid "Kernel deinterlacer"
index af6890d97b1caab90ebd7245940620b35a31046d,d1123d84b755f08b7ef7b67f51b5faa4143ac0ab..7f3da788b2fc99389540b90b14fe107a380ba08f
@@@ -5,9 -5,9 +5,9 @@@
  #
  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-04-09 11:14+0100\n"
+ "POT-Creation-Date: 2013-04-22 15:06+0100\n"
  "PO-Revision-Date: 2013-03-20 00:39+0100\n"
  "Last-Translator: FreeDCP.net <freedcp.net@gmail.com>\n"
  "Language-Team: \n"
@@@ -245,13 -245,13 +245,13 @@@ msgstr "Filtre dé-bloc horizontal
  msgid "Horizontal deblocking filter A"
  msgstr "Filtre dé-bloc horizontal"
  
- #: src/lib/job.cc:92 src/lib/job.cc:101
+ #: src/lib/job.cc:96 src/lib/job.cc:105
  msgid ""
  "It is not known what caused this error.  The best idea is to report the "
 -"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
 +"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
  msgstr ""
 -"Erreur indéterminée. Merci de rapporter le problème à la liste DVD-o-matic "
 -"(dvdomatic@carlh.net)"
 +"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"
index c1ca26ea3b06db351940152d7e145e1bf8ad1db1,5f9e9e8627f0bd63f9d97539d97bc92a08254881..1d7f57536f1a8143de1555a6a9b6763d7fd59ad7
@@@ -245,13 -245,13 +245,13 @@@ msgstr "Filtro sblocco orizzontale
  msgid "Horizontal deblocking filter A"
  msgstr "Filtro A sblocco orizzontale"
  
- #: src/lib/job.cc:92 src/lib/job.cc:101
+ #: src/lib/job.cc:96 src/lib/job.cc:105
  msgid ""
  "It is not known what caused this error.  The best idea is to report the "
 -"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
 +"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
  msgstr ""
  "Non sappiamo cosa ha causato questo errore. La cosa migliore è inviare un "
 -"report del problema alla mailing list di DVD-o-matic (dvdomatic@carlh.net)"
 +"report del problema alla mailing list di DCP-o-matic (dcpomatic@carlh.net)"
  
  #: src/lib/filter.cc:82
  msgid "Kernel deinterlacer"
index f89874e35e9b952399a227c4f8999a2bbec2d025,11aeff9871e61c744ea72aef84536c58fc0c0e59..ff86e23af926d175446fbe5258a912ddbaf6cbfd
@@@ -5,9 -5,9 +5,9 @@@
  #
  msgid ""
  msgstr ""
 -"Project-Id-Version: DVD-o-matic\n"
 +"Project-Id-Version: DCP-o-matic\n"
  "Report-Msgid-Bugs-To: \n"
- "POT-Creation-Date: 2013-04-09 11:14+0100\n"
+ "POT-Creation-Date: 2013-04-22 15:06+0100\n"
  "PO-Revision-Date: 2013-04-10 15:35+0100\n"
  "Last-Translator: Adam Klotblixt <adam.klotblixt@gmail.com>\n"
  "Language-Team: \n"
@@@ -244,13 -245,13 +245,13 @@@ msgstr "Filter för horisontal kantighe
  msgid "Horizontal deblocking filter A"
  msgstr "Filter för horisontal kantighetsutjämning A"
  
- #: src/lib/job.cc:92 src/lib/job.cc:101
+ #: src/lib/job.cc:96 src/lib/job.cc:105
  msgid ""
  "It is not known what caused this error.  The best idea is to report the "
 -"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
 +"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
  msgstr ""
  "Det är inte känt vad som orsakade detta fel. Bästa sättet att rapportera "
 -"problemet är till DVD-o-matics mejl-lista (dvdomatic@carlh.net)"
 +"problemet är till DCP-o-matics mejl-lista (dcpomatic@carlh.net)"
  
  #: src/lib/filter.cc:82
  msgid "Kernel deinterlacer"
index ea3f27ad8f94f10175e1b6e81237b5e1d6059c12,faafcaf8b593821fd6b5600f82f48b758192eeba..2e33931bd50872cc5edbb16c274af19e336a5bb1
@@@ -34,7 -35,8 +34,8 @@@
  #include "gain.h"
  #include "video_decoder.h"
  #include "audio_decoder.h"
 +#include "player.h"
+ #include "trimmer.h"
  
  using std::string;
  using boost::shared_ptr;
@@@ -45,42 -47,88 +46,53 @@@ using boost::dynamic_pointer_cast
   *  @param j Job that we are running under, or 0.
   *  @param e Encoder to use.
   */
 -Transcoder::Transcoder (shared_ptr<Film> f, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
 +Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<Job> j)
        : _job (j)
 -      , _encoder (e)
 -      , _decoders (decoder_factory (f, o))
 +      , _player (f->player ())
 +      , _encoder (new Encoder (f))
  {
 -      assert (_encoder);
 -
 -      shared_ptr<AudioStream> st = f->audio_stream();
 -      if (st) {
 -              _matcher.reset (new Matcher (f->log(), st->sample_rate(), f->source_frame_rate()));
 -      }
 -      _delay_line.reset (new DelayLine (f->log(), f->audio_delay() / 1000.0f));
 +      _matcher.reset (new Matcher (f->log(), f->audio_frame_rate(), f->video_frame_rate()));
 +      _delay_line.reset (new DelayLine (f->log(), f->audio_delay() * f->audio_frame_rate() / 1000));
        _gain.reset (new Gain (f->log(), f->audio_gain()));
  
 -      int const sr = st ? st->sample_rate() : 0;
+       int const trim_start = f->trim_type() == Film::ENCODE ? f->trim_start() : 0;
+       int const trim_end = f->trim_type() == Film::ENCODE ? f->trim_end() : 0;
+       _trimmer.reset (new Trimmer (
 -                              f->log(), trim_start, trim_end, f->length().get_value_or(0),
 -                              sr, f->source_frame_rate(), f->dcp_frame_rate()
++                              f->log(), trim_start, trim_end, f->content_length(),
++                              f->audio_frame_rate(), f->video_frame_rate(), f->dcp_frame_rate()
+                               ));
 -
 -      /* Set up the decoder to use the film's set streams */
 -      _decoders.video->set_subtitle_stream (f->subtitle_stream ());
 -      if (f->audio_stream ()) {
 -          _decoders.audio->set_audio_stream (f->audio_stream ());
++      
 +      if (!f->with_subtitles ()) {
 +              _player->disable_subtitles ();
        }
  
 -      _decoders.video->connect_video (_delay_line);
 -      if (_matcher) {
 -              _delay_line->connect_video (_matcher);
 -              _matcher->connect_video (_trimmer);
 -      } else {
 -              _delay_line->connect_video (_trimmer);
 -      }
 +      _player->connect_video (_delay_line);
 +      _delay_line->connect_video (_matcher);
-       _matcher->connect_video (_encoder);
++      _matcher->connect_video (_trimmer);
+       _trimmer->connect_video (_encoder);
        
 -      _decoders.audio->connect_audio (_delay_line);
 -      if (_matcher) {
 -              _delay_line->connect_audio (_matcher);
 -              _matcher->connect_audio (_gain);
 -      } else {
 -              _delay_line->connect_audio (_gain);
 -      }
 +      _player->connect_audio (_delay_line);
 +      _delay_line->connect_audio (_matcher);
 +      _matcher->connect_audio (_gain);
-       _gain->connect_audio (_encoder);
+       _gain->connect_audio (_trimmer);
+       _trimmer->connect_audio (_encoder);
  }
  
 -/** Run the decoder, passing its output to the encoder, until the decoder
 - *  has no more data to present.
 - */
  void
  Transcoder::go ()
  {
        _encoder->process_begin ();
 -
 -      bool done[2] = { false, false };
 -      
        while (1) {
 -              if (!done[0]) {
 -                      done[0] = _decoders.video->pass ();
 -                      if (_job) {
 -                              _decoders.video->set_progress (_job);
 -                      }
 -              }
 -              
 -              if (!done[1] && _decoders.audio && dynamic_pointer_cast<Decoder> (_decoders.audio) != dynamic_pointer_cast<Decoder> (_decoders.video)) {
 -                      done[1] = _decoders.audio->pass ();
 -              } else {
 -                      done[1] = true;
 -              }
 -              
 -              if (done[0] && done[1]) {
 +              if (_player->pass ()) {
                        break;
                }
 +              _player->set_progress (_job);
        }
 -      
 +
        _delay_line->process_end ();
-       _matcher->process_end ();
+       if (_matcher) {
+               _matcher->process_end ();
+       }
        _gain->process_end ();
        _encoder->process_end ();
  }
index ecc8ebf629a2b245a4c9b0ae22862aebed3fe96b,f5b8ae6e329d3892735bf5d5a4d36f1615c44fc5..97ecaabfc2a54b9ff48ae7dd8d7640da59fd489c
@@@ -29,10 -32,13 +29,11 @@@ class Encoder
  class Matcher;
  class VideoFilter;
  class Gain;
 -class VideoDecoder;
 -class AudioDecoder;
  class DelayLine;
 +class Player;
+ class Trimmer;
  
  /** @class Transcoder
 - *  @brief A class which takes a Film and some Options, then uses those to transcode the film.
   *
   *  A decoder is selected according to the content type, and the encoder can be specified
   *  as a parameter to the constructor.
diff --cc src/lib/util.cc
index 56932720c24a1c1f777a21cd11f08641e2918368,859aa6de7ddda8efa646ee695247023d40169a7a..ec1fd47bd7f03bdca133d791b6201d824f252cab
@@@ -63,29 -63,24 +63,30 @@@ extern "C" 
  
  #include "i18n.h"
  
 -using std::cout;
  using std::string;
  using std::stringstream;
 -using std::list;
 +using std::setfill;
  using std::ostream;
 +using std::endl;
  using std::vector;
 +using std::hex;
 +using std::setw;
  using std::ifstream;
 -using std::istream;
 +using std::ios;
  using std::min;
  using std::max;
 +using std::list;
  using std::multimap;
 +using std::istream;
 +using std::numeric_limits;
  using std::pair;
  using boost::shared_ptr;
 +using boost::thread;
  using boost::lexical_cast;
+ using boost::optional;
  using libdcp::Size;
  
- thread::id ui_thread;
boost::thread::id ui_thread;
  
  /** Convert some number of seconds to a string representation
   *  in hours, minutes and seconds.
@@@ -366,16 -361,16 +367,16 @@@ md5_digest (void const * data, int size
   *  @return MD5 digest of file's contents.
   */
  string
 -md5_digest (string file)
 +md5_digest (boost::filesystem::path file)
  {
-       ifstream f (file.string().c_str(), ios::binary);
 -      ifstream f (file.c_str(), std::ios::binary);
++      ifstream f (file.string().c_str(), std::ios::binary);
        if (!f.good ()) {
 -              throw OpenFileError (file);
 +              throw OpenFileError (file.string());
        }
        
-       f.seekg (0, ios::end);
+       f.seekg (0, std::ios::end);
        int bytes = f.tellg ();
-       f.seekg (0, ios::beg);
+       f.seekg (0, std::ios::beg);
  
        int const buffer_size = 64 * 1024;
        char buffer[buffer_size];
@@@ -869,21 -889,6 +885,21 @@@ AudioBuffers::move (int from, int to, i
        }
  }
  
- AudioBuffers::accumulate (shared_ptr<AudioBuffers> from, int from_channel, int to_channel)
 +/** Add data from from `from', `from_channel' to our channel `to_channel' */
 +void
++AudioBuffers::accumulate (shared_ptr<const AudioBuffers> from, int from_channel, int to_channel)
 +{
 +      int const N = frames ();
 +      assert (from->frames() == N);
 +
 +      float* s = from->data (from_channel);
 +      float* d = _data[to_channel];
 +
 +      for (int i = 0; i < N; ++i) {
 +              *d++ += *s++;
 +      }
 +}
 +
  /** Trip an assert if the caller is not in the UI thread */
  void
  ensure_ui_thread ()
diff --cc src/lib/util.h
index 02cc742aa52f62a8fac1a467d1c7a44b310f8625,99670110edec6d4d47eaf6c4709c60d3be3e5f8c..0edfe2076b3bd8ce80f6077df102d1b3d2564f17
@@@ -182,7 -265,6 +183,7 @@@ public
  
        void copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset);
        void move (int from, int to, int frames);
-       void accumulate (boost::shared_ptr<AudioBuffers>, int, int);
++      void accumulate (boost::shared_ptr<const AudioBuffers>, int, int);
  
  private:
        /** Number of channels */
Simple merge
index ccb76f02018a45268ed74d22d1d3b344b53cfea5,539243402e0824f7551940fe0cb12d2313d77acf..2de4db68d75657a55a2a05f49a0a86914781f908
  #include "video_sink.h"
  
  using boost::shared_ptr;
 +using boost::weak_ptr;
  using boost::bind;
  
- process_video_proxy (weak_ptr<VideoSink> sink, shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s)
 +static void
++process_video_proxy (weak_ptr<VideoSink> sink, shared_ptr<const Image> i, bool same, shared_ptr<Subtitle> s)
 +{
 +      shared_ptr<VideoSink> p = sink.lock ();
 +      if (p) {
 +              p->process_video (i, same, s);
 +      }
 +}
 +
  void
  VideoSource::connect_video (shared_ptr<VideoSink> s)
  {
Simple merge
index e2e1874c4eb21461b8af9153b1dc84bddadbd33a,0000000000000000000000000000000000000000..86c3cf4b14881a3268a96758d9716242af373362
mode 100644,000000..100644
--- /dev/null
@@@ -1,217 -1,0 +1,217 @@@
-       int log_level = 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.
 +
 +*/
 +
 +#include <iostream>
 +#include <iomanip>
 +#include <getopt.h>
 +#include <libdcp/test_mode.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"
 +           << "  -t, --test         run in test mode (repeatable UUID generation, timestamps etc.)\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 test_mode = false;
 +      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'},
 +                      { "test", no_argument, 0, 't'},
 +                      { "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, "vhdtnrl:", 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 't':
 +                      test_mode = true;
 +                      break;
 +              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";
 +
 +      if (test_mode) {
 +              libdcp::enable_test_mode ();
 +              cout << dependency_version_summary() << "\n";
 +      }
 +
 +      shared_ptr<Film> film;
 +      try {
 +              film.reset (new Film (film_dir, true));
 +      } 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 << "Test mode: " << (test_mode ? "yes" : "no") << "\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;
 +}
 +
 +        
index d35f104c6d2d79c683796eacc632aa0226810b5f,1739f97cd57fea0126924ed728ebba815994611a..346aa2b39d1ea2137cba34740f9980c82698e01e
@@@ -5,9 -5,9 +5,9 @@@
  #
  msgid ""
  msgstr ""
 -"Project-Id-Version: DVDOMATIC\n"
 +"Project-Id-Version: DCPOMATIC\n"
  "Report-Msgid-Bugs-To: \n"
- "POT-Creation-Date: 2013-04-09 11:14+0100\n"
+ "POT-Creation-Date: 2013-04-22 15:06+0100\n"
  "PO-Revision-Date: 2013-03-23 21:08-0500\n"
  "Last-Translator: Manuel AC <manuel.acevedo@civantos.>\n"
  "Language-Team: Manuel AC <manuel.acevedo@civantos.com>\n"
index ef2246992ceee39f118ebba1a20f2f1e50f8fc32,c1447f7e669c60f29a63248adab740e2d0e32cde..8ce305ccf8b66073c27892d6d64ff5da41001be4
@@@ -5,9 -5,9 +5,9 @@@
  #
  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-04-09 11:14+0100\n"
+ "POT-Creation-Date: 2013-04-22 15:06+0100\n"
  "PO-Revision-Date: 2013-03-13 22:33+0100\n"
  "Last-Translator: \n"
  "Language-Team: \n"
Simple merge
index 4765c2d98f908c6b8590a1d71b0e47fe12a2e924,8ae68853f0eab39293fbe8c1b80336cb5b7fd262..69706d6479563932ca4cf325de84f006cbe85de5
@@@ -5,9 -5,9 +5,9 @@@
  #
  msgid ""
  msgstr ""
 -"Project-Id-Version: DVD-o-matic\n"
 +"Project-Id-Version: DCP-o-matic\n"
  "Report-Msgid-Bugs-To: \n"
- "POT-Creation-Date: 2013-04-09 11:14+0100\n"
+ "POT-Creation-Date: 2013-04-22 15:06+0100\n"
  "PO-Revision-Date: 2013-04-09 10:12+0100\n"
  "Last-Translator: Adam Klotblixt <adam.klotblixt@gmail.com>\n"
  "Language-Team: \n"
Simple merge
index e742a3e41b7ce9757f476fcd95abb1e4e7ccc97b,4f2985a061db3677ebfb6d0ed5c89bf2fd25c9f8..e9a1a574be01477b884e34a5eeb05e30684edbcc
@@@ -300,7 -308,7 +300,7 @@@ FilmViewer::raw_to_display (
                return;
        }
  
-       shared_ptr<Image> input = _raw_frame;
 -      boost::shared_ptr<const Image> input = _raw_frame;
++      shared_ptr<const Image> input = _raw_frame;
  
        pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
        if (!s.second.empty ()) {
index 814a095af374753ac17a596651e9397211b0b5e0,ed5874fbcc6f945d6285db27bbe74801998fb893..02d862ca0c42472dd8cefb6de594b116725e6a68
@@@ -91,9 -71,10 +91,9 @@@ private
        wxToggleButton* _play_button;
        wxTimer _timer;
  
-       boost::shared_ptr<Image> _raw_frame;
 -      Decoders _decoders;
+       boost::shared_ptr<const Image> _raw_frame;
        boost::shared_ptr<Subtitle> _raw_sub;
-       boost::shared_ptr<Image> _display_frame;
+       boost::shared_ptr<const Image> _display_frame;
        /* The x offset at which we display the actual film content; this corresponds
           to the film's padding converted to our coordinates.
        */
index abb6b780f17b42fd9547d2cc179b35ce1c66dbb5,56c0856bdd20f4b7245470ce3be9d32e86864424..a193325e6c09ad55d08e2602b90ff4a875d114a2
@@@ -5,9 -5,9 +5,9 @@@
  #
  msgid ""
  msgstr ""
 -"Project-Id-Version: libdvdomatic-wx\n"
 +"Project-Id-Version: libdcpomatic-wx\n"
  "Report-Msgid-Bugs-To: \n"
- "POT-Creation-Date: 2013-04-09 11:14+0100\n"
+ "POT-Creation-Date: 2013-04-22 15:06+0100\n"
  "PO-Revision-Date: 2013-04-02 19:08-0500\n"
  "Last-Translator: Manuel AC <manuel.acevedo@civantos.>\n"
  "Language-Team: Manuel AC <manuel.acevedo@civantos.com>\n"
@@@ -22,10 -22,10 +22,10 @@@ msgid "%
  msgstr "%"
  
  #: src/wx/config_dialog.cc:61
 -msgid "(restart DVD-o-matic to see language changes)"
 +msgid "(restart DCP-o-matic to see language changes)"
  msgstr ""
  
- #: src/wx/film_editor.cc:1269
+ #: src/wx/film_editor.cc:1276
  msgid "1 channel"
  msgstr "1 canal"
  
index 2aac7114cb3ece6297a2365fdd9d0296ec7931f0,c7ef31f5ab7fd05fd8579a5ac19e0e0ac24d6441..36ae4a9252727678ed8e53a57c20177d451764c1
@@@ -5,9 -5,9 +5,9 @@@
  #
  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-04-09 11:14+0100\n"
+ "POT-Creation-Date: 2013-04-22 15:06+0100\n"
  "PO-Revision-Date: 2013-03-20 00:34+0100\n"
  "Last-Translator: FreeDCP.net <freedcp.net@gmail.com>\n"
  "Language-Team: \n"
@@@ -21,10 -21,10 +21,10 @@@ msgid "%
  msgstr "%"
  
  #: src/wx/config_dialog.cc:61
 -msgid "(restart DVD-o-matic to see language changes)"
 +msgid "(restart DCP-o-matic to see language changes)"
  msgstr ""
  
- #: src/wx/film_editor.cc:1269
+ #: src/wx/film_editor.cc:1276
  msgid "1 channel"
  msgstr "1 canal"
  
index 7b06495e83959caddcc26491fc06aa905c72af7f,c730a7ff7c2256528588a48eef61c7717b2e9a0f..f53c40b977f8938fb4105c3dc4afeb382a80e4ba
@@@ -22,10 -22,10 +22,10 @@@ msgid "%
  msgstr "%"
  
  #: src/wx/config_dialog.cc:61
 -msgid "(restart DVD-o-matic to see language changes)"
 -msgstr "(riavviare DVD-o-matic per vedere i cambiamenti di lingua)"
 +msgid "(restart DCP-o-matic to see language changes)"
 +msgstr "(riavviare DCP-o-matic per vedere i cambiamenti di lingua)"
  
- #: src/wx/film_editor.cc:1269
+ #: src/wx/film_editor.cc:1276
  msgid "1 channel"
  msgstr "Canale 1"
  
index 96fafadebf3be3f1b27b99ebe82bf08b1d41afcf,4127d77f872df6a830a49fff1cd5a8e25e9d32fc..9ed7ee2bee987350af06f5b6d379008fcbb41abd
@@@ -5,9 -5,9 +5,9 @@@
  #
  msgid ""
  msgstr ""
 -"Project-Id-Version: DVD-o-matic\n"
 +"Project-Id-Version: DCP-o-matic\n"
  "Report-Msgid-Bugs-To: \n"
- "POT-Creation-Date: 2013-04-09 11:14+0100\n"
+ "POT-Creation-Date: 2013-04-22 15:06+0100\n"
  "PO-Revision-Date: 2013-04-09 10:13+0100\n"
  "Last-Translator: Adam Klotblixt <adam.klotblixt@gmail.com>\n"
  "Language-Team: \n"
@@@ -22,10 -22,10 +22,10 @@@ msgid "%
  msgstr "%"
  
  #: src/wx/config_dialog.cc:61
 -msgid "(restart DVD-o-matic to see language changes)"
 -msgstr "(starta om DVD-o-matic för att se språkändringar)"
 +msgid "(restart DCP-o-matic to see language changes)"
 +msgstr "(starta om DCP-o-matic för att se språkändringar)"
  
- #: src/wx/film_editor.cc:1269
+ #: src/wx/film_editor.cc:1276
  msgid "1 channel"
  msgstr "1 kanal"
  
diff --cc test/test.cc
Simple merge