Merge master.
authorCarl Hetherington <cth@carlh.net>
Tue, 9 Jul 2013 19:35:39 +0000 (20:35 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 9 Jul 2013 19:35:39 +0000 (20:35 +0100)
44 files changed:
1  2 
cscript
platform/linux/control-12.04-32
platform/linux/control-12.04-64
platform/linux/control-12.10-32
platform/linux/control-12.10-64
platform/osx/make_dmg.sh
platform/windows/installer.nsi.32.in
platform/windows/installer.nsi.64.in
src/lib/config.cc
src/lib/cross.cc
src/lib/cross.h
src/lib/encoder.cc
src/lib/ffmpeg_decoder.cc
src/lib/film.cc
src/lib/film.h
src/lib/ratio.h
src/lib/util.h
src/lib/version.h
src/lib/writer.cc
src/lib/wscript
src/tools/dcpomatic.cc
src/tools/dcpomatic_cli.cc
src/tools/wscript
src/wx/about_dialog.cc
src/wx/audio_dialog.cc
src/wx/config_dialog.cc
src/wx/config_dialog.h
src/wx/dci_metadata_dialog.cc
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/gain_calculator_dialog.cc
src/wx/imagemagick_content_dialog.cc
src/wx/job_manager_view.cc
src/wx/new_film_dialog.cc
src/wx/new_film_dialog.h
src/wx/properties_dialog.cc
src/wx/server_dialog.cc
src/wx/timecode.cc
src/wx/wscript
src/wx/wx_util.cc
src/wx/wx_util.h
test/test.cc
test/wscript
wscript

diff --combined cscript
index c51f3a033fb3aa22e54cc998e52e85da62858291,6a9b48a891368493a33371dc326c0babdbed6320..e7ef219d4b24f233413010d2e4462b361043ebf8
+++ b/cscript
@@@ -6,33 -6,32 +6,32 @@@ def dependencies(target)
      if target.platform == 'windows':
          return ()
      else:
-         return (('openjpeg-cdist', None),
-                 ('libcxml', None),
-                 ('ffmpeg-cdist', '7a23ec9c771184ab563cfe24ad9b427f38368961'),
-                 ('libdcp', None))
+       # XXX: should be some versions in here
+         return (('ffmpeg-cdist', 'e797834288eaf05a2f406524ae04aaa0f114cb08'),
+                 ('libdcp', 'v0.54'))
  
- def build(env, target):
-     cmd = './waf configure --prefix=%s' % env.work_dir_cscript()
+ def build(target):
+     cmd = './waf configure --prefix=%s' % target.work_dir_cscript()
      if target.platform == 'windows':
          cmd += ' --target-windows'
      elif target.platform == 'linux':
          cmd += ' --static'
-     env.command(cmd)
+     target.command(cmd)
  
-     env.command('./waf')
+     target.command('./waf')
  
      if target.platform == 'linux' or target.platform == 'osx':
-         env.command('./waf install')
+         target.command('./waf install')
  
  
- def package(env, target, version):
+ def package(target, version):
      if target.platform == 'windows':
          shutil.copyfile('build/platform/windows/installer.%s.nsi' % target.bits, 'build/platform/windows/installer2.%s.nsi' % target.bits)
-         env.command('sed -i "s~%%resources%%~%s/platform/windows~g" build/platform/windows/installer2.%s.nsi' % (os.getcwd(), target.bits))
-         env.command('sed -i "s~%%deps%%~%s~g" build/platform/windows/installer2.%s.nsi' % (env.windows_prefix, target.bits))
-         env.command('sed -i "s~%%binaries%%~%s/build~g" build/platform/windows/installer2.%s.nsi' % (os.getcwd(), target.bits))
-         env.command('sed -i "s~%%bits%%~32~g" build/platform/windows/installer2.%s.nsi' % target.bits)
-         env.command('makensis build/platform/windows/installer2.%s.nsi' % target.bits)
+         target.command('sed -i "s~%%resources%%~%s/platform/windows~g" build/platform/windows/installer2.%s.nsi' % (os.getcwd(), target.bits))
+         target.command('sed -i "s~%%deps%%~%s~g" build/platform/windows/installer2.%s.nsi' % (target.windows_prefix, target.bits))
+         target.command('sed -i "s~%%binaries%%~%s/build~g" build/platform/windows/installer2.%s.nsi' % (os.getcwd(), target.bits))
+         target.command('sed -i "s~%%bits%%~32~g" build/platform/windows/installer2.%s.nsi' % target.bits)
+         target.command('makensis build/platform/windows/installer2.%s.nsi' % target.bits)
          return os.path.abspath(glob.glob('build/platform/windows/*%s*.exe' % target.bits)[0])
      elif target.platform == 'linux':
          if target.bits == 32:
              cpu = 'amd64'
  
          shutil.copyfile('platform/linux/control-%s-%d' % (target.version, target.bits), 'debian/control')
-         env.command('./waf dist')
+         target.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)
 -        target.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)
++        target.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'))
-         env.set('CDIST_PKG_CONFIG_PATH', env.get('PKG_CONFIG_PATH'))
-         env.command('dpkg-buildpackage')
+         target.command('dch -b -v %s-1 "New upstream release."' % version)
+         target.set('CDIST_LINKFLAGS', target.get('LINKFLAGS'))
+         target.set('CDIST_CXXFLAGS', target.get('CXXFLAGS'))
+         target.set('CDIST_PKG_CONFIG_PATH', target.get('PKG_CONFIG_PATH'))
+         target.command('dpkg-buildpackage')
          
          debs = []
          for p in glob.glob('../*.deb'):
  
          return debs
      elif target.platform == 'osx':
-         env.command('bash platform/osx/make_dmg.sh')
+         target.command('bash platform/osx/make_dmg.sh')
          return os.path.abspath(glob.glob('build/platform/osx/DVD-o-matic*.dmg')[0])
  
- def make_pot(env):
-     env.command('./waf pot')
+ def make_pot(target):
+     target.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):
+ def make_manual(target):
      os.chdir('doc/manual')
-     env.command('make')
+     target.command('make')
      return [os.path.abspath('pdf'), os.path.abspath('html')]
index dc104958ac13e633d6c81d1d80354305d3b26184,753df49315ee5f39aa9ce80d55943543c470608d..a337944c3a2ab060d7aa804f7db42cbf86be4942
@@@ -1,24 -1,24 +1,24 @@@
 -Source: dvdomatic
 +Source: dcpomatic
  Section: video
  Priority: extra
  Maintainer: Carl Hetherington <cth@carlh.net>
- Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libwxgtk2.8-dev (>= 2.8.12.1), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7)
+ Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7), libgtk2.0-dev (>= 2.24.10)
  Standards-Version: 3.9.3
 -Homepage: http://carlh.net/software/dvdomatic
 +Homepage: http://carlh.net/software/dcpomatic
  
 -Package: dvdomatic
 +Package: dcpomatic
  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)
+ Depends: libc6 (>= 2.15), 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), libgtk2.0-0 (>= 2.24.10)
  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
 +Package: dcpomatic-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.
 +Depends: ${dcpomatic:Depends}, ${misc:Depends}
 +Description: debugging symbols for dcpomatic
 +  This package contains the debugging symbols for dcpomatic.
  
index 09c636e4aca9021a7d6ed8e3ec21aa2d13af4c9d,bb7672f5340937d17ea830b8833ee83e14e3e593..c2cfdf5d7a04150e4a17e965c8ad05653a4bcc30
@@@ -1,24 -1,24 +1,24 @@@
 -Source: dvdomatic
 +Source: dcpomatic
  Section: video
  Priority: extra
  Maintainer: Carl Hetherington <cth@carlh.net>
- Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libwxgtk2.8-dev (>= 2.8.12.1), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7)
+ Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7), libgtk2.0-dev (>= 2.4.10)
  Standards-Version: 3.9.3
 -Homepage: http://carlh.net/software/dvdomatic
 +Homepage: http://carlh.net/software/dcpomatic
  
 -Package: dvdomatic
 +Package: dcpomatic
  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
 +Package: dcpomatic-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.
 +Depends: ${dcpomatic:Depends}, ${misc:Depends}
 +Description: debugging symbols for dcpomatic
 +  This package contains the debugging symbols for dcpomatic.
  
index 1330b3e5fe470e938edba26e2f52f9ca3ece6bff,116e26b759125ada1cdb4a35d21ff554b372f013..14dc5a0dc4907d251c9df6bc18b260c2dcccb71e
@@@ -1,23 -1,23 +1,23 @@@
 -Source: dvdomatic
 +Source: dcpomatic
  Section: video
  Priority: extra
  Maintainer: Carl Hetherington <cth@carlh.net>
- Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libwxgtk2.8-dev (>= 2.8.12.1), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7)
+ Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7), libgtk2.0-dev (>= 2.24.13)
  Standards-Version: 3.9.3
 -Homepage: http://carlh.net/software/dvdomatic
 +Homepage: http://carlh.net/software/dcpomatic
  
 -Package: dvdomatic
 +Package: dcpomatic
  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)
+ Depends: libc6 (>= 2.15), 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), libgtk2.0-0 (>= 2.24.13)
  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
 +Package: dcpomatic-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.
 +Depends: ${dcpomatic:Depends}, ${misc:Depends}
 +Description: debugging symbols for dcpomatic
 +  This package contains the debugging symbols for dcpomatic.
index ea1c491ed8a3ebc785596f599b3e12d5f70f41f1,486d1f22579113a2ff6314942c556defdc7f2f63..8a8019f013e3db3303db8a01b5a1cae129c2b7a0
@@@ -1,24 -1,24 +1,24 @@@
 -Source: dvdomatic
 +Source: dcpomatic
  Section: video
  Priority: extra
  Maintainer: Carl Hetherington <cth@carlh.net>
- Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libwxgtk2.8-dev (>= 2.8.12.1), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7)
+ Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7), libgtk2.0-dev (>= 2.24.13)
  Standards-Version: 3.9.3
 -Homepage: http://carlh.net/software/dvdomatic
 +Homepage: http://carlh.net/software/dcpomatic
  
 -Package: dvdomatic
 +Package: dcpomatic
  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)
+ Depends: libc6 (>= 2.15), 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), libgtk2.0-0 (>= 2.24.13)
  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
 +Package: dcpomatic-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.
 +Depends: ${dcpomatic:Depends}, ${misc:Depends}
 +Description: debugging symbols for dcpomatic
 +  This package contains the debugging symbols for dcpomatic.
  
diff --combined platform/osx/make_dmg.sh
index 1fcdc6d0629782eccce9aa8c7176d95ca7b30fb5,a409d82fe7511ab6fdd773eecb4e5cc12ae360ed..debd4aaac5a5c4af41875739b365f3d5985dce08
@@@ -5,8 -5,8 +5,8 @@@ version=`cat wscript | egrep ^VERSION 
  # DMG size in megabytes
  DMG_SIZE=256
  WORK=build/platform/osx
- ENV=/Users/carl/Environments/osx/10.8
DEPS=/Users/carl/cdist
+ ENV=/Users/carl/Environments/osx
ROOT=/Users/carl/cdist
  
  appdir="DVD-o-matic.app"
  approot=$appdir/Contents
@@@ -19,46 -19,58 +19,58 @@@ mkdir -p $WORK/$maco
  mkdir -p $WORK/$libs
  mkdir -p $WORK/$resources
  
- cp build/src/tools/dcpomatic $WORK/$macos/
- cp build/src/lib/libdcpomatic.dylib $WORK/$libs/
- cp build/src/wx/libdcpomatic-wx.dylib $WORK/$libs/
- cp $DEPS/lib/libdcp.dylib $WORK/$libs/
- cp $DEPS/lib/libasdcp-libdcp.dylib $WORK/$libs/
- cp $DEPS/lib/libkumu-libdcp.dylib $WORK/$libs/
- cp $DEPS/lib/libopenjpeg*.dylib $WORK/$libs/
- cp $DEPS/lib/libavformat*.dylib $WORK/$libs/
- cp $DEPS/lib/libavfilter*.dylib $WORK/$libs/
- cp $DEPS/lib/libavutil*.dylib $WORK/$libs/
- cp $DEPS/lib/libavcodec*.dylib $WORK/$libs/
- cp $DEPS/lib/libswscale*.dylib $WORK/$libs/
- cp $DEPS/lib/libpostproc*.dylib $WORK/$libs/
- cp $DEPS/lib/libswresample*.dylib $WORK/$libs/
- cp $ENV/lib/libboost_system.dylib $WORK/$libs/
- cp $ENV/lib/libboost_filesystem.dylib $WORK/$libs/
- cp $ENV/lib/libboost_thread.dylib $WORK/$libs/
- cp $ENV/lib/libboost_date_time.dylib $WORK/$libs/
- cp $ENV/lib/libssl*.dylib $WORK/$libs/
- cp $ENV/lib/libcrypto*.dylib $WORK/$libs/
- cp $ENV/lib/libxml++-2.6*.dylib $WORK/$libs/
- cp $ENV/lib/libxml2*.dylib $WORK/$libs/
- cp $ENV/lib/libglibmm-2.4*.dylib $WORK/$libs/
- cp $ENV/lib/libgobject*.dylib $WORK/$libs/
- cp $ENV/lib/libgthread*.dylib $WORK/$libs/
- cp $ENV/lib/libgmodule*.dylib $WORK/$libs/
- cp $ENV/lib/libsigc*.dylib $WORK/$libs/
- cp $ENV/lib/libglib-2*.dylib $WORK/$libs/
- cp $ENV/lib/libintl*.dylib $WORK/$libs/
- cp $ENV/lib/libsndfile*.dylib $WORK/$libs/
- cp $ENV/lib/libMagick++*.dylib $WORK/$libs/
- cp $ENV/lib/libMagickCore*.dylib $WORK/$libs/
- cp $ENV/lib/libMagickWand*.dylib $WORK/$libs/
- cp $ENV/lib/libssh*.dylib $WORK/$libs/
- cp $ENV/lib/libwx*.dylib $WORK/$libs/
- cp $ENV/lib/libfontconfig*.dylib $WORK/$libs/
- cp $ENV/lib/libfreetype*.dylib $WORK/$libs/
- cp $ENV/lib/libexpat*.dylib $WORK/$libs/
+ function universal_copy {
+     echo $2
+     for f in $1/32/$2; do
+         if [ -h $f ]; then
+           ln -s $(readlink $f) $3/`basename $f`
+         else
+           g=`echo $f | sed -e "s/\/32\//\/64\//g"`
+         mkdir -p $3
+           lipo -create $f $g -output $3/`basename $f`
+         fi
+     done
+ }
+ universal_copy $ROOT src/dvdomatic/build/src/tools/dvdomatic $WORK/$macos
+ universal_copy $ROOT src/dvdomatic/build/src/lib/libdvdomatic.dylib $WORK/$libs
+ universal_copy $ROOT src/dvdomatic/build/src/wx/libdvdomatic-wx.dylib $WORK/$libs
+ universal_copy $ROOT lib/libcxml.dylib $WORK/$libs
+ universal_copy $ROOT lib/libdcp.dylib $WORK/$libs
+ universal_copy $ROOT lib/libasdcp-libdcp.dylib $WORK/$libs
+ universal_copy $ROOT lib/libkumu-libdcp.dylib $WORK/$libs
+ universal_copy $ROOT lib/libopenjpeg*.dylib $WORK/$libs
+ universal_copy $ROOT lib/libavformat*.dylib $WORK/$libs
+ universal_copy $ROOT lib/libavfilter*.dylib $WORK/$libs
+ universal_copy $ROOT lib/libavutil*.dylib $WORK/$libs
+ universal_copy $ROOT lib/libavcodec*.dylib $WORK/$libs
+ universal_copy $ROOT lib/libswscale*.dylib $WORK/$libs
+ universal_copy $ROOT lib/libpostproc*.dylib $WORK/$libs
+ universal_copy $ROOT lib/libswresample*.dylib $WORK/$libs
+ universal_copy $ENV lib/libboost_system.dylib $WORK/$libs
+ universal_copy $ENV lib/libboost_filesystem.dylib $WORK/$libs
+ universal_copy $ENV lib/libboost_thread.dylib $WORK/$libs
+ universal_copy $ENV lib/libboost_date_time.dylib $WORK/$libs
+ universal_copy $ENV lib/libxml++-2.6*.dylib $WORK/$libs
+ universal_copy $ENV lib/libxml2*.dylib $WORK/$libs
+ universal_copy $ENV lib/libglibmm-2.4*.dylib $WORK/$libs
+ universal_copy $ENV lib/libgobject*.dylib $WORK/$libs
+ universal_copy $ENV lib/libgthread*.dylib $WORK/$libs
+ universal_copy $ENV lib/libgmodule*.dylib $WORK/$libs
+ universal_copy $ENV lib/libsigc*.dylib $WORK/$libs
+ universal_copy $ENV lib/libglib-2*.dylib $WORK/$libs
+ universal_copy $ENV lib/libintl*.dylib $WORK/$libs
+ universal_copy $ENV lib/libsndfile*.dylib $WORK/$libs
+ universal_copy $ENV lib/libMagick++*.dylib $WORK/$libs
+ universal_copy $ENV lib/libMagickCore*.dylib $WORK/$libs
+ universal_copy $ENV lib/libMagickWand*.dylib $WORK/$libs
+ universal_copy $ENV lib/libssh*.dylib $WORK/$libs
+ universal_copy $ENV lib/libwx*.dylib $WORK/$libs
+ universal_copy $ENV lib/libfontconfig*.dylib $WORK/$libs
+ universal_copy $ENV lib/libfreetype*.dylib $WORK/$libs
+ universal_copy $ENV lib/libexpat*.dylib $WORK/$libs
  
 -for obj in $WORK/$macos/dvdomatic $WORK/$libs/*.dylib; do
 +for obj in $WORK/$macos/dcpomatic $WORK/$libs/*.dylib; do
    deps=`otool -L $obj | awk '{print $1}' | egrep "(/Users/carl|libboost|libssh)"`
    changes=""
    for dep in $deps; do
    fi  
  done
  
- pwd
  cp build/platform/osx/Info.plist $WORK/$approot
 -cp icons/dvdomatic.icns $WORK/$resources/DVD-o-matic.icns
 +cp icons/dcpomatic.icns $WORK/$resources/DVD-o-matic.icns
  
 -tmp_dmg=$WORK/dvdomatic_tmp.dmg
 -dmg="$WORK/DVD-o-matic $version.dmg"
 -vol_name=DVD-o-matic-$version
 +tmp_dmg=$WORK/dcpomatic_tmp.dmg
 +dmg="$WORK/DCP-o-matic $version.dmg"
 +vol_name=DCP-o-matic-$version
  
  mkdir -p $WORK/$vol_name
  
@@@ -100,7 -111,7 +111,7 @@@ echo 
             set arrangement of theViewOptions to not arranged
             set icon size of theViewOptions to 64
             make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"}
 -           set position of item "DVD-o-matic.app" of container window to {90, 80}
 +           set position of item "DCP-o-matic.app" of container window to {90, 80}
             set position of item "Applications" of container window to {310, 80}
             close
             open
@@@ -117,8 -128,8 +128,8 @@@ syn
  umount $device
  hdiutil eject $device
  hdiutil convert -format UDZO $tmp_dmg -imagekey zlib-level=9 -o "$dmg"
 -sips -i $WORK/$resources/DVD-o-matic.icns
 -DeRez -only icns $WORK/$resources/DVD-o-matic.icns > $WORK/$resources/DVD-o-matic.rsrc
 -Rez -append $WORK/$resources/DVD-o-matic.rsrc -o "$dmg"
 +sips -i $WORK/$resources/DCP-o-matic.icns
 +DeRez -only icns $WORK/$resources/DCP-o-matic.icns > $WORK/$resources/DCP-o-matic.rsrc
 +Rez -append $WORK/$resources/DCP-o-matic.rsrc -o "$dmg"
  SetFile -a C "$dmg"
  
index 3a2cdb9e824800600683fe441db37085a7cdbe40,6dd1de2d9e322247249f7f85f62cbdf1cae67ccf..314fe176ff6277edb1a9bf6e1297245b1772b47a
@@@ -1,14 -1,14 +1,14 @@@
  !include "MUI2.nsh"
 -Name "DVD-o-matic"
 +Name "DCP-o-matic"
  
  RequestExecutionLevel admin
  
 -outFile "DVD-o-matic @version@ 32-bit Installer.exe"
 -!define MUI_ICON "%resources%/dvdomatic.ico"
 -!define MUI_UNICON "%resources%/dvdomatic.ico"
 -!define MUI_SPECIALBITMAP "%resources%/dvdomatic.bmp"
 +outFile "DCP-o-matic @version@ 32-bit Installer.exe"
 +!define MUI_ICON "%resources%/dcpomatic.ico"
 +!define MUI_UNICON "%resources%/dcpomatic.ico"
 +!define MUI_SPECIALBITMAP "%resources%/dcpomatic.bmp"
  
 -InstallDir "$PROGRAMFILES\DVD-o-matic"
 +InstallDir "$PROGRAMFILES\DCP-o-matic"
  
  !insertmacro MUI_PAGE_WELCOME
  !insertmacro MUI_PAGE_LICENSE "../../../COPYING"
@@@ -32,6 -32,7 +32,7 @@@ File "%deps%/bin/avcodec-55.dll
  File "%deps%/bin/avfilter-3.dll"
  File "%deps%/bin/avformat-55.dll"
  File "%deps%/bin/avutil-52.dll"
+ File "%deps%/bin/avdevice-55.dll"
  File "%deps%/bin/dcp.dll"
  File "%deps%/bin/libintl-8.dll"
  File "%deps%/bin/kumu-libdcp.dll"
@@@ -79,15 -80,15 +80,16 @@@ File "%deps%/bin/libpixman-1-0.dll
  File "%deps%/bin/libfontconfig-1.dll"
  File "%deps%/bin/libexpat-1.dll"
  File "%deps%/bin/libbz2.dll"
 +File "%deps%/bin/cxml.dll"
+ File "%deps%/bin/ffprobe.exe"
  
 -File "%binaries%/src/wx/dvdomatic-wx.dll"
 -File "%binaries%/src/lib/dvdomatic.dll"
 -File "%binaries%/src/tools/dvdomatic.exe"
 -File "%binaries%/src/tools/dvdomatic_batch.exe"
 -File "%binaries%/src/tools/makedcp.exe"
 -File "%binaries%/src/tools/servomatic_cli.exe"
 -File "%binaries%/src/tools/servomatic_gui.exe"
 +File "%binaries%/src/wx/dcpomatic-wx.dll"
 +File "%binaries%/src/lib/dcpomatic.dll"
 +File "%binaries%/src/tools/dcpomatic.exe"
 +File "%binaries%/src/tools/dcpomatic_batch.exe"
 +File "%binaries%/src/tools/dcpomatic_cli.exe"
 +File "%binaries%/src/tools/dcpomatic_server_cli.exe"
 +File "%binaries%/src/tools/dcpomatic_server.exe"
  
  # I don't know why, but sometimes it seems that 
  # delegates.xml must be in with the binaries, and
@@@ -97,34 -98,34 +99,34 @@@ SetOutPath "$PROFILE\.magick
  File "%deps%/etc/ImageMagick/delegates.xml"
  
  SetOutPath "$INSTDIR\locale\fr\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/fr_FR/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/fr_FR/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/fr_FR/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/fr_FR/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/fr_FR/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/fr_FR/dcpomatic.mo"
  SetOutPath "$INSTDIR\locale\it\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/it_IT/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/it_IT/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/it_IT/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/it_IT/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/it_IT/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/it_IT/dcpomatic.mo"
  SetOutPath "$INSTDIR\locale\es\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/es_ES/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/es_ES/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/es_ES/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/es_ES/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/es_ES/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/es_ES/dcpomatic.mo"
  SetOutPath "$INSTDIR\locale\sv\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/sv_SE/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/sv_SE/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/sv_SE/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/sv_SE/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/sv_SE/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/sv_SE/dcpomatic.mo"
  
 -CreateShortCut "$DESKTOP\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" ""
 -CreateShortCut "$DESKTOP\DVD-o-matic batch converter.lnk" "$INSTDIR\bin\dvdomatic_batch.exe" ""
 -CreateShortCut "$DESKTOP\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" ""
 +CreateShortCut "$DESKTOP\DCP-o-matic.lnk" "$INSTDIR\bin\dcpomatic.exe" ""
 +CreateShortCut "$DESKTOP\DCP-o-matic batch converter.lnk" "$INSTDIR\bin\dcpomatic_batch.exe" ""
 +CreateShortCut "$DESKTOP\DCP-o-matic encode server.lnk" "$INSTDIR\bin\dcpomatic_server.exe" ""
   
 -CreateDirectory "$SMPROGRAMS\DVD-o-matic"
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall DVD-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" "" "$INSTDIR\bin\dvdomatic.exe" 0
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic batch converter.lnk" "$INSTDIR\bin\dvdomatic_batch.exe" "" "$INSTDIR\bin\dvdomatic_batch.exe" 0
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" "" "$INSTDIR\bin\servomatic_gui.exe" 0
 +CreateDirectory "$SMPROGRAMS\DCP-o-matic"
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\Uninstall DCP-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic.lnk" "$INSTDIR\bin\dcpomatic.exe" "" "$INSTDIR\bin\dcpomatic.exe" 0
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic batch converter.lnk" "$INSTDIR\bin\dcpomatic.exe" "" "$INSTDIR\bin\dcpomatic_batch.exe" 0
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic encode server.lnk" "$INSTDIR\bin\dcpomatic_server.exe" "" "$INSTDIR\bin\dcpomatic_server.exe" 0
   
 -WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "DisplayName" "DVD-o-matic (remove only)"
 -WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
 +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "DisplayName" "DCP-o-matic (remove only)"
 +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
   
  WriteUninstaller "$INSTDIR\Uninstall.exe"
   
@@@ -135,22 -136,12 +137,12 @@@ Section "Uninstall
   
  RMDir /r "$INSTDIR\*.*"    
  RMDir "$INSTDIR"
- <<<<<<< HEAD
 -Delete "$DESKTOP\DVD-o-matic.lnk"
 -Delete "$DESKTOP\DVD-o-matic batch converter.lnk"
 -Delete "$DESKTOP\DVD-o-matic encode server.lnk"
 -Delete "$SMPROGRAMS\DVD-o-matic\*.*"
 -RmDir  "$SMPROGRAMS\DVD-o-matic"
 -DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DVD-o-matic"
 -DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic"
 +Delete "$DESKTOP\DCP-o-matic.lnk"
 +Delete "$DESKTOP\DCP-o-matic batch converter.lnk"
 +Delete "$DESKTOP\DCP-o-matic encode server.lnk"
 +Delete "$SMPROGRAMS\DCP-o-matic\*.*"
 +RmDir  "$SMPROGRAMS\DCP-o-matic"
 +DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DCP-o-matic"
 +DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic"
- =======
- Delete "$DESKTOP\DVD-o-matic.lnk"
- Delete "$DESKTOP\DVD-o-matic batch converter.lnk"
- Delete "$DESKTOP\DVD-o-matic encode server.lnk"
- Delete "$SMPROGRAMS\DVD-o-matic\*.*"
- RmDir  "$SMPROGRAMS\DVD-o-matic"
- DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DVD-o-matic"
- DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic"
- >>>>>>> master
   
  SectionEnd
index f4f1e90684b546e121285b489137f019b02fd5a4,e98a3a6d8c839249e6e161fad15864e3440ab087..4bf959119579972df9d33171cbf2d26805e7dd60
@@@ -1,16 -1,16 +1,16 @@@
  !include "MUI2.nsh"
  !include "x64.nsh"
  
 -Name "DVD-o-matic"
 +Name "DCP-o-matic"
  
  RequestExecutionLevel admin
  
 -outFile "DVD-o-matic @version@ 64-bit Installer.exe"
 -!define MUI_ICON "%resources%/dvdomatic.ico"
 -!define MUI_UNICON "%resources%/dvdomatic.ico"
 -!define MUI_SPECIALBITMAP "%resources%/dvdomatic.bmp"
 +outFile "DCP-o-matic @version@ 64-bit Installer.exe"
 +!define MUI_ICON "%resources%/dcpomatic.ico"
 +!define MUI_UNICON "%resources%/dcpomatic.ico"
 +!define MUI_SPECIALBITMAP "%resources%/dcpomatic.bmp"
  
 -InstallDir "$PROGRAMFILES\DVD-o-matic"
 +InstallDir "$PROGRAMFILES\DCP-o-matic"
  
  !insertmacro MUI_PAGE_WELCOME
  !insertmacro MUI_PAGE_LICENSE "../../../COPYING"
@@@ -32,7 -32,7 +32,7 @@@ ${If} ${RunningX64
     ; disable registry redirection (enable access to 64-bit portion of registry)
     SetRegView 64
     ; change install dir
 -   StrCpy $INSTDIR "$PROGRAMFILES64\DVD-o-matic"
 +   StrCpy $INSTDIR "$PROGRAMFILES64\DCP-o-matic"
  ${EndIf}
  
  SetOutPath "$INSTDIR\bin"
@@@ -42,6 -42,7 +42,7 @@@ File "%deps%/bin/avcodec-55.dll
  File "%deps%/bin/avfilter-3.dll"
  File "%deps%/bin/avformat-55.dll"
  File "%deps%/bin/avutil-52.dll"
+ File "%deps%/bin/avdevice-55.dll"
  File "%deps%/bin/dcp.dll"
  File "%deps%/bin/libintl-8.dll"
  File "%deps%/bin/kumu-libdcp.dll"
@@@ -89,15 -90,15 +90,16 @@@ File "%deps%/bin/libpixman-1-0.dll
  File "%deps%/bin/libfontconfig-1.dll"
  File "%deps%/bin/libexpat-1.dll"
  File "%deps%/bin/libbz2.dll"
 +File "%deps%/bin/cxml.dll"
+ File "%deps%/bin/ffprobe.exe"
  
 -File "%binaries%/src/wx/dvdomatic-wx.dll"
 -File "%binaries%/src/lib/dvdomatic.dll"
 -File "%binaries%/src/tools/dvdomatic.exe"
 -File "%binaries%/src/tools/dvdomatic_batch.exe"
 -File "%binaries%/src/tools/makedcp.exe"
 -File "%binaries%/src/tools/servomatic_cli.exe"
 -File "%binaries%/src/tools/servomatic_gui.exe"
 +File "%binaries%/src/wx/dcpomatic-wx.dll"
 +File "%binaries%/src/lib/dcpomatic.dll"
 +File "%binaries%/src/tools/dcpomatic.exe"
 +File "%binaries%/src/tools/dcpomatic_batch.exe"
 +File "%binaries%/src/tools/dcpomatic_cli.exe"
 +File "%binaries%/src/tools/dcpomatic_server_cli.exe"
 +File "%binaries%/src/tools/dcpomatic_server.exe"
  
  # I don't know why, but sometimes it seems that 
  # delegates.xml must be in with the binaries, and
@@@ -107,34 -108,34 +109,34 @@@ SetOutPath "$PROFILE\.magick
  File "%deps%/etc/ImageMagick/delegates.xml"
  
  SetOutPath "$INSTDIR\locale\fr\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/fr_FR/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/fr_FR/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/fr_FR/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/fr_FR/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/fr_FR/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/fr_FR/dcpomatic.mo"
  SetOutPath "$INSTDIR\locale\it\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/it_IT/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/it_IT/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/it_IT/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/it_IT/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/it_IT/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/it_IT/dcpomatic.mo"
  SetOutPath "$INSTDIR\locale\es\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/es_ES/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/es_ES/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/es_ES/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/es_ES/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/es_ES/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/es_ES/dcpomatic.mo"
  SetOutPath "$INSTDIR\locale\sv\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/sv_SE/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/sv_SE/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/sv_SE/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/sv_SE/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/sv_SE/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/sv_SE/dcpomatic.mo"
  
 -CreateShortCut "$DESKTOP\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" ""
 -CreateShortCut "$DESKTOP\DVD-o-matic batch converter.lnk" "$INSTDIR\bin\dvdomatic_batch.exe" ""
 -CreateShortCut "$DESKTOP\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" ""
 +CreateShortCut "$DESKTOP\DCP-o-matic.lnk" "$INSTDIR\bin\dcpomatic.exe" ""
 +CreateShortCut "$DESKTOP\DCP-o-matic batch converter.lnk" "$INSTDIR\bin\dcpomatic_batch.exe" ""
 +CreateShortCut "$DESKTOP\DCP-o-matic encode server.lnk" "$INSTDIR\bin\dcpomatic_server.exe" ""
   
 -CreateDirectory "$SMPROGRAMS\DVD-o-matic"
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall DVD-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" "" "$INSTDIR\bin\dvdomatic.exe" 0
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic batch converter.lnk" "$INSTDIR\bin\dvdomatic.exe" "" "$INSTDIR\bin\dvdomatic_batch.exe" 0
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" "" "$INSTDIR\bin\servomatic_gui.exe" 0
 +CreateDirectory "$SMPROGRAMS\DCP-o-matic"
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\Uninstall DCP-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic.lnk" "$INSTDIR\bin\dcpomatic.exe" "" "$INSTDIR\bin\dcpomatic.exe" 0
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic batch converter.lnk" "$INSTDIR\bin\dcpomatic.exe" "" "$INSTDIR\bin\dcpomatic_batch.exe" 0
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic encode server.lnk" "$INSTDIR\bin\dcpomatic_server.exe" "" "$INSTDIR\bin\dcpomatic_server.exe" 0
   
 -WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "DisplayName" "DVD-o-matic (remove only)"
 -WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
 +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "DisplayName" "DCP-o-matic (remove only)"
 +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
   
  WriteUninstaller "$INSTDIR\Uninstall.exe"
   
@@@ -145,12 -146,12 +147,12 @@@ Section "Uninstall
   
  RMDir /r "$INSTDIR\*.*"    
  RMDir "$INSTDIR"
 -Delete "$DESKTOP\DVD-o-matic.lnk"
 -Delete "$DESKTOP\DVD-o-matic batch converter.lnk"
 -Delete "$DESKTOP\DVD-o-matic encode server.lnk"
 -Delete "$SMPROGRAMS\DVD-o-matic\*.*"
 -RmDir  "$SMPROGRAMS\DVD-o-matic"
 -DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DVD-o-matic"
 -DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic"
 +Delete "$DESKTOP\DCP-o-matic.lnk"
 +Delete "$DESKTOP\DCP-o-matic batch converter.lnk"
 +Delete "$DESKTOP\DCP-o-matic encode server.lnk"
 +Delete "$SMPROGRAMS\DCP-o-matic\*.*"
 +RmDir  "$SMPROGRAMS\DCP-o-matic"
 +DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DCP-o-matic"
 +DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic"
   
  SectionEnd
diff --combined src/lib/config.cc
index c9ec730f20aa94c03357b56cbd9398e21698eb46,d2d7fa2fd1da001059a72a440b46e5b88ee11d62..e0fbcc703f58042948cb3a1dc83aa62d58f09fe3
  #include <fstream>
  #include <glib.h>
  #include <boost/filesystem.hpp>
 +#include <libcxml/cxml.h>
  #include "config.h"
  #include "server.h"
  #include "scaler.h"
  #include "filter.h"
 -#include "format.h"
 +#include "ratio.h"
  #include "dcp_content_type.h"
  #include "sound_processor.h"
  
@@@ -37,21 -36,19 +37,22 @@@ using std::vector
  using std::ifstream;
  using std::string;
  using std::ofstream;
 +using std::list;
+ using std::max;
  using boost::shared_ptr;
 +using boost::lexical_cast;
 +using boost::optional;
  
  Config* Config::_instance = 0;
  
  /** Construct default configuration */
  Config::Config ()
-       : _num_local_encoding_threads (2)
+       : _num_local_encoding_threads (max (2U, boost::thread::hardware_concurrency()))
        , _server_port (6192)
 -      , _reference_scaler (Scaler::from_id (N_("bicubic")))
        , _tms_path (N_("."))
        , _sound_processor (SoundProcessor::from_id (N_("dolby_cp750")))
 -      , _default_format (0)
 +      , _default_still_length (10)
 +      , _default_container (Ratio::from_id ("185"))
        , _default_dcp_content_type (0)
  {
        _allowed_dcp_frame_rates.push_back (24);
        _allowed_dcp_frame_rates.push_back (48);
        _allowed_dcp_frame_rates.push_back (50);
        _allowed_dcp_frame_rates.push_back (60);
 +}
 +
 +void
 +Config::read ()
 +{
 +      if (!boost::filesystem::exists (file (false))) {
 +              read_old_metadata ();
 +              return;
 +      }
 +
 +      cxml::File f (file (false), "Config");
 +      optional<string> c;
 +
 +      _num_local_encoding_threads = f.number_child<int> ("NumLocalEncodingThreads");
 +      _default_directory = f.string_child ("DefaultDirectory");
 +      _server_port = f.number_child<int> ("ServerPort");
        
 -      ifstream f (file().c_str ());
 +      list<shared_ptr<cxml::Node> > servers = f.node_children ("Server");
 +      for (list<shared_ptr<cxml::Node> >::iterator i = servers.begin(); i != servers.end(); ++i) {
 +              _servers.push_back (new ServerDescription (*i));
 +      }
 +
 +      _tms_ip = f.string_child ("TMSIP");
 +      _tms_path = f.string_child ("TMSPath");
 +      _tms_user = f.string_child ("TMSUser");
 +      _tms_password = f.string_child ("TMSPassword");
 +
 +      c = f.optional_string_child ("SoundProcessor");
 +      if (c) {
 +              _sound_processor = SoundProcessor::from_id (c.get ());
 +      }
 +
 +      _language = f.optional_string_child ("Language");
 +
 +      c = f.optional_string_child ("DefaultContainer");
 +      if (c) {
 +              _default_container = Ratio::from_id (c.get ());
 +      }
 +
 +      c = f.optional_string_child ("DefaultDCPContentType");
 +      if (c) {
 +              _default_dcp_content_type = DCPContentType::from_dci_name (c.get ());
 +      }
 +
 +      _dcp_metadata.issuer = f.optional_string_child ("DCPMetadataIssuer").get_value_or ("");
 +      _dcp_metadata.creator = f.optional_string_child ("DCPMetadataCreator").get_value_or ("");
 +
 +      _default_dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
 +      _default_still_length = f.optional_number_child<int>("DefaultStillLength").get_value_or (10);
 +}
 +
 +void
 +Config::read_old_metadata ()
 +{
 +      ifstream f (file(true).c_str ());
        string line;
        while (getline (f, line)) {
                if (line.empty ()) {
                        _default_directory = v;
                } else if (k == N_("server_port")) {
                        _server_port = atoi (v.c_str ());
 -              } else if (k == N_("reference_scaler")) {
 -                      _reference_scaler = Scaler::from_id (v);
 -              } else if (k == N_("reference_filter")) {
 -                      _reference_filters.push_back (Filter::from_id (v));
                } else if (k == N_("server")) {
                        _servers.push_back (ServerDescription::create_from_metadata (v));
                } else if (k == N_("tms_ip")) {
                        _sound_processor = SoundProcessor::from_id (v);
                } else if (k == "language") {
                        _language = v;
 -              } else if (k == "default_format") {
 -                      _default_format = Format::from_metadata (v);
 +              } else if (k == "default_container") {
 +                      _default_container = Ratio::from_id (v);
                } else if (k == "default_dcp_content_type") {
                        _default_dcp_content_type = DCPContentType::from_dci_name (v);
                } else if (k == "dcp_metadata_issuer") {
                        _dcp_metadata.issue_date = v;
                }
  
 -              _default_dci_metadata.read (k, v);
 +              _default_dci_metadata.read_old_metadata (k, v);
        }
  }
  
  /** @return Filename to write configuration to */
  string
 -Config::file () const
 +Config::file (bool old) const
  {
        boost::filesystem::path p;
        p /= g_get_user_config_dir ();
        boost::system::error_code ec;
        boost::filesystem::create_directory (p, ec);
 -      p /= N_(".dvdomatic");
 +      if (old) {
 +              p /= ".dvdomatic";
 +      } else {
 +              p /= ".dcpomatic.xml";
 +      }
        return p.string ();
  }
  
@@@ -191,13 -135,6 +192,13 @@@ Config::instance (
  {
        if (_instance == 0) {
                _instance = new Config;
 +              try {
 +                      _instance->read ();
 +              } catch (...) {
 +                      /* configuration load failed; never mind, just
 +                         stick with the default.
 +                      */
 +              }
        }
  
        return _instance;
  void
  Config::write () const
  {
 -      ofstream f (file().c_str ());
 -      f << "num_local_encoding_threads " << _num_local_encoding_threads << "\n"
 -        << "default_directory " << _default_directory << "\n"
 -        << "server_port " << _server_port << "\n";
 -
 -      if (_reference_scaler) {
 -              f << "reference_scaler " << _reference_scaler->id () << "\n";
 -      }
 +      xmlpp::Document doc;
 +      xmlpp::Element* root = doc.create_root_node ("Config");
  
 -      for (vector<Filter const *>::const_iterator i = _reference_filters.begin(); i != _reference_filters.end(); ++i) {
 -              f << "reference_filter " << (*i)->id () << "\n";
 -      }
 +      root->add_child("NumLocalEncodingThreads")->add_child_text (lexical_cast<string> (_num_local_encoding_threads));
 +      root->add_child("DefaultDirectory")->add_child_text (_default_directory);
 +      root->add_child("ServerPort")->add_child_text (lexical_cast<string> (_server_port));
        
        for (vector<ServerDescription*>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) {
 -              f << "server " << (*i)->as_metadata () << "\n";
 +              (*i)->as_xml (root->add_child ("Server"));
        }
  
 -      f << "tms_ip " << _tms_ip << "\n";
 -      f << "tms_path " << _tms_path << "\n";
 -      f << "tms_user " << _tms_user << "\n";
 -      f << "tms_password " << _tms_password << "\n";
 +      root->add_child("TMSIP")->add_child_text (_tms_ip);
 +      root->add_child("TMSPath")->add_child_text (_tms_path);
 +      root->add_child("TMSUser")->add_child_text (_tms_user);
 +      root->add_child("TMSPassword")->add_child_text (_tms_password);
        if (_sound_processor) {
 -              f << "sound_processor " << _sound_processor->id () << "\n";
 +              root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ());
        }
        if (_language) {
 -              f << "language " << _language.get() << "\n";
 +              root->add_child("Language")->add_child_text (_language.get());
        }
 -      if (_default_format) {
 -              f << "default_format " << _default_format->as_metadata() << "\n";
 +      if (_default_container) {
 +              root->add_child("DefaultContainer")->add_child_text (_default_container->id ());
        }
        if (_default_dcp_content_type) {
 -              f << "default_dcp_content_type " << _default_dcp_content_type->dci_name() << "\n";
 +              root->add_child("DefaultDCPContentType")->add_child_text (_default_dcp_content_type->dci_name ());
        }
 -      f << "dcp_metadata_issuer " << _dcp_metadata.issuer << "\n";
 -      f << "dcp_metadata_creator " << _dcp_metadata.creator << "\n";
 -      f << "dcp_metadata_issue_date " << _dcp_metadata.issue_date << "\n";
 +      root->add_child("DCPMetadataIssuer")->add_child_text (_dcp_metadata.issuer);
 +      root->add_child("DCPMetadataCreator")->add_child_text (_dcp_metadata.creator);
 +
 +      _default_dci_metadata.as_xml (root->add_child ("DCIMetadata"));
 +
 +      root->add_child("DefaultStillLength")->add_child_text (lexical_cast<string> (_default_still_length));
  
 -      _default_dci_metadata.write (f);
 +      doc.write_to_file_formatted (file (false));
  }
  
  string
diff --combined src/lib/cross.cc
index ffd44eb02810cd0107145e8a3e5eaeedb917509f,124697fb446aca92ad5e89c490a67f7b51a51fd9..ee0ef89b2ea0c9394b53124b700605d5c703a3af
  #include <fstream>
  #include <boost/algorithm/string.hpp>
  #include "cross.h"
- #ifdef DCPOMATIC_POSIX
+ #include "compose.hpp"
+ #include "log.h"
 -#ifdef DVDOMATIC_LINUX
++#ifdef DCPOMATIC_LINUX
  #include <unistd.h>
+ #include <mntent.h>
  #endif
 -#ifdef DVDOMATIC_WINDOWS
 +#ifdef DCPOMATIC_WINDOWS
- #include "windows.h"
+ #include <windows.h>
+ #undef DATADIR
+ #include <shlwapi.h>
  #endif
 -#ifdef DVDOMATIC_OSX
 +#ifdef DCPOMATIC_OSX
  #include <sys/sysctl.h>
  #endif
  
  using std::pair;
+ using std::list;
  using std::ifstream;
  using std::string;
+ using std::make_pair;
+ using boost::shared_ptr;
  
  void
 -dvdomatic_sleep (int s)
 +dcpomatic_sleep (int s)
  {
 -#ifdef DVDOMATIC_POSIX
 +#ifdef DCPOMATIC_POSIX
        sleep (s);
  #endif
 -#ifdef DVDOMATIC_WINDOWS
 +#ifdef DCPOMATIC_WINDOWS
        Sleep (s * 1000);
  #endif
  }
@@@ -52,7 -60,7 +60,7 @@@ cpu_info (
        pair<string, int> info;
        info.second = 0;
        
 -#ifdef DVDOMATIC_LINUX
 +#ifdef DCPOMATIC_LINUX
        ifstream f ("/proc/cpuinfo");
        while (f.good ()) {
                string l;
@@@ -68,7 -76,7 +76,7 @@@
        }
  #endif
  
 -#ifdef DVDOMATIC_OSX
 +#ifdef DCPOMATIC_OSX
        size_t N = sizeof (info.second);
        sysctlbyname ("hw.ncpu", &info.second, &N, 0, 0);
        char buffer[64];
        return info;
  }
  
 -#ifdef DVDOMATIC_WINDOWS
+ void
+ run_ffprobe (boost::filesystem::path content, boost::filesystem::path out, shared_ptr<Log> log)
+ {
 -#ifdef DVDOMATIC_LINUX
++#ifdef DCPOMATIC_WINDOWS
+       SECURITY_ATTRIBUTES security;
+       security.nLength = sizeof (security);
+       security.bInheritHandle = TRUE;
+       security.lpSecurityDescriptor = 0;
+       HANDLE child_stderr_read;
+       HANDLE child_stderr_write;
+       if (!CreatePipe (&child_stderr_read, &child_stderr_write, &security, 0)) {
+               log->log ("ffprobe call failed (could not CreatePipe)");
+               return;
+       }
+       wchar_t dir[512];
+       GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
+       PathRemoveFileSpec (dir);
+       SetCurrentDirectory (dir);
+       STARTUPINFO startup_info;
+       ZeroMemory (&startup_info, sizeof (startup_info));
+       startup_info.cb = sizeof (startup_info);
+       startup_info.hStdError = child_stderr_write;
+       startup_info.dwFlags |= STARTF_USESTDHANDLES;
+       wchar_t command[512];
+       wcscpy (command, L"ffprobe.exe \"");
+       wchar_t file[512];
+       MultiByteToWideChar (CP_UTF8, 0, content.string().c_str(), -1, file, sizeof(file));
+       wcscat (command, file);
+       wcscat (command, L"\"");
+       PROCESS_INFORMATION process_info;
+       ZeroMemory (&process_info, sizeof (process_info));
+       if (!CreateProcess (0, command, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
+               log->log ("ffprobe call failed (could not CreateProcess)");
+               return;
+       }
+       FILE* o = fopen (out.string().c_str(), "w");
+       if (!o) {
+               log->log ("ffprobe call failed (could not create output file)");
+               return;
+       }
+       CloseHandle (child_stderr_write);
+       while (1) {
+               char buffer[512];
+               DWORD read;
+               if (!ReadFile(child_stderr_read, buffer, sizeof(buffer), &read, 0) || read == 0) {
+                       break;
+               }
+               fwrite (buffer, read, 1, o);
+       }
+       fclose (o);
+       WaitForSingleObject (process_info.hProcess, INFINITE);
+       CloseHandle (process_info.hProcess);
+       CloseHandle (process_info.hThread);
+       CloseHandle (child_stderr_read);
+ #else
+       string ffprobe = "ffprobe \"" + content.string() + "\" 2> \"" + out.string() + "\"";
+       log->log (String::compose ("Probing with %1", ffprobe));
+       system (ffprobe.c_str ());
+ #endif        
+ }
+ list<pair<string, string> >
+ mount_info ()
+ {
+       list<pair<string, string> > m;
+       
++#ifdef DCPOMATIC_LINUX
+       FILE* f = setmntent ("/etc/mtab", "r");
+       if (!f) {
+               return m;
+       }
+       
+       while (1) {
+               struct mntent* mnt = getmntent (f);
+               if (!mnt) {
+                       break;
+               }
+               m.push_back (make_pair (mnt->mnt_dir, mnt->mnt_type));
+       }
+       endmntent (f);
+ #endif
+       return m;
+ }
diff --combined src/lib/cross.h
index d185286b1f381d02395ba36261313eaac3c10432,d9cc2d12f4b2981b48378515fcbfae8d3d15dacd..a00fee67917c95031a038c7b14fdeec0b8d85f30
  
  */
  
 -#include <string>
+ #include <boost/filesystem.hpp>
 -class Log;
 -
 -#ifdef DVDOMATIC_WINDOWS
 +#ifdef DCPOMATIC_WINDOWS
  #define WEXITSTATUS(w) (w)
  #endif
  
 -extern void dvdomatic_sleep (int);
++class Log;
++
 +void dcpomatic_sleep (int);
  extern std::pair<std::string, int> cpu_info ();
+ extern void run_ffprobe (boost::filesystem::path, boost::filesystem::path, boost::shared_ptr<Log>);
+ extern std::list<std::pair<std::string, std::string> > mount_info ();
diff --combined src/lib/encoder.cc
index c3865d2c16ebf44852cede103a97d94f93c3c022,0ac32d3bfa9cd2eb099360080d9ac6e4657d715d..d3181acd9b1f7df35f0224a96be318b6203f5f1d
   */
  
  #include <iostream>
 -#include <boost/filesystem.hpp>
 -#include <boost/lexical_cast.hpp>
 -#include <libdcp/picture_asset.h>
  #include "encoder.h"
  #include "util.h"
 -#include "options.h"
  #include "film.h"
  #include "log.h"
 -#include "exceptions.h"
 -#include "filter.h"
  #include "config.h"
  #include "dcp_video_frame.h"
  #include "server.h"
 -#include "format.h"
  #include "cross.h"
  #include "writer.h"
  
@@@ -40,17 -47,20 +40,18 @@@ using std::stringstream
  using std::vector;
  using std::list;
  using std::cout;
+ using std::min;
  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<const 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)
  {
@@@ -68,6 -78,35 +69,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
 -
 -      if (_film->audio_channels() == 0 && _film->minimum_audio_channels() > 0) {
 -              /* Put audio in where there is none at all */
 -              int64_t af = video_frames_to_audio_frames (_video_frames_out, 48000, _film->dcp_frame_rate ());
 -              while (af) {
 -                      int64_t const this_time = min (af, static_cast<int64_t> (24000));
 -                      shared_ptr<AudioBuffers> out (new AudioBuffers (_film->minimum_audio_channels(), this_time));
 -                      out->make_silent ();
 -                      out->set_frames (this_time);
 -                      write_audio (out);
 -
 -                      af -= this_time;
 -              }
 -      }
 -
        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) {
@@@ -169,8 -246,15 +170,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)
  {
 -      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),
 -                                                _film->subtitle_offset(), _film->subtitle_scale(),
 -                                                _film->scaler(), _video_frames_out, _film->dcp_frame_rate(), s.second,
 -                                                _film->colour_lut(), _film->j2k_bandwidth(),
 -                                                _film->log()
 +                                                image, _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)
  {
 -      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
@@@ -260,7 -383,7 +261,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);
 -      if (m.dcp_channels() != _film->audio_channels()) {
 -
 -              /* Remap and pad with silence */
 -
 -              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 a3fdaf9b13a1f3acc013802609cbd15853d9e318,c2143b949e7d3618d4315ee3f54a4f6bd100b671..bf094913082104a1392048f2b8003daf245c825e
  #include <iostream>
  #include <stdint.h>
  #include <boost/lexical_cast.hpp>
 +#include <sndfile.h>
  extern "C" {
  #include <libavcodec/avcodec.h>
  #include <libavformat/avformat.h>
 -#include <libswscale/swscale.h>
 -#include <libpostproc/postprocess.h>
  }
 -#include <sndfile.h>
  #include "film.h"
 -#include "format.h"
 -#include "transcoder.h"
 -#include "job.h"
  #include "filter.h"
 -#include "options.h"
  #include "exceptions.h"
  #include "image.h"
  #include "util.h"
@@@ -42,7 -48,6 +42,7 @@@
  #include "ffmpeg_decoder.h"
  #include "filter_graph.h"
  #include "subtitle.h"
 +#include "audio_buffers.h"
  
  #include "i18n.h"
  
@@@ -51,67 -56,182 +51,66 @@@ 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;
  
 -boost::mutex FFmpegDecoder::_mutex;
 -
 -FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o)
 -      : Decoder (f, o)
 -      , VideoDecoder (f, o)
 -      , AudioDecoder (f, o)
 -      , _format_context (0)
 -      , _video_stream (-1)
 -      , _frame (0)
 -      , _video_codec_context (0)
 -      , _video_codec (0)
 -      , _audio_codec_context (0)
 -      , _audio_codec (0)
 +FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
 +      : Decoder (f)
 +      , VideoDecoder (f)
 +      , AudioDecoder (f)
 +      , FFmpeg (c)
        , _subtitle_codec_context (0)
        , _subtitle_codec (0)
 +      , _decode_video (video)
 +      , _decode_audio (audio)
 +      , _pts_offset (0)
  {
 -      setup_general ();
 -      setup_video ();
 -      setup_audio ();
        setup_subtitle ();
 -}
 -
 -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);
 -      }
 -
 -      if (_subtitle_codec_context) {
 -              avcodec_close (_subtitle_codec_context);
 -      }
 -
 -      av_free (_frame);
 -      
 -      avformat_close_input (&_format_context);
 -}     
 -
 -void
 -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_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 */
 -
 -      for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
 -              AVStream* s = _format_context->streams[i];
 -              if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
 -                      _video_stream = i;
 -              } else if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
 -
 -                      /* This is a hack; sometimes it seems that _audio_codec_context->channel_layout isn't set up,
 -                         so bodge it here.  No idea why we should have to do this.
 -                      */
 -
 -                      if (s->codec->channel_layout == 0) {
 -                              s->codec->channel_layout = av_get_default_channel_layout (s->codec->channels);
 -                      }
 -                      
 -                      _audio_streams.push_back (
 -                              shared_ptr<AudioStream> (
 -                                      new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channel_layout)
 -                                      )
 -                              );
 -                      
 -              } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
 -                      _subtitle_streams.push_back (
 -                              shared_ptr<SubtitleStream> (
 -                                      new SubtitleStream (stream_name (s), i)
 -                                      )
 -                              );
 -              }
 -      }
 -
 -      if (_video_stream < 0) {
 -              throw DecodeError (N_("could not find video stream"));
 -      }
  
 -      _frame = avcodec_alloc_frame ();
 -      if (_frame == 0) {
 -              throw DecodeError (N_("could not allocate frame"));
 +      if (video && audio && c->audio_stream() && c->first_video() && c->audio_stream()->first_audio) {
 +              _pts_offset = compute_pts_offset (c->first_video().get(), c->audio_stream()->first_audio.get(), c->video_frame_rate());
        }
  }
  
 -void
 -FFmpegDecoder::setup_video ()
 +double
 +FFmpegDecoder::compute_pts_offset (double first_video, double first_audio, float video_frame_rate)
  {
-       assert (first_video >= 0);
-       assert (first_audio >= 0);
-       
 -      boost::mutex::scoped_lock lm (_mutex);
 +      double const old_first_video = first_video;
        
 -      _video_codec_context = _format_context->streams[_video_stream]->codec;
 -      _video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
 -
 -      if (_video_codec == 0) {
 -              throw DecodeError (_("could not find video decoder"));
 +      /* Round the first video to a frame boundary */
 +      if (fabs (rint (first_video * video_frame_rate) - first_video * video_frame_rate) > 1e-6) {
 +              first_video = ceil (first_video * video_frame_rate) / video_frame_rate;
        }
  
 -      if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
 -              throw DecodeError (N_("could not open video decoder"));
 -      }
 +      /* Compute the required offset (also removing any common start delay) */
 +      return first_video - old_first_video - min (first_video, first_audio);
  }
  
 -void
 -FFmpegDecoder::setup_audio ()
 +FFmpegDecoder::~FFmpegDecoder ()
  {
 -      
 -      if (!_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 = avcodec_find_decoder (_audio_codec_context->codec_id);
+       boost::mutex::scoped_lock lm (_mutex);
 -      if (_audio_codec == 0) {
 -              throw DecodeError (_("could not find audio decoder"));
 -      }
 -
 -      if (avcodec_open2 (_audio_codec_context, _audio_codec, 0) < 0) {
 -              throw DecodeError (N_("could not open audio decoder"));
 +      if (_subtitle_codec_context) {
 +              avcodec_close (_subtitle_codec_context);
        }
 -}
 +}     
  
  void
 -FFmpegDecoder::setup_subtitle ()
 -{
 -      boost::mutex::scoped_lock lm (_mutex);
 -      
 -      if (!_subtitle_stream || _subtitle_stream->id() >= int (_format_context->nb_streams)) {
 -              return;
 -      }
 -
 -      _subtitle_codec_context = _format_context->streams[_subtitle_stream->id()]->codec;
 -      _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
 -
 -      if (_subtitle_codec == 0) {
 -              throw DecodeError (_("could not find subtitle decoder"));
 -      }
 -      
 -      if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
 -              throw DecodeError (N_("could not open subtitle decoder"));
 -      }
 -}
 -
 -
 -bool
  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;
 +
 +              /* Stop us being asked for any more data */
 +              _video_position = _ffmpeg_content->video_length ();
 +              _audio_position = _ffmpeg_content->audio_length ();
 +              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) {
 +#if 0         
  
                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);
                }
 +#endif                
        }
 -      
 +
        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));
                }
        }
        return audio;
  }
  
 -float
 -FFmpegDecoder::frames_per_second () const
 -{
 -      AVStream* s = _format_context->streams[_video_stream];
 -
 -      if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
 -              return av_q2d (s->avg_frame_rate);
 -      }
 -
 -      return av_q2d (s->r_frame_rate);
 -}
 -
  AVSampleFormat
  FFmpegDecoder::audio_sample_format () const
  {
 -      if (_audio_codec_context == 0) {
 +      if (!_ffmpeg_content->audio_stream()) {
                return (AVSampleFormat) 0;
        }
        
 -      return _audio_codec_context->sample_fmt;
 -}
 -
 -libdcp::Size
 -FFmpegDecoder::native_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
 -{
 -      stringstream n;
 -
 -      if (s->metadata) {
 -              AVDictionaryEntry const * lang = av_dict_get (s->metadata, N_("language"), 0, 0);
 -              if (lang) {
 -                      n << lang->value;
 -              }
 -              
 -              AVDictionaryEntry const * title = av_dict_get (s->metadata, N_("title"), 0, 0);
 -              if (title) {
 -                      if (!n.str().empty()) {
 -                              n << N_(" ");
 -                      }
 -                      n << title->value;
 -              }
 -      }
 -
 -      if (n.str().empty()) {
 -              n << N_("unknown");
 -      }
 -
 -      return n.str ();
 +      return audio_codec_context()->sample_fmt;
  }
  
  int
@@@ -281,29 -492,91 +280,29 @@@ FFmpegDecoder::bytes_per_audio_sample (
  }
  
  void
 -FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s)
 +FFmpegDecoder::seek (VideoContent::Frame frame)
  {
 -      AudioDecoder::set_audio_stream (s);
 -      setup_audio ();
 +      do_seek (frame, false, false);
  }
  
  void
 -FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
 -{
 -      VideoDecoder::set_subtitle_stream (s);
 -      setup_subtitle ();
 -      OutputChanged ();
 -}
 -
 -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 (_video_position == 0) {
                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 (_video_position - 1, true, true);
  }
  
  void
 -FFmpegDecoder::seek_forward ()
 -{
 -      do_seek (last_source_time() - 0.5 / frames_per_second(), true, true);
 -}
 -
 -bool
 -FFmpegDecoder::do_seek (double p, bool backwards, bool accurate)
 +FFmpegDecoder::do_seek (VideoContent::Frame frame, bool backwards, bool accurate)
  {
 -      int64_t const vt = p / av_q2d (_format_context->streams[_video_stream]->time_base);
 +      int64_t const vt = frame * _ffmpeg_content->video_frame_rate() / av_q2d (_format_context->streams[_video_stream]->time_base);
 +      av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
 +      _video_position = frame;
  
 -      int const r = av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
 -
 -      avcodec_flush_buffers (_video_codec_context);
 +      avcodec_flush_buffers (video_codec_context());
        if (_subtitle_codec_context) {
                avcodec_flush_buffers (_subtitle_codec_context);
        }
                while (1) {
                        int r = av_read_frame (_format_context, &_packet);
                        if (r < 0) {
 -                              return true;
 +                              return;
                        }
                        
                        avcodec_get_frame_defaults (_frame);
                        
                        if (_packet.stream_index == _video_stream) {
                                int finished = 0;
 -                              int const r = avcodec_decode_video2 (_video_codec_context, _frame, &finished, &_packet);
 +                              int const r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
                                if (r >= 0 && finished) {
                                        int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
                                        if (bet > vt) {
 +                                              _video_position = (bet * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset)
 +                                                      * _ffmpeg_content->video_frame_rate();
                                                break;
                                        }
                                }
                        av_free_packet (&_packet);
                }
        }
 -              
 -      return r < 0;
  }
  
 -shared_ptr<FFmpegAudioStream>
 -FFmpegAudioStream::create (string t, optional<int> v)
 +void
 +FFmpegDecoder::decode_audio_packet ()
  {
 -      if (!v) {
 -              /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
 -              return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
 -      }
 +      /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
 +         several times.
 +      */
 +      
 +      AVPacket copy_packet = _packet;
  
 -      stringstream s (t);
 -      string type;
 -      s >> type;
 -      if (type != N_("ffmpeg")) {
 -              return shared_ptr<FFmpegAudioStream> ();
 -      }
 +      while (copy_packet.size > 0) {
  
 -      return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
 +              int frame_finished;
 +              int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
 +              if (decode_result >= 0) {
 +                      if (frame_finished) {
 +
 +                              if (_audio_position == 0) {
 +                                      /* Where we are in the source, in seconds */
 +                                      double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
 +                                              * av_frame_get_best_effort_timestamp(_frame) - _pts_offset;
 +
 +                                      if (pts > 0) {
 +                                              /* Emit some silence */
 +                                              shared_ptr<AudioBuffers> silence (
 +                                                      new AudioBuffers (
 +                                                              _ffmpeg_content->audio_channels(),
 +                                                              pts * _ffmpeg_content->content_audio_frame_rate()
 +                                                              )
 +                                                      );
 +                                              
 +                                              silence->make_silent ();
 +                                              audio (silence, _audio_position);
 +                                      }
 +                              }
 +                                      
-                               
-                               int const data_size = av_samples_get_buffer_size (
-                                       0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
-                                       );
-                               
-                               assert (audio_codec_context()->channels == _ffmpeg_content->audio_channels());
-                               audio (deinterleave_audio (_frame->data, data_size), _audio_position);
++                              copy_packet.data += decode_result;
++                              copy_packet.size -= decode_result;
 +                      }
-                       
-                       copy_packet.data += decode_result;
-                       copy_packet.size -= decode_result;
 +              }
 +      }
  }
  
 -FFmpegAudioStream::FFmpegAudioStream (string t, optional<int> version)
 +bool
 +FFmpegDecoder::decode_video_packet ()
  {
 -      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"));
 +      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);
  
 -      for (int i = 0; i < name_index; ++i) {
 -              size_t const s = t.find (' ');
 -              if (s != string::npos) {
 -                      t = t.substr (s + 1);
 -              }
 +      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;
        }
  
 -      _name = t;
 -}
 +      if (i == _filter_graphs.end ()) {
 +              shared_ptr<const Film> film = _film.lock ();
 +              assert (film);
  
 -string
 -FFmpegAudioStream::to_string () const
 -{
 -      return String::compose (N_("ffmpeg %1 %2 %3 %4"), _id, _sample_rate, _channel_layout, _name);
 -}
 +              graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
 +              _filter_graphs.push_back (graph);
  
 -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 ();
 +              film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
 +      } else {
 +              graph = *i;
        }
 -      OutputChanged ();
 -      break;
  
 -      default:
 -              break;
 +      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) {
 +
 +                      double const pts = bet * av_q2d (_format_context->streams[_video_stream]->time_base) - _pts_offset;
 +                      double const next = _video_position / _ffmpeg_content->video_frame_rate();
 +                      double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
 +                      double delta = pts - next;
 +
 +                      while (delta > one_frame) {
 +                              /* This PTS is more than one frame forward in time of where we think we should be; emit
 +                                 a black frame.
 +                              */
 +                              boost::shared_ptr<Image> black (
 +                                      new SimpleImage (
 +                                              static_cast<AVPixelFormat> (_frame->format),
 +                                              libdcp::Size (video_codec_context()->width, video_codec_context()->height),
 +                                              true
 +                                              )
 +                                      );
 +                              
 +                              black->make_black ();
 +                              video (image, false, _video_position);
 +                              delta -= one_frame;
 +                      }
 +
 +                      if (delta > -one_frame) {
 +                              /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
 +                              video (image, false, _video_position);
 +                      }
 +              } else {
 +                      shared_ptr<const Film> film = _film.lock ();
 +                      assert (film);
 +                      film->log()->log ("Dropping frame without PTS");
 +              }
        }
 -}
  
 -/** @return Length (in video frames) according to our content's header */
 -SourceFrame
 -FFmpegDecoder::length () const
 -{
 -      return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second();
 +      return true;
  }
  
 +      
  void
 -FFmpegDecoder::decode_audio_packet ()
 +FFmpegDecoder::setup_subtitle ()
  {
 -      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.
 -      */
 +      boost::mutex::scoped_lock lm (_mutex);
        
 -      AVPacket copy_packet = _packet;
 +      if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->id >= int (_format_context->nb_streams)) {
 +              return;
 +      }
  
 -      while (copy_packet.size > 0) {
 +      _subtitle_codec_context = _format_context->streams[_ffmpeg_content->subtitle_stream()->id]->codec;
 +      _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
  
 -              int frame_finished;
 -              int const decode_result = avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &copy_packet);
 -              if (decode_result < 0) {
 -                      /* error */
 -                      break;
 -              }
 -              
 -              if (frame_finished) {
 -                      
 -                      /* Where we are in the source, in seconds */
 -                      double const source_pts_seconds = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
 -                              * av_frame_get_best_effort_timestamp(_frame);
 -                      
 -                      int const data_size = av_samples_get_buffer_size (
 -                              0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
 -                              );
 -                      
 -                      assert (_audio_codec_context->channels == _film->audio_channels());
 -                      Audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds);
 -              }
 -              
 -              copy_packet.data += decode_result;
 -              copy_packet.size -= decode_result;
 +      if (_subtitle_codec == 0) {
 +              throw DecodeError (_("could not find subtitle decoder"));
 +      }
 +      
 +      if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
 +              throw DecodeError (N_("could not open subtitle decoder"));
        }
  }
 +
 +bool
 +FFmpegDecoder::done () const
 +{
 +      bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
 +      bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
 +      return vd && ad;
 +}
 +      
diff --combined src/lib/film.cc
index ad565aca0b2e6bcd16717fd9f64f6d31f0ec8187,ce555ac8b29b3ac33d17e906ec05a3699620da7e..fa75ab1f1d444905dc61a0d864db5fe7a9a8cf6c
  #include <boost/algorithm/string.hpp>
  #include <boost/lexical_cast.hpp>
  #include <boost/date_time.hpp>
 +#include <libxml++/libxml++.h>
 +#include <libcxml/cxml.h>
  #include "film.h"
 -#include "format.h"
  #include "job.h"
  #include "filter.h"
 -#include "transcoder.h"
  #include "util.h"
  #include "job_manager.h"
 -#include "ab_transcode_job.h"
  #include "transcode_job.h"
  #include "scp_dcp_job.h"
  #include "log.h"
 -#include "options.h"
  #include "exceptions.h"
  #include "examine_content_job.h"
  #include "scaler.h"
 -#include "decoder_factory.h"
  #include "config.h"
  #include "version.h"
  #include "ui_signaller.h"
 -#include "video_decoder.h"
 -#include "audio_decoder.h"
 -#include "sndfile_decoder.h"
 -#include "analyse_audio_job.h"
 +#include "playlist.h"
 +#include "player.h"
 +#include "ffmpeg_content.h"
 +#include "imagemagick_content.h"
 +#include "sndfile_content.h"
 +#include "dcp_content_type.h"
 +#include "ratio.h"
  #include "cross.h"
  
  #include "i18n.h"
@@@ -68,12 -68,9 +68,12 @@@ 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::weak_ptr;
  using boost::lexical_cast;
 +using boost::dynamic_pointer_cast;
  using boost::to_upper_copy;
  using boost::ends_with;
  using boost::starts_with;
@@@ -82,31 -79,40 +82,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)
        , _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)
 +      , _dcp_video_frame_rate (24)
 +      , _dcp_audio_channels (MAX_AUDIO_CHANNELS)
+       , _minimum_audio_channels (0)
 -      , _source_frame_rate (0)
        , _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 ();
 -      
--      _log.reset (new FileLog (file ("log")));
 -      
 -      if (must_exist) {
 -              read_metadata ();
 -      } else {
 -              write_metadata ();
 -      }
  }
  
  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)
        , _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)
        , _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)
+       , _minimum_audio_channels (o._minimum_audio_channels)
 -      , _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
          << "_" << scaler()->id()
          << "_" << j2k_bandwidth()
 -        << "_" << boost::lexical_cast<int> (colour_lut());
 -
 -      if (trim_type() == ENCODE) {
 -              s << "_" << trim_start() << "_" << trim_end();
 -      }
 -
 -      if (dcp_ab()) {
 -              pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
 -              s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
 -      }
 +        << "_" << lexical_cast<int> (colour_lut());
  
        return s.str ();
  }
@@@ -184,7 -247,6 +185,6 @@@ Film::info_dir () cons
  string
  Film::internal_video_mxf_dir () const
  {
-       boost::filesystem::path p;
        return dir ("video");
  }
  
@@@ -222,12 -284,13 +222,12 @@@ Film::filename_safe_name () cons
        return o;
  }
  
 -string
 -Film::audio_analysis_path () const
 +boost::filesystem::path
 +Film::audio_analysis_path (shared_ptr<const AudioContent> c) const
  {
 -      boost::filesystem::path p;
 -      p /= "analysis";
 -      p /= content_digest();
 -      return file (p.string ());
 +      boost::filesystem::path p = dir ("analysis");
 +      p /= c->digest();
 +      return p;
  }
  
  /** Add suitable Jobs to the JobManager to create a DCP for this Film */
@@@ -240,7 -303,7 +240,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()));
 -      if (use_content_audio()) {
 -              log()->log ("Using content's audio");
 -      } else {
 -              log()->log (String::compose ("Using external audio (%1 files)", external_audio().size()));
 -      }
 -#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.");
  #endif
        pair<string, int> const c = cpu_info ();
        log()->log (String::compose ("CPU: %1, %2 processors", c.first, c.second));
+       list<pair<string, string> > const m = mount_info ();
+       for (list<pair<string, string> >::const_iterator i = m.begin(); i != m.end(); ++i) {
+               log()->log (String::compose ("Mount: %1 %2", i->first, i->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)));
 -      } else {
 -              r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od)));
 -      }
 -}
 -
 -/** Start a job to analyse the audio of our content file */
 -void
 -Film::analyse_audio ()
 -{
 -      if (_analyse_audio_job) {
 -              return;
 -      }
 -
 -      _analyse_audio_job.reset (new AnalyseAudioJob (shared_from_this()));
 -      _analyse_audio_job->Finished.connect (bind (&Film::analyse_audio_finished, this));
 -      JobManager::instance()->add (_analyse_audio_job);
 -}
 -
 -/** Start a job to examine our content file */
 -void
 -Film::examine_content ()
 -{
 -      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);
 -}
 -
 -void
 -Film::analyse_audio_finished ()
 -{
 -      ensure_ui_thread ();
 -
 -      if (_analyse_audio_job->finished_ok ()) {
 -              AudioAnalysisSucceeded ();
 -      }
 -      
 -      _analyse_audio_job.reset ();
 -}
 -
 -void
 -Film::examine_content_finished ()
 -{
 -      _examine_content_job.reset ();
 +      JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
  }
  
  /** Start a job to send our DCP to the configured TMS */
@@@ -302,7 -427,7 +306,7 @@@ 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;
 +              root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
        }
 -      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 << "minimum_audio_channels " << _minimum_audio_channels << endl;
 -      f << "width " << _size.width << endl;
 -      f << "height " << _size.height << endl;
 -      f << "length " << _length.get_value_or(0) << endl;
 -      f << "content_digest " << _content_digest << endl;
 -
 -      for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
 -              f << "content_audio_stream " << (*i)->to_string () << endl;
 -      }
 -
 -      f << "external_audio_stream " << _sndfile_stream->to_string() << endl;
  
 -      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 ());
        }
  
 -      f << "source_frame_rate " << _source_frame_rate << endl;
 +      root->add_child("Scaler")->add_child_text (_scaler->id ());
 +      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));
++      root->add_child("MinimumAudioChannels")->add_child_text (lexical_cast<string> (_minimum_audio_channels));
 +      _playlist->as_xml (root->add_child ("Playlist"));
 +
 +      doc.write_to_file_formatted (file ("metadata.xml"));
        
        _dirty = false;
  }
@@@ -367,45 -536,179 +372,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;
 +      _name = f.string_child ("Name");
 +      _use_dci_name = f.bool_child ("UseDCIName");
  
 -              if (k == "audio_sample_rate") {
 -                      audio_sample_rate = atoi (v.c_str());
 -              }
 -
 -              /* 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 ());
 -              } else if (k == "minimum_audio_channels") {
 -                      _minimum_audio_channels = 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 = Ratio::from_id (c.get ());
                }
        }
  
 -      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"));
 +      _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");
++      _minimum_audio_channels = f.number_child<int> ("MinimumAudioChannels");
  
 -              /* 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.
   */
@@@ -441,6 -744,67 +447,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;
@@@ -550,6 -929,110 +556,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;
 -      }
 -
 -      /* Do this before we start using FFmpeg ourselves */
 -      run_ffprobe (c, file ("ffprobe.log"), _log);
 -      
 -      /* 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)
 +Film::set_container (Ratio const * 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;
 +              _container = c;
        }
 -      signal_changed (CROP);
 -}
 -
 -void
 -Film::set_filters (vector<Filter const *> f)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _filters = f;
 -      }
 -      signal_changed (FILTERS);
 +      signal_changed (CONTAINER);
  }
  
  void
@@@ -580,6 -1140,123 +586,6 @@@ Film::set_scaler (Scaler const * s
        signal_changed (SCALER);
  }
  
 -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)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_audio_stream = s;
 -      }
 -      signal_changed (CONTENT_AUDIO_STREAM);
 -}
 -
 -void
 -Film::set_external_audio (vector<string> a)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _external_audio = a;
 -      }
 -
 -      shared_ptr<SndfileDecoder> decoder (new SndfileDecoder (shared_from_this(), DecodeOptions()));
 -      if (decoder->audio_stream()) {
 -              _sndfile_stream = decoder->audio_stream ();
 -      }
 -      
 -      signal_changed (EXTERNAL_AUDIO);
 -}
 -
 -void
 -Film::set_use_content_audio (bool e)
 -{
 -      {
 -              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);
 -}
 -
  void
  Film::set_with_subtitles (bool w)
  {
@@@ -641,16 -1318,96 +647,26 @@@ 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_minimum_audio_channels (int c)
+ {
+       {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               _minimum_audio_channels = c;
+       }
+       signal_changed (MINIMUM_AUDIO_CHANNELS);
+ }
+                       
  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 ()
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _length = boost::none;
 -      }
 -      signal_changed (LENGTH);
 -}
 -
 -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)
 +Film::set_dcp_video_frame_rate (int f)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _subtitle_streams = s;
 +              _dcp_video_frame_rate = f;
        }
 -      signal_changed (SUBTITLE_STREAMS);
 +      signal_changed (DCP_VIDEO_FRAME_RATE);
  }
  
 -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
@@@ -678,6 -1438,16 +694,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
  {
@@@ -733,146 -1503,20 +749,146 @@@ Film::have_dcp () cons
        return true;
  }
  
 -bool
 -Film::has_audio () const
 +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::examine_and_add_content (shared_ptr<Content> c)
  {
 -      if (use_content_audio()) {
 -              return audio_stream();
 +      shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
 +      j->Finished.connect (bind (&Film::add_content_weak, this, boost::weak_ptr<Content> (c)));
 +      JobManager::instance()->add (j);
 +}
 +
 +void
 +Film::add_content_weak (weak_ptr<Content> c)
 +{
 +      shared_ptr<Content> content = c.lock ();
 +      if (content) {
 +              add_content (content);
        }
 +}
  
 -      vector<string> const e = external_audio ();
 -      for (vector<string>::const_iterator i = e.begin(); i != e.end(); ++i) {
 -              if (!i->empty ()) {
 -                      return true;
 -              }
 +void
 +Film::add_content (shared_ptr<Content> c)
 +{
 +      /* Add video content after any existing content */
 +      if (dynamic_pointer_cast<VideoContent> (c)) {
 +              c->set_start (_playlist->video_end ());
 +      }
 +
 +      _playlist->add (c);
 +}
 +
 +void
 +Film::remove_content (shared_ptr<Content> c)
 +{
 +      _playlist->remove (c);
 +}
 +
 +Time
 +Film::length () const
 +{
 +      return _playlist->length ();
 +}
 +
 +bool
 +Film::has_subtitles () const
 +{
 +      return _playlist->has_subtitles ();
 +}
 +
 +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);
 +}     
  
 -      return false;
 +int
 +Film::loop () const
 +{
 +      return _playlist->loop ();
 +}
 +
 +void
 +Film::set_loop (int c)
 +{
 +      _playlist->set_loop (c);
 +}
 +
 +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;
  }
  
 +void
 +Film::set_sequence_video (bool s)
 +{
 +      _playlist->set_sequence_video (s);
 +}
 +
 +libdcp::Size
 +Film::full_frame () const
 +{
 +      return libdcp::Size (2048, 1080);
 +}
diff --combined src/lib/film.h
index 5bb9acf297be8091ef7db70934eed336aa657c39,ca9bd57f43a0fbd4439d5db12247b3b2e194ea3f..f5a7c1246c38c2f133f8d82044be6531b26662f8
  */
  
  /** @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 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 info_path (int f) const;
        std::string internal_video_mxf_dir () const;
        std::string internal_video_mxf_filename () const;
 -      std::string audio_analysis_path () const;
 +      boost::filesystem::path audio_analysis_path (boost::shared_ptr<const AudioContent>) const;
  
        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;
 -
 -      void set_dci_date_today ();
 +      libdcp::Size full_frame () const;
  
        bool have_dcp () const;
  
 -      enum TrimType {
 -              CPL,
 -              ENCODE
 -      };
 +      boost::shared_ptr<Player> player () const;
 +      boost::shared_ptr<Playlist> playlist () const;
 +
 +      OutputAudioFrame dcp_audio_frame_rate () 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;
 +
 +      /* 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;
 +
 +      void set_sequence_video (bool);
  
        /** 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,
                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,
                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,
+               MINIMUM_AUDIO_CHANNELS
        };
  
  
                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 {
 +      Ratio const * container () const {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              return _crop;
 -      }
 -
 -      std::vector<Filter const *> filters () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _filters;
 +              return _container;
        }
  
        Scaler const * scaler () const {
                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 {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _still_duration;
 -      }
 -
 -      int still_duration_in_frames () const;
 -
 -      boost::shared_ptr<SubtitleStream> subtitle_stream () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _subtitle_stream;
 -      }
 -
        bool with_subtitles () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _with_subtitles;
                return _dci_metadata;
        }
  
 -      int dcp_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;
 -      }
 -
 -      boost::optional<SourceFrame> length () const {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              return _length;
 -      }
 -      
 -      std::string content_digest () const {
 +      /* XXX: -> "video_frame_rate" */
 +      int dcp_video_frame_rate () 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;
 +              return _dcp_video_frame_rate;
        }
  
 -      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;
 -      
+       int minimum_audio_channels () const {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               return _minimum_audio_channels;
+       }
        /* 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 examine_and_add_content (boost::shared_ptr<Content>);
 +      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 (Ratio 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_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);
 +      void set_dcp_video_frame_rate (int);
 +      void set_dci_date_today ();
+       void set_minimum_audio_channels (int);
  
 -      /** Emitted when some property has changed */
 +      /** Emitted when some property has of the Film has changed */
        mutable boost::signals2::signal<void (Property)> Changed;
  
 -      boost::signals2::signal<void ()> AudioAnalysisSucceeded;
 +      /** Emitted when some property of our content has changed */
 +      mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
  
        /** Current version number of the state file */
        static int const state_version;
  
  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;
 +      void add_content_weak (boost::weak_ptr<Content>);
 +
 +      /** Log to write to */
 +      boost::shared_ptr<Log> _log;
 +      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.) */
 +      Ratio const * _container;
        /** 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;
        /** 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;
 +      int _dcp_audio_channels;
+       int _minimum_audio_channels;
  
 -      /* 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;
 -
        /** true if our state has changed since we last saved it */
        mutable bool _dirty;
  
diff --combined src/lib/ratio.h
index 6916a74912cdaa5c9c322312e867747b40d9f700,0000000000000000000000000000000000000000..5480eee124929b9f99239ea536a909a73ffdfe42
mode 100644,000000..100644
--- /dev/null
@@@ -1,66 -1,0 +1,71 @@@
 +/*
 +    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_RATIO_H
++#define DCPOMATIC_RATIO_H
++
 +#include <vector>
 +#include <libdcp/util.h>
 +
 +class Ratio
 +{
 +public:
 +      Ratio (float ratio, std::string id, std::string n, std::string d)
 +              : _ratio (ratio)
 +              , _id (id)
 +              , _nickname (n)
 +              , _dci_name (d)
 +      {}
 +
 +      libdcp::Size size (libdcp::Size) const;
 +
 +      std::string id () const {
 +              return _id;
 +      }
 +
 +      std::string nickname () const {
 +              return _nickname;
 +      }
 +
 +      std::string dci_name () const {
 +              return _dci_name;
 +      }
 +
 +      float ratio () const {
 +              return _ratio;
 +      }
 +
 +      static void setup_ratios ();
 +      static Ratio const * from_id (std::string i);
 +      static std::vector<Ratio const *> all () {
 +              return _ratios;
 +      }
 +
 +private:
 +      float _ratio;
 +      /** id for use in metadata */
 +      std::string _id;
 +      /** nickname (e.g. Flat, Scope) */
 +      std::string _nickname;
 +      std::string _dci_name;
 +
 +      static std::vector<Ratio const *> _ratios;      
 +};
++
++#endif
diff --combined src/lib/util.h
index c68bb4f1619614b29e91b9ceb7f45c88898547b1,c9e5bef16c0388c34ecbf50182b6ce4d90ac44c9..7af8ffedf27b07e357011bfaa467c6b8a0eb9ff5
@@@ -22,8 -22,8 +22,8 @@@
   *  @brief Some utility functions and classes.
   */
  
 -#ifndef DVDOMATIC_UTIL_H
 -#define DVDOMATIC_UTIL_H
 +#ifndef DCPOMATIC_UTIL_H
 +#define DCPOMATIC_UTIL_H
  
  #include <string>
  #include <vector>
@@@ -37,10 -37,8 +37,10 @@@ extern "C" 
  #include <libavfilter/avfilter.h>
  }
  #include "compose.hpp"
 +#include "types.h"
 +#include "video_content.h"
  
 -#ifdef DVDOMATIC_DEBUG
 +#ifdef DCPOMATIC_DEBUG
  #define TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TIMING);
  #else
  #define TIMING(...)
  #define MAX_AUDIO_CHANNELS 6
  
  class Scaler;
+ class Film;
  
  extern std::string seconds_to_hms (int);
 +extern std::string time_to_hms (Time);
  extern std::string seconds_to_approximate_hms (int);
  extern void stacktrace (std::ostream &, int);
  extern std::string dependency_version_summary ();
  extern double seconds (struct timeval);
 -extern void dvdomatic_setup ();
 -extern void dvdomatic_setup_gettext_i18n (std::string);
 +extern void dcpomatic_setup ();
 +extern void dcpomatic_setup_gettext_i18n (std::string);
  extern std::vector<std::string> split_at_spaces_considering_quotes (std::string);
 -extern std::string md5_digest (std::string);
 +extern std::string md5_digest (boost::filesystem::path);
  extern std::string md5_digest (void const *, int);
  extern void ensure_ui_thread ();
  extern std::string audio_channel_name (int);
 -#ifdef DVDOMATIC_WINDOWS
 +#ifdef DCPOMATIC_WINDOWS
  extern boost::filesystem::path mo_path ();
  #endif
  
 -typedef int SourceFrame;
 -
  struct FrameRateConversion
  {
        FrameRateConversion (float, int);
        std::string description;
  };
  
 -int best_dcp_frame_rate (float);
 -
 -enum ContentType {
 -      STILL, ///< content is still images
 -      VIDEO  ///< content is a video
 -};
 -
 -/** @struct Crop
 - *  @brief A description of the crop of an image or video.
 - */
 -struct Crop
 -{
 -      Crop () : left (0), right (0), top (0), bottom (0) {}
 -
 -      /** Number of pixels to remove from the left-hand side */
 -      int left;
 -      /** Number of pixels to remove from the right-hand side */
 -      int right;
 -      /** Number of pixels to remove from the top */
 -      int top;
 -      /** Number of pixels to remove from the bottom */
 -      int bottom;
 -};
 -
 -extern bool operator== (Crop const & a, Crop const & b);
 -extern bool operator!= (Crop const & a, Crop const & b);
 -
 -/** @struct Position
 - *  @brief A position.
 - */
 -struct Position
 -{
 -      Position ()
 -              : x (0)
 -              , y (0)
 -      {}
 -
 -      Position (int x_, int y_)
 -              : x (x_)
 -              , y (y_)
 -      {}
 -
 -      /** x coordinate */
 -      int x;
 -      /** y coordinate */
 -      int y;
 -};
 -
 -namespace dvdomatic
 -{
 -      
 -/** @struct Rect
 - *  @brief A rectangle.
 - */
 -struct Rect
 -{
 -      Rect ()
 -              : x (0)
 -              , y (0)
 -              , width (0)
 -              , height (0)
 -      {}
 -
 -      Rect (int x_, int y_, int w_, int h_)
 -              : x (x_)
 -              , y (y_)
 -              , width (w_)
 -              , height (h_)
 -      {}
 -
 -      int x;
 -      int y;
 -      int width;
 -      int height;
 -
 -      Position position () const {
 -              return Position (x, y);
 -      }
 -
 -      libdcp::Size size () const {
 -              return libdcp::Size (width, height);
 -      }
 -
 -      Rect intersection (Rect const & other) const;
 -};
 -
 -}
 -
  extern std::string crop_string (Position, libdcp::Size);
 -extern int dcp_audio_sample_rate (int);
 +extern int dcp_audio_frame_rate (int);
  extern std::string colour_lut_index_to_name (int index);
  extern int stride_round_up (int, int const *, int);
  extern int stride_lookup (int c, int const * stride);
@@@ -119,7 -207,7 +120,7 @@@ extern std::string get_optional_string 
  
  /** @class Socket
   *  @brief A class to wrap a boost::asio::ip::tcp::socket with some things
 - *  that are useful for DVD-o-matic.
 + *  that are useful for DCP-o-matic.
   *
   *  This class wraps some things that I could not work out how to do with boost;
   *  most notably, sync read/write calls with timeouts.
@@@ -153,7 -241,68 +154,7 @@@ private
        int _timeout;
  };
  
 -/** @class AudioBuffers
 - *  @brief A class to hold multi-channel audio data in float format.
 - */
 -class AudioBuffers
 -{
 -public:
 -      AudioBuffers (int channels, int frames);
 -      AudioBuffers (AudioBuffers const &);
 -      AudioBuffers (boost::shared_ptr<const AudioBuffers>);
 -      ~AudioBuffers ();
 -
 -      float** data () const {
 -              return _data;
 -      }
 -      
 -      float* data (int) const;
 -
 -      int channels () const {
 -              return _channels;
 -      }
 -
 -      int frames () const {
 -              return _frames;
 -      }
 -
 -      void set_frames (int f);
 -
 -      void make_silent ();
 -      void make_silent (int c);
 -
 -      void copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset);
 -      void move (int from, int to, int frames);
 -
 -private:
 -      /** Number of channels */
 -      int _channels;
 -      /** Number of frames (where a frame is one sample across all channels) */
 -      int _frames;
 -      /** Number of frames that _data can hold */
 -      int _allocated_frames;
 -      /** Audio data (so that, e.g. _data[2][6] is channel 2, sample 6) */
 -      float** _data;
 -};
 -
 -class AudioMapping
 -{
 -public:
 -      AudioMapping (boost::shared_ptr<const Film>);
 -
 -      boost::optional<libdcp::Channel> source_to_dcp (int c) const;
 -      boost::optional<int> dcp_to_source (libdcp::Channel c) const;
 -
 -      int minimum_dcp_channels () const;
 -      int dcp_channels () const;
 -
 -private:
 -      int _source_channels;
 -      int _minimum_channels;
 -};
 -
 -extern int64_t video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second);
 -extern bool still_image_file (std::string);
 +extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
  
  class LocaleGuard
  {
diff --combined src/lib/version.h
index 518862fc472082f77a21bbe974696df57c1d4766,e1ec9067cf69189a34a01ef5c2e1f4e7f70a0b10..b70be8343ec11b4fe86bc9e16c74b6604f5d3fa1
@@@ -1,3 -1,4 +1,4 @@@
  
 -extern char const * dvdomatic_version;
 -extern char const * dvdomatic_git_commit;
 -extern char const * dvdomatic_cxx_flags;
 +extern char const * dcpomatic_version;
 +extern char const * dcpomatic_git_commit;
++extern char const * dcpomatic_cxx_flags;
diff --combined src/lib/writer.cc
index cf81d5b70a8e4a902a697a7b94899b2a20f52a7a,cff0b5be23925ea09592b03dbcc27410f6e64e9c..cbb84a94019bc0a97fc67abf14e05545ccaf4675
  */
  
  #include <fstream>
+ #include <cerrno>
  #include <libdcp/picture_asset.h>
  #include <libdcp/sound_asset.h>
  #include <libdcp/picture_frame.h>
  #include <libdcp/reel.h>
 +#include <libdcp/dcp.h>
  #include <libdcp/cpl.h>
  #include "writer.h"
  #include "compose.hpp"
  #include "film.h"
 -#include "format.h"
 +#include "ratio.h"
  #include "log.h"
  #include "dcp_video_frame.h"
 +#include "dcp_content_type.h"
 +#include "player.h"
 +#include "audio_mapping.h"
  #include "config.h"
 +#include "job.h"
  
  #include "i18n.h"
  
@@@ -48,9 -44,8 +49,9 @@@ using boost::shared_ptr
  
  int const Writer::_maximum_frames_in_memory = 8;
  
 -Writer::Writer (shared_ptr<Film> f)
 +Writer::Writer (shared_ptr<const Film> f, shared_ptr<Job> j)
        : _film (f)
 +      , _job (j)
        , _first_nonexistant_frame (0)
        , _thread (0)
        , _finish (false)
                new libdcp::MonoPictureAsset (
                        _film->internal_video_mxf_dir (),
                        _film->internal_video_mxf_filename (),
 -                      _film->dcp_frame_rate (),
 -                      _film->format()->dcp_size ()
 +                      _film->dcp_video_frame_rate (),
 +                      _film->container()->size (_film->full_frame ())
                        )
                );
  
        _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
  
 -      AudioMapping m (_film);
 +      _sound_asset.reset (
 +              new libdcp::SoundAsset (
 +                      _film->dir (_film->dcp_name()),
 +                      _film->dcp_audio_mxf_filename (),
 +                      _film->dcp_video_frame_rate (),
 +                      _film->dcp_audio_channels (),
 +                      _film->dcp_audio_frame_rate()
 +                      )
 +              );
        
 -      if (m.dcp_channels() > 0) {
 -              _sound_asset.reset (
 -                      new libdcp::SoundAsset (
 -                              _film->dir (_film->dcp_name()),
 -                              _film->dcp_audio_mxf_filename (),
 -                              _film->dcp_frame_rate (),
 -                              m.dcp_channels (),
 -                              dcp_audio_sample_rate (_film->audio_stream()->sample_rate())
 -                              )
 -                      );
 -
 -              _sound_asset_writer = _sound_asset->start_write ();
 -      }
 +      _sound_asset_writer = _sound_asset->start_write ();
  
        _thread = new boost::thread (boost::bind (&Writer::thread, this));
  }
@@@ -202,10 -201,6 +203,10 @@@ tr
                        }
                        }
                        lock.lock ();
 +                      
 +                      if (_film->length ()) {
 +                              _job->set_progress (float(_full_written + _fake_written + _repeat_written) / _film->time_to_video_frames (_film->length()));
 +                      }
  
                        ++_last_written_frame;
                }
@@@ -261,11 -256,21 +262,11 @@@ Writer::finish (
        _thread = 0;
  
        _picture_asset_writer->finalize ();
 -
 -      if (_sound_asset_writer) {
 -              _sound_asset_writer->finalize ();
 -      }
 -
 +      _sound_asset_writer->finalize ();
 +      
        int const frames = _last_written_frame + 1;
 -      int duration = 0;
 -      if (_film->trim_type() == Film::CPL) {
 -              duration = frames - _film->trim_start() - _film->trim_end();
 -              _picture_asset->set_entry_point (_film->trim_start ());
 -      } else {
 -              duration = frames;
 -      }
        
 -      _picture_asset->set_duration (duration);
 +      _picture_asset->set_duration (frames);
  
        /* Hard-link the video MXF into the DCP */
  
  
        _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
        _picture_asset->set_file_name (_film->dcp_video_mxf_filename ());
 -
 -      if (_sound_asset) {
 -              if (_film->trim_type() == Film::CPL) {
 -                      _sound_asset->set_entry_point (_film->trim_start ());
 -              }
 -              _sound_asset->set_duration (duration);
 -      }
 +      _sound_asset->set_duration (frames);
        
        libdcp::DCP dcp (_film->dir (_film->dcp_name()));
  
                        _film->dcp_name(),
                        _film->dcp_content_type()->libdcp_kind (),
                        frames,
 -                      _film->dcp_frame_rate ()
 +                      _film->dcp_video_frame_rate ()
                        )
                );
        
@@@ -342,8 -353,9 +343,9 @@@ Writer::check_existing_picture_mxf (
        boost::filesystem::path p;
        p /= _film->internal_video_mxf_dir ();
        p /= _film->internal_video_mxf_filename ();
-       FILE* mxf = fopen (p.string().c_str(), N_("rb"));
+       FILE* mxf = fopen (p.string().c_str(), "rb");
        if (!mxf) {
+               _film->log()->log (String::compose ("Could not open existing MXF at %1 (errno=%2)", p.string(), errno));
                return;
        }
  
diff --combined src/lib/wscript
index 3e7f2e33be88ada8822bfd2b8e5312f02f1173cb,7c7a64d580bd63c002b03ba0f06469710af655c2..2f86539841da3efade020391e8b9a97aeb8972a6
@@@ -2,57 -2,53 +2,57 @@@ import o
  import i18n
  
  sources = """
 -          ab_transcode_job.cc
 -        ab_transcoder.cc
            analyse_audio_job.cc
            audio_analysis.cc
 +          audio_buffers.cc
 +          audio_content.cc
            audio_decoder.cc
 -          audio_source.cc
 +          audio_mapping.cc
            config.cc
 -          combiner.cc
 +          content.cc
            cross.cc
            dci_metadata.cc
            dcp_content_type.cc
            dcp_video_frame.cc
            decoder.cc
 -          decoder_factory.cc
 -          delay_line.cc
            dolby_cp750.cc
            encoder.cc
            examine_content_job.cc
            exceptions.cc
            filter_graph.cc
 +          ffmpeg.cc
 +          ffmpeg_content.cc
            ffmpeg_decoder.cc
 +          ffmpeg_examiner.cc
            film.cc
            filter.cc
 -          format.cc
 -          gain.cc
            image.cc
 +          imagemagick_content.cc
            imagemagick_decoder.cc
 +          imagemagick_examiner.cc
            job.cc
            job_manager.cc
            log.cc
            lut.cc
 -          matcher.cc
 +          player.cc
 +          playlist.cc
 +          ratio.cc
 +          resampler.cc
            scp_dcp_job.cc
            scaler.cc
            server.cc
 +          sndfile_content.cc
            sndfile_decoder.cc
            sound_processor.cc
 -          stream.cc
            subtitle.cc
            timer.cc
            transcode_job.cc
            transcoder.cc
 -          trimmer.cc
 +          types.cc
            ui_signaller.cc
            util.cc
 +          video_content.cc
            video_decoder.cc
 -          video_source.cc
            writer.cc
            """
  
@@@ -62,28 -58,26 +62,28 @@@ 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.uselib += ' WINSOCK2 BFD DBGHELP IBERTY SHLWAPI'
          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')
diff --combined src/tools/dcpomatic.cc
index 683e60ceb2b930e56b23c492ac889d23a4dca38b,0000000000000000000000000000000000000000..ac39d4fedb823ccdf76c83de4f35f5cd04871dfc
mode 100644,000000..100644
--- /dev/null
@@@ -1,518 -1,0 +1,518 @@@
- #endif        
 +/*
 +    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +*/
 +
 +#include <iostream>
 +#include <fstream>
 +#include <boost/filesystem.hpp>
 +#ifdef __WXMSW__
 +#include <shellapi.h>
 +#endif
 +#ifdef __WXOSX__
 +#include <ApplicationServices/ApplicationServices.h>
 +#endif
 +#include <wx/generic/aboutdlgg.h>
 +#include <wx/stdpaths.h>
 +#include <wx/cmdline.h>
 +#include "wx/film_viewer.h"
 +#include "wx/film_editor.h"
 +#include "wx/job_manager_view.h"
 +#include "wx/config_dialog.h"
 +#include "wx/job_wrapper.h"
 +#include "wx/wx_util.h"
 +#include "wx/new_film_dialog.h"
 +#include "wx/properties_dialog.h"
 +#include "wx/wx_ui_signaller.h"
 +#include "wx/about_dialog.h"
 +#include "lib/film.h"
 +#include "lib/config.h"
 +#include "lib/util.h"
 +#include "lib/version.h"
 +#include "lib/ui_signaller.h"
 +#include "lib/log.h"
 +
 +using std::cout;
 +using std::string;
 +using std::wstring;
 +using std::stringstream;
 +using std::map;
 +using std::make_pair;
 +using std::exception;
 +using std::ofstream;
 +using boost::shared_ptr;
 +
 +static FilmEditor* film_editor = 0;
 +static FilmViewer* film_viewer = 0;
 +static shared_ptr<Film> film;
 +static std::string log_level;
 +static std::string film_to_load;
 +static std::string film_to_create;
 +static wxMenu* jobs_menu = 0;
 +
 +static void set_menu_sensitivity ();
 +
 +class FilmChangedDialog
 +{
 +public:
 +      FilmChangedDialog ()
 +      {
 +              _dialog = new wxMessageDialog (
 +                      0,
 +                      wxString::Format (_("Save changes to film \"%s\" before closing?"), std_to_wx (film->name ()).data()),
 +                      _("Film changed"),
 +                      wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION
 +                      );
 +      }
 +
 +      ~FilmChangedDialog ()
 +      {
 +              _dialog->Destroy ();
 +      }
 +
 +      int run ()
 +      {
 +              return _dialog->ShowModal ();
 +      }
 +
 +private:      
 +      wxMessageDialog* _dialog;
 +};
 +
 +
 +void
 +maybe_save_then_delete_film ()
 +{
 +      if (!film) {
 +              return;
 +      }
 +                      
 +      if (film->dirty ()) {
 +              FilmChangedDialog d;
 +              switch (d.run ()) {
 +              case wxID_NO:
 +                      break;
 +              case wxID_YES:
 +                      film->write_metadata ();
 +                      break;
 +              }
 +      }
 +      
 +      film.reset ();
 +}
 +
 +enum Sensitivity {
 +      ALWAYS,
 +      NEEDS_FILM
 +};
 +
 +map<wxMenuItem*, Sensitivity> menu_items;
 +      
 +void
 +add_item (wxMenu* menu, wxString text, int id, Sensitivity sens)
 +{
 +      wxMenuItem* item = menu->Append (id, text);
 +      menu_items.insert (make_pair (item, sens));
 +}
 +
 +void
 +set_menu_sensitivity ()
 +{
 +      for (map<wxMenuItem*, Sensitivity>::iterator i = menu_items.begin(); i != menu_items.end(); ++i) {
 +              if (i->second == NEEDS_FILM) {
 +                      i->first->Enable (film != 0);
 +              } else {
 +                      i->first->Enable (true);
 +              }
 +      }
 +}
 +
 +enum {
 +      ID_file_new = 1,
 +      ID_file_open,
 +      ID_file_save,
 +      ID_file_properties,
 +      ID_jobs_make_dcp,
 +      ID_jobs_send_dcp_to_tms,
 +      ID_jobs_show_dcp,
 +};
 +
 +void
 +setup_menu (wxMenuBar* m)
 +{
 +      wxMenu* file = new wxMenu;
 +      add_item (file, _("New..."), ID_file_new, ALWAYS);
 +      add_item (file, _("&Open..."), ID_file_open, ALWAYS);
 +      file->AppendSeparator ();
 +      add_item (file, _("&Save"), ID_file_save, NEEDS_FILM);
 +      file->AppendSeparator ();
 +      add_item (file, _("&Properties..."), ID_file_properties, NEEDS_FILM);
 +#ifndef __WXOSX__     
 +      file->AppendSeparator ();
++#endif
 +      add_item (file, _("&Exit"), wxID_EXIT, ALWAYS);
 +
 +#ifdef __WXOSX__      
 +      add_item (file, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
 +#else
 +      wxMenu* edit = new wxMenu;
 +      add_item (edit, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
 +#endif        
 +
 +      jobs_menu = new wxMenu;
 +      add_item (jobs_menu, _("&Make DCP"), ID_jobs_make_dcp, NEEDS_FILM);
 +      add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM);
 +      add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM);
 +
 +      wxMenu* help = new wxMenu;
 +#ifdef __WXOSX__      
 +      add_item (help, _("About DCP-o-matic"), wxID_ABOUT, ALWAYS);
 +#else 
 +      add_item (help, _("About"), wxID_ABOUT, ALWAYS);
 +#endif        
 +
 +      m->Append (file, _("&File"));
 +#ifndef __WXOSX__     
 +      m->Append (edit, _("&Edit"));
 +#endif        
 +      m->Append (jobs_menu, _("&Jobs"));
 +      m->Append (help, _("&Help"));
 +}
 +
 +bool
 +window_closed (wxCommandEvent &)
 +{
 +      maybe_save_then_delete_film ();
 +      return false;
 +}
 +
 +class Frame : public wxFrame
 +{
 +public:
 +      Frame (wxString const & title)
 +              : wxFrame (NULL, -1, title)
 +      {
 +              wxMenuBar* bar = new wxMenuBar;
 +              setup_menu (bar);
 +              SetMenuBar (bar);
 +
 +              Connect (ID_file_new, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_new));
 +              Connect (ID_file_open, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_open));
 +              Connect (ID_file_save, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_save));
 +              Connect (ID_file_properties, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_properties));
 +              Connect (wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_exit));
 +              Connect (wxID_PREFERENCES, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::edit_preferences));
 +              Connect (ID_jobs_make_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_make_dcp));
 +              Connect (ID_jobs_send_dcp_to_tms, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_send_dcp_to_tms));
 +              Connect (ID_jobs_show_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_show_dcp));
 +              Connect (wxID_ABOUT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::help_about));
 +
 +              Connect (wxID_ANY, wxEVT_MENU_OPEN, wxMenuEventHandler (Frame::menu_opened));
 +
 +              film_editor = new FilmEditor (film, this);
 +              film_viewer = new FilmViewer (film, this);
 +              JobManagerView* job_manager_view = new JobManagerView (this, static_cast<JobManagerView::Buttons> (0));
 +
 +              wxBoxSizer* right_sizer = new wxBoxSizer (wxVERTICAL);
 +              right_sizer->Add (film_viewer, 2, wxEXPAND | wxALL, 6);
 +              right_sizer->Add (job_manager_view, 1, wxEXPAND | wxALL, 6);
 +
 +              wxBoxSizer* main_sizer = new wxBoxSizer (wxHORIZONTAL);
 +              main_sizer->Add (film_editor, 1, wxEXPAND | wxALL, 6);
 +              main_sizer->Add (right_sizer, 2, wxEXPAND | wxALL, 6);
 +
 +              set_menu_sensitivity ();
 +
 +              film_editor->FileChanged.connect (bind (&Frame::file_changed, this, _1));
 +              if (film) {
 +                      file_changed (film->directory ());
 +              } else {
 +                      file_changed ("");
 +              }
 +
 +              set_film ();
 +              SetSizer (main_sizer);
 +      }
 +
 +private:
 +
 +      void menu_opened (wxMenuEvent& ev)
 +      {
 +              if (ev.GetMenu() != jobs_menu) {
 +                      return;
 +              }
 +
 +              bool const have_dcp = film && film->have_dcp();
 +              jobs_menu->Enable (ID_jobs_send_dcp_to_tms, have_dcp);
 +              jobs_menu->Enable (ID_jobs_show_dcp, have_dcp);
 +      }
 +
 +      void set_film ()
 +      {
 +              film_viewer->set_film (film);
 +              film_editor->set_film (film);
 +              set_menu_sensitivity ();
 +      }
 +
 +      void file_changed (string f)
 +      {
 +              stringstream s;
 +              s << wx_to_std (_("DCP-o-matic"));
 +              if (!f.empty ()) {
 +                      s << " - " << f;
 +              }
 +              
 +              SetTitle (std_to_wx (s.str()));
 +      }
 +      
 +      void file_new (wxCommandEvent &)
 +      {
 +              NewFilmDialog* d = new NewFilmDialog (this);
 +              int const r = d->ShowModal ();
 +              
 +              if (r == wxID_OK) {
 +
 +                      if (boost::filesystem::exists (d->get_path()) && !boost::filesystem::is_empty(d->get_path())) {
 +                              if (!confirm_dialog (
 +                                          this,
 +                                          std_to_wx (
 +                                                  String::compose (wx_to_std (_("The directory %1 already exists and is not empty.  "
 +                                                                                "Are you sure you want to use it?")),
 +                                                                   d->get_path().c_str())
 +                                                  )
 +                                          )) {
 +                                      return;
 +                              }
 +                      }
 +                      
 +                      maybe_save_then_delete_film ();
 +                      film.reset (new Film (d->get_path ()));
 +                      film->write_metadata ();
 +                      film->log()->set_level (log_level);
 +                      film->set_name (boost::filesystem::path (d->get_path()).filename().generic_string());
 +                      set_film ();
 +              }
 +              
 +              d->Destroy ();
 +      }
 +
 +      void file_open (wxCommandEvent &)
 +      {
 +              wxDirDialog* c = new wxDirDialog (this, _("Select film to open"), wxStandardPaths::Get().GetDocumentsDir(), wxDEFAULT_DIALOG_STYLE | wxDD_DIR_MUST_EXIST);
 +              int r;
 +              while (1) {
 +                      r = c->ShowModal ();
 +                      if (r == wxID_OK && c->GetPath() == wxStandardPaths::Get().GetDocumentsDir()) {
 +                              error_dialog (this, _("You did not select a folder.  Make sure that you select a folder before clicking Open."));
 +                      } else {
 +                              break;
 +                      }
 +              }
 +                      
 +              if (r == wxID_OK) {
 +                      maybe_save_then_delete_film ();
 +                      try {
 +                              film.reset (new Film (wx_to_std (c->GetPath ())));
 +                              film->log()->set_level (log_level);
 +                              set_film ();
 +                      } catch (std::exception& e) {
 +                              wxString p = c->GetPath ();
 +                              wxCharBuffer b = p.ToUTF8 ();
 +                              error_dialog (this, wxString::Format (_("Could not open film at %s (%s)"), p.data(), std_to_wx (e.what()).data()));
 +                      }
 +              }
 +
 +              c->Destroy ();
 +      }
 +
 +      void file_save (wxCommandEvent &)
 +      {
 +              film->write_metadata ();
 +      }
 +
 +      void file_properties (wxCommandEvent &)
 +      {
 +              PropertiesDialog* d = new PropertiesDialog (this, film);
 +              d->ShowModal ();
 +              d->Destroy ();
 +      }
 +      
 +      void file_exit (wxCommandEvent &)
 +      {
 +              maybe_save_then_delete_film ();
 +              Close (true);
 +      }
 +
 +      void edit_preferences (wxCommandEvent &)
 +      {
 +              ConfigDialog* d = new ConfigDialog (this);
 +              d->ShowModal ();
 +              d->Destroy ();
 +              Config::instance()->write ();
 +      }
 +
 +      void jobs_make_dcp (wxCommandEvent &)
 +      {
 +              JobWrapper::make_dcp (this, film);
 +      }
 +      
 +      void jobs_send_dcp_to_tms (wxCommandEvent &)
 +      {
 +              film->send_dcp_to_tms ();
 +      }
 +
 +      void jobs_show_dcp (wxCommandEvent &)
 +      {
 +#ifdef __WXMSW__
 +              string d = film->directory();
 +              wstring w;
 +              w.assign (d.begin(), d.end());
 +              ShellExecute (0, L"open", w.c_str(), 0, 0, SW_SHOWDEFAULT);
 +#else
 +              int r = system ("which nautilus");
 +              if (WEXITSTATUS (r) == 0) {
 +                      system (string ("nautilus " + film->directory()).c_str ());
 +              } else {
 +                      int r = system ("which konqueror");
 +                      if (WEXITSTATUS (r) == 0) {
 +                              system (string ("konqueror " + film->directory()).c_str ());
 +                      }
 +              }
 +#endif                
 +      }
 +
 +      void help_about (wxCommandEvent &)
 +      {
 +              AboutDialog* d = new AboutDialog (this);
 +              d->ShowModal ();
 +              d->Destroy ();
 +      }
 +};
 +
 +#if wxMINOR_VERSION == 9
 +static const wxCmdLineEntryDesc command_line_description[] = {
 +      { wxCMD_LINE_OPTION, "l", "log", "set log level (silent, verbose or timing)", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
 +      { wxCMD_LINE_SWITCH, "n", "new", "create new film", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
 +        { wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
 +      { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
 +};
 +#else
 +static const wxCmdLineEntryDesc command_line_description[] = {
 +      { wxCMD_LINE_OPTION, wxT("l"), wxT("log"), wxT("set log level (silent, verbose or timing)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
 +      { wxCMD_LINE_SWITCH, wxT("n"), wxT("new"), wxT("create new film"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
 +        { wxCMD_LINE_PARAM, 0, 0, wxT("film to load or create"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
 +      { wxCMD_LINE_NONE, wxT(""), wxT(""), wxT(""), wxCmdLineParamType (0), 0 }
 +};
 +#endif
 +
 +class App : public wxApp
 +{
 +      bool OnInit ()
 +      {
 +              if (!wxApp::OnInit()) {
 +                      return false;
 +              }
 +              
 +#ifdef DCPOMATIC_LINUX        
 +              unsetenv ("UBUNTU_MENUPROXY");
 +#endif
 +
 +#ifdef __WXOSX__              
 +              ProcessSerialNumber serial;
 +              GetCurrentProcess (&serial);
 +              TransformProcessType (&serial, kProcessTransformToForegroundApplication);
 +#endif                
 +
 +              wxInitAllImageHandlers ();
 +
 +              /* Enable i18n; this will create a Config object
 +                 to look for a force-configured language.  This Config
 +                 object will be wrong, however, because dcpomatic_setup
 +                 hasn't yet been called and there aren't any scalers, filters etc.
 +                 set up yet.
 +              */
 +              dcpomatic_setup_i18n ();
 +
 +              /* Set things up, including scalers / filters etc.
 +                 which will now be internationalised correctly.
 +              */
 +              dcpomatic_setup ();
 +
 +              /* Force the configuration to be re-loaded correctly next
 +                 time it is needed.
 +              */
 +              Config::drop ();
 +
 +              if (!film_to_load.empty() && boost::filesystem::is_directory (film_to_load)) {
 +                      try {
 +                              film.reset (new Film (film_to_load));
 +                              film->read_metadata ();
 +                              film->log()->set_level (log_level);
 +                      } catch (exception& e) {
 +                              error_dialog (0, std_to_wx (String::compose (wx_to_std (_("Could not load film %1 (%2)")), film_to_load, e.what())));
 +                      }
 +              }
 +
 +              if (!film_to_create.empty ()) {
 +                      film.reset (new Film (film_to_create));
 +                      film->write_metadata ();
 +                      film->log()->set_level (log_level);
 +                      film->set_name (boost::filesystem::path (film_to_create).filename().generic_string ());
 +              }
 +
 +              Frame* f = new Frame (_("DCP-o-matic"));
 +              SetTopWindow (f);
 +              f->Maximize ();
 +              f->Show ();
 +
 +              ui_signaller = new wxUISignaller (this);
 +              this->Connect (-1, wxEVT_IDLE, wxIdleEventHandler (App::idle));
 +
 +              return true;
 +      }
 +
 +      void OnInitCmdLine (wxCmdLineParser& parser)
 +      {
 +              parser.SetDesc (command_line_description);
 +              parser.SetSwitchChars (wxT ("-"));
 +      }
 +
 +      bool OnCmdLineParsed (wxCmdLineParser& parser)
 +      {
 +              if (parser.GetParamCount() > 0) {
 +                      if (parser.Found (wxT ("new"))) {
 +                              film_to_create = wx_to_std (parser.GetParam (0));
 +                      } else {
 +                              film_to_load = wx_to_std (parser.GetParam(0));
 +                      }
 +              }
 +
 +              wxString log;
 +              if (parser.Found (wxT ("log"), &log)) {
 +                      log_level = wx_to_std (log);
 +              }
 +
 +              return true;
 +      }
 +
 +      void idle (wxIdleEvent &)
 +      {
 +              ui_signaller->ui_idle ();
 +      }
 +};
 +
 +IMPLEMENT_APP (App)
index 8623d919468ab3f95d7e2230000a2cc7e667c2f4,0000000000000000000000000000000000000000..ee9e2cdc08fddc0746bd08f5afbdef63745e4f7f
mode 100644,000000..100644
--- /dev/null
@@@ -1,199 -1,0 +1,204 @@@
-               int c = getopt_long (argc, argv, "vhdnrl:", long_options, &option_index);
 +/*
 +    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 "film.h"
 +#include "filter.h"
 +#include "transcode_job.h"
 +#include "job_manager.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"
++           << "  -f, --flags        show flags passed to C++ compiler on build\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'},
++                      { "flags", no_argument, 0, 'f'},
 +                      { "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, "vhdfnrl:", 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 'f':
++                      cout << dcpomatic_cxx_flags << "\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 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/tools/wscript
index 38d986f25fe0af7ae42383af65dc5fea56cc6bbf,20a92cad2f4adb38df59f88394426b4f0306853b..c7ab4460401ef99656ddacdbd11edcd2576038db
@@@ -4,29 -4,31 +4,31 @@@ from waflib import Log
  import i18n
  
  def build(bld):
 -    for t in ['makedcp', 'servomatic_cli', 'servomatictest']:
 +    for t in ['dcpomatic_cli', 'dcpomatic_server_cli']:
          obj = bld(features = 'cxx cxxprogram')
-         obj.uselib = 'BOOST_THREAD OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC'
 -        obj.uselib = 'BOOST_THREAD OPENJPEG DCP AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML WXWIDGETS'
++        obj.uselib = 'BOOST_THREAD OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC WXWIDGETS'
          obj.includes = ['..']
 -        obj.use    = ['libdvdomatic']
 +        obj.use    = ['libdcpomatic']
          obj.source = '%s.cc' % t
          obj.target = t
  
      if not bld.env.DISABLE_GUI:
 -        for t in ['dvdomatic', 'dvdomatic_batch', 'servomatic_gui']:
 +        for t in ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server']:
              obj = bld(features = 'cxx cxxprogram')
-             obj.uselib = 'DCP CXML OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC'
+             obj.uselib = 'DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML WXWIDGETS'
+             if bld.env.STATIC:
+                 obj.uselib += ' GTK'
              obj.includes = ['..']
 -            obj.use    = ['libdvdomatic', 'libdvdomatic-wx']
 +            obj.use    = ['libdcpomatic', 'libdcpomatic-wx']
              obj.source = '%s.cc' % t
              if bld.env.TARGET_WINDOWS:
 -                obj.source += ' ../../platform/windows/dvdomatic.rc'
 +                obj.source += ' ../../platform/windows/dcpomatic.rc'
              obj.target = t
  
 -        i18n.po_to_mo(os.path.join('src', 'tools'), 'dvdomatic', bld)
 +        i18n.po_to_mo(os.path.join('src', 'tools'), 'dcpomatic', bld)
  
  def pot(bld):
 -    i18n.pot(os.path.join('src', 'tools'), 'dvdomatic.cc', 'dvdomatic')
 +    i18n.pot(os.path.join('src', 'tools'), 'dcpomatic.cc', 'dcpomatic')
  
  def pot_merge(bld):
 -    i18n.pot_merge(os.path.join('src', 'tools'), 'dvdomatic')
 +    i18n.pot_merge(os.path.join('src', 'tools'), 'dcpomatic')
diff --combined src/wx/about_dialog.cc
index 0c56cf1be0ea56c865922f93c590e006f6c4346d,cbac3ed4339541c21a84cdd348da77adccc48af8..ca19326a1de2abacd87ef7311332d4258cc1c4fe
@@@ -27,7 -27,7 +27,7 @@@
  using std::vector;
  
  AboutDialog::AboutDialog (wxWindow* parent)
 -      : wxDialog (parent, wxID_ANY, _("About DVD-o-matic"))
 +      : wxDialog (parent, wxID_ANY, _("About DCP-o-matic"))
  {
        wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
        
        wxFont version_font (*wxNORMAL_FONT);
        version_font.SetWeight (wxFONTWEIGHT_BOLD);
        
 -      wxStaticText* t = new wxStaticText (this, wxID_ANY, _("DVD-o-matic"));
 +      wxStaticText* t = new wxStaticText (this, wxID_ANY, _("DCP-o-matic"));
        t->SetFont (title_font);
        sizer->Add (t, wxSizerFlags().Centre().Border());
  
        wxString s;
 -      if (strcmp (dvdomatic_git_commit, "release") == 0) {
 -              t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1", dvdomatic_version)));
 +      if (strcmp (dcpomatic_git_commit, "release") == 0) {
 +              t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1", dcpomatic_version)));
        } else {
 -              t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1 git %2", dvdomatic_version, dvdomatic_git_commit)));
 +              t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1 git %2", dcpomatic_version, dcpomatic_git_commit)));
        }
        t->SetFont (version_font);
        sizer->Add (t, wxSizerFlags().Centre().Border());
@@@ -62,8 -62,8 +62,8 @@@
  
        wxHyperlinkCtrl* h = new wxHyperlinkCtrl (
                this, wxID_ANY,
 -              wxT ("www.carlh.net/software/dvdomatic"),
 -              wxT ("http://www.carlh.net/software/dvdomatic")
 +              wxT ("dcpomatic.com"),
 +              wxT ("http://dcpomatic.com")
                );
  
        sizer->Add (h, wxSizerFlags().Centre().Border());
  
        sizer->Add (_notebook, wxSizerFlags().Centre().Border().Expand());
        
 -#if 0 
 -      info.SetWebSite (wxT ("http://carlh.net/software/dvdomatic"));
 -#endif
 -
        SetSizerAndFit (sizer);
  }
  
@@@ -138,7 -142,7 +138,7 @@@ AboutDialog::add_section (wxString name
  
        int c = 0;
        for (size_t i = 0; i < credits.Count(); ++i) {
-               add_label_to_sizer (sizers[c], panel, credits[i]);
+               add_label_to_sizer (sizers[c], panel, credits[i], false);
                ++c;
                if (c == N) {
                        c = 0;
diff --combined src/wx/audio_dialog.cc
index ea7cc60dd0d135044ce8acc94c452bcc5fc8ffa0,21e4e5940e4b100c089ec1e8f1175371c434aa17..22e09cc7a8c1988973c21011a2027f064803ad38
  */
  
  #include <boost/filesystem.hpp>
 +#include "lib/audio_analysis.h"
 +#include "lib/film.h"
  #include "audio_dialog.h"
  #include "audio_plot.h"
 -#include "audio_analysis.h"
 -#include "film.h"
  #include "wx_util.h"
  
  using boost::shared_ptr;
@@@ -43,6 -43,7 +43,6 @@@ AudioDialog::AudioDialog (wxWindow* par
                wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Channels"));
                side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
        }
 -      
  
        for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
                _channel_checkbox[i] = new wxCheckBox (this, wxID_ANY, std_to_wx (audio_channel_name (i)));
  }
  
  void
 -AudioDialog::set_film (boost::shared_ptr<Film> f)
 +AudioDialog::set_content (shared_ptr<AudioContent> c)
  {
 -      _film_changed_connection.disconnect ();
 -      _film_audio_analysis_succeeded_connection.disconnect ();
 +      _content_changed_connection.disconnect ();
        
 -      _film = f;
 +      _content = c;
  
        try_to_load_analysis ();
 -      setup_channels ();
 -      _plot->set_gain (_film->audio_gain ());
 +      _plot->set_gain (_content->audio_gain ());
  
 -      _film_changed_connection = _film->Changed.connect (bind (&AudioDialog::film_changed, this, _1));
 -      _film_audio_analysis_succeeded_connection = _film->AudioAnalysisSucceeded.connect (bind (&AudioDialog::try_to_load_analysis, this));
 +      _content_changed_connection = _content->Changed.connect (bind (&AudioDialog::content_changed, this, _2));
  
 -      SetTitle (wxString::Format (_("DVD-o-matic audio - %s"), std_to_wx(_film->name()).data()));
 +      SetTitle (wxString::Format (_("DCP-o-matic audio - %s"), std_to_wx(_content->file().filename().string()).data()));
  }
  
 -void
 -AudioDialog::setup_channels ()
 -{
 -      if (!_film->audio_stream()) {
 -              return;
 -      }
 -
 -      AudioMapping m (_film);
 -      
 -      for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
 -              if (m.dcp_to_source(static_cast<libdcp::Channel>(i))) {
 -                      _channel_checkbox[i]->Show ();
 -              } else {
 -                      _channel_checkbox[i]->Hide ();
 -              }
 -      }
 -}     
--
  void
  AudioDialog::try_to_load_analysis ()
  {
        shared_ptr<AudioAnalysis> a;
  
 -      if (boost::filesystem::exists (_film->audio_analysis_path())) {
 -              a.reset (new AudioAnalysis (_film->audio_analysis_path ()));
 +      if (boost::filesystem::exists (_content->audio_analysis_path())) {
 +              a.reset (new AudioAnalysis (_content->audio_analysis_path ()));
        } else {
                if (IsShown ()) {
 -                      _film->analyse_audio ();
 +                      _content->analyse_audio (bind (&AudioDialog::try_to_load_analysis, this));
                }
        }
                
        _plot->set_analysis (a);
  
 -      AudioMapping m (_film);
 -      optional<libdcp::Channel> c = m.source_to_dcp (0);
 -      if (c) {
 -              _channel_checkbox[c.get()]->SetValue (true);
 -              _plot->set_channel_visible (0, true);
 +      if (_channel_checkbox[0]) {
 +              _channel_checkbox[0]->SetValue (true);
        }
 +      _plot->set_channel_visible (0, true);
  
        for (int i = 0; i < AudioPoint::COUNT; ++i) {
                _type_checkbox[i]->SetValue (true);
@@@ -134,14 -157,27 +133,14 @@@ AudioDialog::channel_clicked (wxCommand
  
        assert (c < MAX_AUDIO_CHANNELS);
  
 -      AudioMapping m (_film);
 -      optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (c));
 -      if (s) {
 -              _plot->set_channel_visible (s.get(), _channel_checkbox[c]->GetValue ());
 -      }
 +      _plot->set_channel_visible (c, _channel_checkbox[c]->GetValue ());
  }
  
  void
 -AudioDialog::film_changed (Film::Property p)
 +AudioDialog::content_changed (int p)
  {
 -      switch (p) {
 -      case Film::AUDIO_GAIN:
 -              _plot->set_gain (_film->audio_gain ());
 -              break;
 -      case Film::CONTENT_AUDIO_STREAM:
 -      case Film::EXTERNAL_AUDIO:
 -      case Film::USE_CONTENT_AUDIO:
 -              setup_channels ();
 -              break;
 -      default:
 -              break;
 +      if (p == AudioContentProperty::AUDIO_GAIN) {
 +              _plot->set_gain (_content->audio_gain ());
        }
  }
  
diff --combined src/wx/config_dialog.cc
index 844b03ad7e65c5653a698c26c6992f87e835d1fd,e622c331b647b91033995e04c3a167919919c3e4..e66be174d003045b1bc8807a8f21e14935e81a99
@@@ -28,7 -28,7 +28,7 @@@
  #include <wx/notebook.h>
  #include "lib/config.h"
  #include "lib/server.h"
 -#include "lib/format.h"
 +#include "lib/ratio.h"
  #include "lib/scaler.h"
  #include "lib/filter.h"
  #include "lib/dcp_content_type.h"
@@@ -57,6 -57,8 +57,6 @@@ ConfigDialog::ConfigDialog (wxWindow* p
        _notebook->AddPage (_metadata_panel, _("Metadata"), false);
        make_tms_panel ();
        _notebook->AddPage (_tms_panel, _("TMS"), false);
 -      make_ab_panel ();
 -      _notebook->AddPage (_ab_panel, _("A/B mode"), false);
  
        wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
        overall_sizer->Add (s, 1, wxEXPAND | wxALL, 6);
@@@ -78,7 -80,7 +78,7 @@@ ConfigDialog::make_misc_panel (
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
        _misc_panel->SetSizer (s);
  
-       wxFlexGridSizer* table = new wxFlexGridSizer (3, 6, 6);
 -      wxFlexGridSizer* table = new wxFlexGridSizer (3, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
++      wxFlexGridSizer* table = new wxFlexGridSizer (3, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
        s->Add (table, 1, wxALL | wxEXPAND, 8);
  
@@@ -93,7 -95,7 +93,7 @@@
        table->Add (_language, 1, wxEXPAND);
        table->AddSpacer (0);
  
-       wxStaticText* restart = add_label_to_sizer (table, _misc_panel, _("(restart DVD-o-matic to see language changes)"));
+       wxStaticText* restart = add_label_to_sizer (table, _misc_panel, _("(restart DVD-o-matic to see language changes)"), false);
        wxFont font = restart->GetFont();
        font.SetStyle (wxFONTSTYLE_ITALIC);
        font.SetPointSize (font.GetPointSize() - 1);
        table->AddSpacer (0);
        table->AddSpacer (0);
  
-       add_label_to_sizer (table, _misc_panel, _("Threads to use for encoding on this host"));
+       add_label_to_sizer (table, _misc_panel, _("Threads to use for encoding on this host"), true);
        _num_local_encoding_threads = new wxSpinCtrl (_misc_panel);
-       table->Add (_num_local_encoding_threads, 1, wxEXPAND);
+       table->Add (_num_local_encoding_threads, 1);
        table->AddSpacer (0);
  
-       add_label_to_sizer (table, _misc_panel, _("Default duration of still images"));
++      add_label_to_sizer (table, _misc_panel, _("Default duration of still images"), true);
 +      _default_still_length = new wxSpinCtrl (_misc_panel);
 +      table->Add (_default_still_length, 1, wxEXPAND);
-       add_label_to_sizer (table, _misc_panel, _("s"));
++      add_label_to_sizer (table, _misc_panel, _("s"), false);
 +
-       add_label_to_sizer (table, _misc_panel, _("Default directory for new films"));
- #ifdef __WXMSW__
+       add_label_to_sizer (table, _misc_panel, _("Default directory for new films"), true);
 -#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
++#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
        _default_directory = new DirPickerCtrl (_misc_panel);
  #else 
        _default_directory = new wxDirPickerCtrl (_misc_panel, wxDD_DIR_MUST_EXIST);
        table->Add (_default_directory, 1, wxEXPAND);
        table->AddSpacer (0);
  
-       add_label_to_sizer (table, _misc_panel, _("Default DCI name details"));
+       add_label_to_sizer (table, _misc_panel, _("Default DCI name details"), true);
        _default_dci_metadata_button = new wxButton (_misc_panel, wxID_ANY, _("Edit..."));
        table->Add (_default_dci_metadata_button);
        table->AddSpacer (1);
  
-       add_label_to_sizer (table, _misc_panel, _("Default container"));
 -      add_label_to_sizer (table, _misc_panel, _("Default format"), true);
 -      _default_format = new wxChoice (_misc_panel, wxID_ANY);
 -      table->Add (_default_format);
++      add_label_to_sizer (table, _misc_panel, _("Default container"), true);
 +      _default_container = new wxChoice (_misc_panel, wxID_ANY);
 +      table->Add (_default_container);
        table->AddSpacer (1);
  
-       add_label_to_sizer (table, _misc_panel, _("Default content type"));
+       add_label_to_sizer (table, _misc_panel, _("Default content type"), true);
        _default_dcp_content_type = new wxChoice (_misc_panel, wxID_ANY);
        table->Add (_default_dcp_content_type);
        table->AddSpacer (1);
        _num_local_encoding_threads->SetValue (config->num_local_encoding_threads ());
        _num_local_encoding_threads->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ConfigDialog::num_local_encoding_threads_changed), 0, this);
  
 +      _default_still_length->SetRange (1, 3600);
 +      _default_still_length->SetValue (config->default_still_length ());
 +      _default_still_length->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ConfigDialog::default_still_length_changed), 0, this);
 +
        _default_directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir()))));
        _default_directory->Connect (wxID_ANY, wxEVT_COMMAND_DIRPICKER_CHANGED, wxCommandEventHandler (ConfigDialog::default_directory_changed), 0, this);
  
        _default_dci_metadata_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_default_dci_metadata_clicked), 0, this);
  
 -      vector<Format const *> fmt = Format::all ();
 +      vector<Ratio const *> ratio = Ratio::all ();
        int n = 0;
 -      for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
 -              _default_format->Append (std_to_wx ((*i)->name ()));
 -              if (*i == config->default_format ()) {
 -                      _default_format->SetSelection (n);
 +      for (vector<Ratio const *>::iterator i = ratio.begin(); i != ratio.end(); ++i) {
 +              _default_container->Append (std_to_wx ((*i)->nickname ()));
 +              if (*i == config->default_container ()) {
 +                      _default_container->SetSelection (n);
                }
                ++n;
        }
  
 -      _default_format->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::default_format_changed), 0, this);
 +      _default_container->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::default_container_changed), 0, this);
        
        vector<DCPContentType const *> const ct = DCPContentType::all ();
        n = 0;
@@@ -201,23 -194,23 +201,23 @@@ ConfigDialog::make_tms_panel (
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
        _tms_panel->SetSizer (s);
  
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
 -      wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
++      wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
        s->Add (table, 1, wxALL | wxEXPAND, 8);
  
-       add_label_to_sizer (table, _tms_panel, _("IP address"));
+       add_label_to_sizer (table, _tms_panel, _("IP address"), true);
        _tms_ip = new wxTextCtrl (_tms_panel, wxID_ANY);
        table->Add (_tms_ip, 1, wxEXPAND);
  
-       add_label_to_sizer (table, _tms_panel, _("Target path"));
+       add_label_to_sizer (table, _tms_panel, _("Target path"), true);
        _tms_path = new wxTextCtrl (_tms_panel, wxID_ANY);
        table->Add (_tms_path, 1, wxEXPAND);
  
-       add_label_to_sizer (table, _tms_panel, _("User name"));
+       add_label_to_sizer (table, _tms_panel, _("User name"), true);
        _tms_user = new wxTextCtrl (_tms_panel, wxID_ANY);
        table->Add (_tms_user, 1, wxEXPAND);
  
-       add_label_to_sizer (table, _tms_panel, _("Password"));
+       add_label_to_sizer (table, _tms_panel, _("Password"), true);
        _tms_password = new wxTextCtrl (_tms_panel, wxID_ANY);
        table->Add (_tms_password, 1, wxEXPAND);
  
@@@ -240,15 -233,15 +240,15 @@@ ConfigDialog::make_metadata_panel (
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
        _metadata_panel->SetSizer (s);
  
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
 -      wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
++      wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
        s->Add (table, 1, wxALL | wxEXPAND, 8);
  
-       add_label_to_sizer (table, _metadata_panel, _("Issuer"));
+       add_label_to_sizer (table, _metadata_panel, _("Issuer"), true);
        _issuer = new wxTextCtrl (_metadata_panel, wxID_ANY);
        table->Add (_issuer, 1, wxEXPAND);
  
-       add_label_to_sizer (table, _metadata_panel, _("Creator"));
+       add_label_to_sizer (table, _metadata_panel, _("Creator"), true);
        _creator = new wxTextCtrl (_metadata_panel, wxID_ANY);
        table->Add (_creator, 1, wxEXPAND);
  
        _creator->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::creator_changed), 0, this);
  }
  
 -void
 -ConfigDialog::make_ab_panel ()
 -{
 -      _ab_panel = new wxPanel (_notebook);
 -      wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
 -      _ab_panel->SetSizer (s);
 -
 -      wxFlexGridSizer* table = new wxFlexGridSizer (3, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
 -      table->AddGrowableCol (1, 1);
 -      s->Add (table, 1, wxALL, 8);
 -      
 -      add_label_to_sizer (table, _ab_panel, _("Reference scaler"), true);
 -      _reference_scaler = new wxChoice (_ab_panel, wxID_ANY);
 -      vector<Scaler const *> const sc = Scaler::all ();
 -      for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
 -              _reference_scaler->Append (std_to_wx ((*i)->name ()));
 -      }
 -
 -      table->Add (_reference_scaler, 1, wxEXPAND);
 -      table->AddSpacer (0);
 -
 -      {
 -              add_label_to_sizer (table, _ab_panel, _("Reference filters"), true);
 -              wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 -              _reference_filters = new wxStaticText (_ab_panel, wxID_ANY, wxT (""));
 -              s->Add (_reference_filters, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 6);
 -              _reference_filters_button = new wxButton (_ab_panel, wxID_ANY, _("Edit..."));
 -              s->Add (_reference_filters_button, 0);
 -              table->Add (s, 1, wxEXPAND);
 -              table->AddSpacer (0);
 -      }
 -
 -      Config* config = Config::instance ();
 -      
 -      _reference_scaler->SetSelection (Scaler::as_index (config->reference_scaler ()));
 -      _reference_scaler->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::reference_scaler_changed), 0, this);
 -
 -      pair<string, string> p = Filter::ffmpeg_strings (config->reference_filters ());
 -      _reference_filters->SetLabel (std_to_wx (p.first) + N_(" ") + std_to_wx (p.second));
 -      _reference_filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_reference_filters_clicked), 0, this);
 -}
 -
  void
  ConfigDialog::make_servers_panel ()
  {
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
        _servers_panel->SetSizer (s);
  
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
 -      wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
++      wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (0, 1);
        s->Add (table, 1, wxALL | wxEXPAND, 8);
  
        {
                wxSizer* s = new wxBoxSizer (wxVERTICAL);
                _add_server = new wxButton (_servers_panel, wxID_ANY, _("Add"));
-               s->Add (_add_server);
+               s->Add (_add_server, 0, wxTOP | wxBOTTOM, 2);
                _edit_server = new wxButton (_servers_panel, wxID_ANY, _("Edit"));
-               s->Add (_edit_server);
+               s->Add (_edit_server, 0, wxTOP | wxBOTTOM, 2);
                _remove_server = new wxButton (_servers_panel, wxID_ANY, _("Remove"));
-               s->Add (_remove_server);
+               s->Add (_remove_server, 0, wxTOP | wxBOTTOM, 2);
                table->Add (s, 0);
        }
  
@@@ -439,6 -474,32 +439,6 @@@ ConfigDialog::server_selection_changed 
        _remove_server->Enable (i >= 0);
  }
  
 -void
 -ConfigDialog::reference_scaler_changed (wxCommandEvent &)
 -{
 -      int const n = _reference_scaler->GetSelection ();
 -      if (n >= 0) {
 -              Config::instance()->set_reference_scaler (Scaler::from_index (n));
 -      }
 -}
 -
 -void
 -ConfigDialog::edit_reference_filters_clicked (wxCommandEvent &)
 -{
 -      FilterDialog* d = new FilterDialog (this, Config::instance()->reference_filters ());
 -      d->ActiveChanged.connect (boost::bind (&ConfigDialog::reference_filters_changed, this, _1));
 -      d->ShowModal ();
 -      d->Destroy ();
 -}
 -
 -void
 -ConfigDialog::reference_filters_changed (vector<Filter const *> f)
 -{
 -      Config::instance()->set_reference_filters (f);
 -      pair<string, string> p = Filter::ffmpeg_strings (Config::instance()->reference_filters ());
 -      _reference_filters->SetLabel (std_to_wx (p.first) + N_(" ") + std_to_wx (p.second));
 -}
 -
  void
  ConfigDialog::edit_default_dci_metadata_clicked (wxCommandEvent &)
  {
@@@ -466,16 -527,10 +466,16 @@@ ConfigDialog::setup_language_sensitivit
  }
  
  void
 -ConfigDialog::default_format_changed (wxCommandEvent &)
 +ConfigDialog::default_still_length_changed (wxCommandEvent &)
 +{
 +      Config::instance()->set_default_still_length (_default_still_length->GetValue ());
 +}
 +
 +void
 +ConfigDialog::default_container_changed (wxCommandEvent &)
  {
 -      vector<Format const *> fmt = Format::all ();
 -      Config::instance()->set_default_format (fmt[_default_format->GetSelection()]);
 +      vector<Ratio const *> ratio = Ratio::all ();
 +      Config::instance()->set_default_container (ratio[_default_container->GetSelection()]);
  }
  
  void
diff --combined src/wx/config_dialog.h
index 3da48fd08f3758207a59de2616ca5e966f6bf15a,a97788942654fdf6e560750094775f86c1efbda0..459d64dd701327c2e8bd07f6f5f9e027aae58ebd
@@@ -25,6 -25,7 +25,7 @@@
  #include <wx/spinctrl.h>
  #include <wx/listctrl.h>
  #include <wx/filepicker.h>
+ #include "wx_util.h"
  
  class DirPickerCtrl;
  class wxNotebook;
@@@ -47,14 -48,16 +48,14 @@@ private
        void tms_user_changed (wxCommandEvent &);
        void tms_password_changed (wxCommandEvent &);
        void num_local_encoding_threads_changed (wxCommandEvent &);
 +      void default_still_length_changed (wxCommandEvent &);
        void default_directory_changed (wxCommandEvent &);
        void edit_default_dci_metadata_clicked (wxCommandEvent &);
 -      void reference_scaler_changed (wxCommandEvent &);
 -      void edit_reference_filters_clicked (wxCommandEvent &);
 -      void reference_filters_changed (std::vector<Filter const *>);
        void add_server_clicked (wxCommandEvent &);
        void edit_server_clicked (wxCommandEvent &);
        void remove_server_clicked (wxCommandEvent &);
        void server_selection_changed (wxListEvent &);
 -      void default_format_changed (wxCommandEvent &);
 +      void default_container_changed (wxCommandEvent &);
        void default_dcp_content_type_changed (wxCommandEvent &);
        void issuer_changed (wxCommandEvent &);
        void creator_changed (wxCommandEvent &);
        void make_misc_panel ();
        void make_tms_panel ();
        void make_metadata_panel ();
 -      void make_ab_panel ();
        void make_servers_panel ();
  
        wxNotebook* _notebook;
        wxPanel* _misc_panel;
        wxPanel* _tms_panel;
 -      wxPanel* _ab_panel;
        wxPanel* _servers_panel;
        wxPanel* _metadata_panel;
        wxCheckBox* _set_language;
        wxChoice* _language;
 -      wxChoice* _default_format;
 +      wxChoice* _default_container;
        wxChoice* _default_dcp_content_type;
        wxTextCtrl* _tms_ip;
        wxTextCtrl* _tms_path;
        wxTextCtrl* _tms_user;
        wxTextCtrl* _tms_password;
        wxSpinCtrl* _num_local_encoding_threads;
 -#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
 +      wxSpinCtrl* _default_still_length;
- #ifdef __WXMSW__      
++#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
        DirPickerCtrl* _default_directory;
  #else
        wxDirPickerCtrl* _default_directory;
  #endif
        wxButton* _default_dci_metadata_button;
 -      wxChoice* _reference_scaler;
 -      wxStaticText* _reference_filters;
 -      wxButton* _reference_filters_button;
        wxListCtrl* _servers;
        wxButton* _add_server;
        wxButton* _edit_server;
index c08c58ed460abb15846f0c637968119024c51be5,9c124ed74704cafaf70ff4be809fb093af731509..df3a24f626aa786f74aaab693de2199ab04036d9
@@@ -27,34 -27,34 +27,34 @@@ using boost::shared_ptr
  DCIMetadataDialog::DCIMetadataDialog (wxWindow* parent, DCIMetadata dm)
        : wxDialog (parent, wxID_ANY, _("DCI name"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
  {
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
 -      wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
++      wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
  
-       add_label_to_sizer (table, this, _("Audio Language (e.g. EN)"));
+       add_label_to_sizer (table, this, _("Audio Language (e.g. EN)"), true);
        _audio_language = new wxTextCtrl (this, wxID_ANY);
        table->Add (_audio_language, 1, wxEXPAND);
  
-       add_label_to_sizer (table, this, _("Subtitle Language (e.g. FR)"));
+       add_label_to_sizer (table, this, _("Subtitle Language (e.g. FR)"), true);
        _subtitle_language = new wxTextCtrl (this, wxID_ANY);
        table->Add (_subtitle_language, 1, wxEXPAND);
        
-       add_label_to_sizer (table, this, _("Territory (e.g. UK)"));
+       add_label_to_sizer (table, this, _("Territory (e.g. UK)"), true);
        _territory = new wxTextCtrl (this, wxID_ANY);
        table->Add (_territory, 1, wxEXPAND);
  
-       add_label_to_sizer (table, this, _("Rating (e.g. 15)"));
+       add_label_to_sizer (table, this, _("Rating (e.g. 15)"), true);
        _rating = new wxTextCtrl (this, wxID_ANY);
        table->Add (_rating, 1, wxEXPAND);
  
-       add_label_to_sizer (table, this, _("Studio (e.g. TCF)"));
+       add_label_to_sizer (table, this, _("Studio (e.g. TCF)"), true);
        _studio = new wxTextCtrl (this, wxID_ANY);
        table->Add (_studio, 1, wxEXPAND);
  
-       add_label_to_sizer (table, this, _("Facility (e.g. DLA)"));
+       add_label_to_sizer (table, this, _("Facility (e.g. DLA)"), true);
        _facility = new wxTextCtrl (this, wxID_ANY);
        table->Add (_facility, 1, wxEXPAND);
  
-       add_label_to_sizer (table, this, _("Package Type (e.g. OV)"));
+       add_label_to_sizer (table, this, _("Package Type (e.g. OV)"), true);
        _package_type = new wxTextCtrl (this, wxID_ANY);
        table->Add (_package_type, 1, wxEXPAND);
  
diff --combined src/wx/film_editor.cc
index 27803fb9a7e2464778a2eae26f0fb76c811d8d7d,a54b412b30ff51b24171d3516d924f193315bc6b..2d8c752a1229883cfd313031e9eb627805d98bb8
  #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>
 -#include "lib/format.h"
  #include "lib/film.h"
  #include "lib/transcode_job.h"
  #include "lib/exceptions.h"
 -#include "lib/ab_transcode_job.h"
  #include "lib/job_manager.h"
  #include "lib/filter.h"
 +#include "lib/ratio.h"
  #include "lib/config.h"
 -#include "lib/ffmpeg_decoder.h"
 -#include "lib/log.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 "timecode.h"
  
  using std::string;
  using std::cout;
@@@ -60,114 -55,155 +60,119 @@@ 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_ratios ();
  
 -      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);
 -      wxGridBagSizer* grid = new wxGridBagSizer (DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
 -      _film_sizer->Add (grid, 0, wxALL, 8);
++      wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
 +      _dcp_sizer->Add (grid, 0, wxEXPAND | wxALL, 8);
  
        int r = 0;
        
-       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Name"), wxGBPosition (r, 0));
 -      add_label_to_grid_bag_sizer (grid, _film_panel, _("Name"), true, wxGBPosition (r, 0));
 -      _name = new wxTextCtrl (_film_panel, wxID_ANY);
++      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Name"), true, wxGBPosition (r, 0));
 +      _name = new wxTextCtrl (_dcp_panel, wxID_ANY);
-       grid->Add (_name, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
+       grid->Add (_name, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND | wxLEFT | wxRIGHT);
        ++r;
        
-       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Name"), wxGBPosition (r, 0));
 -      add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Name"), true, wxGBPosition (r, 0));
 -      _dcp_name = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
++      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Name"), true, 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"));
+       int flags = wxALIGN_CENTER_VERTICAL;
+ #ifdef __WXOSX__
+       flags |= wxALIGN_RIGHT;
+ #endif        
-       grid->Add (_use_dci_name, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
++
 +      _use_dci_name = new wxCheckBox (_dcp_panel, wxID_ANY, _("Use DCI name"));
 -      _edit_dci_button = new wxButton (_film_panel, wxID_ANY, _("Details..."));
+       grid->Add (_use_dci_name, wxGBPosition (r, 0), wxDefaultSpan, flags);
 +      _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, _dcp_panel, _("Container"), wxGBPosition (r, 0));
 -      add_label_to_grid_bag_sizer (grid, _film_panel, _("Content"), true, 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"), true, wxGBPosition (r, 0));
 +      _container = new wxChoice (_dcp_panel, wxID_ANY);
-       grid->Add (_container, wxGBPosition (r, 1));
++      grid->Add (_container, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
        ++r;
  
-       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Content Type"), wxGBPosition (r, 0));
 -      add_label_to_grid_bag_sizer (grid, _film_panel, _("Content Type"), true, wxGBPosition (r, 0));
 -      _dcp_content_type = new wxChoice (_film_panel, wxID_ANY);
++      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Content Type"), true, 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"), true, 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, _dcp_panel, _("DCP Frame Rate"), wxGBPosition (r, 0));
 -              add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Frame Rate"), true, wxGBPosition (r, 0));
++              add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Frame Rate"), true, 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"), true, wxGBPosition (r, 0)));
 -      _length = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
 -      grid->Add (video_control (_length), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
 -      ++r;
 -
 -
        {
-               add_label_to_grid_bag_sizer (grid, _dcp_panel, _("JPEG2000 bandwidth"), wxGBPosition (r, 0));
 -              video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim frames"), true, wxGBPosition (r, 0)));
 -              wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 -              video_control (add_label_to_sizer (s, _film_panel, _("Start"), true));
 -              _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"), true));
 -              _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"), true, 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"));
++              add_label_to_sizer (s, _dcp_panel, _("MBps"), false);
                grid->Add (s, wxGBPosition (r, 1));
        }
        ++r;
  
-       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Scaler"), wxGBPosition (r, 0));
 -      video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim method"), true, 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"), true, wxGBPosition (r, 0));
 +      _scaler = new wxChoice (_dcp_panel, wxID_ANY);
-       grid->Add (_scaler, wxGBPosition (r, 1));
++      grid->Add (_scaler, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
        ++r;
  
 -      _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B"));
 -      video_control (_dcp_ab);
 -      grid->Add (_dcp_ab, wxGBPosition (r, 0));
 -      ++r;
 +      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"), true, 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"), false));
 -              grid->Add (s, wxGBPosition (r, 1));
 +      vector<Ratio const *> const ratio = Ratio::all ();
 +      for (vector<Ratio const *>::const_iterator i = ratio.begin(); i != ratio.end(); ++i) {
 +              _container->Append (std_to_wx ((*i)->nickname ()));
        }
 -      ++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);
 -      _pad_with_silence->Connect     (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::pad_with_silence_toggled), 0, this);
 -      _minimum_audio_channels->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,   wxCommandEventHandler (FilmEditor::minimum_audio_channels_changed), 0, this);
 -      _dcp_ab->Connect               (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this);
 -      _still_duration->Connect       (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this);
 -      _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);
 +      _ratio->Connect                  (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::ratio_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);
++//    _pad_with_silence->Connect       (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::pad_with_silence_toggled), 0, this);
++//    _minimum_audio_channels->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,   wxCommandEventHandler (FilmEditor::minimum_audio_channels_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);
 +      _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  (boost::bind (&FilmEditor::audio_mapping_changed, this, _1));
 +      _start->Changed.connect          (boost::bind (&FilmEditor::start_changed, this));
 +      _length->Changed.connect         (boost::bind (&FilmEditor::length_changed, this));
  }
  
  void
  FilmEditor::make_video_panel ()
  {
 -      _video_panel = new wxPanel (_notebook);
 -      _video_sizer = new wxBoxSizer (wxVERTICAL);
 -      _video_panel->SetSizer (_video_sizer);
 +      _video_panel = new wxPanel (_content_notebook);
 +      wxBoxSizer* video_sizer = new wxBoxSizer (wxVERTICAL);
 +      _video_panel->SetSizer (video_sizer);
        
-       wxGridBagSizer* grid = new wxGridBagSizer (4, 4);
 -      wxGridBagSizer* grid = new wxGridBagSizer (DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
 -      _video_sizer->Add (grid, 0, wxALL, 8);
++      wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
 +      video_sizer->Add (grid, 0, wxALL, 8);
  
        int r = 0;
-       add_label_to_grid_bag_sizer (grid, _video_panel, _("Left crop"), wxGBPosition (r, 0));
 -      add_label_to_grid_bag_sizer (grid, _video_panel, _("Format"), true, 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"), true, wxGBPosition (r, 0));
        _left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
        grid->Add (_left_crop, wxGBPosition (r, 1));
        ++r;
  
-       add_label_to_grid_bag_sizer (grid, _video_panel, _("Right crop"), wxGBPosition (r, 0));
+       add_label_to_grid_bag_sizer (grid, _video_panel, _("Right crop"), true, wxGBPosition (r, 0));
        _right_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
        grid->Add (_right_crop, wxGBPosition (r, 1));
        ++r;
        
-       add_label_to_grid_bag_sizer (grid, _video_panel, _("Top crop"), wxGBPosition (r, 0));
+       add_label_to_grid_bag_sizer (grid, _video_panel, _("Top crop"), true, wxGBPosition (r, 0));
        _top_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
        grid->Add (_top_crop, wxGBPosition (r, 1));
        ++r;
        
-       add_label_to_grid_bag_sizer (grid, _video_panel, _("Bottom crop"), wxGBPosition (r, 0));
+       add_label_to_grid_bag_sizer (grid, _video_panel, _("Bottom crop"), true, wxGBPosition (r, 0));
        _bottom_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
        grid->Add (_bottom_crop, wxGBPosition (r, 1));
        ++r;
  
-       add_label_to_grid_bag_sizer (grid, _video_panel, _("Scale to"), wxGBPosition (r, 0));
++      add_label_to_grid_bag_sizer (grid, _video_panel, _("Scale to"), true, wxGBPosition (r, 0));
 +      _ratio = new wxChoice (_video_panel, wxID_ANY);
 +      grid->Add (_ratio, 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 */
        {
-               add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), wxGBPosition (r, 0));
 -              video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), true, wxGBPosition (r, 0)));
++              add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), true, 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..."));
-               s->Add (_filters_button, 0);
 -              video_control (_filters_button);
+               s->Add (_filters_button, 0, wxALIGN_CENTER_VERTICAL);
                grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
        }
        ++r;
  
-       add_label_to_grid_bag_sizer (grid, _video_panel, _("Colour look-up table"), wxGBPosition (r, 0));
 -      video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Scaler"), true, 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"), true, wxGBPosition (r, 0));
        _colour_lut = new wxChoice (_video_panel, wxID_ANY);
        for (int i = 0; i < 2; ++i) {
                _colour_lut->Append (std_to_wx (colour_lut_index_to_name (i)));
        grid->Add (_colour_lut, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
        ++r;
  
 -      {
 -              add_label_to_grid_bag_sizer (grid, _video_panel, _("JPEG2000 bandwidth"), true, 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"), false);
 -              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, 24 * 60 * 60);
 -      _trim_end->SetRange (0, 24 * 60 * 60);
 -      _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"));
++      add_label_to_sizer (h, _content_panel, _("times"), false);
 +      _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);
 +      make_timing_panel ();
 +      _content_notebook->AddPage (_timing_panel, _("Timing"), false);
 +
 +      _loop_count->SetRange (2, 1024);
  }
  
  void
  FilmEditor::make_audio_panel ()
  {
 -      _audio_panel = new wxPanel (_notebook);
 -      _audio_sizer = new wxBoxSizer (wxVERTICAL);
 -      _audio_panel->SetSizer (_audio_sizer);
 +      _audio_panel = new wxPanel (_content_notebook);
 +      wxBoxSizer* audio_sizer = new wxBoxSizer (wxVERTICAL);
 +      _audio_panel->SetSizer (audio_sizer);
        
-       wxFlexGridSizer* grid = new wxFlexGridSizer (3, 4, 4);
 -      wxFlexGridSizer* grid = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
 -      _audio_sizer->Add (grid, 0, wxALL, 8);
++      wxFlexGridSizer* grid = new wxFlexGridSizer (3, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
 +      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"));
++      add_label_to_sizer (grid, _audio_panel, _("Audio Gain"), true);
        {
 -              video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Gain"), true));
                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"), false));
 -              _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"));
++              add_label_to_sizer (s, _audio_panel, _("dB"), false);
 +              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"));
++      add_label_to_sizer (grid, _audio_panel, _("Audio Delay"), false);
        {
 -              video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Delay"), true));
                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
-               add_label_to_sizer (s, _audio_panel, _("ms"));
 -              video_control (add_label_to_sizer (s, _audio_panel, _("ms"), false));
++              add_label_to_sizer (s, _audio_panel, _("ms"), false);
                grid->Add (s);
        }
  
-       add_label_to_sizer (grid, _audio_panel, _("Audio Stream"));
 +      grid->AddSpacer (0);
 +
 -
 -      _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)), true);
 -              _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"), true);
 +      _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);
 +
++#if 0 
+       {
+               _pad_with_silence = new wxCheckBox (_audio_panel, wxID_ANY, _("Pad with silence to"));
+               grid->Add (_pad_with_silence);
+               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _minimum_audio_channels = new wxSpinCtrl (_audio_panel);
+               s->Add (_minimum_audio_channels, 1);
+               add_label_to_sizer (s, _audio_panel, _("channels"), false);
+               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);
+       }
++#endif        
        _audio_gain->SetRange (-60, 60);
        _audio_delay->SetRange (-1000, 1000);
 -      _minimum_audio_channels->SetRange (0, MAX_AUDIO_CHANNELS);
++//    _minimum_audio_channels->SetRange (0, MAX_AUDIO_CHANNELS);
  }
  
  void
  FilmEditor::make_subtitle_panel ()
  {
 -      _subtitle_panel = new wxPanel (_notebook);
 -      _subtitle_sizer = new wxBoxSizer (wxVERTICAL);
 -      _subtitle_panel->SetSizer (_subtitle_sizer);
 -      wxFlexGridSizer* grid = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
 -      _subtitle_sizer->Add (grid, 0, wxALL, 8);
 +      _subtitle_panel = new wxPanel (_content_notebook);
 +      wxBoxSizer* subtitle_sizer = new wxBoxSizer (wxVERTICAL);
 +      _subtitle_panel->SetSizer (subtitle_sizer);
-       wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
++      wxFlexGridSizer* grid = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
 +      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));
 -
        {
-               add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"));
 -              video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"), true));
++              add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"), true);
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _subtitle_offset = new wxSpinCtrl (_subtitle_panel);
                s->Add (_subtitle_offset);
-               add_label_to_sizer (s, _subtitle_panel, _("pixels"));
 -              video_control (add_label_to_sizer (s, _subtitle_panel, _("pixels"), false));
++              add_label_to_sizer (s, _subtitle_panel, _("pixels"), false);
                grid->Add (s);
        }
  
        {
-               add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"));
 -              video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"), true));
++              add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"), true);
                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, _("%"), false));
 +              s->Add (_subtitle_scale);
-               add_label_to_sizer (s, _subtitle_panel, _("%"));
++              add_label_to_sizer (s, _subtitle_panel, _("%"), false);
                grid->Add (s);
        }
  
-       add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Stream"));
++      add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Stream"), true);
 +      _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);
  }
  
-       add_label_to_sizer (grid, _timing_panel, _("Start time"));
 +void
 +FilmEditor::make_timing_panel ()
 +{
 +      _timing_panel = new wxPanel (_content_notebook);
 +      wxBoxSizer* timing_sizer = new wxBoxSizer (wxVERTICAL);
 +      _timing_panel->SetSizer (timing_sizer);
 +      wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
 +      timing_sizer->Add (grid, 0, wxALL, 8);
 +
-       add_label_to_sizer (grid, _timing_panel, _("Length"));
++      add_label_to_sizer (grid, _timing_panel, _("Start time"), true);
 +      _start = new Timecode (_timing_panel);
 +      grid->Add (_start);
++      add_label_to_sizer (grid, _timing_panel, _("Length"), true);
 +      _length = new Timecode (_timing_panel);
 +      grid->Add (_length);
 +}
 +
 +
  /** Called when the left crop widget has been changed */
  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 */
@@@ -567,7 -612,7 +598,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 ()))
                        )
@@@ -594,29 -639,118 +625,30 @@@ FilmEditor::film_changed (Film::Propert
        case Film::NONE:
                break;
        case Film::CONTENT:
 -              checked_set (_content, _film->content ());
 -              setup_visibility ();
 -              setup_formats ();
 -              setup_subtitle_control_sensitivity ();
 -              setup_streams ();
 -              setup_show_audio_sensitivity ();
 -              setup_frame_rate_description ();
 -              setup_minimum_audio_channels ();
 -              break;
 -      case Film::TRUST_CONTENT_HEADER:
 -              checked_set (_trust_content_header, _film->trust_content_header ());
 -              break;
 -      case Film::SUBTITLE_STREAMS:
 +              setup_content ();
                setup_subtitle_control_sensitivity ();
 -              setup_streams ();
 -              break;
 -      case Film::CONTENT_AUDIO_STREAMS:
 -              setup_streams ();
                setup_show_audio_sensitivity ();
 -              setup_frame_rate_description ();
+               setup_minimum_audio_channels ();
                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 ();
 +      case Film::LOOP:
 +              checked_set (_loop_content, _film->loop() > 1);
 +              checked_set (_loop_count, _film->loop());
 +              setup_loop_sensitivity ();
                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));
 -              }
 -              _film_sizer->Layout ();
 +      case Film::CONTAINER:
 +              setup_container ();
                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 ();
 -              setup_minimum_audio_channels ();
 -              break;
 -      case Film::USE_CONTENT_AUDIO:
 -              _film->log()->log (String::compose ("Film::USE_CONTENT_AUDIO changed; setting GUI using %1", _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 ();
 -              setup_minimum_audio_channels ();
 -              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 ();
 -              setup_minimum_audio_channels ();
 -              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;
 +      }
+       case Film::MINIMUM_AUDIO_CHANNELS:
 -              checked_set (_minimum_audio_channels, _film->minimum_audio_channels ());
++//            checked_set (_minimum_audio_channels, _film->minimum_audio_channels ());
+               setup_minimum_audio_channels ();
+               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);
 +      /* We can't use case {} here */
 +      
 +      if (property == ContentProperty::START) {
 +              if (content) {
 +                      _start->set (content->start (), _film->dcp_video_frame_rate ());
 +              } else {
 +                      _start->set (0, 24);
 +              }
 +      } else if (property == ContentProperty::LENGTH) {
 +              if (content) {
 +                      _length->set (content->length (), _film->dcp_video_frame_rate ());
 +              } else {
 +                      _length->set (0, 24);
 +              }
 +      } else 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 == VideoContentProperty::VIDEO_RATIO) {
 +              if (video_content) {
 +                      int n = 0;
 +                      vector<Ratio const *> ratios = Ratio::all ();
 +                      vector<Ratio const *>::iterator i = ratios.begin ();
 +                      while (i != ratios.end() && *i != video_content->ratio()) {
 +                              ++i;
 +                              ++n;
 +                      }
 +
 +                      if (i == ratios.end()) {
 +                              checked_set (_ratio, -1);
 +                      } else {
 +                              checked_set (_ratio, n);
 +                      }
 +              } else {
 +                      checked_set (_ratio, -1);
 +              }
 +      } 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 == AudioContentProperty::AUDIO_MAPPING) {
 +              _audio_mapping->set (audio_content ? audio_content->audio_mapping () : AudioMapping ());
 +      } 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<Ratio const *> ratios = Ratio::all ();
 +      vector<Ratio const *>::iterator i = ratios.begin ();
 +      while (i != ratios.end() && *i != _film->container ()) {
 +              ++i;
 +              ++n;
 +      }
 +      
 +      if (i == ratios.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<Ratio const *> ratios = Ratio::all ();
 +              assert (n < int (ratios.size()));
 +              _film->set_container (ratios[n]);
        }
  }
  
@@@ -829,17 -899,12 +865,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) {
                FileChanged ("");
        }
  
 -      if (_audio_dialog) {
 -              _audio_dialog->set_film (_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::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);
 +
 +      wxListEvent ev;
 +      content_selection_changed (ev);
  }
  
  /** Updates the sensitivity of lots of widgets to a given value.
@@@ -878,48 -961,41 +914,48 @@@ FilmEditor::set_things_sensitive (bool 
        _name->Enable (s);
        _use_dci_name->Enable (s);
        _edit_dci_button->Enable (s);
 -      _format->Enable (s);
 +      _ratio->Enable (s);
        _content->Enable (s);
 -      _trust_content_header->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);
 +      _container->Enable (s);
 +      _loop_content->Enable (s);
 +      _loop_count->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 ();
  }
@@@ -941,39 -1017,105 +977,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);
 -      }
 -
 -      for (list<wxControl*>::iterator i = _still_controls.begin(); i != _still_controls.end(); ++i) {
 -              (*i)->Show (c == STILL);
 -      }
 +      _main_notebook->InvalidateBestSize ();
  
 -      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 ()
 +FilmEditor::setup_ratios ()
  {
 -      ContentType c = VIDEO;
 +      _ratios = Ratio::all ();
  
 -      if (_film) {
 -              c = _film->content_type ();
 +      _ratio->Clear ();
 +      for (vector<Ratio const *>::iterator i = _ratios.begin(); i != _ratios.end(); ++i) {
 +              _ratio->Append (std_to_wx ((*i)->nickname ()));
        }
 -      
 -      _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);
 -              }
 -      }
 -
 -      _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
@@@ -1029,7 -1184,7 +1065,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);
 -      }
 -
 -      _pad_with_silence->Enable (_generally_sensitive && _film && _film->audio_stream() && _film->audio_stream()->channels() < MAX_AUDIO_CHANNELS);
 -      _minimum_audio_channels->Enable (_generally_sensitive && _pad_with_silence->GetValue ());
 -}
 -
  void
  FilmEditor::use_dci_name_toggled (wxCommandEvent &)
  {
@@@ -1067,202 -1241,143 +1103,202 @@@ 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) {
 +      if (_audio_dialog) {
 +              _audio_dialog->Destroy ();
 +              _audio_dialog = 0;
 +      }
 +
 +      shared_ptr<Content> c = selected_content ();
 +      if (!c) {
                return;
        }
  
 -      _film->set_content_audio_stream (
 -              audio_stream_factory (
 -                      string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ())),
 -                      Film::state_version
 -                      )
 -              );
 +      shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (c);
 +      if (!ac) {
 +              return;
 +      }
 +      
 +      _audio_dialog = new AudioDialog (this);
 +      _audio_dialog->Show ();
 +      _audio_dialog->set_content (ac);
  }
  
  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 none 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]));
 +
 +              shared_ptr<Content> c;
 +
 +              if (ImageMagickContent::valid_file (p)) {
 +                      c.reset (new ImageMagickContent (_film, p));
 +              } else if (SndfileContent::valid_file (p)) {
 +                      c.reset (new SndfileContent (_film, p));
 +              } else {
 +                      c.reset (new FFmpegContent (_film, p));
 +              }
 +
 +              _film->examine_and_add_content (c);
 +      }
  }
  
  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()));
 +        setup_content_sensitivity ();
 +      shared_ptr<Content> s = selected_content ();
 +      
 +      if (_audio_dialog && s && dynamic_pointer_cast<AudioContent> (s)) {
 +              _audio_dialog->set_content (dynamic_pointer_cast<AudioContent> (s));
        }
 -
 -      _film->set_external_audio (a);
 +      
 +      film_content_changed (s, ContentProperty::START);
 +      film_content_changed (s, ContentProperty::LENGTH);
 +      film_content_changed (s, VideoContentProperty::VIDEO_CROP);
 +      film_content_changed (s, VideoContentProperty::VIDEO_RATIO);
 +      film_content_changed (s, AudioContentProperty::AUDIO_GAIN);
 +      film_content_changed (s, AudioContentProperty::AUDIO_DELAY);
 +      film_content_changed (s, AudioContentProperty::AUDIO_MAPPING);
 +      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);
 +      _timing_panel->Enable   (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
@@@ -1270,22 -1385,20 +1306,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->video_size ());
+       if ((crop.left || crop.right || crop.top || crop.bottom) && _film->size() != libdcp::Size (0, 0)) {
+               libdcp::Size const cropped = _film->cropped_size (_film->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 &)
 +{
 +      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_trim_type (_trim_type->GetSelection () == 0 ? Film::CPL : Film::ENCODE);
 +      _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<AudioContent> ac = dynamic_pointer_cast<AudioContent> (c);
 +      if (!ac) {
 +              return;
 +      }
 +
 +      ac->set_audio_mapping (m);
 +}
 +
 +void
 +FilmEditor::start_changed ()
 +{
 +      shared_ptr<Content> c = selected_content ();
 +      if (!c) {
 +              return;
 +      }
 +
 +      c->set_start (_start->get (_film->dcp_video_frame_rate ()));
 +}
 +
 +void
 +FilmEditor::length_changed ()
 +{
 +      shared_ptr<Content> c = selected_content ();
 +      if (!c) {
 +              return;
 +      }
 +
 +      shared_ptr<ImageMagickContent> ic = dynamic_pointer_cast<ImageMagickContent> (c);
 +      if (ic) {
 +              ic->set_video_length (_length->get(_film->dcp_video_frame_rate()) * ic->video_frame_rate() / TIME_HZ);
 +      }
 +}
 +
 +void
 +FilmEditor::set_selection (weak_ptr<Content> wc)
 +{
 +      Playlist::ContentList content = _film->content ();
 +      for (size_t i = 0; i < content.size(); ++i) {
 +              if (content[i] == wc.lock ()) {
 +                      _content->SetItemState (i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
 +              } else {
 +                      _content->SetItemState (i, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
 +              }
 +      }
 +}
 +
 +void
 +FilmEditor::ratio_changed (wxCommandEvent &)
 +{
 +      if (!_film) {
 +              return;
 +      }
 +
 +      shared_ptr<Content> c = selected_content ();
 +      if (!c) {
 +              return;
 +      }
 +
 +      shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c);
 +      if (!vc) {
 +              return;
 +      }
 +      
 +      int const n = _ratio->GetSelection ();
 +      if (n >= 0) {
 +              vector<Ratio const *> ratios = Ratio::all ();
 +              assert (n < int (ratios.size()));
 +              vc->set_ratio (ratios[n]);
 +      }
  }
 -      setup_audio_control_sensitivity ();
+ void
+ FilmEditor::setup_minimum_audio_channels ()
+ {
++#if 0 
+       if (!_film || !_film->audio_stream ()) {
+               _pad_with_silence->SetValue (false);
+               return;
+       }
+       _pad_with_silence->SetValue (_film->audio_stream()->channels() < _film->minimum_audio_channels());
+       AudioMapping m (_film);
+       _minimum_audio_channels->SetRange (m.minimum_dcp_channels() + 1, MAX_AUDIO_CHANNELS);
++#endif        
+ }
+ void
+ FilmEditor::pad_with_silence_toggled (wxCommandEvent &)
+ {
 -      _film->set_minimum_audio_channels (_minimum_audio_channels->GetValue ());
++
+ }
+ void
+ FilmEditor::minimum_audio_channels_changed (wxCommandEvent &)
+ {
+       if (!_film) {
+               return;
+       }
++//    _film->set_minimum_audio_channels (_minimum_audio_channels->GetValue ());
+ }
diff --combined src/wx/film_editor.h
index 4b096a2e15242ac592280e6075da30e8798a6f7d,c2d064ca202bc1d3305c52182b35092ecaa088e1..705eb16af9b98f6305a3d28d4d7d251f339e56fc
@@@ -16,7 -16,7 +16,7 @@@
      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  
  */
 -
 + 
  /** @file src/film_editor.h
   *  @brief A wx widget to edit a film's metadata, and perform various functions.
   */
  #include "lib/film.h"
  
  class wxNotebook;
 +class wxListCtrl;
 +class wxListEvent;
  class Film;
  class AudioDialog;
 +class TimelineDialog;
 +class AudioMappingView;
 +class Ratio;
 +class Timecode;
  
  /** @class FilmEditor
   *  @brief A wx widget to edit a film's metadata, and perform various functions.
@@@ -47,17 -41,15 +47,17 @@@ public
        FilmEditor (boost::shared_ptr<Film>, wxWindow *);
  
        void set_film (boost::shared_ptr<Film>);
 -      void setup_visibility ();
 +      void set_selection (boost::weak_ptr<Content>);
  
        boost::signals2::signal<void (std::string)> FileChanged;
  
  private:
 -      void make_film_panel ();
 +      void make_dcp_panel ();
 +      void make_content_panel ();
        void make_video_panel ();
        void make_audio_panel ();
        void make_subtitle_panel ();
 +      void make_timing_panel ();
        void connect_to_widgets ();
        
        /* Handle changes to the view */
        void right_crop_changed (wxCommandEvent &);
        void top_crop_changed (wxCommandEvent &);
        void bottom_crop_changed (wxCommandEvent &);
 -      void content_changed (wxCommandEvent &);
 -      void trust_content_header_changed (wxCommandEvent &);
 -      void format_changed (wxCommandEvent &);
 -      void trim_start_changed (wxCommandEvent &);
 -      void trim_end_changed (wxCommandEvent &);
 -      void trim_type_changed (wxCommandEvent &);
 +      void trust_content_headers_changed (wxCommandEvent &);
 +      void content_selection_changed (wxListEvent &);
 +      void content_add_clicked (wxCommandEvent &);
 +      void content_remove_clicked (wxCommandEvent &);
 +      void imagemagick_video_length_changed (wxCommandEvent &);
 +      void container_changed (wxCommandEvent &);
        void dcp_content_type_changed (wxCommandEvent &);
 -      void dcp_ab_toggled (wxCommandEvent &);
        void scaler_changed (wxCommandEvent &);
        void audio_gain_changed (wxCommandEvent &);
        void audio_gain_calculate_button_clicked (wxCommandEvent &);
        void subtitle_scale_changed (wxCommandEvent &);
        void colour_lut_changed (wxCommandEvent &);
        void j2k_bandwidth_changed (wxCommandEvent &);
 -      void still_duration_changed (wxCommandEvent &);
 -      void audio_stream_changed (wxCommandEvent &);
 -      void subtitle_stream_changed (wxCommandEvent &);
 -      void use_audio_changed (wxCommandEvent &);
 -      void external_audio_changed (wxCommandEvent &);
        void dcp_frame_rate_changed (wxCommandEvent &);
        void best_dcp_frame_rate_clicked (wxCommandEvent &);
 +      void edit_filters_clicked (wxCommandEvent &);
 +      void loop_content_toggled (wxCommandEvent &);
 +      void loop_count_changed (wxCommandEvent &);
 +      void content_timeline_clicked (wxCommandEvent &);
 +      void audio_stream_changed (wxCommandEvent &);
 +      void subtitle_stream_changed (wxCommandEvent &);
 +      void audio_mapping_changed (AudioMapping);
 +      void start_changed ();
 +      void length_changed ();
 +      void ratio_changed (wxCommandEvent &);
+       void pad_with_silence_toggled (wxCommandEvent &);
+       void minimum_audio_channels_changed (wxCommandEvent &);
  
        /* Handle changes to the model */
        void film_changed (Film::Property);
 -
 -      /* Button clicks */
 -      void edit_filters_clicked (wxCommandEvent &);
 +      void film_content_changed (boost::weak_ptr<Content>, int);
  
        void set_things_sensitive (bool);
 -      void setup_formats ();
 +      void setup_ratios ();
        void setup_subtitle_control_sensitivity ();
 -      void setup_audio_control_sensitivity ();
 -      void setup_streams ();
 -      void setup_audio_details ();
        void setup_dcp_name ();
        void setup_show_audio_sensitivity ();
        void setup_scaling_description ();
 -      void setup_notebook_size ();
 -      void setup_frame_rate_description ();
 +      void setup_main_notebook_size ();
 +      void setup_content ();
 +      void setup_container ();
 +      void setup_content_sensitivity ();
 +      void setup_loop_sensitivity ();
+       void setup_minimum_audio_channels ();
        
 -      wxControl* video_control (wxControl *);
 -      wxControl* still_control (wxControl *);
 -
        void active_jobs_changed (bool);
 -
 -      wxNotebook* _notebook;
 -      wxPanel* _film_panel;
 -      wxSizer* _film_sizer;
 +      boost::shared_ptr<Content> selected_content ();
 +      boost::shared_ptr<VideoContent> selected_video_content ();
 +      boost::shared_ptr<AudioContent> selected_audio_content ();
 +
 +      wxNotebook* _main_notebook;
 +      wxNotebook* _content_notebook;
 +      wxPanel* _dcp_panel;
 +      wxSizer* _dcp_sizer;
 +      wxPanel* _content_panel;
 +      wxSizer* _content_sizer;
        wxPanel* _video_panel;
 -      wxSizer* _video_sizer;
        wxPanel* _audio_panel;
 -      wxSizer* _audio_sizer;
        wxPanel* _subtitle_panel;
 -      wxSizer* _subtitle_sizer;
 +      wxPanel* _timing_panel;
  
        /** The film we are editing */
        boost::shared_ptr<Film> _film;
 -      /** The Film's name */
        wxTextCtrl* _name;
        wxStaticText* _dcp_name;
        wxCheckBox* _use_dci_name;
 +      wxChoice* _container;
 +      wxListCtrl* _content;
 +      wxButton* _content_add;
 +      wxButton* _content_remove;
 +      wxButton* _content_earlier;
 +      wxButton* _content_later;
 +      wxButton* _content_timeline;
 +      wxCheckBox* _loop_content;
 +      wxSpinCtrl* _loop_count;
        wxButton* _edit_dci_button;
 -      /** The Film's format */
 -      wxChoice* _format;
 +      wxChoice* _ratio;
 +      wxStaticText* _ratio_description;
        wxStaticText* _scaling_description;
 -      /** The Film's content file */
 -      wxFilePickerCtrl* _content;
 -      wxCheckBox* _trust_content_header;
 -      /** The Film's left crop */
        wxSpinCtrl* _left_crop;
 -      /** The Film's right crop */
        wxSpinCtrl* _right_crop;
 -      /** The Film's top crop */
        wxSpinCtrl* _top_crop;
 -      /** The Film's bottom crop */
        wxSpinCtrl* _bottom_crop;
 -      /** Currently-applied filters */
        wxStaticText* _filters;
 -      /** Button to open the filters dialogue */
        wxButton* _filters_button;
 -      /** The Film's scaler */
        wxChoice* _scaler;
 -      wxRadioButton* _use_content_audio;
 -      wxChoice* _audio_stream;
 -      wxRadioButton* _use_external_audio;
 -      wxFilePickerCtrl* _external_audio[MAX_AUDIO_CHANNELS];
 -      /** The Film's audio gain */
        wxSpinCtrl* _audio_gain;
 -      /** A button to open the gain calculation dialogue */
        wxButton* _audio_gain_calculate_button;
        wxButton* _show_audio;
 -      /** The Film's audio delay */
        wxSpinCtrl* _audio_delay;
        wxCheckBox* _with_subtitles;
 -      wxChoice* _subtitle_stream;
        wxSpinCtrl* _subtitle_offset;
        wxSpinCtrl* _subtitle_scale;
        wxChoice* _colour_lut;
        wxSpinCtrl* _j2k_bandwidth;
 -      /** The Film's DCP content type */
        wxChoice* _dcp_content_type;
 -      /** The Film's source frame rate */
 -      wxStaticText* _source_frame_rate;
        wxChoice* _dcp_frame_rate;
        wxButton* _best_dcp_frame_rate;
 -      wxStaticText* _frame_rate_description;
 -      /** The Film's length */
 -      wxStaticText* _length;
 -      /** The Film's audio details */
 -      wxStaticText* _audio;
 -      /** The Film's duration for still sources */
 -      wxSpinCtrl* _still_duration;
 -
 -      wxSpinCtrl* _trim_start;
 -      wxSpinCtrl* _trim_end;
 -      wxChoice* _trim_type;
 -      /** Selector to generate an A/B comparison DCP */
 -      wxCheckBox* _dcp_ab;
 -
 -      std::list<wxControl*> _video_controls;
 -      std::list<wxControl*> _still_controls;
+       wxCheckBox* _pad_with_silence;
+       wxSpinCtrl* _minimum_audio_channels;
 +      wxChoice* _audio_stream;
 +      wxStaticText* _audio_description;
 +      wxChoice* _subtitle_stream;
 +      AudioMappingView* _audio_mapping;
 +      Timecode* _start;
 +      Timecode* _length;
  
 -      std::vector<Format const *> _formats;
 +      std::vector<Ratio const *> _ratios;
  
        bool _generally_sensitive;
        AudioDialog* _audio_dialog;
 +      TimelineDialog* _timeline_dialog;
  };
index 22e6b447ab1bb96f69f0946986b0c0f4b3d8651f,7499cbf8ba472659e25b807e02eae1c97c3d2d5e..17ebbb9831c37ad7ab297903ad0d62bc90571ce3
@@@ -26,14 -26,14 +26,14 @@@ using namespace boost
  GainCalculatorDialog::GainCalculatorDialog (wxWindow* parent)
        : wxDialog (parent, wxID_ANY, _("Gain Calculator"))
  {
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
 -      wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
++      wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
  
-       add_label_to_sizer (table, this, _("I want to play this back at fader"));
+       add_label_to_sizer (table, this, _("I want to play this back at fader"), true);
        _wanted = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, wxTextValidator (wxFILTER_NUMERIC));
        table->Add (_wanted, 1, wxEXPAND);
  
-       add_label_to_sizer (table, this, _("But I have to use fader"));
+       add_label_to_sizer (table, this, _("But I have to use fader"), true);
        _actual = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, wxTextValidator (wxFILTER_NUMERIC));
        table->Add (_actual, 1, wxEXPAND);
  
index dfe4df2e79883e33cf3f60e3f089c3fd4266da36,0000000000000000000000000000000000000000..6aa75626059dcc1dd84500c7efddceaceb582fd8
mode 100644,000000..100644
--- /dev/null
@@@ -1,70 -1,0 +1,70 @@@
-               add_label_to_sizer (grid, this, (_("Duration")));
 +/*
 +    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 <wx/spinctrl.h>
 +#include "lib/imagemagick_content.h"
 +#include "imagemagick_content_dialog.h"
 +#include "wx_util.h"
 +
 +using boost::shared_ptr;
 +using boost::dynamic_pointer_cast;
 +
 +ImageMagickContentDialog::ImageMagickContentDialog (wxWindow* parent, shared_ptr<ImageMagickContent> content)
 +      : wxDialog (parent, wxID_ANY, _("Image"))
 +      , _content (content)
 +{
 +      wxFlexGridSizer* grid = new wxFlexGridSizer (3, 6, 6);
 +      grid->AddGrowableCol (1, 1);
 +
 +      {
-               add_label_to_sizer (s, this, _("s"));
++              add_label_to_sizer (grid, this, _("Duration"), true);
 +              wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 +              _video_length = new wxSpinCtrl (this);
 +              s->Add (_video_length);
 +              /// TRANSLATORS: this is an abbreviation for seconds, the unit of time
++              add_label_to_sizer (s, this, _("s"), false);
 +              grid->Add (s);
 +      }
 +
 +      wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
 +      overall_sizer->Add (grid, 1, wxEXPAND | wxALL, 6);
 +
 +      wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
 +      if (buttons) {
 +              overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
 +      }
 +
 +      SetSizer (overall_sizer);
 +      overall_sizer->Layout ();
 +      overall_sizer->SetSizeHints (this);
 +
 +      checked_set (_video_length, content->video_length () / 24);
 +      _video_length->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ImageMagickContentDialog::video_length_changed), 0, this);
 +}
 +
 +void
 +ImageMagickContentDialog::video_length_changed (wxCommandEvent &)
 +{
 +      shared_ptr<ImageMagickContent> c = _content.lock ();
 +      if (!c) {
 +              return;
 +      }
 +      
 +      c->set_video_length (_video_length->GetValue() * 24);
 +}
index 1594dfc91e62a51523d033b281015c7dbc7903ca,cfe09aec83454abac8d7b5a353d9d2f19cf8d6e9..b5c66bd3ed5740f74136b29a49195b56e5543f8e
@@@ -86,7 -86,6 +86,7 @@@ JobManagerView::update (
                        JobRecord r;
                        int n = 1;
                        r.finalised = false;
 +                      r.scroll_nudged = false;
                        r.gauge = new wxGauge (_panel, wxID_ANY, 100);
                        _table->Insert (index + n, r.gauge, 1, wxEXPAND | wxLEFT | wxRIGHT);
                        ++n;
                        ++n;
                        
                        _job_records[*i] = r;
 +
                }
  
                string const st = (*i)->status ();
                                checked_set (_job_records[*i].message, wx_to_std (_("Running")));
                                _job_records[*i].gauge->Pulse ();
                        }
 +
                }
  
 +              if (!_job_records[*i].scroll_nudged && ((*i)->running () || (*i)->finished())) {
 +                      int x, y;
 +                      _job_records[*i].gauge->GetPosition (&x, &y);
 +                      int px, py;
 +                      GetScrollPixelsPerUnit (&px, &py);
 +                      int vx, vy;
 +                      GetViewStart (&vx, &vy);
 +                      int sx, sy;
 +                      GetClientSize (&sx, &sy);
 +
 +                      if (y > (vy * py + sy / 2)) {
 +                              Scroll (-1, y / py);
 +                              _job_records[*i].scroll_nudged = true;
 +                      }
 +              }
 +                      
                if ((*i)->finished() && !_job_records[*i].finalised) {
                        checked_set (_job_records[*i].message, st);
                        if (!(*i)->finished_cancelled()) {
                                _job_records[*i].gauge->SetValue (100);
                        }
-                       (*i)->Finished ();
                        _job_records[*i].finalised = true;
                        _job_records[*i].cancel->Enable (false);
                        if (!(*i)->error_details().empty ()) {
@@@ -175,7 -155,7 +174,7 @@@ JobManagerView::details_clicked (wxComm
  {
        wxObject* o = ev.GetEventObject ();
  
 -      for (map<boost::shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
 +      for (map<shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
                if (i->second.details == o) {
                        string s = i->first->error_summary();
                        s[0] = toupper (s[0]);
@@@ -189,7 -169,7 +188,7 @@@ JobManagerView::cancel_clicked (wxComma
  {
        wxObject* o = ev.GetEventObject ();
  
 -      for (map<boost::shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
 +      for (map<shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
                if (i->second.cancel == o) {
                        i->first->cancel ();
                }
index 191482a7cbcdb23ca3da8f48720dbbcdc7bf2d06,289926b8e6a0f870abc62b492c95dcceafe05c26..d4b78d5bff1ad845afd1732c3ef79c83720aecf7
  #include <wx/stdpaths.h>
  #include "lib/config.h"
  #include "new_film_dialog.h"
- #ifdef __WXMSW__
+ #include "wx_util.h"
 -#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
++#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
  #include "dir_picker_ctrl.h"
  #endif
- #include "wx_util.h"
  
  using namespace std;
  using namespace boost;
@@@ -37,17 -37,18 +37,18 @@@ NewFilmDialog::NewFilmDialog (wxWindow
        wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
        SetSizer (overall_sizer);
        
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
 -      wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
++      wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
        overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6);
  
-       add_label_to_sizer (table, this, _("Film name"));
+       add_label_to_sizer (table, this, _("Film name"), true);
        _name = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_name, 1, wxEXPAND);
+       table->Add (_name, 0, wxEXPAND);
+       add_label_to_sizer (table, this, _("Create in folder"), true);
  
-       add_label_to_sizer (table, this, _("Create in folder"));
- #ifdef __WXMSW__
-       _folder = new DirPickerCtrl (this);
 -#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
++#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
+       _folder = new DirPickerCtrl (this); 
  #else 
        _folder = new wxDirPickerCtrl (this, wxDD_DIR_MUST_EXIST);
  #endif
diff --combined src/wx/new_film_dialog.h
index bfcbd423c2cb31a3cc60a9a07bfbc15ab8ad7e17,220bba732ef1858c499583e944979cac4619f92c..f8f3aa08dd1ca5f360afaa495299afd1d8470346
@@@ -19,6 -19,7 +19,7 @@@
  
  #include <wx/wx.h>
  #include <wx/filepicker.h>
+ #include "wx_util.h"
  
  class DirPickerCtrl;
  
@@@ -32,10 -33,10 +33,10 @@@ public
  
  private:
        wxTextCtrl* _name;
- #ifdef __WXMSW__      
 -#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
++#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
        DirPickerCtrl* _folder;
- #else
+ #else 
        wxDirPickerCtrl* _folder;
- #endif
+ #endif        
        static boost::optional<std::string> _directory;
  };
index 40527ded712b038c25d4c3b3ff13e329400504fb,aa97623cd943ca3dbc29deba2a1e9b80452e4660..d525fe38b115d059bd2cc2f279952540dccb456b
@@@ -36,25 -36,32 +36,25 @@@ PropertiesDialog::PropertiesDialog (wxW
        : wxDialog (parent, wxID_ANY, _("Film Properties"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
        , _film (film)
  {
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 3, 6);
 -      wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
++      wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
  
-       add_label_to_sizer (table, this, _("Frames"));
+       add_label_to_sizer (table, this, _("Frames"), true);
        _frames = new wxStaticText (this, wxID_ANY, wxT (""));
        table->Add (_frames, 1, wxALIGN_CENTER_VERTICAL);
  
-       add_label_to_sizer (table, this, _("Disk space required"));
+       add_label_to_sizer (table, this, _("Disk space required"), true);
        _disk = new wxStaticText (this, wxID_ANY, wxT (""));
        table->Add (_disk, 1, wxALIGN_CENTER_VERTICAL);
  
-       add_label_to_sizer (table, this, _("Frames already encoded"));
+       add_label_to_sizer (table, this, _("Frames already encoded"), true);
        _encoded = new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this));
        table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL);
  
 -      if (_film->length()) {
 -              _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().get())));
 -              FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate());
 -              int const dcp_length = _film->length().get() * frc.factor();
 -              double const disk = ((double) _film->j2k_bandwidth() / 8) * dcp_length / (_film->dcp_frame_rate() * 1073741824.0f);
 -              stringstream s;
 -              s << fixed << setprecision (1) << disk << wx_to_std (_("Gb"));
 -              _disk->SetLabel (std_to_wx (s.str ()));
 -      } else {
 -              _frames->SetLabel (_("unknown"));
 -              _disk->SetLabel (_("unknown"));
 -      }
 +      _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->time_to_video_frames (_film->length()))));
 +      double const disk = ((double) _film->j2k_bandwidth() / 8) * _film->length() / (TIME_HZ * 1073741824.0f);
 +      stringstream s;
 +      s << fixed << setprecision (1) << disk << wx_to_std (_("Gb"));
 +      _disk->SetLabel (std_to_wx (s.str ()));
  
        wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
        overall_sizer->Add (table, 0, wxALL, 6);
@@@ -80,7 -87,7 +80,7 @@@ PropertiesDialog::frames_already_encode
        
        if (_film->length()) {
                /* XXX: encoded_frames() should check which frames have been encoded */
 -              u << " (" << (_film->encoded_frames() * 100 / _film->length().get()) << "%)";
 +              u << " (" << (_film->encoded_frames() * 100 / _film->time_to_video_frames (_film->length())) << "%)";
        }
        return u.str ();
  }
diff --combined src/wx/server_dialog.cc
index 7a9cf95c7a98eeb2f13aa929c0907bc0a634ad08,30e3c0f83d07637a46403b0810cfee2e07809648..33cb392bfe52886f2878ebfd33ad001022483f58
@@@ -30,14 -30,14 +30,14 @@@ ServerDialog::ServerDialog (wxWindow* p
                _server = new ServerDescription (wx_to_std (N_("localhost")), 1);
        }
                
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 4, 4);
 -      wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
++      wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
  
-       add_label_to_sizer (table, this, _("Host name or IP address"));
+       add_label_to_sizer (table, this, _("Host name or IP address"), true);
        _host = new wxTextCtrl (this, wxID_ANY);
        table->Add (_host, 1, wxEXPAND);
  
-       add_label_to_sizer (table, this, _("Threads to use"));
+       add_label_to_sizer (table, this, _("Threads to use"), true);
        _threads = new wxSpinCtrl (this, wxID_ANY);
        table->Add (_threads, 1, wxEXPAND);
  
diff --combined src/wx/timecode.cc
index 9072fb99e0327f151c45362b450eba6047c823b3,0000000000000000000000000000000000000000..6ce1c1cb8370c729714489cfbdb3d75ada23f9b6
mode 100644,000000..100644
--- /dev/null
@@@ -1,115 -1,0 +1,115 @@@
-       add_label_to_sizer (sizer, this, wxT (":"));
 +/*
 +    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 <boost/lexical_cast.hpp>
 +#include "timecode.h"
 +#include "wx_util.h"
 +
 +using std::string;
 +using std::cout;
 +using boost::lexical_cast;
 +
 +Timecode::Timecode (wxWindow* parent)
 +      : wxPanel (parent)
 +      , _in_set (false)
 +{
 +      wxClientDC dc (parent);
 +      wxSize size = dc.GetTextExtent (wxT ("9999"));
 +      size.SetHeight (-1);
 +
 +      wxTextValidator validator (wxFILTER_INCLUDE_CHAR_LIST);
 +      wxArrayString list;
 +
 +      wxString n (wxT ("0123456789"));
 +      for (size_t i = 0; i < n.Length(); ++i) {
 +              list.Add (n[i]);
 +      }
 +
 +      validator.SetIncludes (list);
 +      
 +      wxBoxSizer* sizer = new wxBoxSizer (wxHORIZONTAL);
 +      _hours = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size, 0, validator);
 +      _hours->SetMaxLength (2);
 +      sizer->Add (_hours);
-       add_label_to_sizer (sizer, this, wxT (":"));
++      add_label_to_sizer (sizer, this, wxT (":"), false);
 +      _minutes = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size);
 +      _minutes->SetMaxLength (2);
 +      sizer->Add (_minutes);
-       add_label_to_sizer (sizer, this, wxT ("."));
++      add_label_to_sizer (sizer, this, wxT (":"), false);
 +      _seconds = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size);
 +      _seconds->SetMaxLength (2);
 +      sizer->Add (_seconds);
++      add_label_to_sizer (sizer, this, wxT ("."), false);
 +      _frames = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size);
 +      _frames->SetMaxLength (2);
 +      sizer->Add (_frames);
 +
 +      _hours->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this);
 +      _minutes->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this);
 +      _seconds->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this);
 +      _frames->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this);
 +
 +      SetSizerAndFit (sizer);
 +}
 +
 +void
 +Timecode::set (Time t, int fps)
 +{
 +      _in_set = true;
 +      
 +      int const h = t / (3600 * TIME_HZ);
 +      t -= h * 3600 * TIME_HZ;
 +      int const m = t / (60 * TIME_HZ);
 +      t -= m * 60 * TIME_HZ;
 +      int const s = t / TIME_HZ;
 +      t -= s * TIME_HZ;
 +      int const f = t * fps / TIME_HZ;
 +
 +      _hours->SetValue (wxString::Format (wxT ("%d"), h));
 +      _minutes->SetValue (wxString::Format (wxT ("%d"), m));
 +      _seconds->SetValue (wxString::Format (wxT ("%d"), s));
 +      _frames->SetValue (wxString::Format (wxT ("%d"), f));
 +
 +      _in_set = false;
 +}
 +
 +Time
 +Timecode::get (int fps) const
 +{
 +      Time t = 0;
 +      string const h = wx_to_std (_hours->GetValue ());
 +      t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ;
 +      string const m = wx_to_std (_minutes->GetValue());
 +      t += lexical_cast<int> (m.empty() ? "0" : m) * 60 * TIME_HZ;
 +      string const s = wx_to_std (_seconds->GetValue());
 +      t += lexical_cast<int> (s.empty() ? "0" : s) * TIME_HZ;
 +      string const f = wx_to_std (_frames->GetValue());
 +      t += lexical_cast<int> (f.empty() ? "0" : f) * TIME_HZ / fps;
 +      return t;
 +}
 +
 +void
 +Timecode::changed (wxCommandEvent &)
 +{
 +      if (_in_set) {
 +              return;
 +      }
 +      
 +      Changed ();
 +}
diff --combined src/wx/wscript
index 992f31175eb0e27cfb36ed1209f7c411d53567a8,345c02b087b10a9698780207be3dc1435525a404..1205fb21b22e833b07acd6f98ed5c06a406fc84e
@@@ -6,7 -6,6 +6,7 @@@ import i18
  sources = """
            about_dialog.cc
            audio_dialog.cc
 +          audio_mapping_view.cc
            audio_plot.cc
            config_dialog.cc
            dci_metadata_dialog.cc
            filter_dialog.cc
            filter_view.cc
            gain_calculator_dialog.cc
 +          imagemagick_content_dialog.cc
            job_manager_view.cc
            job_wrapper.cc
            new_film_dialog.cc
            properties_dialog.cc
            server_dialog.cc
 +          timecode.cc
 +          timeline.cc
 +          timeline_dialog.cc
            wx_util.cc
            wx_ui_signaller.cc
            """
  
  def configure(conf):
-     conf.check_cfg(package = '', path = conf.options.wx_config, args = '--cppflags --cxxflags --libs', uselib_store = 'WXWIDGETS', mandatory = True)
+     conf.check_cfg(msg='Checking for wxWidgets', package='', path=conf.options.wx_config, args='--cppflags --cxxflags --libs',
+                    uselib_store='WXWIDGETS', mandatory=True)
+     if conf.env.STATIC:
+       # wx-config returns its static libraries as full paths, without -l prefixes, which confuses
+         # check_cfg(), so just hard-code it all.
+         conf.env.STLIB_WXWIDGETS = ['wx_gtk2u_xrc-2.9', 'wx_gtk2u_qa-2.9', 'wx_baseu_net-2.9', 'wx_gtk2u_html-2.9',
+                                     'wx_gtk2u_adv-2.9', 'wx_gtk2u_core-2.9', 'wx_baseu_xml-2.9', 'wx_baseu-2.9']
+         conf.env.LIB_WXWIDGETS = ['tiff', 'SM']
+  
+     conf.in_msg = 1
+     wx_version = conf.check_cfg(package='', path=conf.options.wx_config, args='--version').strip()
+     conf.im_msg = 0
+     if wx_version != '2.9.4':
+         conf.fatal('wxwidgets version 2.9.4 is required; %s found' % wx_version)
  
  def build(bld):
      if bld.env.STATIC:
      else:
          obj = bld(features = 'cxx cxxshlib')
  
 -    obj.name   = 'libdvdomatic-wx'
 +    obj.name   = 'libdcpomatic-wx'
      obj.includes = [ '..' ]
      obj.export_includes = ['.']
      obj.uselib = 'WXWIDGETS'
 -    obj.use = 'libdvdomatic'
+     if bld.env.TARGET_LINUX:
+         obj.uselib += ' GTK'
 +    obj.use = 'libdcpomatic'
      obj.source = sources
 -    obj.target = 'dvdomatic-wx'
 +    obj.target = 'dcpomatic-wx'
  
 -    i18n.po_to_mo(os.path.join('src', 'wx'), 'libdvdomatic-wx', bld)
 +    i18n.po_to_mo(os.path.join('src', 'wx'), 'libdcpomatic-wx', bld)
  
  def pot(bld):
 -    i18n.pot(os.path.join('src', 'wx'), sources, 'libdvdomatic-wx')
 +    i18n.pot(os.path.join('src', 'wx'), sources, 'libdcpomatic-wx')
  
  def pot_merge(bld):
 -    i18n.pot_merge(os.path.join('src', 'wx'), 'libdvdomatic-wx')
 +    i18n.pot_merge(os.path.join('src', 'wx'), 'libdcpomatic-wx')
diff --combined src/wx/wx_util.cc
index 5691d341a0eba0ff0bd501c666c5032e050648cb,b9e78932ae88b7664bf360b12ea3d859f5119ca1..c5887e17d80b5f3f8b5faade084fac29c73978ec
@@@ -35,21 -35,37 +35,37 @@@ using namespace boost
   *  @param s Sizer to add to.
   *  @param p Parent window for the wxStaticText.
   *  @param t Text for the wxStaticText.
+  *  @param left true if this label is a `left label'; ie the sort
+  *  of label which should be right-aligned on OS X.
   *  @param prop Proportion to pass when calling Add() on the wxSizer.
   */
  wxStaticText *
- add_label_to_sizer (wxSizer* s, wxWindow* p, wxString t, int prop)
+ add_label_to_sizer (wxSizer* s, wxWindow* p, wxString t, bool left, int prop)
  {
+       int flags = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT;
+ #ifdef __WXOSX__
+       if (left) {
+               flags |= wxALIGN_RIGHT;
+               t += wxT (":");
+       }
+ #endif        
        wxStaticText* m = new wxStaticText (p, wxID_ANY, t);
-       s->Add (m, prop, wxALIGN_CENTER_VERTICAL | wxALL, 6);
+       s->Add (m, prop, flags, 6);
        return m;
  }
  
  wxStaticText *
- add_label_to_grid_bag_sizer (wxGridBagSizer* s, wxWindow* p, wxString t, wxGBPosition pos, wxGBSpan span)
+ add_label_to_grid_bag_sizer (wxGridBagSizer* s, wxWindow* p, wxString t, bool left, wxGBPosition pos, wxGBSpan span)
  {
+       int flags = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT;
+ #ifdef __WXOSX__
+       if (left) {
+               flags |= wxALIGN_RIGHT;
+               t += wxT (":");
+       }
+ #endif        
        wxStaticText* m = new wxStaticText (p, wxID_ANY, t);
-       s->Add (m, pos, span, wxALIGN_CENTER_VERTICAL | wxALL, 6);
+       s->Add (m, pos, span, flags);
        return m;
  }
  
@@@ -60,7 -76,7 +76,7 @@@
  void
  error_dialog (wxWindow* parent, wxString m)
  {
 -      wxMessageDialog* d = new wxMessageDialog (parent, m, _("DVD-o-matic"), wxOK);
 +      wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxOK);
        d->ShowModal ();
        d->Destroy ();
  }
@@@ -68,7 -84,7 +84,7 @@@
  bool
  confirm_dialog (wxWindow* parent, wxString m)
  {
 -      wxMessageDialog* d = new wxMessageDialog (parent, m, _("DVD-o-matic"), wxYES_NO | wxICON_QUESTION);
 +      wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxYES_NO | wxICON_QUESTION);
        int const r = d->ShowModal ();
        d->Destroy ();
        return r == wxID_YES;
@@@ -215,7 -231,7 +231,7 @@@ checked_set (wxRadioButton* widget, boo
  }
  
  void
 -dvdomatic_setup_i18n ()
 +dcpomatic_setup_i18n ()
  {
        int language = wxLANGUAGE_DEFAULT;
  
        if (wxLocale::IsAvailable (language)) {
                locale = new wxLocale (language, wxLOCALE_LOAD_DEFAULT);
  
 -#ifdef DVDOMATIC_WINDOWS
 +#ifdef DCPOMATIC_WINDOWS
                locale->AddCatalogLookupPathPrefix (std_to_wx (mo_path().string()));
  #endif                
  
 -              locale->AddCatalog (wxT ("libdvdomatic-wx"));
 -              locale->AddCatalog (wxT ("dvdomatic"));
 +              locale->AddCatalog (wxT ("libdcpomatic-wx"));
 +              locale->AddCatalog (wxT ("dcpomatic"));
                
                if (!locale->IsOk()) {
                        delete locale;
        }
  
        if (locale) {
 -              dvdomatic_setup_gettext_i18n (wx_to_std (locale->GetCanonicalName ()));
 +              dcpomatic_setup_gettext_i18n (wx_to_std (locale->GetCanonicalName ()));
        }
  }
diff --combined src/wx/wx_util.h
index bff11647edb653c92eade0b72ac6d3a939f04538,18a9f251c7a42c1ceba81510ce338af04bd2aa71..de6a09c35298fcc0b53460e4e8be8ef299695bed
  
  */
  
 -#ifndef DVDOMATIC_WX_UTIL_H
 -#define DVDOMATIC_WX_UTIL_H
++#ifndef DCPOMATIC_WX_UTIL_H
++#define DCPOMATIC_WX_UTIL_H
  #include <wx/wx.h>
  #include <wx/gbsizer.h>
  #include <boost/function.hpp>
  #include <boost/thread.hpp>
+ #ifdef __WXGTK__
+ #include <gtk/gtk.h>
+ #endif
  
  class wxFilePickerCtrl;
  class wxSpinCtrl;
  class wxGridBagSizer;
  
 -#define DVDOMATIC_SIZER_X_GAP 8
 -#define DVDOMATIC_SIZER_Y_GAP 8
++#define DCPOMATIC_SIZER_X_GAP 8
++#define DCPOMATIC_SIZER_Y_GAP 8
  /** @file src/wx/wx_util.h
   *  @brief Some utility functions and classes.
   */
  
  extern void error_dialog (wxWindow *, wxString);
  extern bool confirm_dialog (wxWindow *, wxString);
- extern wxStaticText* add_label_to_sizer (wxSizer *, wxWindow *, wxString, int prop = 0);
- extern wxStaticText* add_label_to_grid_bag_sizer (wxGridBagSizer *, wxWindow *, wxString, wxGBPosition, wxGBSpan span = wxDefaultSpan);
+ extern wxStaticText* add_label_to_sizer (wxSizer *, wxWindow *, wxString, bool left, int prop = 0);
+ extern wxStaticText* add_label_to_grid_bag_sizer (wxGridBagSizer *, wxWindow *, wxString, bool, wxGBPosition, wxGBSpan span = wxDefaultSpan);
  extern std::string wx_to_std (wxString);
  extern wxString std_to_wx (std::string);
 -extern void dvdomatic_setup_i18n ();
 +extern void dcpomatic_setup_i18n ();
  
  /** @class ThreadedStaticText
   *
@@@ -69,3 -78,12 +78,12 @@@ extern void checked_set (wxTextCtrl* wi
  extern void checked_set (wxCheckBox* widget, bool value);
  extern void checked_set (wxRadioButton* widget, bool value);
  extern void checked_set (wxStaticText* widget, std::string value);
 -#define DVDOMATIC_USE_OWN_DIR_PICKER
+ /* GTK 2.24.17 has a buggy GtkFileChooserButton and it was put in Ubuntu 13.04.
+    Use our own dir picker as this is the least bad option I can think of.
+ */
+ #if defined(__WXMSW__) || (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION == 24 && GTK_MICRO_VERSION == 17)
++#define DCPOMATIC_USE_OWN_DIR_PICKER
+ #endif
+ #endif
diff --combined test/test.cc
index 4494540a2ac31e5069f1a6c3f192b4c8be837b14,74d967a46c28a57128cc94a907c71bedcf90c03e..d6c7842d711692cc2acd39d296a41916af6cba57
@@@ -22,8 -22,7 +22,8 @@@
  #include <boost/filesystem.hpp>
  #include <boost/algorithm/string/predicate.hpp>
  #include <boost/date_time.hpp>
 -#include "format.h"
 +#include <libdcp/dcp.h>
 +#include "ratio.h"
  #include "film.h"
  #include "filter.h"
  #include "job_manager.h"
  #include "scaler.h"
  #include "ffmpeg_decoder.h"
  #include "sndfile_decoder.h"
 -#include "trimmer.h"
 +#include "dcp_content_type.h"
  #include "ui_signaller.h"
++#include "ratio.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;
  using std::list;
  using std::stringstream;
  using std::vector;
 +using std::min;
 +using std::cout;
  using boost::shared_ptr;
  using boost::thread;
  using boost::dynamic_pointer_cast;
@@@ -60,14 -57,14 +61,14 @@@ struct TestConfi
  {
        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 ());
-               Config::instance()->set_default_container (0);
-               Config::instance()->set_default_dcp_content_type (0);
 -              Config::instance()->set_default_format (static_cast<Format*> (0));
++              Config::instance()->set_default_container (static_cast<Ratio*> (0));
+               Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
  
                ui_signaller = new UISignaller ();
        }
@@@ -93,78 -90,18 +94,78 @@@ 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;
  }
  
 +void
 +check_file (string ref, string check)
 +{
 +      uintmax_t N = boost::filesystem::file_size (ref);
 +      BOOST_CHECK_EQUAL (N, boost::filesystem::file_size(check));
 +      FILE* ref_file = fopen (ref.c_str(), "rb");
 +      BOOST_CHECK (ref_file);
 +      FILE* check_file = fopen (check.c_str(), "rb");
 +      BOOST_CHECK (check_file);
 +      
 +      int const buffer_size = 65536;
 +      uint8_t* ref_buffer = new uint8_t[buffer_size];
 +      uint8_t* check_buffer = new uint8_t[buffer_size];
 +
 +      while (N) {
 +              uintmax_t this_time = min (uintmax_t (buffer_size), N);
 +              size_t r = fread (ref_buffer, 1, this_time, ref_file);
 +              BOOST_CHECK_EQUAL (r, this_time);
 +              r = fread (check_buffer, 1, this_time, check_file);
 +              BOOST_CHECK_EQUAL (r, this_time);
 +
 +              BOOST_CHECK_EQUAL (memcmp (ref_buffer, check_buffer, this_time), 0);
 +              N -= this_time;
 +      }
 +
 +      delete[] ref_buffer;
 +      delete[] check_buffer;
 +
 +      fclose (ref_file);
 +      fclose (check_file);
 +}
 +
 +static void
 +note (libdcp::NoteType, string n)
 +{
 +      cout << n << "\n";
 +}
 +
 +void
 +check_dcp (string ref, string check)
 +{
 +      libdcp::DCP ref_dcp (ref);
 +      ref_dcp.read ();
 +      libdcp::DCP check_dcp (check);
 +      check_dcp.read ();
 +
 +      libdcp::EqualityOptions options;
 +      options.max_mean_pixel_error = 5;
 +      options.max_std_dev_pixel_error = 5;
 +      options.max_audio_sample_error = 255;
 +      options.cpl_names_can_differ = true;
 +      options.mxf_names_can_differ = true;
 +      
 +      BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2)));
 +}
 +
 +#include "ffmpeg_pts_offset.cc"
 +#include "ffmpeg_examiner_test.cc"
 +#include "black_fill_test.cc"
 +#include "scaling_test.cc"
 +#include "ratio_test.cc"
  #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 "ffmpeg_dcp_test.cc"
  #include "frame_rate_test.cc"
  #include "job_test.cc"
  #include "client_server_test.cc"
diff --combined test/wscript
index 2fbbdacbf881d84390fcb0b9e7281809b56cbbd0,71636a05d7fbaed510005fce9c00d4c866a118a0..60d846aeab386e647084164e829e62de732aab22
@@@ -12,8 -12,8 +12,8 @@@ def configure(conf)
  def build(bld):
      obj = bld(features = 'cxx cxxprogram')
      obj.name   = 'unit-tests'
-     obj.uselib = 'BOOST_TEST CXML DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC'
+     obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
 -    obj.use    = 'libdvdomatic'
 +    obj.use    = 'libdcpomatic'
      obj.source = 'test.cc'
      obj.target = 'unit-tests'
      obj.install_path = ''
diff --combined wscript
index b1d7eafe2a155fa134fe2298db66060f52ba412c,9d3a566febd6e0fcee695125049035d58beaa085..2c0a145801310f92ac5dd233206d801a20eccb35
+++ b/wscript
@@@ -2,8 -2,8 +2,8 @@@ import subproces
  import os
  import sys
  
 -APPNAME = 'dvdomatic'
 -VERSION = '0.109pre'
 +APPNAME = 'dcpomatic'
 +VERSION = '1.00pre'
  
  def options(opt):
      opt.load('compiler_cxx')
@@@ -29,10 -29,10 +29,10 @@@ def configure(conf)
      conf.env.TARGET_LINUX = not conf.env.TARGET_WINDOWS and not conf.env.TARGET_OSX
  
      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'])
+                                        '-Wall', '-Wno-attributes', '-Wextra', '-D_FILE_OFFSET_BITS=64'])
  
      if conf.env.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:
          conf.check(lib = 'bfd', uselib_store = 'BFD', msg = "Checking for library bfd")
          conf.check(lib = 'dbghelp', uselib_store = 'DBGHELP', msg = "Checking for library dbghelp")
          conf.check(lib = 'iberty', uselib_store = 'IBERTY', msg = "Checking for library iberty")
+         conf.check(lib = 'shlwapi', uselib_store = 'SHLWAPI', msg = "Checking for library shlwapi")
          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')
      if conf.env.TARGET_LINUX:
          # libxml2 seems to be linked against this on Ubuntu but it doesn't mention it in its .pc file
          conf.env.append_value('LIB', 'lzma')
 -        conf.env.append_value('CXXFLAGS', '-DDVDOMATIC_LINUX')
 +        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_LINUX')
  
      if conf.env.TARGET_OSX:
 -        conf.env.append_value('CXXFLAGS', '-DDVDOMATIC_OSX')
 +        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_OSX')
  
      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.52', 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
          # statically and some dynamically, or things get horribly confused and the dynamic linker (I think)
-         # crashes horribly.  These calls do what the check_cfg calls would have done, but specify the
+         # crashes.  These calls do what the check_cfg calls would have done, but specify the
          # different bits as static or dynamic as required.  It'll break if you look at it funny, but
          # I think anyone else who builds would do so dynamically.
+         conf.env.HAVE_CXML = 1
+         conf.env.STLIB_CXML = ['cxml']
          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.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='DCP', mandatory=True)
 +        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.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)
  
      if conf.env.TARGET_LINUX:
          conf.check_cfg(package='liblzma', args='--cflags --libs', uselib_store='LZMA', mandatory=True)
+         if conf.env.STATIC:
+             conf.check_cfg(package='gtk+-2.0', args='--cflags --libs', uselib_store='GTK', mandatory=True)
+         else:
+             # On Linux we need to be able to include <gtk/gtk.h> to check GTK's version
+             conf.check_cfg(package='gtk+-2.0', args='--cflags', uselib_store='GTK', mandatory=True)
  
      conf.check_cfg(package = '', path = conf.options.magickpp_config, args = '--cppflags --cxxflags --libs', uselib_store = 'MAGICK', mandatory = True)
  
      conf.recurse('test')
  
  def build(bld):
-     create_version_cc(VERSION)
+     create_version_cc(VERSION, bld.env.CXXFLAGS)
  
      bld.recurse('src')
      bld.recurse('test')
          bld.recurse('platform/osx')
  
      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)
  
@@@ -223,7 -232,7 +231,7 @@@ def dist(ctx)
                 GRSYMS GRTAGS GSYMS GTAGS
                 """
  
- def create_version_cc(version):
+ def create_version_cc(version, cxx_flags):
      if os.path.exists('.git'):
          cmd = "LANG= git log --abbrev HEAD^..HEAD ."
          output = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0].splitlines()
  
      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
+         t = ''
+         for f in cxx_flags:
+             f = f.replace('"', '\\"')
+             t += f + ' '
 -        text += 'char const * dvdomatic_cxx_flags = \"%s\";\n' % t[:-1]
++        text += 'char const * dcpomatic_cxx_flags = \"%s\";\n' % t[:-1]
          print('Writing version information to src/lib/version.cc')
          o = open('src/lib/version.cc', 'w')
          o.write(text)