Merge master.
authorCarl Hetherington <cth@carlh.net>
Sun, 2 Jun 2013 22:03:08 +0000 (23:03 +0100)
committerCarl Hetherington <cth@carlh.net>
Sun, 2 Jun 2013 22:03:08 +0000 (23:03 +0100)
24 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/windows/installer.nsi.32.in
platform/windows/installer.nsi.64.in
src/lib/config.cc
src/lib/ffmpeg_decoder.cc
src/lib/film.cc
src/lib/filter_graph.cc
src/lib/image.cc
src/lib/subtitle.cc
src/lib/subtitle.h
src/lib/util.h
src/lib/video_content.cc
src/tools/dcpomatic.cc
src/tools/dcpomatic_server.cc
src/tools/wscript
src/wx/audio_plot.cc
src/wx/config_dialog.cc
src/wx/film_viewer.cc
test/wscript
wscript

diff --combined cscript
index ffbca416880d87703525b4aa4c759b1f5d672c6d,dd39befda7fa4e3a217715063ef2eb1724f56643..97e8dbd9239583517ead27d8e1806dbaf10e50c7
+++ b/cscript
@@@ -7,9 -7,8 +7,9 @@@ def dependencies(target)
          return ()
      else:
          return (('openjpeg-cdist', None),
 +                ('libcxml', None),
                  ('ffmpeg-cdist', '7a23ec9c771184ab563cfe24ad9b427f38368961'),
 -                ('libdcp', 'v0.52'))
 +                ('libdcp', None))
  
  def build(env, target):
      cmd = './waf configure --prefix=%s' % env.work_dir_cscript()
  
      env.command('./waf')
  
-     if target.platform == 'linux':
+     if target.platform == 'linux' or target.platform == 'osx':
          env.command('./waf install')
  
  
  def package(env, target, version):
      if target.platform == 'windows':
-         shutil.copyfile('build/windows/installer.%s.nsi' % target.bits, 'build/windows/installer2.%s.nsi' % target.bits)
-         env.command('sed -i "s~%%resources%%~%s/windows~g" build/windows/installer2.%s.nsi' % (os.getcwd(), target.bits))
-         env.command('sed -i "s~%%deps%%~%s~g" build/windows/installer2.%s.nsi' % (env.windows_prefix, target.bits))
-         env.command('sed -i "s~%%binaries%%~%s/build~g" build/windows/installer2.%s.nsi' % (os.getcwd(), target.bits))
-         env.command('sed -i "s~%%bits%%~32~g" build/windows/installer2.%s.nsi' % target.bits)
-         env.command('makensis build/windows/installer2.%s.nsi' % target.bits)
-         return os.path.abspath(glob.glob('build/windows/*%s*.exe' % target.bits)[0])
+         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)
+         return os.path.abspath(glob.glob('build/platform/windows/*%s*.exe' % target.bits)[0])
      elif target.platform == 'linux':
          if target.bits == 32:
              cpu = 'i386'
          else:
              cpu = 'amd64'
  
-         shutil.copyfile('builds/control-%s-%d' % (target.version, target.bits), 'debian/control')
+         shutil.copyfile('platform/linux/control-%s-%d' % (target.version, target.bits), 'debian/control')
          env.command('./waf dist')
          f = open('debian/files', 'w')
 -        print >>f,'dvdomatic_%s-1_%s.deb video extra' % (version, cpu)
 +        print >>f,'dcpomatic_%s-1_%s.deb video extra' % (version, cpu)
          shutil.rmtree('build/deb', ignore_errors=True)
  
          os.makedirs('build/deb')
          os.chdir('build/deb')
 -        shutil.move('../../dvdomatic-%s.tar.bz2' % version, 'dvdomatic_%s.orig.tar.bz2' % version)
 -        env.command('tar xjf dvdomatic_%s.orig.tar.bz2' % version)
 -        os.chdir('dvdomatic-%s' % version)
 +        shutil.move('../../dcpomatic-%s.tar.bz2' % version, 'dcpomatic_%s.orig.tar.bz2' % version)
 +        env.command('tar xjf dcpomatic_%s.orig.tar.bz2' % version)
 +        os.chdir('dcpomatic-%s' % version)
          env.command('dch -b -v %s-1 "New upstream release."' % version)
          env.set('CDIST_LINKFLAGS', env.get('LINKFLAGS'))
          env.set('CDIST_CXXFLAGS', env.get('CXXFLAGS'))
              debs.append(os.path.abspath(p))
  
          return debs
+     elif target.platform == 'osx':
+         env.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')
 -    return [os.path.abspath('build/src/lib/libdvdomatic.pot'),
 -            os.path.abspath('build/src/wx/libdvdomatic-wx.pot'),
 -          os.path.abspath('build/src/tools/dvdomatic.pot')]
 +    return [os.path.abspath('build/src/lib/libdcpomatic.pot'),
 +            os.path.abspath('build/src/wx/libdcpomatic-wx.pot'),
 +          os.path.abspath('build/src/tools/dcpomatic.pot')]
  
  def make_manual(env):
      os.chdir('doc/manual')
index 0000000000000000000000000000000000000000,0f52d03ae4e984ba4297f2b5c0e765c92df10851..dc104958ac13e633d6c81d1d80354305d3b26184
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -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)
+ 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)
+ 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 0000000000000000000000000000000000000000,fa4b4476e77773708487f52073177edf0df8851c..09c636e4aca9021a7d6ed8e3ec21aa2d13af4c9d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -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)
+ 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 0000000000000000000000000000000000000000,0e5fc1f466ecfced9203d368b8343f0fc92b51fb..1330b3e5fe470e938edba26e2f52f9ca3ece6bff
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -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)
+ 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)
+ 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 0000000000000000000000000000000000000000,24e16b4b5afe3c21d3abb531e2d431fef0a0943e..ea1c491ed8a3ebc785596f599b3e12d5f70f41f1
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -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)
+ 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)
+ 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 0000000000000000000000000000000000000000,81de3a25ceae9036dbff79d268bcbb7ec81ee6b8..6649047672fafe3198f94d8dbf71c8f8a313fa8f
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,144 +1,145 @@@
 -Name "DVD-o-matic"
+ !include "MUI2.nsh"
 -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"
++Name "DCP-o-matic"
+ RequestExecutionLevel admin
 -InstallDir "$PROGRAMFILES\DVD-o-matic"
++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"
 -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/servomatic_cli.exe"
 -File "%binaries%/src/tools/servomatic_gui.exe"
++InstallDir "$PROGRAMFILES\DCP-o-matic"
+ !insertmacro MUI_PAGE_WELCOME
+ !insertmacro MUI_PAGE_LICENSE "../../../COPYING"
+ !insertmacro MUI_PAGE_DIRECTORY
+ !insertmacro MUI_PAGE_INSTFILES
+ !insertmacro MUI_PAGE_FINISH
+ !insertmacro MUI_UNPAGE_WELCOME
+ !insertmacro MUI_UNPAGE_CONFIRM
+ !insertmacro MUI_UNPAGE_INSTFILES
+ !insertmacro MUI_UNPAGE_FINISH
+  
+ !insertmacro MUI_LANGUAGE "English"
+ Section "install" "Installation info"
+  
+ SetOutPath "$INSTDIR\bin"
+ File "%deps%/bin/asdcp-libdcp.dll"
+ 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/dcp.dll"
+ File "%deps%/bin/libintl-8.dll"
+ File "%deps%/bin/kumu-libdcp.dll"
+ File "%deps%/bin/libboost_chrono-mt.dll"
+ File "%deps%/bin/libboost_filesystem-mt.dll"
+ File "%deps%/bin/libboost_system-mt.dll"
+ File "%deps%/bin/libboost_thread_win32-mt.dll"
+ File "%deps%/bin/libboost_date_time-mt.dll"
+ File "%deps%/bin/libeay32.dll"
+ File "%deps%/bin/libgcc_s_sjlj-1.dll"
+ File "%deps%/bin/libgio-2.0-0.dll"
+ File "%deps%/bin/libglib-2.0-0.dll"
+ File "%deps%/bin/libgobject-2.0-0.dll"
+ File "%deps%/bin/libiconv-2.dll"
+ File "%deps%/bin/libjpeg-8.dll"
+ File "%deps%/bin/libMagick++-5.dll"
+ File "%deps%/bin/libMagickCore-5.dll"
+ File "%deps%/bin/libMagickWand-5.dll"
+ File "%deps%/bin/libopenjpeg-1.dll"
+ File "%deps%/bin/libpng15-15.dll"
+ File "%deps%/bin/libsigc-2.0-0.dll"
+ File "%deps%/bin/libsndfile-1.dll"
+ File "%deps%/bin/libssh.dll"
+ File "%deps%/bin/libstdc++-6.dll"
+ File "%deps%/bin/postproc-52.dll"
+ File "%deps%/bin/swresample-0.dll"
+ File "%deps%/bin/swscale-2.dll"
+ File "%deps%/bin/zlib1.dll"
+ File "%deps%/bin/libjpeg-8.dll"
+ File "%deps%/bin/wxbase294u_gcc_custom.dll"
+ File "%deps%/bin/wxmsw294u_core_gcc_custom.dll"
+ File "%deps%/bin/wxmsw294u_adv_gcc_custom.dll"
+ File "%deps%/bin/libcairo-2.dll"
+ File "%deps%/bin/libfreetype-6.dll"
+ File "%deps%/bin/libgthread-2.0-0.dll"
+ File "%deps%/bin/libpango-1.0-0.dll"
+ File "%deps%/bin/libgmodule-2.0-0.dll"
+ File "%deps%/bin/libpangocairo-1.0-0.dll"
+ File "%deps%/bin/libpangowin32-1.0-0.dll"
+ File "%deps%/bin/libtiff-5.dll"
+ File "%deps%/bin/libglibmm-2.4-1.dll"
+ File "%deps%/bin/libxml++-2.6-2.dll"
+ File "%deps%/bin/libxml2-2.dll"
+ 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 "%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/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_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
+ # sometimes in the $PROFILE.  Meh.
+ File "%deps%/etc/ImageMagick/delegates.xml"
+ SetOutPath "$PROFILE\.magick"
+ File "%deps%/etc/ImageMagick/delegates.xml"
+ SetOutPath "$INSTDIR\locale\fr\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/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/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/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/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/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"
 -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" ""
++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"
 -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
++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" ""
+  
 -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"
++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
+  
 -Delete "$DESKTOP\DVD-o-matic.lnk"
 -Delete "$DESKTOP\DVD-o-matic bach 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"
++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"
+  
+ SectionEnd
+  
+  
+ Section "Uninstall"
+  
+ RMDir /r "$INSTDIR\*.*"    
+ RMDir "$INSTDIR"
++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
index 0000000000000000000000000000000000000000,f2d79704bbffaf7bc496f9a02244c4d71e834ac0..fd1237727ae2bc7fff9628649c605a918b849e2d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,154 +1,155 @@@
 -Name "DVD-o-matic"
+ !include "MUI2.nsh"
+ !include "x64.nsh"
 -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"
++Name "DCP-o-matic"
+ RequestExecutionLevel admin
 -InstallDir "$PROGRAMFILES\DVD-o-matic"
++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"
 -   StrCpy $INSTDIR "$PROGRAMFILES64\DVD-o-matic"
++InstallDir "$PROGRAMFILES\DCP-o-matic"
+ !insertmacro MUI_PAGE_WELCOME
+ !insertmacro MUI_PAGE_LICENSE "../../../COPYING"
+ !insertmacro MUI_PAGE_DIRECTORY
+ !insertmacro MUI_PAGE_INSTFILES
+ !insertmacro MUI_PAGE_FINISH
+ !insertmacro MUI_UNPAGE_WELCOME
+ !insertmacro MUI_UNPAGE_CONFIRM
+ !insertmacro MUI_UNPAGE_INSTFILES
+ !insertmacro MUI_UNPAGE_FINISH
+  
+ !insertmacro MUI_LANGUAGE "English"
+ Section "install" "Installation info"
+ ${If} ${RunningX64}
+    DetailPrint "Installer running on 64-bit host"
+    ; disable registry redirection (enable access to 64-bit portion of registry)
+    SetRegView 64
+    ; change install dir
 -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/servomatic_cli.exe"
 -File "%binaries%/src/tools/servomatic_gui.exe"
++   StrCpy $INSTDIR "$PROGRAMFILES64\DCP-o-matic"
+ ${EndIf}
+ SetOutPath "$INSTDIR\bin"
+ File "%deps%/bin/asdcp-libdcp.dll"
+ 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/dcp.dll"
+ File "%deps%/bin/libintl-8.dll"
+ File "%deps%/bin/kumu-libdcp.dll"
+ File "%deps%/bin/libboost_chrono-mt.dll"
+ File "%deps%/bin/libboost_filesystem-mt.dll"
+ File "%deps%/bin/libboost_system-mt.dll"
+ File "%deps%/bin/libboost_thread_win32-mt.dll"
+ File "%deps%/bin/libboost_date_time-mt.dll"
+ File "%deps%/bin/libeay32.dll"
+ File "%deps%/bin/libgcc_s_sjlj-1.dll"
+ File "%deps%/bin/libgio-2.0-0.dll"
+ File "%deps%/bin/libglib-2.0-0.dll"
+ File "%deps%/bin/libgobject-2.0-0.dll"
+ File "%deps%/bin/libiconv-2.dll"
+ File "%deps%/bin/libjpeg-8.dll"
+ File "%deps%/bin/libMagick++-5.dll"
+ File "%deps%/bin/libMagickCore-5.dll"
+ File "%deps%/bin/libMagickWand-5.dll"
+ File "%deps%/bin/libopenjpeg-1.dll"
+ File "%deps%/bin/libpng15-15.dll"
+ File "%deps%/bin/libsigc-2.0-0.dll"
+ File "%deps%/bin/libsndfile-1.dll"
+ File "%deps%/bin/libssh.dll"
+ File "%deps%/bin/libstdc++-6.dll"
+ File "%deps%/bin/postproc-52.dll"
+ File "%deps%/bin/swresample-0.dll"
+ File "%deps%/bin/swscale-2.dll"
+ File "%deps%/bin/zlib1.dll"
+ File "%deps%/bin/libjpeg-8.dll"
+ File "%deps%/bin/wxbase294u_gcc_custom.dll"
+ File "%deps%/bin/wxmsw294u_core_gcc_custom.dll"
+ File "%deps%/bin/wxmsw294u_adv_gcc_custom.dll"
+ File "%deps%/bin/libcairo-2.dll"
+ File "%deps%/bin/libfreetype-6.dll"
+ File "%deps%/bin/libgthread-2.0-0.dll"
+ File "%deps%/bin/libpango-1.0-0.dll"
+ File "%deps%/bin/libgmodule-2.0-0.dll"
+ File "%deps%/bin/libpangocairo-1.0-0.dll"
+ File "%deps%/bin/libpangowin32-1.0-0.dll"
+ File "%deps%/bin/libtiff-5.dll"
+ File "%deps%/bin/libglibmm-2.4-1.dll"
+ File "%deps%/bin/libxml++-2.6-2.dll"
+ File "%deps%/bin/libxml2-2.dll"
+ 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 "%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/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_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
+ # sometimes in the $PROFILE.  Meh.
+ File "%deps%/etc/ImageMagick/delegates.xml"
+ SetOutPath "$PROFILE\.magick"
+ File "%deps%/etc/ImageMagick/delegates.xml"
+ SetOutPath "$INSTDIR\locale\fr\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/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/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/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/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/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"
 -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" ""
++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"
 -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
++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" ""
+  
 -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"
++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
+  
 -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"
++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"
+  
+ SectionEnd
+  
+  
+ Section "Uninstall"
+  
+ RMDir /r "$INSTDIR\*.*"    
+ RMDir "$INSTDIR"
++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 978428b02dcc61aba49667f3f2cb8e5cde38e45e,7d8e823351656fdff8c019f31ce963d7510315b1..6fbd34d05e5f772f4cb8872b7aa3190bc33c705b
  #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,10 -36,7 +37,10 @@@ using std::vector
  using std::ifstream;
  using std::string;
  using std::ofstream;
 +using std::list;
  using boost::shared_ptr;
 +using boost::lexical_cast;
 +using boost::optional;
  
  Config* Config::_instance = 0;
  
@@@ -51,8 -47,7 +51,8 @@@ Config::Config (
        , _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 (0)
        , _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");
 +      c = f.optional_string_child ("ReferenceScaler");
 +      if (c) {
 +              _reference_scaler = Scaler::from_id (c.get ());
 +      }
 +
 +      list<shared_ptr<cxml::Node> > filters = f.node_children ("ReferenceFilter");
 +      for (list<shared_ptr<cxml::Node> >::iterator i = filters.begin(); i != filters.end(); ++i) {
 +              _reference_filters.push_back (Filter::from_id ((*i)->content ()));
 +      }
        
 -      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 ()) {
                        _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 ();
 -      p /= N_(".dvdomatic");
+       boost::system::error_code ec;
+       boost::filesystem::create_directory (p, ec);
 +      if (old) {
 +              p /= ".dvdomatic";
 +      } else {
 +              p /= ".dcpomatic.xml";
 +      }
        return p.string ();
  }
  
@@@ -203,13 -134,6 +205,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";
 +      xmlpp::Document doc;
 +      xmlpp::Element* root = doc.create_root_node ("Config");
  
 +      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));
        if (_reference_scaler) {
 -              f << "reference_scaler " << _reference_scaler->id () << "\n";
 +              root->add_child("ReferenceScaler")->add_child_text (_reference_scaler->id ());
        }
  
        for (vector<Filter const *>::const_iterator i = _reference_filters.begin(); i != _reference_filters.end(); ++i) {
 -              f << "reference_filter " << (*i)->id () << "\n";
 +              root->add_child("ReferenceFilter")->add_child_text ((*i)->id ());
        }
        
        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
index 67587a56499b8438de1b9d53d12c155a2ebe1937,bcfbea4316df8999af3d7b31440aa2ffcaf48047..1d000b62bc3d210f3fdbbb26e4adea73553d71be
@@@ -1,5 -1,3 +1,5 @@@
 +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
 +
  /*
      Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
  
@@@ -31,7 -29,6 +31,6 @@@
  #include <stdint.h>
  #include <boost/lexical_cast.hpp>
  extern "C" {
- #include <tiffio.h>
  #include <libavcodec/avcodec.h>
  #include <libavformat/avformat.h>
  #include <libswscale/swscale.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"
@@@ -47,7 -48,6 +46,7 @@@
  #include "ffmpeg_decoder.h"
  #include "filter_graph.h"
  #include "subtitle.h"
 +#include "audio_buffers.h"
  
  #include "i18n.h"
  
@@@ -56,19 -56,15 +55,19 @@@ using std::string
  using std::vector;
  using std::stringstream;
  using std::list;
 +using std::min;
  using boost::shared_ptr;
  using boost::optional;
  using boost::dynamic_pointer_cast;
  using libdcp::Size;
  
 -FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o)
 -      : Decoder (f, o)
 -      , VideoDecoder (f, o)
 -      , AudioDecoder (f, o)
 +boost::mutex FFmpegDecoder::_mutex;
 +
 +FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio, bool subtitles)
 +      : Decoder (f)
 +      , VideoDecoder (f, c)
 +      , AudioDecoder (f, c)
 +      , _ffmpeg_content (c)
        , _format_context (0)
        , _video_stream (-1)
        , _frame (0)
@@@ -78,9 -74,6 +77,9 @@@
        , _audio_codec (0)
        , _subtitle_codec_context (0)
        , _subtitle_codec (0)
 +      , _decode_video (video)
 +      , _decode_audio (audio)
 +      , _decode_subtitles (subtitles)
  {
        setup_general ();
        setup_video ();
  
  FFmpegDecoder::~FFmpegDecoder ()
  {
 +      boost::mutex::scoped_lock lm (_mutex);
 +      
        if (_audio_codec_context) {
                avcodec_close (_audio_codec_context);
        }
 -      
 +
        if (_video_codec_context) {
                avcodec_close (_video_codec_context);
        }
@@@ -114,15 -105,15 +113,15 @@@ FFmpegDecoder::setup_general (
  {
        av_register_all ();
  
 -      if (avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0) < 0) {
 -              throw OpenFileError (_film->content_path ());
 +      if (avformat_open_input (&_format_context, _ffmpeg_content->file().string().c_str(), 0, 0) < 0) {
 +              throw OpenFileError (_ffmpeg_content->file().string ());
        }
  
        if (avformat_find_stream_info (_format_context, 0) < 0) {
                throw DecodeError (_("could not find stream information"));
        }
  
 -      /* Find video, audio and subtitle streams and choose the first of each */
 +      /* Find video, audio and subtitle streams */
  
        for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
                AVStream* s = _format_context->streams[i];
                        }
                        
                        _audio_streams.push_back (
 -                              shared_ptr<AudioStream> (
 -                                      new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channel_layout)
 +                              shared_ptr<FFmpegAudioStream> (
 +                                      new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channels)
                                        )
                                );
 -                      
 +
                } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
 -                      _subtitle_streams.push_back (
 -                              shared_ptr<SubtitleStream> (
 -                                      new SubtitleStream (stream_name (s), i)
 -                                      )
 -                              );
 +                      _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (stream_name (s), i)));
                }
        }
  
  void
  FFmpegDecoder::setup_video ()
  {
 +      boost::mutex::scoped_lock lm (_mutex);
 +      
        _video_codec_context = _format_context->streams[_video_stream]->codec;
        _video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
  
  void
  FFmpegDecoder::setup_audio ()
  {
 -      if (!_audio_stream) {
 +      boost::mutex::scoped_lock lm (_mutex);
 +      
 +      if (!_ffmpeg_content->audio_stream ()) {
                return;
        }
  
 -      shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
 -      assert (ffa);
 -      
 -      _audio_codec_context = _format_context->streams[ffa->id()]->codec;
 +      _audio_codec_context = _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec;
        _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id);
  
        if (_audio_codec == 0) {
  void
  FFmpegDecoder::setup_subtitle ()
  {
 -      if (!_subtitle_stream || _subtitle_stream->id() >= int (_format_context->nb_streams)) {
 +      boost::mutex::scoped_lock lm (_mutex);
 +      
 +      if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->id >= int (_format_context->nb_streams)) {
                return;
        }
  
 -      _subtitle_codec_context = _format_context->streams[_subtitle_stream->id()]->codec;
 +      _subtitle_codec_context = _format_context->streams[_ffmpeg_content->subtitle_stream()->id]->codec;
        _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
  
        if (_subtitle_codec == 0) {
  }
  
  
 -bool
 +void
  FFmpegDecoder::pass ()
  {
        int r = av_read_frame (_format_context, &_packet);
 -      
 +
        if (r < 0) {
                if (r != AVERROR_EOF) {
                        /* Maybe we should fail here, but for now we'll just finish off instead */
                        char buf[256];
                        av_strerror (r, buf, sizeof(buf));
 -                      _film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
 +                      shared_ptr<const Film> film = _film.lock ();
 +                      assert (film);
 +                      film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
                }
  
                /* Get any remaining frames */
                
                /* XXX: should we reset _packet.data and size after each *_decode_* call? */
                
 -              int frame_finished;
 -              
 -              if (_opt.decode_video) {
 -                      while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
 -                              filter_and_emit_video ();
 -                      }
 +              if (_decode_video) {
 +                      while (decode_video_packet ());
                }
 -              
 -              if (_audio_stream && _opt.decode_audio) {
 +
 +              if (_ffmpeg_content->audio_stream() && _decode_audio) {
                        decode_audio_packet ();
                }
                        
 -              return true;
 +              return;
        }
  
        avcodec_get_frame_defaults (_frame);
  
 -      shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
 -
 -      if (_packet.stream_index == _video_stream && _opt.decode_video) {
 -
 -              int frame_finished;
 -              int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet);
 -              if (r >= 0 && frame_finished) {
 -
 -                      if (r != _packet.size) {
 -                              _film->log()->log (String::compose (N_("Used only %1 bytes of %2 in packet"), r, _packet.size));
 -                      }
 -
 -                      filter_and_emit_video ();
 -              }
 -
 -      } else if (ffa && _packet.stream_index == ffa->id() && _opt.decode_audio) {
 +      if (_packet.stream_index == _video_stream && _decode_video) {
 +              decode_video_packet ();
 +      } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->id && _decode_audio) {
                decode_audio_packet ();
 -      } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles) {
 +      } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id && _decode_subtitles) {
  
                int got_subtitle;
                AVSubtitle sub;
                        if (sub.num_rects > 0) {
                                shared_ptr<TimedSubtitle> ts;
                                try {
 -                                      emit_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub)));
 +                                      subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub)));
                                } catch (...) {
                                        /* some problem with the subtitle; we probably didn't understand it */
                                }
                        } else {
 -                              emit_subtitle (shared_ptr<TimedSubtitle> ());
 +                              subtitle (shared_ptr<TimedSubtitle> ());
                        }
                        avsubtitle_free (&sub);
                }
        }
 -      
 +
        av_free_packet (&_packet);
 -      return false;
  }
  
  /** @param data pointer to array of pointers to buffers.
  shared_ptr<AudioBuffers>
  FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
  {
 -      assert (_film->audio_channels());
 +      assert (_ffmpeg_content->audio_channels());
        assert (bytes_per_audio_sample());
  
 -      shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
 -      assert (ffa);
 -      
        /* Deinterleave and convert to float */
  
 -      assert ((size % (bytes_per_audio_sample() * ffa->channels())) == 0);
 +      assert ((size % (bytes_per_audio_sample() * _ffmpeg_content->audio_channels())) == 0);
  
        int const total_samples = size / bytes_per_audio_sample();
 -      int const frames = total_samples / _film->audio_channels();
 -      shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), frames));
 +      int const frames = total_samples / _ffmpeg_content->audio_channels();
 +      shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames));
  
        switch (audio_sample_format()) {
        case AV_SAMPLE_FMT_S16:
                        audio->data(channel)[sample] = float(*p++) / (1 << 15);
  
                        ++channel;
 -                      if (channel == _film->audio_channels()) {
 +                      if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
        case AV_SAMPLE_FMT_S16P:
        {
                int16_t** p = reinterpret_cast<int16_t **> (data);
 -              for (int i = 0; i < _film->audio_channels(); ++i) {
 +              for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
                        for (int j = 0; j < frames; ++j) {
                                audio->data(i)[j] = static_cast<float>(p[i][j]) / (1 << 15);
                        }
                        audio->data(channel)[sample] = static_cast<float>(*p++) / (1 << 31);
  
                        ++channel;
 -                      if (channel == _film->audio_channels()) {
 +                      if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
                        audio->data(channel)[sample] = *p++;
  
                        ++channel;
 -                      if (channel == _film->audio_channels()) {
 +                      if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
        case AV_SAMPLE_FMT_FLTP:
        {
                float** p = reinterpret_cast<float**> (data);
 -              for (int i = 0; i < _film->audio_channels(); ++i) {
 +              for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
                        memcpy (audio->data(i), p[i], frames * sizeof(float));
                }
        }
  }
  
  float
 -FFmpegDecoder::frames_per_second () const
 +FFmpegDecoder::video_frame_rate () const
  {
        AVStream* s = _format_context->streams[_video_stream];
  
@@@ -402,11 -413,41 +401,11 @@@ FFmpegDecoder::audio_sample_format () c
  }
  
  libdcp::Size
 -FFmpegDecoder::native_size () const
 +FFmpegDecoder::video_size () const
  {
        return libdcp::Size (_video_codec_context->width, _video_codec_context->height);
  }
  
 -PixelFormat
 -FFmpegDecoder::pixel_format () const
 -{
 -      return _video_codec_context->pix_fmt;
 -}
 -
 -int
 -FFmpegDecoder::time_base_numerator () const
 -{
 -      return _video_codec_context->time_base.num;
 -}
 -
 -int
 -FFmpegDecoder::time_base_denominator () const
 -{
 -      return _video_codec_context->time_base.den;
 -}
 -
 -int
 -FFmpegDecoder::sample_aspect_ratio_numerator () const
 -{
 -      return _video_codec_context->sample_aspect_ratio.num;
 -}
 -
 -int
 -FFmpegDecoder::sample_aspect_ratio_denominator () const
 -{
 -      return _video_codec_context->sample_aspect_ratio.den;
 -}
 -
  string
  FFmpegDecoder::stream_name (AVStream* s) const
  {
@@@ -441,36 -482,89 +440,36 @@@ FFmpegDecoder::bytes_per_audio_sample (
  }
  
  void
 -FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s)
 +FFmpegDecoder::seek (Time t)
  {
 -      AudioDecoder::set_audio_stream (s);
 -      setup_audio ();
 -}
 -
 -void
 -FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
 -{
 -      VideoDecoder::set_subtitle_stream (s);
 -      setup_subtitle ();
 -      OutputChanged ();
 +      do_seek (t, false, false);
  }
  
  void
 -FFmpegDecoder::filter_and_emit_video ()
 +FFmpegDecoder::seek_back ()
  {
 -      int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
 -      if (bet == AV_NOPTS_VALUE) {
 -              _film->log()->log ("Dropping frame without PTS");
 +      if (next() < (2.5 * TIME_HZ / video_frame_rate())) {
                return;
        }
        
 -      shared_ptr<FilterGraph> graph;
 -
 -      {
 -              boost::mutex::scoped_lock lm (_filter_graphs_mutex);
 -              
 -              list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
 -              while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
 -                      ++i;
 -              }
 -              
 -              if (i == _filter_graphs.end ()) {
 -                      graph = filter_graph_factory (_film, this, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format);
 -                      _filter_graphs.push_back (graph);
 -                      _film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
 -              } else {
 -                      graph = *i;
 -              }
 -      }
 -
 -      list<shared_ptr<Image> > images = graph->process (_frame);
 -
 -      for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
 -              emit_video (*i, false, bet * av_q2d (_format_context->streams[_video_stream]->time_base));
 -      }
 -}
 -
 -bool
 -FFmpegDecoder::seek (double p)
 -{
 -      return do_seek (p, false, false);
 -}
 -
 -bool
 -FFmpegDecoder::seek_to_last ()
 -{
 -      /* This AVSEEK_FLAG_BACKWARD in do_seek is a bit of a hack; without it, if we ask for a seek to the same place as last time
 -         (used when we change decoder parameters and want to re-fetch the frame) we end up going forwards rather than
 -         staying in the same place.
 -      */
 -      return do_seek (last_source_time(), true, false);
 -}
 -
 -void
 -FFmpegDecoder::seek_back ()
 -{
 -      do_seek (last_source_time() - 2.5 / frames_per_second (), true, true);
 +      do_seek (next() - 2.5 * TIME_HZ / video_frame_rate(), true, true);
  }
  
  void
  FFmpegDecoder::seek_forward ()
  {
 -      do_seek (last_source_time() - 0.5 / frames_per_second(), true, true);
 +      if (next() >= (_ffmpeg_content->length() - 0.5 * TIME_HZ / video_frame_rate())) {
 +              return;
 +      }
 +      
 +      do_seek (next() - 0.5 * TIME_HZ / video_frame_rate(), true, true);
  }
  
 -bool
 -FFmpegDecoder::do_seek (double p, bool backwards, bool accurate)
 +void
 +FFmpegDecoder::do_seek (Time t, bool backwards, bool accurate)
  {
 -      int64_t const vt = p / av_q2d (_format_context->streams[_video_stream]->time_base);
 -
 -      int const r = av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
 +      int64_t const vt = t / (av_q2d (_format_context->streams[_video_stream]->time_base) * TIME_HZ);
 +      av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
  
        avcodec_flush_buffers (_video_codec_context);
        if (_subtitle_codec_context) {
                while (1) {
                        int r = av_read_frame (_format_context, &_packet);
                        if (r < 0) {
 -                              return true;
 +                              return;
                        }
                        
                        avcodec_get_frame_defaults (_frame);
                        av_free_packet (&_packet);
                }
        }
 -              
 -      return r < 0;
 -}
 -
 -shared_ptr<FFmpegAudioStream>
 -FFmpegAudioStream::create (string t, optional<int> v)
 -{
 -      if (!v) {
 -              /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
 -              return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
 -      }
 -
 -      stringstream s (t);
 -      string type;
 -      s >> type;
 -      if (type != N_("ffmpeg")) {
 -              return shared_ptr<FFmpegAudioStream> ();
 -      }
 -
 -      return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
 -}
 -
 -FFmpegAudioStream::FFmpegAudioStream (string t, optional<int> version)
 -{
 -      stringstream n (t);
 -      
 -      int name_index = 4;
 -      if (!version) {
 -              name_index = 2;
 -              int channels;
 -              n >> _id >> channels;
 -              _channel_layout = av_get_default_channel_layout (channels);
 -              _sample_rate = 0;
 -      } else {
 -              string type;
 -              /* Current (marked version 1) */
 -              n >> type >> _id >> _sample_rate >> _channel_layout;
 -              assert (type == N_("ffmpeg"));
 -      }
 -
 -      for (int i = 0; i < name_index; ++i) {
 -              size_t const s = t.find (' ');
 -              if (s != string::npos) {
 -                      t = t.substr (s + 1);
 -              }
 -      }
 -
 -      _name = t;
 -}
  
 -string
 -FFmpegAudioStream::to_string () const
 -{
 -      return String::compose (N_("ffmpeg %1 %2 %3 %4"), _id, _sample_rate, _channel_layout, _name);
 -}
 -
 -void
 -FFmpegDecoder::film_changed (Film::Property p)
 -{
 -      switch (p) {
 -      case Film::CROP:
 -      case Film::FILTERS:
 -      {
 -              boost::mutex::scoped_lock lm (_filter_graphs_mutex);
 -              _filter_graphs.clear ();
 -      }
 -      OutputChanged ();
 -      break;
 -
 -      default:
 -              break;
 -      }
 +      return;
  }
  
  /** @return Length (in video frames) according to our content's header */
 -SourceFrame
 -FFmpegDecoder::length () const
 +ContentVideoFrame
 +FFmpegDecoder::video_length () const
  {
 -      return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second();
 +      return (double(_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
  }
  
  void
  FFmpegDecoder::decode_audio_packet ()
  {
 -      shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
 -      assert (ffa);
 -
        /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
           several times.
        */
                                        0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
                                        );
                                
 -                              assert (_audio_codec_context->channels == _film->audio_channels());
 -                              Audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds);
 +                              assert (_audio_codec_context->channels == _ffmpeg_content->audio_channels());
 +                              audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds);
                        }
                        
                        copy_packet.data += decode_result;
                }
        }
  }
 +
 +bool
 +FFmpegDecoder::decode_video_packet ()
 +{
 +      int frame_finished;
 +      if (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) < 0 || !frame_finished) {
 +              return false;
 +      }
 +              
 +      boost::mutex::scoped_lock lm (_filter_graphs_mutex);
 +
 +      shared_ptr<FilterGraph> graph;
 +      
 +      list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
 +      while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
 +              ++i;
 +      }
 +
 +      if (i == _filter_graphs.end ()) {
 +              shared_ptr<const Film> film = _film.lock ();
 +              assert (film);
 +
 +              graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
 +              _filter_graphs.push_back (graph);
 +
 +              film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
 +      } else {
 +              graph = *i;
 +      }
 +
 +      list<shared_ptr<Image> > images = graph->process (_frame);
 +
 +      string post_process = Filter::ffmpeg_strings (_ffmpeg_content->filters()).second;
 +      
 +      for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
 +
 +              shared_ptr<Image> image = *i;
 +              if (!post_process.empty ()) {
 +                      image = image->post_process (post_process, true);
 +              }
 +              
 +              int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
 +              if (bet != AV_NOPTS_VALUE) {
 +                      /* XXX: may need to insert extra frames / remove frames here ...
 +                         (as per old Matcher)
 +                      */
 +                      Time const t = bet * av_q2d (_format_context->streams[_video_stream]->time_base) * TIME_HZ;
 +                      video (image, false, t);
 +              } else {
 +                      shared_ptr<const Film> film = _film.lock ();
 +                      assert (film);
 +                      film->log()->log ("Dropping frame without PTS");
 +              }
 +      }
 +
 +      return true;
 +}
 +
 +Time
 +FFmpegDecoder::next () const
 +{
 +      if (_decode_video && _decode_audio && _audio_codec_context) {
 +              return min (_next_video, _next_audio);
 +      }
 +
 +      if (_decode_audio && _audio_codec_context) {
 +              return _next_audio;
 +      }
 +
 +      return _next_video;
 +}
 +
 +bool
 +FFmpegDecoder::done () const
 +{
 +      return (!_decode_audio || !_audio_codec_context || audio_done()) && (!_decode_video || video_done());
 +}
 +      
diff --combined src/lib/film.cc
index ef29d35fdde57806a2d0ef8f0a5f9a3def734789,5573ee9d2867f87ae1d4b57703df7883be687147..57e3791a26a53c14584c7b0f7dd0faf86b09e70f
  #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 "i18n.h"
  
@@@ -69,11 -67,8 +69,11 @@@ using std::setfill
  using std::min;
  using std::make_pair;
  using std::endl;
 +using std::cout;
 +using std::list;
  using boost::shared_ptr;
  using boost::lexical_cast;
 +using boost::dynamic_pointer_cast;
  using boost::to_upper_copy;
  using boost::ends_with;
  using boost::starts_with;
@@@ -82,32 -77,39 +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)
 +      , _ab (false)
        , _with_subtitles (false)
        , _subtitle_offset (0)
        , _subtitle_scale (1)
        , _colour_lut (0)
        , _j2k_bandwidth (200000000)
        , _dci_metadata (Config::instance()->default_dci_metadata ())
 -      , _dcp_frame_rate (0)
 -      , _source_frame_rate (0)
 +      , _dcp_video_frame_rate (0)
 +      , _dcp_audio_channels (MAX_AUDIO_CHANNELS)
        , _dirty (false)
  {
        set_dci_date_today ();
 +
 +      _playlist->Changed.connect (bind (&Film::playlist_changed, this));
 +      _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
        
        /* Make state.directory a complete path without ..s (where possible)
           (Code swiped from Adam Bowen on stackoverflow)
        }
  
        set_directory (result.string ());
 -      
 -      if (!boost::filesystem::exists (directory())) {
 -              if (must_exist) {
 -                      throw OpenFileError (directory());
 -              } else {
 -                      boost::filesystem::create_directory (directory());
 -              }
 -      }
 -
 -      _sndfile_stream = SndfileStream::create ();
 -      
 -      if (must_exist) {
 -              read_metadata ();
 -      } else {
 -              write_metadata ();
 -      }
 -
        _log.reset (new FileLog (file ("log")));
  }
  
@@@ -135,42 -154,75 +135,46 @@@ 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)
 +      , _ab                (o._ab)
        , _with_subtitles    (o._with_subtitles)
        , _subtitle_offset   (o._subtitle_offset)
        , _subtitle_scale    (o._subtitle_scale)
        , _colour_lut        (o._colour_lut)
        , _j2k_bandwidth     (o._j2k_bandwidth)
        , _dci_metadata      (o._dci_metadata)
 +      , _dcp_video_frame_rate (o._dcp_video_frame_rate)
        , _dci_date          (o._dci_date)
 -      , _dcp_frame_rate    (o._dcp_frame_rate)
 -      , _size              (o._size)
 -      , _length            (o._length)
 -      , _content_digest    (o._content_digest)
 -      , _content_audio_streams (o._content_audio_streams)
 -      , _sndfile_stream    (o._sndfile_stream)
 -      , _subtitle_streams  (o._subtitle_streams)
 -      , _source_frame_rate (o._source_frame_rate)
        , _dirty             (o._dirty)
  {
 -      
 -}
 -
 -Film::~Film ()
 -{
 -
 +      _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
  }
  
  string
  Film::video_state_identifier () const
  {
 -      assert (format ());
 +      assert (container ());
        LocaleGuard lg;
  
 -      pair<string, string> f = Filter::ffmpeg_strings (filters());
 -
        stringstream s;
 -      s << format()->id()
 -        << "_" << content_digest()
 -        << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
 -        << "_" << _dcp_frame_rate
 -        << "_" << f.first << "_" << f.second
 +      s << container()->id()
 +        << "_" << _playlist->video_digest()
 +        << "_" << _dcp_video_frame_rate
          << "_" << scaler()->id()
          << "_" << j2k_bandwidth()
 -        << "_" << boost::lexical_cast<int> (colour_lut());
 +        << "_" << lexical_cast<int> (colour_lut());
  
 -      if (dcp_ab()) {
+       if (trim_type() == ENCODE) {
+               s << "_" << trim_start() << "_" << trim_end();
+       }
 +      if (ab()) {
                pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
                s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
        }
@@@ -234,7 -286,7 +238,7 @@@ Film::audio_analysis_path () cons
  {
        boost::filesystem::path p;
        p /= "analysis";
 -      p /= content_digest();
 +      p /= _playlist->audio_digest();
        return file (p.string ());
  }
  
@@@ -248,7 -300,7 +252,7 @@@ Film::make_dcp (
                throw BadSettingError (_("name"), _("cannot contain slashes"));
        }
        
 -      log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary()));
 +      log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
  
        {
                char buffer[128];
                log()->log (String::compose ("Starting to make DCP on %1", buffer));
        }
        
 -      log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
 -      if (length()) {
 -              log()->log (String::compose ("Content length %1", length().get()));
 -      }
 -      log()->log (String::compose ("Content digest %1", content_digest()));
 -      log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
 +//    log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
 +//    if (length()) {
 +//            log()->log (String::compose ("Content length %1", length().get()));
 +//    }
 +//    log()->log (String::compose ("Content digest %1", content_digest()));
 +//    log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
        log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
        log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
 -#ifdef DVDOMATIC_DEBUG
 -      log()->log ("DVD-o-matic built in debug mode.");
 +#ifdef DCPOMATIC_DEBUG
 +      log()->log ("DCP-o-matic built in debug mode.");
  #else
 -      log()->log ("DVD-o-matic built in optimised mode.");
 +      log()->log ("DCP-o-matic built in optimised mode.");
  #endif
  #ifdef LIBDCP_DEBUG
        log()->log ("libdcp built in debug mode.");
        pair<string, int> const c = cpu_info ();
        log()->log (String::compose ("CPU: %1, %2 processors", c.first, c.second));
        
 -      if (format() == 0) {
 -              throw MissingSettingError (_("format"));
 +      if (container() == 0) {
 +              throw MissingSettingError (_("container"));
        }
  
 -      if (content().empty ()) {
 -              throw MissingSettingError (_("content"));
 +      if (_playlist->content().empty ()) {
 +              throw StringError (_("You must add some content to the DCP before creating it"));
        }
  
        if (dcp_content_type() == 0) {
                throw MissingSettingError (_("name"));
        }
  
 -      DecodeOptions od;
 -      od.decode_subtitles = with_subtitles ();
 -
        shared_ptr<Job> r;
  
 -      if (dcp_ab()) {
 -              r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od)));
 +      if (ab()) {
 +              r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this())));
        } else {
 -              r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od)));
 +              r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
        }
  }
  
 -/** Start a job to analyse the audio of our content file */
 +/** Start a job to analyse the audio in our Playlist */
  void
  Film::analyse_audio ()
  {
        JobManager::instance()->add (_analyse_audio_job);
  }
  
 -/** Start a job to examine our content file */
 -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 ()
  {
        _analyse_audio_job.reset ();
  }
  
 -void
 -Film::examine_content_finished ()
 -{
 -      _examine_content_job.reset ();
 -}
 -
  /** Start a job to send our DCP to the configured TMS */
  void
  Film::send_dcp_to_tms ()
  int
  Film::encoded_frames () const
  {
 -      if (format() == 0) {
 +      if (container() == 0) {
                return 0;
        }
  
  void
  Film::write_metadata () const
  {
 +      if (!boost::filesystem::exists (directory())) {
 +              boost::filesystem::create_directory (directory());
 +      }
 +      
        boost::mutex::scoped_lock lm (_state_mutex);
        LocaleGuard lg;
  
        boost::filesystem::create_directories (directory());
  
 -      string const m = file ("metadata");
 -      ofstream f (m.c_str ());
 -      if (!f.good ()) {
 -              throw CreateFileError (m);
 -      }
 +      xmlpp::Document doc;
 +      xmlpp::Element* root = doc.create_root_node ("Metadata");
  
 -      f << "version " << state_version << endl;
 +      root->add_child("Version")->add_child_text (lexical_cast<string> (state_version));
 +      root->add_child("Name")->add_child_text (_name);
 +      root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0");
  
 -      /* User stuff */
 -      f << "name " << _name << endl;
 -      f << "use_dci_name " << _use_dci_name << endl;
 -      f << "content " << _content << endl;
 -      f << "trust_content_header " << (_trust_content_header ? "1" : "0") << endl;
        if (_dcp_content_type) {
 -              f << "dcp_content_type " << _dcp_content_type->dci_name () << endl;
 -      }
 -      if (_format) {
 -              f << "format " << _format->as_metadata () << endl;
 -      }
 -      f << "left_crop " << _crop.left << endl;
 -      f << "right_crop " << _crop.right << endl;
 -      f << "top_crop " << _crop.top << endl;
 -      f << "bottom_crop " << _crop.bottom << endl;
 -      for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
 -              f << "filter " << (*i)->id () << endl;
 -      }
 -      f << "scaler " << _scaler->id () << endl;
 -      f << "trim_start " << _trim_start << endl;
 -      f << "trim_end " << _trim_end << endl;
 -      switch (_trim_type) {
 -      case CPL:
 -              f << "trim_type cpl\n";
 -              break;
 -      case ENCODE:
 -              f << "trim_type encode\n";
 -              break;
 -      }
 -      f << "dcp_ab " << (_dcp_ab ? "1" : "0") << endl;
 -      if (_content_audio_stream) {
 -              f << "selected_content_audio_stream " << _content_audio_stream->to_string() << endl;
 -      }
 -      for (vector<string>::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) {
 -              f << "external_audio " << *i << endl;
 -      }
 -      f << "use_content_audio " << (_use_content_audio ? "1" : "0") << endl;
 -      f << "audio_gain " << _audio_gain << endl;
 -      f << "audio_delay " << _audio_delay << endl;
 -      f << "still_duration " << _still_duration << endl;
 -      if (_subtitle_stream) {
 -              f << "selected_subtitle_stream " << _subtitle_stream->to_string() << endl;
 -      }
 -      f << "with_subtitles " << _with_subtitles << endl;
 -      f << "subtitle_offset " << _subtitle_offset << endl;
 -      f << "subtitle_scale " << _subtitle_scale << endl;
 -      f << "colour_lut " << _colour_lut << endl;
 -      f << "j2k_bandwidth " << _j2k_bandwidth << endl;
 -      _dci_metadata.write (f);
 -      f << "dci_date " << boost::gregorian::to_iso_string (_dci_date) << endl;
 -      f << "dcp_frame_rate " << _dcp_frame_rate << endl;
 -      f << "width " << _size.width << endl;
 -      f << "height " << _size.height << endl;
 -      f << "length " << _length.get_value_or(0) << endl;
 -      f << "content_digest " << _content_digest << endl;
 -
 -      for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
 -              f << "content_audio_stream " << (*i)->to_string () << endl;
 +              root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
        }
  
 -      f << "external_audio_stream " << _sndfile_stream->to_string() << endl;
 -
 -      for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
 -              f << "subtitle_stream " << (*i)->to_string () << endl;
 +      if (_container) {
 +              root->add_child("Container")->add_child_text (_container->id ());
        }
  
 -      f << "source_frame_rate " << _source_frame_rate << endl;
 +      root->add_child("Scaler")->add_child_text (_scaler->id ());
 +      root->add_child("AB")->add_child_text (_ab ? "1" : "0");
 +      root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
 +      root->add_child("SubtitleOffset")->add_child_text (lexical_cast<string> (_subtitle_offset));
 +      root->add_child("SubtitleScale")->add_child_text (lexical_cast<string> (_subtitle_scale));
 +      root->add_child("ColourLUT")->add_child_text (lexical_cast<string> (_colour_lut));
 +      root->add_child("J2KBandwidth")->add_child_text (lexical_cast<string> (_j2k_bandwidth));
 +      _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
 +      root->add_child("DCPVideoFrameRate")->add_child_text (lexical_cast<string> (_dcp_video_frame_rate));
 +      root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
 +      root->add_child("DCPAudioChannels")->add_child_text (lexical_cast<string> (_dcp_audio_channels));
 +      _playlist->as_xml (root->add_child ("Playlist"));
 +
 +      doc.write_to_file_formatted (file ("metadata.xml"));
        
        _dirty = false;
  }
@@@ -407,46 -523,177 +411,46 @@@ Film::read_metadata (
        boost::mutex::scoped_lock lm (_state_mutex);
        LocaleGuard lg;
  
 -      _external_audio.clear ();
 -      _content_audio_streams.clear ();
 -      _subtitle_streams.clear ();
 -
 -      boost::optional<int> version;
 -
 -      /* Backward compatibility things */
 -      boost::optional<int> audio_sample_rate;
 -      boost::optional<int> audio_stream_index;
 -      boost::optional<int> subtitle_stream_index;
 -
 -      ifstream f (file ("metadata").c_str());
 -      if (!f.good()) {
 -              throw OpenFileError (file ("metadata"));
 +      if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
 +              throw StringError (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version.  You will need to create a new Film, re-add your content and set it up again.  Sorry!"));
        }
 -      
 -      multimap<string, string> kv = read_key_value (f);
  
 -      /* We need version before anything else */
 -      multimap<string, string>::iterator v = kv.find ("version");
 -      if (v != kv.end ()) {
 -              version = atoi (v->second.c_str());
 -      }
 +      cxml::File f (file ("metadata.xml"), "Metadata");
        
 -      for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
 -              string const k = i->first;
 -              string const v = i->second;
 -
 -              if (k == "audio_sample_rate") {
 -                      audio_sample_rate = atoi (v.c_str());
 -              }
 +      _name = f.string_child ("Name");
 +      _use_dci_name = f.bool_child ("UseDCIName");
  
 -              /* User-specified stuff */
 -              if (k == "name") {
 -                      _name = v;
 -              } else if (k == "use_dci_name") {
 -                      _use_dci_name = (v == "1");
 -              } else if (k == "content") {
 -                      _content = v;
 -              } else if (k == "trust_content_header") {
 -                      _trust_content_header = (v == "1");
 -              } else if (k == "dcp_content_type") {
 -                      if (version < 3) {
 -                              _dcp_content_type = DCPContentType::from_pretty_name (v);
 -                      } else {
 -                              _dcp_content_type = DCPContentType::from_dci_name (v);
 -                      }
 -              } else if (k == "format") {
 -                      _format = Format::from_metadata (v);
 -              } else if (k == "left_crop") {
 -                      _crop.left = atoi (v.c_str ());
 -              } else if (k == "right_crop") {
 -                      _crop.right = atoi (v.c_str ());
 -              } else if (k == "top_crop") {
 -                      _crop.top = atoi (v.c_str ());
 -              } else if (k == "bottom_crop") {
 -                      _crop.bottom = atoi (v.c_str ());
 -              } else if (k == "filter") {
 -                      _filters.push_back (Filter::from_id (v));
 -              } else if (k == "scaler") {
 -                      _scaler = Scaler::from_id (v);
 -              } else if ( ((!version || version < 2) && k == "dcp_trim_start") || k == "trim_start") {
 -                      _trim_start = atoi (v.c_str ());
 -              } else if ( ((!version || version < 2) && k == "dcp_trim_end") || k == "trim_end") {
 -                      _trim_end = atoi (v.c_str ());
 -              } else if (k == "trim_type") {
 -                      if (v == "cpl") {
 -                              _trim_type = CPL;
 -                      } else if (v == "encode") {
 -                              _trim_type = ENCODE;
 -                      }
 -              } else if (k == "dcp_ab") {
 -                      _dcp_ab = (v == "1");
 -              } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
 -                      if (!version) {
 -                              audio_stream_index = atoi (v.c_str ());
 -                      } else {
 -                              _content_audio_stream = audio_stream_factory (v, version);
 -                      }
 -              } else if (k == "external_audio") {
 -                      _external_audio.push_back (v);
 -              } else if (k == "use_content_audio") {
 -                      _use_content_audio = (v == "1");
 -              } else if (k == "audio_gain") {
 -                      _audio_gain = atof (v.c_str ());
 -              } else if (k == "audio_delay") {
 -                      _audio_delay = atoi (v.c_str ());
 -              } else if (k == "still_duration") {
 -                      _still_duration = atoi (v.c_str ());
 -              } else if (k == "selected_subtitle_stream") {
 -                      if (!version) {
 -                              subtitle_stream_index = atoi (v.c_str ());
 -                      } else {
 -                              _subtitle_stream = subtitle_stream_factory (v, version);
 -                      }
 -              } else if (k == "with_subtitles") {
 -                      _with_subtitles = (v == "1");
 -              } else if (k == "subtitle_offset") {
 -                      _subtitle_offset = atoi (v.c_str ());
 -              } else if (k == "subtitle_scale") {
 -                      _subtitle_scale = atof (v.c_str ());
 -              } else if (k == "colour_lut") {
 -                      _colour_lut = atoi (v.c_str ());
 -              } else if (k == "j2k_bandwidth") {
 -                      _j2k_bandwidth = atoi (v.c_str ());
 -              } else if (k == "dci_date") {
 -                      _dci_date = boost::gregorian::from_undelimited_string (v);
 -              } else if (k == "dcp_frame_rate") {
 -                      _dcp_frame_rate = atoi (v.c_str ());
 +      {
 +              optional<string> c = f.optional_string_child ("DCPContentType");
 +              if (c) {
 +                      _dcp_content_type = DCPContentType::from_dci_name (c.get ());
                }
 +      }
  
 -              _dci_metadata.read (k, v);
 -              
 -              /* Cached stuff */
 -              if (k == "width") {
 -                      _size.width = atoi (v.c_str ());
 -              } else if (k == "height") {
 -                      _size.height = atoi (v.c_str ());
 -              } else if (k == "length") {
 -                      int const vv = atoi (v.c_str ());
 -                      if (vv) {
 -                              _length = vv;
 -                      }
 -              } else if (k == "content_digest") {
 -                      _content_digest = v;
 -              } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
 -                      _content_audio_streams.push_back (audio_stream_factory (v, version));
 -              } else if (k == "external_audio_stream") {
 -                      _sndfile_stream = audio_stream_factory (v, version);
 -              } else if (k == "subtitle_stream") {
 -                      _subtitle_streams.push_back (subtitle_stream_factory (v, version));
 -              } else if (k == "source_frame_rate") {
 -                      _source_frame_rate = atof (v.c_str ());
 -              } else if (version < 4 && k == "frames_per_second") {
 -                      _source_frame_rate = atof (v.c_str ());
 -                      /* Fill in what would have been used for DCP frame rate by the older version */
 -                      _dcp_frame_rate = best_dcp_frame_rate (_source_frame_rate);
 +      {
 +              optional<string> c = f.optional_string_child ("Container");
 +              if (c) {
 +                      _container = 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"));
 +      _ab = f.bool_child ("AB");
 +      _with_subtitles = f.bool_child ("WithSubtitles");
 +      _subtitle_offset = f.number_child<float> ("SubtitleOffset");
 +      _subtitle_scale = f.number_child<float> ("SubtitleScale");
 +      _colour_lut = f.number_child<int> ("ColourLUT");
 +      _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
 +      _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
 +      _dcp_video_frame_rate = f.number_child<int> ("DCPVideoFrameRate");
 +      _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
 +      _dcp_audio_channels = f.number_child<int> ("DCPAudioChannels");
  
 -              /* also the selected stream was specified as an index */
 -              if (audio_stream_index && audio_stream_index.get() >= 0 && audio_stream_index.get() < (int) _content_audio_streams.size()) {
 -                      _content_audio_stream = _content_audio_streams[audio_stream_index.get()];
 -              }
 +      _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"));
  
 -              /* similarly the subtitle */
 -              if (subtitle_stream_index && subtitle_stream_index.get() >= 0 && subtitle_stream_index.get() < (int) _subtitle_streams.size()) {
 -                      _subtitle_stream = _subtitle_streams[subtitle_stream_index.get()];
 -              }
 -      }
 -              
        _dirty = false;
  }
  
 -libdcp::Size
 -Film::cropped_size (libdcp::Size s) const
 -{
 -      boost::mutex::scoped_lock lm (_state_mutex);
 -      s.width -= _crop.left + _crop.right;
 -      s.height -= _crop.top + _crop.bottom;
 -      return s;
 -}
 -
  /** Given a directory name, return its full path within the Film's directory.
   *  The directory (and its parents) will be created if they do not exist.
   */
@@@ -482,6 -729,67 +486,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;
@@@ -591,6 -914,107 +595,6 @@@ Film::set_use_dci_name (bool u
        signal_changed (USE_DCI_NAME);
  }
  
 -void
 -Film::set_content (string c)
 -{
 -      string check = directory ();
 -
 -      boost::filesystem::path slash ("/");
 -      string platform_slash = slash.make_preferred().string ();
 -
 -      if (!ends_with (check, platform_slash)) {
 -              check += platform_slash;
 -      }
 -      
 -      if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) {
 -              c = c.substr (_directory.length() + 1);
 -      }
 -
 -      string old_content;
 -      
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              if (c == _content) {
 -                      return;
 -              }
 -
 -              old_content = _content;
 -              _content = c;
 -      }
 -
 -      /* Reset streams here in case the new content doesn't have one or the other */
 -      _content_audio_stream = shared_ptr<AudioStream> ();
 -      _subtitle_stream = shared_ptr<SubtitleStream> ();
 -
 -      /* Start off using content audio */
 -      set_use_content_audio (true);
 -
 -      /* Create a temporary decoder so that we can get information
 -         about the content.
 -      */
 -
 -      try {
 -              Decoders d = decoder_factory (shared_from_this(), DecodeOptions());
 -              
 -              set_size (d.video->native_size ());
 -              set_source_frame_rate (d.video->frames_per_second ());
 -              set_dcp_frame_rate (best_dcp_frame_rate (source_frame_rate ()));
 -              set_subtitle_streams (d.video->subtitle_streams ());
 -              if (d.audio) {
 -                      set_content_audio_streams (d.audio->audio_streams ());
 -              }
 -
 -              {
 -                      boost::mutex::scoped_lock lm (_state_mutex);
 -                      _content = c;
 -              }
 -              
 -              signal_changed (CONTENT);
 -              
 -              /* Start off with the first audio and subtitle streams */
 -              if (d.audio && !d.audio->audio_streams().empty()) {
 -                      set_content_audio_stream (d.audio->audio_streams().front());
 -              }
 -              
 -              if (!d.video->subtitle_streams().empty()) {
 -                      set_subtitle_stream (d.video->subtitle_streams().front());
 -              }
 -              
 -              examine_content ();
 -
 -      } catch (...) {
 -
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content = old_content;
 -              throw;
 -
 -      }
 -
 -      /* Default format */
 -      set_format (Config::instance()->default_format ());
 -
 -      /* Still image DCPs must use external audio */
 -      if (content_type() == STILL) {
 -              set_use_content_audio (false);
 -      }
 -}
 -
 -void
 -Film::set_trust_content_header (bool t)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _trust_content_header = t;
 -      }
 -      
 -      signal_changed (TRUST_CONTENT_HEADER);
 -
 -      if (!_trust_content_header && !content().empty()) {
 -              /* We just said that we don't trust the content's header */
 -              examine_content ();
 -      }
 -}
 -             
  void
  Film::set_dcp_content_type (DCPContentType const * t)
  {
  }
  
  void
 -Film::set_format (Format const * f)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _format = f;
 -      }
 -      signal_changed (FORMAT);
 -}
 -
 -void
 -Film::set_crop (Crop c)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _crop = c;
 -      }
 -      signal_changed (CROP);
 -}
 -
 -void
 -Film::set_left_crop (int c)
 +Film::set_container (Ratio const * c)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              
 -              if (_crop.left == c) {
 -                      return;
 -              }
 -              
 -              _crop.left = c;
 +              _container = c;
        }
 -      signal_changed (CROP);
 -}
 -
 -void
 -Film::set_right_crop (int c)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              if (_crop.right == c) {
 -                      return;
 -              }
 -              
 -              _crop.right = c;
 -      }
 -      signal_changed (CROP);
 -}
 -
 -void
 -Film::set_top_crop (int c)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              if (_crop.top == c) {
 -                      return;
 -              }
 -              
 -              _crop.top = c;
 -      }
 -      signal_changed (CROP);
 -}
 -
 -void
 -Film::set_bottom_crop (int c)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              if (_crop.bottom == c) {
 -                      return;
 -              }
 -              
 -              _crop.bottom = c;
 -      }
 -      signal_changed (CROP);
 -}
 -
 -void
 -Film::set_filters (vector<Filter const *> f)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _filters = f;
 -      }
 -      signal_changed (FILTERS);
 +      signal_changed (CONTAINER);
  }
  
  void
@@@ -622,13 -1123,120 +626,13 @@@ Film::set_scaler (Scaler const * s
  }
  
  void
 -Film::set_trim_start (int t)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _trim_start = t;
 -      }
 -      signal_changed (TRIM_START);
 -}
 -
 -void
 -Film::set_trim_end (int t)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _trim_end = t;
 -      }
 -      signal_changed (TRIM_END);
 -}
 -
 -void
 -Film::set_trim_type (TrimType t)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _trim_type = t;
 -      }
 -      signal_changed (TRIM_TYPE);
 -}
 -
 -void
 -Film::set_dcp_ab (bool a)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _dcp_ab = a;
 -      }
 -      signal_changed (DCP_AB);
 -}
 -
 -void
 -Film::set_content_audio_stream (shared_ptr<AudioStream> s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_audio_stream = s;
 -      }
 -      signal_changed (CONTENT_AUDIO_STREAM);
 -}
 -
 -void
 -Film::set_external_audio (vector<string> a)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _external_audio = a;
 -      }
 -
 -      shared_ptr<SndfileDecoder> decoder (new SndfileDecoder (shared_from_this(), DecodeOptions()));
 -      if (decoder->audio_stream()) {
 -              _sndfile_stream = decoder->audio_stream ();
 -      }
 -      
 -      signal_changed (EXTERNAL_AUDIO);
 -}
 -
 -void
 -Film::set_use_content_audio (bool e)
 +Film::set_ab (bool a)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _use_content_audio = e;
 +              _ab = a;
        }
 -
 -      signal_changed (USE_CONTENT_AUDIO);
 -}
 -
 -void
 -Film::set_audio_gain (float g)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _audio_gain = g;
 -      }
 -      signal_changed (AUDIO_GAIN);
 -}
 -
 -void
 -Film::set_audio_delay (int d)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _audio_delay = d;
 -      }
 -      signal_changed (AUDIO_DELAY);
 -}
 -
 -void
 -Film::set_still_duration (int d)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _still_duration = d;
 -      }
 -      signal_changed (STILL_DURATION);
 -}
 -
 -void
 -Film::set_subtitle_stream (shared_ptr<SubtitleStream> s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _subtitle_stream = s;
 -      }
 -      signal_changed (SUBTITLE_STREAM);
 +      signal_changed (AB);
  }
  
  void
@@@ -693,15 -1301,85 +697,15 @@@ Film::set_dci_metadata (DCIMetadata m
  
  
  void
 -Film::set_dcp_frame_rate (int f)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _dcp_frame_rate = f;
 -      }
 -      signal_changed (DCP_FRAME_RATE);
 -}
 -
 -void
 -Film::set_size (libdcp::Size s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _size = s;
 -      }
 -      signal_changed (SIZE);
 -}
 -
 -void
 -Film::set_length (SourceFrame l)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _length = l;
 -      }
 -      signal_changed (LENGTH);
 -}
 -
 -void
 -Film::unset_length ()
 -{
 -      {
 -              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
@@@ -729,6 -1410,16 +733,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
  {
@@@ -784,136 -1475,20 +788,136 @@@ 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
  {
 -      if (use_content_audio()) {
 -              return audio_stream();
 +      return _playlist->content ();
 +}
 +
 +void
 +Film::examine_and_add_content (shared_ptr<Content> c)
 +{
 +      shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
 +      JobManager::instance()->add (j);
 +}
 +
 +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 ());
        }
  
 -      vector<string> const e = external_audio ();
 -      for (vector<string>::const_iterator i = e.begin(); i != e.end(); ++i) {
 -              if (!i->empty ()) {
 -                      return true;
 -              }
 +      _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));
        }
 +}
  
 -      return false;
 +void
 +Film::playlist_changed ()
 +{
 +      signal_changed (CONTENT);
 +}     
 +
 +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/filter_graph.cc
index 7ec2466c588a4dc29e6e02b6274a7ca6ee81fbdf,8ff5e75df3d3ee6f5575778147db7210e0350d77..472480de300dee5834555ddde7aba22f5fb369a2
@@@ -33,6 -33,7 +33,6 @@@ extern "C" 
  #include "filter.h"
  #include "exceptions.h"
  #include "image.h"
 -#include "film.h"
  #include "ffmpeg_decoder.h"
  
  #include "i18n.h"
  using std::stringstream;
  using std::string;
  using std::list;
 +using std::cout;
  using boost::shared_ptr;
 +using boost::weak_ptr;
  using libdcp::Size;
  
 -/** Construct a FFmpegFilterGraph for the settings in a film.
 - *  @param film Film.
 - *  @param decoder Decoder that we are using.
 +/** Construct a FilterGraph for the settings in a piece of content.
 + *  @param content Content.
   *  @param s Size of the images to process.
   *  @param p Pixel format of the images to process.
   */
 -FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p)
 +FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p)
        : _buffer_src_context (0)
        , _buffer_sink_context (0)
        , _size (s)
  {
        _frame = av_frame_alloc ();
        
 -      string filters = Filter::ffmpeg_strings (film->filters()).first;
 +      string filters = Filter::ffmpeg_strings (content->filters()).first;
        if (!filters.empty ()) {
 -              filters += N_(",");
 +              filters += ",";
        }
  
 -      filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size()));
 +      Crop crop = content->crop ();
 +      libdcp::Size cropped_size = _size;
 +      cropped_size.width -= crop.left + crop.right;
 +      cropped_size.height -= crop.top + crop.bottom;
 +      filters += crop_string (Position (crop.left, crop.top), cropped_size);
  
        AVFilterGraph* graph = avfilter_graph_alloc();
        if (graph == 0) {
@@@ -87,8 -83,8 +87,8 @@@
        stringstream a;
        a << "video_size=" << _size.width << "x" << _size.height << ":"
          << "pix_fmt=" << _pixel_format << ":"
 -        << "time_base=" << decoder->time_base_numerator() << "/" << decoder->time_base_denominator() << ":"
 -        << "pixel_aspect=" << decoder->sample_aspect_ratio_numerator() << "/" << decoder->sample_aspect_ratio_denominator();
 +        << "time_base=1/1:"
 +        << "pixel_aspect=1/1";
  
        int r;
  
        /* XXX: leaking `inputs' / `outputs' ? */
  }
  
 -FFmpegFilterGraph::~FFmpegFilterGraph ()
 +FilterGraph::~FilterGraph ()
  {
        av_frame_free (&_frame);
  }
   *  set of Images.  Caller handles memory management of the input frame.
   */
  list<shared_ptr<Image> >
 -FFmpegFilterGraph::process (AVFrame* frame)
 +FilterGraph::process (AVFrame* frame)
  {
        list<shared_ptr<Image> > images;
  
                }
  
                images.push_back (shared_ptr<Image> (new SimpleImage (_frame)));
+               av_frame_unref (_frame);
        }
        
        return images;
   *  @return true if this chain can process images with `s' and `p', otherwise false.
   */
  bool
 -FFmpegFilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
 +FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
  {
        return (_size == s && _pixel_format == p);
  }
 -
 -list<shared_ptr<Image> >
 -EmptyFilterGraph::process (AVFrame* frame)
 -{
 -      list<shared_ptr<Image> > im;
 -      im.push_back (shared_ptr<Image> (new SimpleImage (frame)));
 -      return im;
 -}
 -
 -shared_ptr<FilterGraph>
 -filter_graph_factory (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size size, AVPixelFormat pixel_format)
 -{
 -      if (film->filters().empty() && film->crop() == Crop()) {
 -              return shared_ptr<FilterGraph> (new EmptyFilterGraph);
 -      }
 -
 -      return shared_ptr<FilterGraph> (new FFmpegFilterGraph (film, decoder, size, pixel_format));
 -}
diff --combined src/lib/image.cc
index a12c61b3e8a9ce3992d5641d351926481e818361,bd527e91ef5c3fcc2961b605e9fc3dfb50b28ed9..f0a38f4e9418bdd941f86950279e4962434de779
@@@ -68,7 -68,7 +68,7 @@@ Image::lines (int n) cons
                throw PixelFormatError (N_("lines()"), _pixel_format);
        }
        
-       return size().height / pow(2, d->log2_chroma_h);
+       return size().height / pow(2.0f, d->log2_chroma_h);
  }
  
  /** @return Number of components */
@@@ -121,7 -121,7 +121,7 @@@ Image::scale (libdcp::Size out_size, Sc
   *  @param scaler Scaler to use.
   */
  shared_ptr<Image>
 -Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler const * scaler, bool result_aligned) const
 +Image::scale_and_convert_to_rgb (libdcp::Size out_size, Scaler const * scaler, bool result_aligned) const
  {
        assert (scaler);
        /* Empirical testing suggests that sws_scale() will crash if
        */
        assert (aligned ());
  
 -      libdcp::Size content_size = out_size;
 -      content_size.width -= (padding * 2);
 -
 -      shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, content_size, result_aligned));
 +      shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, out_size, result_aligned));
  
        struct SwsContext* scale_context = sws_getContext (
                size().width, size().height, pixel_format(),
 -              content_size.width, content_size.height, PIX_FMT_RGB24,
 +              out_size.width, out_size.height, PIX_FMT_RGB24,
                scaler->ffmpeg_id (), 0, 0, 0
                );
  
                rgb->data(), rgb->stride()
                );
  
 -      /* Put the image in the right place in a black frame if are padding; this is
 -         a bit grubby and expensive, but probably inconsequential in the great
 -         scheme of things.
 -      */
 -      if (padding > 0) {
 -              shared_ptr<Image> padded_rgb (new SimpleImage (PIX_FMT_RGB24, out_size, result_aligned));
 -              padded_rgb->make_black ();
 -
 -              /* XXX: we are cheating a bit here; we know the frame is RGB so we can
 -                 make assumptions about its composition.
 -              */
 -              uint8_t* p = padded_rgb->data()[0] + padding * 3;
 -              uint8_t* q = rgb->data()[0];
 -              for (int j = 0; j < rgb->lines(0); ++j) {
 -                      memcpy (p, q, rgb->line_size()[0]);
 -                      p += padded_rgb->stride()[0];
 -                      q += rgb->stride()[0];
 -              }
 -
 -              rgb = padded_rgb;
 -      }
 -
        sws_freeContext (scale_context);
  
        return rgb;
@@@ -351,21 -376,6 +351,21 @@@ Image::alpha_blend (shared_ptr<const Im
        }
  }
  
 +void
 +Image::copy (shared_ptr<const Image> other, Position position)
 +{
 +      /* Only implemented for RGB24 onto RGB24 so far */
 +      assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGB24);
 +      assert (position.x >= 0 && position.y >= 0);
 +
 +      int const N = min (position.x + other->size().width, size().width) - position.x;
 +      for (int ty = position.y, oy = 0; ty < size().height && oy < other->size().height; ++ty, ++oy) {
 +              uint8_t * const tp = data()[0] + ty * stride()[0] + position.x * 3;
 +              uint8_t * const op = other->data()[0] + oy * other->stride()[0];
 +              memcpy (tp, op, N * 3);
 +      }
 +}     
 +
  void
  Image::read_from_socket (shared_ptr<Socket> socket)
  {
@@@ -407,13 -417,13 +407,13 @@@ Image::bytes_per_pixel (int c) cons
  
        bpp[0] = floor ((d->comp[0].depth_minus1 + 1 + 7) / 8);
        if (d->nb_components > 1) {
-               bpp[1] = floor ((d->comp[1].depth_minus1 + 1 + 7) / 8) / pow (2, d->log2_chroma_w);
+               bpp[1] = floor ((d->comp[1].depth_minus1 + 1 + 7) / 8) / pow (2.0f, d->log2_chroma_w);
        }
        if (d->nb_components > 2) {
-               bpp[2] = floor ((d->comp[2].depth_minus1 + 1 + 7) / 8) / pow (2, d->log2_chroma_w);
+               bpp[2] = floor ((d->comp[2].depth_minus1 + 1 + 7) / 8) / pow (2.0f, d->log2_chroma_w);
        }
        if (d->nb_components > 3) {
-               bpp[3] = floor ((d->comp[3].depth_minus1 + 1 + 7) / 8) / pow (2, d->log2_chroma_w);
+               bpp[3] = floor ((d->comp[3].depth_minus1 + 1 + 7) / 8) / pow (2.0f, d->log2_chroma_w);
        }
        
        if ((d->flags & PIX_FMT_PLANAR) == 0) {
diff --combined src/lib/subtitle.cc
index eafccd9b503651e2744bb53117c8c699e8c6bc0c,5c2a0d0b5f31d65efacf76f4194a812c104bff3f..2815fccd8e19ce538b0e760506dd634ddd8e5728
@@@ -1,5 -1,3 +1,5 @@@
 +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
 +
  /*
      Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
  
@@@ -47,8 -45,8 +47,8 @@@ TimedSubtitle::TimedSubtitle (AVSubtitl
        double const packet_time = static_cast<double> (sub.pts) / AV_TIME_BASE;
        
        /* hence start time for this sub */
 -      _from = packet_time + (double (sub.start_display_time) / 1e3);
 -      _to = packet_time + (double (sub.end_display_time) / 1e3);
 +      _from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
 +      _to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
  
        if (sub.num_rects > 1) {
                throw DecodeError (_("multi-part subtitles not yet supported"));
@@@ -82,9 -80,9 +82,9 @@@
        _subtitle.reset (new Subtitle (Position (rect->x, rect->y), image));
  }     
  
 -/** @param t Time in seconds from the start of the source */
 +/** @param t Time from the start of the source */
  bool
 -TimedSubtitle::displayed_at (double t) const
 +TimedSubtitle::displayed_at (Time t) const
  {
        return t >= _from && t <= _to;
  }
@@@ -110,13 -108,13 +110,13 @@@ Subtitle::Subtitle (Position p, shared_
   *  in the coordinate space of the source.
   *  @param subtitle_scale scaling factor to apply to the subtitle image.
   */
- Rect
dvdomatic::Rect
  subtitle_transformed_area (
        float target_x_scale, float target_y_scale,
-       Rect sub_area, int subtitle_offset, float subtitle_scale
+       dvdomatic::Rect sub_area, int subtitle_offset, float subtitle_scale
        )
  {
-       Rect tx;
+       dvdomatic::Rect tx;
  
        sub_area.y += subtitle_offset;
  
  }
  
  /** @return area that this subtitle takes up, in the original uncropped source's coordinate space */
- Rect
dvdomatic::Rect
  Subtitle::area () const
  {
-       return Rect (_position.x, _position.y, _image->size().width, _image->size().height);
+       return dvdomatic::Rect (_position.x, _position.y, _image->size().width, _image->size().height);
  }
diff --combined src/lib/subtitle.h
index 52bd35923b8c0e1b9101dcd676c7ea8d507e083e,e3a853695f0fb313f2f20f2f5e6775e359eada92..c3929d676ef721b39de7f6d68bbca40e6dce6513
@@@ -1,5 -1,3 +1,5 @@@
 +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
 +
  /*
      Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
  
@@@ -25,7 -23,7 +25,7 @@@
  
  #include <list>
  #include <boost/shared_ptr.hpp>
 -#include "util.h"
 +#include "types.h"
  
  struct AVSubtitle;
  class Image;
@@@ -48,17 -46,17 +48,17 @@@ public
                return _image;
        }
  
-       Rect area () const;
+       dvdomatic::Rect area () const;
        
  private:
        Position _position;
        boost::shared_ptr<Image> _image;
  };
  
- Rect
dvdomatic::Rect
  subtitle_transformed_area (
        float target_x_scale, float target_y_scale,
-       Rect sub_area, int subtitle_offset, float subtitle_scale
+       dvdomatic::Rect sub_area, int subtitle_offset, float subtitle_scale
        );
  
  /** A Subtitle class with details of the time over which it should be shown */
@@@ -67,7 -65,7 +67,7 @@@ class TimedSubtitl
  public:
        TimedSubtitle (AVSubtitle const &);
  
 -      bool displayed_at (double t) const;
 +      bool displayed_at (Time) const;
        
        boost::shared_ptr<Subtitle> subtitle () const {
                return _subtitle;
@@@ -76,8 -74,8 +76,8 @@@
  private:
        /** the subtitle */
        boost::shared_ptr<Subtitle> _subtitle;
 -      /** display from time in seconds from the start of the film */
 -      double _from;
 -      /** display to time in seconds from the start of the film */
 -      double _to;
 +      /** display from time from the start of the content */
 +      Time _from;
 +      /** display to time from the start of the content */
 +      Time _to;
  };
diff --combined src/lib/util.h
index 65859309daaf9de91f8af1a2ce39ad182ffb71e7,3e1d7f4b407d8326adf7869c0b0e15bd8785b269..be70eb25997ca23b6473483bdc1e9df2c37a9e8f
@@@ -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,36 -37,38 +37,38 @@@ extern "C" 
  #include <libavfilter/avfilter.h>
  }
  #include "compose.hpp"
 +#include "types.h"
  
 -#ifdef DVDOMATIC_DEBUG
 +#ifdef DCPOMATIC_DEBUG
  #define TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TIMING);
  #else
  #define TIMING(...)
  #endif
  
+ #undef check
  /** The maximum number of audio channels that we can cope with */
  #define MAX_AUDIO_CHANNELS 6
  
  class Scaler;
  
  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);
@@@ -116,7 -206,7 +118,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.
@@@ -150,7 -240,65 +152,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 (int);
 -
 -      boost::optional<libdcp::Channel> source_to_dcp (int c) const;
 -      boost::optional<int> dcp_to_source (libdcp::Channel c) const;
 -      int dcp_channels () const;
 -
 -private:
 -      int _source_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 (ContentVideoFrame v, float audio_sample_rate, float frames_per_second);
  extern std::pair<std::string, int> cpu_info ();
  
  class LocaleGuard
diff --combined src/lib/video_content.cc
index 18a128a5d120687a9e166567267b845bfd6d8af8,0000000000000000000000000000000000000000..84dee81d1f22935abaa6dd6abdf3e7383879a911
mode 100644,000000..100644
--- /dev/null
@@@ -1,220 -1,0 +1,222 @@@
 +/*
 +    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +*/
 +
 +#include <libcxml/cxml.h>
 +#include "video_content.h"
 +#include "video_decoder.h"
 +#include "ratio.h"
 +
 +#include "i18n.h"
 +
 +int const VideoContentProperty::VIDEO_SIZE     = 0;
 +int const VideoContentProperty::VIDEO_FRAME_RATE = 1;
 +int const VideoContentProperty::VIDEO_CROP     = 2;
 +int const VideoContentProperty::VIDEO_RATIO    = 3;
 +
 +using std::string;
 +using std::stringstream;
 +using std::setprecision;
 +using boost::shared_ptr;
 +using boost::lexical_cast;
 +using boost::optional;
 +
 +VideoContent::VideoContent (shared_ptr<const Film> f, Time s, ContentVideoFrame len)
 +      : Content (f, s)
 +      , _video_length (len)
++      , _video_frame_rate (0)
 +      , _ratio (0)
 +{
 +
 +}
 +
 +VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p)
 +      : Content (f, p)
 +      , _video_length (0)
++      , _video_frame_rate (0)
 +      , _ratio (0)
 +{
 +
 +}
 +
 +VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
 +      : Content (f, node)
 +{
 +      _video_length = node->number_child<ContentVideoFrame> ("VideoLength");
 +      _video_size.width = node->number_child<int> ("VideoWidth");
 +      _video_size.height = node->number_child<int> ("VideoHeight");
 +      _video_frame_rate = node->number_child<float> ("VideoFrameRate");
 +      _crop.left = node->number_child<int> ("LeftCrop");
 +      _crop.right = node->number_child<int> ("RightCrop");
 +      _crop.top = node->number_child<int> ("TopCrop");
 +      _crop.bottom = node->number_child<int> ("BottomCrop");
 +      optional<string> r = node->optional_string_child ("Ratio");
 +      if (r) {
 +              _ratio = Ratio::from_id (r.get ());
 +      }
 +}
 +
 +VideoContent::VideoContent (VideoContent const & o)
 +      : Content (o)
 +      , _video_length (o._video_length)
 +      , _video_size (o._video_size)
 +      , _video_frame_rate (o._video_frame_rate)
 +      , _ratio (o._ratio)
 +{
 +
 +}
 +
 +void
 +VideoContent::as_xml (xmlpp::Node* node) const
 +{
 +      boost::mutex::scoped_lock lm (_mutex);
 +      node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length));
 +      node->add_child("VideoWidth")->add_child_text (lexical_cast<string> (_video_size.width));
 +      node->add_child("VideoHeight")->add_child_text (lexical_cast<string> (_video_size.height));
 +      node->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate));
 +      node->add_child("LeftCrop")->add_child_text (boost::lexical_cast<string> (_crop.left));
 +      node->add_child("RightCrop")->add_child_text (boost::lexical_cast<string> (_crop.right));
 +      node->add_child("TopCrop")->add_child_text (boost::lexical_cast<string> (_crop.top));
 +      node->add_child("BottomCrop")->add_child_text (boost::lexical_cast<string> (_crop.bottom));
 +      if (_ratio) {
 +              node->add_child("Ratio")->add_child_text (_ratio->id ());
 +      }
 +}
 +
 +void
 +VideoContent::take_from_video_decoder (shared_ptr<VideoDecoder> d)
 +{
 +      /* These decoder calls could call other content methods which take a lock on the mutex */
 +      libdcp::Size const vs = d->video_size ();
 +      float const vfr = d->video_frame_rate ();
 +      
 +        {
 +                boost::mutex::scoped_lock lm (_mutex);
 +                _video_size = vs;
 +              _video_frame_rate = vfr;
 +        }
 +        
 +        signal_changed (VideoContentProperty::VIDEO_SIZE);
 +        signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
 +}
 +
 +
 +string
 +VideoContent::information () const
 +{
 +      if (video_size().width == 0 || video_size().height == 0) {
 +              return "";
 +      }
 +      
 +      stringstream s;
 +
 +      s << String::compose (
 +              _("%1x%2 pixels (%3:1)"),
 +              video_size().width,
 +              video_size().height,
 +              setprecision (3), float (video_size().width) / video_size().height
 +              );
 +      
 +      return s.str ();
 +}
 +
 +void
 +VideoContent::set_crop (Crop c)
 +{
 +      {
 +              boost::mutex::scoped_lock lm (_mutex);
 +              _crop = c;
 +      }
 +      signal_changed (VideoContentProperty::VIDEO_CROP);
 +}
 +
 +void
 +VideoContent::set_left_crop (int c)
 +{
 +      {
 +              boost::mutex::scoped_lock lm (_mutex);
 +              
 +              if (_crop.left == c) {
 +                      return;
 +              }
 +              
 +              _crop.left = c;
 +      }
 +      
 +      signal_changed (VideoContentProperty::VIDEO_CROP);
 +}
 +
 +void
 +VideoContent::set_right_crop (int c)
 +{
 +      {
 +              boost::mutex::scoped_lock lm (_mutex);
 +              if (_crop.right == c) {
 +                      return;
 +              }
 +              
 +              _crop.right = c;
 +      }
 +      
 +      signal_changed (VideoContentProperty::VIDEO_CROP);
 +}
 +
 +void
 +VideoContent::set_top_crop (int c)
 +{
 +      {
 +              boost::mutex::scoped_lock lm (_mutex);
 +              if (_crop.top == c) {
 +                      return;
 +              }
 +              
 +              _crop.top = c;
 +      }
 +      
 +      signal_changed (VideoContentProperty::VIDEO_CROP);
 +}
 +
 +void
 +VideoContent::set_bottom_crop (int c)
 +{
 +      {
 +              boost::mutex::scoped_lock lm (_mutex);
 +              if (_crop.bottom == c) {
 +                      return;
 +              }
 +              
 +              _crop.bottom = c;
 +      }
 +
 +      signal_changed (VideoContentProperty::VIDEO_CROP);
 +}
 +
 +void
 +VideoContent::set_ratio (Ratio const * r)
 +{
 +      {
 +              boost::mutex::scoped_lock lm (_mutex);
 +              if (_ratio == r) {
 +                      return;
 +              }
 +
 +              _ratio = r;
 +      }
 +
 +      signal_changed (VideoContentProperty::VIDEO_RATIO);
 +}
diff --combined src/tools/dcpomatic.cc
index 8d3de53ffd5d884d476584beaba78c75119fcda0,0000000000000000000000000000000000000000..ebd647861817ea98cacdf3289ae189e378796aaf
mode 100644,000000..100644
--- /dev/null
@@@ -1,533 -1,0 +1,551 @@@
-       ID_file_quit,
-       ID_edit_preferences,
 +/*
 +    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/aboutdlg.h>
 +#include <wx/stdpaths.h>
 +#include <wx/cmdline.h>
 +#include "wx/film_viewer.h"
 +#include "wx/film_editor.h"
 +#include "wx/job_manager_view.h"
 +#include "wx/config_dialog.h"
 +#include "wx/job_wrapper.h"
 +#include "wx/wx_util.h"
 +#include "wx/new_film_dialog.h"
 +#include "wx/properties_dialog.h"
 +#include "wx/wx_ui_signaller.h"
 +#include "lib/film.h"
 +#include "lib/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_help_about
 +      ID_jobs_make_dcp,
 +      ID_jobs_send_dcp_to_tms,
 +      ID_jobs_show_dcp,
 +      ID_jobs_analyse_audio,
-       add_item (file, _("&Quit"), ID_file_quit, ALWAYS);
 +};
 +
 +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 ();
-       add_item (edit, _("&Preferences..."), ID_edit_preferences, ALWAYS);
++#endif        
++      add_item (file, _("&Exit"), wxID_EXIT, ALWAYS);
 +
++#ifdef __WXOSX__      
++      add_item (file, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
++#else
 +      wxMenu* edit = new wxMenu;
-       add_item (help, _("About"), ID_help_about, ALWAYS);
++      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);
 +      jobs_menu->AppendSeparator ();
 +      add_item (jobs_menu, _("&Analyse audio"), ID_jobs_analyse_audio, NEEDS_FILM);
 +
 +      wxMenu* help = new wxMenu;
-               Connect (ID_file_quit, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_quit));
-               Connect (ID_edit_preferences, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::edit_preferences));
++#ifdef __WXOSX__      
++      add_item (help, _("About DVD-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 (ID_help_about, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::help_about));
++              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 (ID_jobs_analyse_audio, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_analyse_audio));
-       void file_quit (wxCommandEvent &)
++              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 jobs_analyse_audio (wxCommandEvent &)
 +      {
 +              film->analyse_audio ();
 +      }
 +      
 +      void help_about (wxCommandEvent &)
 +      {
 +              wxAboutDialogInfo info;
 +              info.SetName (_("DCP-o-matic"));
 +              if (strcmp (dcpomatic_git_commit, "release") == 0) {
 +                      info.SetVersion (std_to_wx (String::compose ("version %1", dcpomatic_version)));
 +              } else {
 +                      info.SetVersion (std_to_wx (String::compose ("version %1 git %2", dcpomatic_version, dcpomatic_git_commit)));
 +              }
 +              info.SetDescription (_("Free, open-source DCP generation from almost anything."));
 +              info.SetCopyright (_("(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen"));
 +
 +              wxArrayString authors;
 +              authors.Add (wxT ("Carl Hetherington"));
 +              authors.Add (wxT ("Terrence Meiczinger"));
 +              authors.Add (wxT ("Paul Davis"));
 +              authors.Add (wxT ("Ole Laursen"));
 +              info.SetDevelopers (authors);
 +
 +              wxArrayString translators;
 +              translators.Add (wxT ("Olivier Perriere"));
 +              translators.Add (wxT ("Lilian Lefranc"));
 +              translators.Add (wxT ("Thierry Journet"));
 +              translators.Add (wxT ("Massimiliano Broggi"));
 +              translators.Add (wxT ("Manuel AC"));
 +              translators.Add (wxT ("Adam Klotblixt"));
 +              info.SetTranslators (translators);
 +              
 +              info.SetWebSite (wxT ("http://carlh.net/software/dcpomatic"));
 +              wxAboutBox (info);
 +      }
 +};
 +
 +#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_POSIX                
 +              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 152e063c1e59104b78d5f280bdfa0a3973e4b3df,0000000000000000000000000000000000000000..d3a353154eba34e908eebda120f94373f836e012
mode 100644,000000..100644
--- /dev/null
@@@ -1,174 -1,0 +1,177 @@@
- #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 <boost/thread.hpp>
 +#include <wx/taskbar.h>
 +#include <wx/icon.h>
 +#include "wx_util.h"
 +#include "lib/util.h"
 +#include "lib/server.h"
 +#include "lib/config.h"
 +
 +using std::cout;
 +using std::string;
 +using boost::shared_ptr;
 +using boost::thread;
 +using boost::bind;
 +
 +enum {
 +      ID_status = 1,
 +      ID_quit,
 +      ID_timer
 +};
 +
 +class MemoryLog : public Log
 +{
 +public:
 +
 +      string get () const {
 +              boost::mutex::scoped_lock (_mutex);
 +              return _log;
 +      }
 +
 +private:
 +      void do_log (string m)
 +      {
 +              _log = m;
 +      }
 +
 +      string _log;    
 +};
 +
 +static shared_ptr<MemoryLog> memory_log (new MemoryLog);
 +
 +class StatusDialog : public wxDialog
 +{
 +public:
 +      StatusDialog ()
 +              : wxDialog (0, wxID_ANY, _("DCP-o-matic encode server"), wxDefaultPosition, wxSize (600, 80), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
 +              , _timer (this, ID_timer)
 +      {
 +              _sizer = new wxFlexGridSizer (1, 6, 6);
 +              _sizer->AddGrowableCol (0, 1);
 +
 +              _text = new wxTextCtrl (this, wxID_ANY, _(""), wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
 +              _sizer->Add (_text, 1, wxEXPAND);
 +
 +              SetSizer (_sizer);
 +              _sizer->Layout ();
 +
 +              Connect (ID_timer, wxEVT_TIMER, wxTimerEventHandler (StatusDialog::update));
 +              _timer.Start (1000);
 +      }
 +
 +private:
 +      void update (wxTimerEvent &)
 +      {
 +              _text->ChangeValue (std_to_wx (memory_log->get ()));
 +              _sizer->Layout ();
 +      }
 +
 +      wxFlexGridSizer* _sizer;
 +      wxTextCtrl* _text;
 +      wxTimer _timer;
 +};
 +
 +class TaskBarIcon : public wxTaskBarIcon
 +{
 +public:
 +      TaskBarIcon ()
 +      {
 +#ifdef __WXMSW__              
 +              wxIcon icon (std_to_wx ("taskbar_icon"));
 +#endif
 +#ifdef __WXGTK__
 +              wxInitAllImageHandlers();
 +              wxBitmap bitmap (wxString::Format (wxT ("%s/taskbar_icon.png"), POSIX_ICON_PREFIX), wxBITMAP_TYPE_PNG);
 +              wxIcon icon;
 +              icon.CopyFromBitmap (bitmap);
++#endif
++#ifndef __WXOSX__
++              /* XXX: fix this for OS X */
 +              SetIcon (icon, std_to_wx ("DCP-o-matic encode server"));
++#endif                
 +
 +              Connect (ID_status, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (TaskBarIcon::status));
 +              Connect (ID_quit, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (TaskBarIcon::quit));
 +      }
 +      
 +      wxMenu* CreatePopupMenu ()
 +      {
 +              wxMenu* menu = new wxMenu;
 +              menu->Append (ID_status, std_to_wx ("Status..."));
 +              menu->Append (ID_quit, std_to_wx ("Quit"));
 +              return menu;
 +      }
 +
 +private:
 +      void status (wxCommandEvent &)
 +      {
 +              StatusDialog* d = new StatusDialog;
 +              d->Show ();
 +      }
 +
 +      void quit (wxCommandEvent &)
 +      {
 +              wxTheApp->ExitMainLoop ();
 +      }
 +};
 +
 +class App : public wxApp
 +{
 +public:
 +      App ()
 +              : wxApp ()
 +              , _thread (0)
 +              , _icon (0)
 +      {}
 +
 +private:      
 +      
 +      bool OnInit ()
 +      {
 +              if (!wxApp::OnInit ()) {
 +                      return false;
 +              }
 +              
 +              dcpomatic_setup ();
 +
 +              _icon = new TaskBarIcon;
 +              _thread = new thread (bind (&App::main_thread, this));
 +              
 +              return true;
 +      }
 +
 +      int OnExit ()
 +      {
 +              delete _icon;
 +              return wxApp::OnExit ();
 +      }
 +
 +      void main_thread ()
 +      {
 +              Server server (memory_log);
 +              server.run (Config::instance()->num_local_encoding_threads ());
 +      }
 +
 +      boost::thread* _thread;
 +      TaskBarIcon* _icon;
 +};
 +
 +IMPLEMENT_APP (App)
diff --combined src/tools/wscript
index cddd07b7db71447cce08ca3bf610a4b826b5ca77,ee4e7edefc38f8c2c500653fe926e02cbf1f0309..38d986f25fe0af7ae42383af65dc5fea56cc6bbf
@@@ -4,29 -4,29 +4,29 @@@ 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 AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC'
 +        obj.uselib = 'BOOST_THREAD OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC'
          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 OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC'
 +            obj.uselib = 'DCP CXML OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC'
              obj.includes = ['..']
 -            obj.use    = ['libdvdomatic', 'libdvdomatic-wx']
 +            obj.use    = ['libdcpomatic', 'libdcpomatic-wx']
              obj.source = '%s.cc' % t
              if bld.env.TARGET_WINDOWS:
-                 obj.source += ' ../../windows/dcpomatic.rc'
 -                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/audio_plot.cc
index 46c64c9bf4eb8337f05f71b939e5a228cb4f2fe1,3fec1d3fe3dec0f2e2af8026e94c1e9aed497f20..fb02fea7b9ebe734c8b43571998e95a0ca88688c
@@@ -1,5 -1,3 +1,5 @@@
 +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
 +
  /*
      Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
  
@@@ -23,6 -21,7 +23,6 @@@
  #include <boost/bind.hpp>
  #include <wx/graphics.h>
  #include "audio_plot.h"
 -#include "lib/decoder_factory.h"
  #include "lib/audio_decoder.h"
  #include "lib/audio_analysis.h"
  #include "wx/wx_util.h"
@@@ -39,11 -38,13 +39,13 @@@ int const AudioPlot::_minimum = -70
  int const AudioPlot::max_smoothing = 128;
  
  AudioPlot::AudioPlot (wxWindow* parent)
 -      : wxPanel (parent)
 +      : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
        , _gain (0)
        , _smoothing (max_smoothing / 2)
  {
+ #ifndef __WXOSX__     
        SetDoubleBuffered (true);
+ #endif        
  
        for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
                _channel_visible[i] = false;
diff --combined src/wx/config_dialog.cc
index 3efd7857a667355c888832c1430ff70b6e34b9f1,4daf581ba410191b71773b3655cb2c6d1bed96d7..0b13b9c8888d3abd1fd9474b6365e69f4cb28e7a
@@@ -18,7 -18,7 +18,7 @@@
  */
  
  /** @file src/config_dialog.cc
-  *  @brief A dialogue to edit DCP-o-matic configuration.
+  *  @brief A dialogue to edit DVD-o-matic configuration.
   */
  
  #include <iostream>
@@@ -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"
@@@ -43,7 -43,7 +43,7 @@@ using namespace std
  using boost::bind;
  
  ConfigDialog::ConfigDialog (wxWindow* parent)
-       : wxDialog (parent, wxID_ANY, _("DCP-o-matic Preferences"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+       : wxDialog (parent, wxID_ANY, _("DVD-o-matic Preferences"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
  {
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
        _notebook = new wxNotebook (this, wxID_ANY);
@@@ -95,7 -95,7 +95,7 @@@ ConfigDialog::make_misc_panel (
        table->Add (_language, 1, wxEXPAND);
        table->AddSpacer (0);
  
-       wxStaticText* restart = add_label_to_sizer (table, _misc_panel, _("(restart DCP-o-matic to see language changes)"));
+       wxStaticText* restart = add_label_to_sizer (table, _misc_panel, _("(restart DVD-o-matic to see language changes)"));
        wxFont font = restart->GetFont();
        font.SetStyle (wxFONTSTYLE_ITALIC);
        font.SetPointSize (font.GetPointSize() - 1);
        table->Add (_num_local_encoding_threads, 1, wxEXPAND);
        table->AddSpacer (0);
  
 +      add_label_to_sizer (table, _misc_panel, _("Default duration of still images"));
 +      _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, _("Default directory for new films"));
  #ifdef __WXMSW__
        _default_directory = new DirPickerCtrl (_misc_panel);
        table->Add (_default_dci_metadata_button);
        table->AddSpacer (1);
  
 -      add_label_to_sizer (table, _misc_panel, _("Default format"));
 -      _default_format = new wxChoice (_misc_panel, wxID_ANY);
 -      table->Add (_default_format);
 +      add_label_to_sizer (table, _misc_panel, _("Default container"));
 +      _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"));
        _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;
@@@ -235,6 -226,33 +235,6 @@@ ConfigDialog::make_tms_panel (
        _tms_password->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::tms_password_changed), 0, this);
  }
  
 -void
 -ConfigDialog::make_metadata_panel ()
 -{
 -      _metadata_panel = new wxPanel (_notebook);
 -      wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
 -      _metadata_panel->SetSizer (s);
 -
 -      wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
 -      table->AddGrowableCol (1, 1);
 -      s->Add (table, 1, wxALL | wxEXPAND, 8);
 -
 -      add_label_to_sizer (table, _metadata_panel, _("Issuer"));
 -      _issuer = new wxTextCtrl (_metadata_panel, wxID_ANY);
 -      table->Add (_issuer, 1, wxEXPAND);
 -
 -      add_label_to_sizer (table, _metadata_panel, _("Creator"));
 -      _creator = new wxTextCtrl (_metadata_panel, wxID_ANY);
 -      table->Add (_creator, 1, wxEXPAND);
 -
 -      Config* config = Config::instance ();
 -
 -      _issuer->SetValue (std_to_wx (config->dcp_metadata().issuer));
 -      _issuer->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::issuer_changed), 0, this);
 -      _creator->SetValue (std_to_wx (config->dcp_metadata().creator));
 -      _creator->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::creator_changed), 0, this);
 -}
 -
  void
  ConfigDialog::make_ab_panel ()
  {
                add_label_to_sizer (table, _ab_panel, _("Reference filters"));
                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);
 +              s->Add (_reference_filters, 1, wxEXPAND);
                _reference_filters_button = new wxButton (_ab_panel, wxID_ANY, _("Edit..."));
                s->Add (_reference_filters_button, 0);
                table->Add (s, 1, wxEXPAND);
                table->AddSpacer (0);
        }
 +}
 +
 +void
 +ConfigDialog::make_metadata_panel ()
 +{
 +      _metadata_panel = new wxPanel (_notebook);
 +      wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
 +      _metadata_panel->SetSizer (s);
 +
 +      wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
 +      table->AddGrowableCol (1, 1);
 +      s->Add (table, 1, wxALL | wxEXPAND, 8);
 +
 +      add_label_to_sizer (table, _metadata_panel, _("Issuer"));
 +      _issuer = new wxTextCtrl (_metadata_panel, wxID_ANY);
 +      table->Add (_issuer, 1, wxEXPAND);
 +
 +      add_label_to_sizer (table, _metadata_panel, _("Creator"));
 +      _creator = new wxTextCtrl (_metadata_panel, wxID_ANY);
 +      table->Add (_creator, 1, wxEXPAND);
  
        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);
 +      _issuer->SetValue (std_to_wx (config->dcp_metadata().issuer));
 +      _issuer->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::issuer_changed), 0, this);
 +      _creator->SetValue (std_to_wx (config->dcp_metadata().creator));
 +      _creator->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::creator_changed), 0, this);
  }
  
  void
@@@ -527,16 -527,10 +527,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/film_viewer.cc
index 26f99db116b83036e14d40fedd90d1ceb63d5aa9,6845031cf96cf2688775bef192da3548093de5bb..e1471d94eb3805ddb754d77dc8812b9f849ba8fb
@@@ -1,5 -1,3 +1,3 @@@
- /* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
  /*
      Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
  
  #include <iomanip>
  #include <wx/tglbtn.h>
  #include "lib/film.h"
 -#include "lib/format.h"
 +#include "lib/ratio.h"
  #include "lib/util.h"
  #include "lib/job_manager.h"
 -#include "lib/options.h"
 -#include "lib/subtitle.h"
  #include "lib/image.h"
  #include "lib/scaler.h"
  #include "lib/exceptions.h"
  #include "lib/examine_content_job.h"
  #include "lib/filter.h"
 +#include "lib/player.h"
 +#include "lib/video_content.h"
 +#include "lib/ffmpeg_content.h"
 +#include "lib/imagemagick_content.h"
  #include "film_viewer.h"
  #include "wx_util.h"
  #include "video_decoder.h"
@@@ -49,8 -45,6 +47,8 @@@ using std::max
  using std::cout;
  using std::list;
  using boost::shared_ptr;
 +using boost::dynamic_pointer_cast;
 +using boost::weak_ptr;
  using libdcp::Size;
  
  FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        , _display_frame_x (0)
        , _got_frame (false)
  {
+ #ifndef __WXOSX__
        _panel->SetDoubleBuffered (true);
+ #endif
+       
  #if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
        _panel->SetBackgroundStyle (wxBG_STYLE_PAINT);
  #endif        
@@@ -114,26 -111,49 +115,26 @@@ voi
  FilmViewer::film_changed (Film::Property p)
  {
        switch (p) {
 -      case Film::FORMAT:
 +      case Film::CONTAINER:
                calculate_sizes ();
                update_from_raw ();
                break;
        case Film::CONTENT:
        {
 -              DecodeOptions o;
 -              o.decode_audio = false;
 -              o.decode_subtitles = true;
 -              o.video_sync = false;
 -
 -              try {
 -                      _decoders = decoder_factory (_film, o);
 -              } catch (StringError& e) {
 -                      error_dialog (this, wxString::Format (_("Could not open content file (%s)"), std_to_wx(e.what()).data()));
 -                      return;
 -              }
 -              
 -              if (_decoders.video == 0) {
 -                      break;
 -              }
 -              _decoders.video->set_subtitle_stream (_film->subtitle_stream());
 -              _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3, _4));
 -              _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this));
                calculate_sizes ();
 -              get_frame ();
 -              _panel->Refresh ();
 -              _slider->Show (_film->content_type() == VIDEO);
 -              _play_button->Show (_film->content_type() == VIDEO);
 -              _v_sizer->Layout ();
 +              wxScrollEvent ev;
 +              slider_moved (ev);
                break;
        }
        case Film::WITH_SUBTITLES:
        case Film::SUBTITLE_OFFSET:
        case Film::SUBTITLE_SCALE:
 -      case Film::SCALER:
 -      case Film::FILTERS:
 -              update_from_raw ();
 +              raw_to_display ();
 +              _panel->Refresh ();
 +              _panel->Update ();
                break;
 -      case Film::SUBTITLE_STREAM:
 -              if (_decoders.video) {
 -                      _decoders.video->set_subtitle_stream (_film->subtitle_stream ());
 -              }
 +      case Film::SCALER:
 +              update_from_decoder ();
                break;
        default:
                break;
@@@ -146,7 -166,7 +147,7 @@@ FilmViewer::set_film (shared_ptr<Film> 
        if (_film == f) {
                return;
        }
 -      
 +
        _film = f;
  
        _raw_frame.reset ();
                return;
        }
  
 +      _player = f->player ();
 +      _player->disable_audio ();
 +      /* Don't disable subtitles here as we may need them, and it's nice to be able to turn them
 +         on and off without needing obtain a new Player.
 +      */
 +      
 +      _player->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3));
 +      
        _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
 +      _film->ContentChanged.connect (boost::bind (&FilmViewer::film_content_changed, this, _1, _2));
  
        film_changed (Film::CONTENT);
 -      film_changed (Film::FORMAT);
 +      film_changed (Film::CONTAINER);
        film_changed (Film::WITH_SUBTITLES);
        film_changed (Film::SUBTITLE_OFFSET);
        film_changed (Film::SUBTITLE_SCALE);
 -      film_changed (Film::SUBTITLE_STREAM);
  }
  
  void
 -FilmViewer::decoder_changed ()
 +FilmViewer::update_from_decoder ()
  {
 -      if (_decoders.video == 0 || _decoders.video->seek_to_last ()) {
 +      if (!_player) {
                return;
        }
  
 +      _player->seek (_player->position ());
        get_frame ();
        _panel->Refresh ();
        _panel->Update ();
  void
  FilmViewer::timer (wxTimerEvent &)
  {
 -      if (!_film || !_decoders.video) {
 +      if (!_player) {
                return;
        }
        
-       _panel->Refresh ();
-       _panel->Update ();
        get_frame ();
  
        if (_film->length()) {
 -              int const new_slider_position = 4096 * _decoders.video->last_source_time() / (_film->length().get() / _film->source_frame_rate());
 +              int const new_slider_position = 4096 * _player->position() / _film->length();
                if (new_slider_position != _slider->GetValue()) {
                        _slider->SetValue (new_slider_position);
                }
        }
+       _panel->Refresh ();
+       _panel->Update ();
  }
  
  
@@@ -231,6 -242,12 +232,6 @@@ FilmViewer::paint_panel (wxPaintEvent &
        wxBitmap frame_bitmap (frame);
        dc.DrawBitmap (frame_bitmap, _display_frame_x, 0);
  
 -      if (_film->with_subtitles() && _display_sub) {
 -              wxImage sub (_display_sub->size().width, _display_sub->size().height, _display_sub->data()[0], _display_sub->alpha(), true);
 -              wxBitmap sub_bitmap (sub);
 -              dc.DrawBitmap (sub_bitmap, _display_sub_position.x, _display_sub_position.y);
 -      }
 -
        if (_out_size.width < _panel_size.width) {
                wxPen p (GetBackgroundColour ());
                wxBrush b (GetBackgroundColour ());
  void
  FilmViewer::slider_moved (wxScrollEvent &)
  {
 -      if (!_film || !_film->length() || !_decoders.video) {
 -              return;
 -      }
 +      cout << "slider " << _slider->GetValue() << " " << _film->length() << "\n";
        
 -      if (_decoders.video->seek (_slider->GetValue() * _film->length().get() / (4096 * _film->source_frame_rate()))) {
 -              return;
 +      if (_film && _player) {
 +              _player->seek (_slider->GetValue() * _film->length() / 4096);
        }
        
        get_frame ();
@@@ -292,21 -311,49 +293,21 @@@ FilmViewer::raw_to_display (
                return;
        }
  
 -      boost::shared_ptr<const Image> input = _raw_frame;
 -
 -      pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
 -      if (!s.second.empty ()) {
 -              input = input->post_process (s.second, true);
 -      }
 -      
        /* Get a compacted image as we have to feed it to wxWidgets */
 -      _display_frame = input->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false);
 -
 -      if (_raw_sub) {
 -
 -              /* Our output is already cropped by the decoder, so we need to account for that
 -                 when working out the scale that we are applying.
 -              */
 -
 -              Size const cropped_size = _film->cropped_size (_film->size ());
 -
 -              dvdomatic::Rect tx = subtitle_transformed_area (
 -                      float (_film_size.width) / cropped_size.width,
 -                      float (_film_size.height) / cropped_size.height,
 -                      _raw_sub->area(), _film->subtitle_offset(), _film->subtitle_scale()
 -                      );
 -              
 -              _display_sub.reset (new RGBPlusAlphaImage (_raw_sub->image()->scale (tx.size(), _film->scaler(), false)));
 -              _display_sub_position = tx.position();
 -              _display_sub_position.x += _display_frame_x;
 -      } else {
 -              _display_sub.reset ();
 -      }
 +      _display_frame = _raw_frame->scale_and_convert_to_rgb (_film_size, _film->scaler(), false);
  }     
  
  void
  FilmViewer::calculate_sizes ()
  {
 -      if (!_film) {
 +      if (!_film || !_player) {
                return;
        }
  
 -      Format const * format = _film->format ();
 +      Ratio const * container = _film->container ();
        
        float const panel_ratio = static_cast<float> (_panel_size.width) / _panel_size.height;
 -      float const film_ratio = format ? format->container_ratio () : 1.78;
 +      float const film_ratio = container ? container->ratio () : 1.78;
                        
        if (panel_ratio < film_ratio) {
                /* panel is less widscreen than the film; clamp width */
           of our _display_frame.
        */
        _display_frame_x = 0;
 -      if (format) {
 -              _display_frame_x = static_cast<float> (format->dcp_padding (_film)) * _out_size.width / format->dcp_size().width;
 -      }
 +//    if (format) {
 +//            _display_frame_x = static_cast<float> (format->dcp_padding (_film)) * _out_size.width / format->dcp_size().width;
 +//    }
  
        _film_size = _out_size;
        _film_size.width -= _display_frame_x * 2;
@@@ -344,30 -391,32 +345,31 @@@ FilmViewer::play_clicked (wxCommandEven
  void
  FilmViewer::check_play_state ()
  {
 -      if (!_film) {
 +      if (!_film || _film->dcp_video_frame_rate() == 0) {
                return;
        }
        
        if (_play_button->GetValue()) {
 -              _timer.Start (1000 / _film->source_frame_rate());
 +              _timer.Start (1000 / _film->dcp_video_frame_rate());
        } else {
                _timer.Stop ();
        }
  }
  
  void
 -FilmViewer::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subtitle> sub, double t)
 +FilmViewer::process_video (shared_ptr<const Image> image, bool, Time t)
  {
        _raw_frame = image;
 -      _raw_sub = sub;
  
        raw_to_display ();
  
        _got_frame = true;
  
 -      double const fps = _decoders.video->frames_per_second ();
 +      double const fps = _film->dcp_video_frame_rate ();
-       _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ))));
+       /* Count frame number from 1 ... not sure if this is the best idea */
 -      _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps)) + 1));
++      _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ)) + 1));
  
 -      double w = t;
 +      double w = static_cast<double>(t) / TIME_HZ;
        int const h = (w / 3600);
        w -= h * 3600;
        int const m = (w / 60);
        _timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d:%02d"), h, m, s, f));
  }
  
 +/** Get a new _raw_frame from the decoder and then do
 + *  raw_to_display ().
 + */
  void
  FilmViewer::get_frame ()
  {
        /* Clear our raw frame in case we don't get a new one */
        _raw_frame.reset ();
  
 -      if (_decoders.video == 0) {
 +      if (!_player) {
                _display_frame.reset ();
                return;
        }
        try {
                _got_frame = false;
                while (!_got_frame) {
 -                      if (_decoders.video->pass ()) {
 +                      if (_player->pass ()) {
                                /* We didn't get a frame before the decoder gave up,
                                   so clear our display frame.
                                */
@@@ -430,26 -476,14 +432,26 @@@ FilmViewer::active_jobs_changed (bool a
        _play_button->Enable (!a);
  }
  
 +void
 +FilmViewer::film_content_changed (weak_ptr<Content>, int p)
 +{
 +      if (p == ContentProperty::LENGTH) {
 +              /* Force an update to our frame */
 +              wxScrollEvent ev;
 +              slider_moved (ev);
 +      } else if (p == VideoContentProperty::VIDEO_CROP) {
 +              update_from_decoder ();
 +      }               
 +}
 +
  void
  FilmViewer::back_clicked (wxCommandEvent &)
  {
 -      if (!_decoders.video) {
 +      if (!_player) {
                return;
        }
        
 -      _decoders.video->seek_back ();
 +      _player->seek_back ();
        get_frame ();
        _panel->Refresh ();
        _panel->Update ();
  void
  FilmViewer::forward_clicked (wxCommandEvent &)
  {
 -      if (!_decoders.video) {
 +      if (!_player) {
                return;
        }
  
 -      _decoders.video->seek_forward ();
 +      _player->seek_forward ();
        get_frame ();
        _panel->Refresh ();
        _panel->Update ();
diff --combined test/wscript
index 61c391663496477306d0cfe24674c622f8081a7b,f1a508a538a9acb56ddabf48cc5a649fc9872145..2fbbdacbf881d84390fcb0b9e7281809b56cbbd0
@@@ -1,15 -1,19 +1,19 @@@
  def configure(conf):
+     boost_test_suffix=''
+     if conf.env.TARGET_WINDOWS:
+         boost_test_suffix='-mt'
      conf.check_cxx(fragment = """
                                #define BOOST_TEST_MODULE Config test\n
                              #include <boost/test/unit_test.hpp>\n
                                int main() {}
-                               """, msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework-mt', uselib_store = 'BOOST_TEST')
+                               """, msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework%s' % boost_test_suffix, uselib_store = 'BOOST_TEST')
  
  def build(bld):
      obj = bld(features = 'cxx cxxprogram')
      obj.name   = 'unit-tests'
 -    obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC'
 -    obj.use    = 'libdvdomatic'
 +    obj.uselib = 'BOOST_TEST CXML DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC'
 +    obj.use    = 'libdcpomatic'
      obj.source = 'test.cc'
      obj.target = 'unit-tests'
      obj.install_path = ''
diff --combined wscript
index b82af4c37438afa59591b3a8fd0154a370174f16,1d471f043db2d53b4a5e168cbde3420a067944f6..2237758eee8ce84c902c7967a683a55de545fdc9
+++ b/wscript
@@@ -2,30 -2,31 +2,31 @@@ import subproces
  import os
  import sys
  
 -APPNAME = 'dvdomatic'
 -VERSION = '0.94beta1'
 +APPNAME = 'dcpomatic'
 +VERSION = '1.00pre'
  
  def options(opt):
      opt.load('compiler_cxx')
      opt.load('winres')
  
-     opt.add_option('--enable-debug', action='store_true', default = False, help = 'build with debugging information and without optimisation')
-     opt.add_option('--disable-gui', action='store_true', default = False, help = 'disable building of GUI tools')
-     opt.add_option('--target-windows', action='store_true', default = False, help = 'set up to do a cross-compile to Windows')
-     opt.add_option('--static', action='store_true', default = False, help = 'build statically, and link statically to libdcp and FFmpeg')
-     opt.add_option('--magickpp-config', action='store', default='Magick++-config', help = 'path to Magick++-config')
-     opt.add_option('--wx-config', action='store', default='wx-config', help = 'path to wx-config')
+     opt.add_option('--enable-debug', action='store_true', default=False, help='build with debugging information and without optimisation')
+     opt.add_option('--disable-gui', action='store_true', default=False, help='disable building of GUI tools')
+     opt.add_option('--target-windows', action='store_true', default=False, help='set up to do a cross-compile to Windows')
+     opt.add_option('--static', action='store_true', default=False, help='build statically, and link statically to libdcp and FFmpeg')
+     opt.add_option('--magickpp-config', action='store', default='Magick++-config', help='path to Magick++-config')
+     opt.add_option('--wx-config', action='store', default='wx-config', help='path to wx-config')
+     opt.add_option('--osx', action='store_true', default=False, help='build on OS X')
  
  def configure(conf):
      conf.load('compiler_cxx')
      if conf.options.target_windows:
          conf.load('winres')
  
 -    conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS', '-msse', '-mfpmath=sse', '-ffast-math', '-fno-strict-aliasing',
 +    conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS', '-D__STDC_LIMIT_MACROS', '-msse', '-mfpmath=sse', '-ffast-math', '-fno-strict-aliasing',
                                         '-Wall', '-Wno-attributes', '-Wextra'])
  
      if conf.options.target_windows:
 -        conf.env.append_value('CXXFLAGS', ['-DDVDOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE'])
 +        conf.env.append_value('CXXFLAGS', ['-DDCPOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE'])
          wxrc = os.popen('wx-config --rescomp').read().split()[1:]
          conf.env.append_value('WINRCFLAGS', wxrc)
          if conf.options.enable_debug:
@@@ -38,9 -39,9 +39,9 @@@
          boost_lib_suffix = '-mt'
          boost_thread = 'boost_thread_win32-mt'
      else:
 -        conf.env.append_value('CXXFLAGS', '-DDVDOMATIC_POSIX')
 +        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_POSIX')
          conf.env.append_value('CXXFLAGS', '-DPOSIX_LOCALE_PREFIX="%s/share/locale"' % conf.env['PREFIX'])
 -        conf.env.append_value('CXXFLAGS', '-DPOSIX_ICON_PREFIX="%s/share/dvdomatic"' % conf.env['PREFIX'])
 +        conf.env.append_value('CXXFLAGS', '-DPOSIX_ICON_PREFIX="%s/share/dcpomatic"' % conf.env['PREFIX'])
          boost_lib_suffix = ''
          boost_thread = 'boost_thread'
          conf.env.append_value('LINKFLAGS', '-pthread')
      conf.env.DISABLE_GUI = conf.options.disable_gui
      conf.env.STATIC = conf.options.static
      conf.env.VERSION = VERSION
+     conf.env.TARGET_OSX = conf.options.osx
+     conf.env.TARGET_LINUX = not conf.options.target_windows and not conf.options.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.49', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
 +        conf.check_cfg(package = 'libcxml', atleast_version = '0.01', args = '--cflags --libs', uselib_store = 'CXML', mandatory = True)
          conf.check_cfg(package = 'libavformat', args = '--cflags --libs', uselib_store = 'AVFORMAT', mandatory = True)
          conf.check_cfg(package = 'libavfilter', args = '--cflags --libs', uselib_store = 'AVFILTER', mandatory = True)
          conf.check_cfg(package = 'libavcodec', args = '--cflags --libs', uselib_store = 'AVCODEC', mandatory = True)
          conf.check_cfg(package = 'libavutil', args = '--cflags --libs', uselib_store = 'AVUTIL', mandatory = True)
          conf.check_cfg(package = 'libswscale', args = '--cflags --libs', uselib_store = 'SWSCALE', mandatory = True)
 -        conf.check_cfg(package = 'libswresample', args = '--cflags --libs', uselib_store = 'SWRESAMPLE', mandatory = False)
 +        conf.check_cfg(package = 'libswresample', args = '--cflags --libs', uselib_store = 'SWRESAMPLE', mandatory = True)
          conf.check_cfg(package = 'libpostproc', args = '--cflags --libs', uselib_store = 'POSTPROC', mandatory = True)
      else:
          # This is hackio grotesquio for static builds (ie for .deb packages).  We need to link some things
@@@ -76,9 -78,6 +79,9 @@@
          conf.env.HAVE_DCP = 1
          conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
          conf.env.LIB_DCP = ['glibmm-2.4', 'xml++-2.6', 'ssl', 'crypto', 'bz2']
 +        conf.env.HAVE_CXML = 1
 +        conf.env.STLIB_CXML = ['cxml']
 +        conf.check_cfg(package = 'libxml++-2.6', args = '--cflags --libs', uselib_store = 'XML++', mandatory = True)
          conf.env.HAVE_AVFORMAT = 1
          conf.env.STLIB_AVFORMAT = ['avformat']
          conf.env.HAVE_AVFILTER = 1
          conf.env.STLIB_AVUTIL = ['avutil']
          conf.env.HAVE_SWSCALE = 1
          conf.env.STLIB_SWSCALE = ['swscale']
 -        conf.env.HAVE_SWRESAMPLE = 1
 -        conf.env.STLIB_SWRESAMPLE = ['swresample']
          conf.env.HAVE_POSTPROC = 1
          conf.env.STLIB_POSTPROC = ['postproc']
 -
 -        # This doesn't seem to be set up, and we need it otherwise resampling support
 -        # won't be included.  Hack upon a hack, obviously
 -        conf.env.append_value('CXXFLAGS', ['-DHAVE_SWRESAMPLE=1'])
 +        conf.env.HAVE_SWRESAMPLE = 1
 +        conv.env.STLIB_SWRESAMPLE = ['swresample']
  
      conf.check_cfg(package = 'sndfile', args = '--cflags --libs', uselib_store = 'SNDFILE', mandatory = True)
      conf.check_cfg(package = 'glib-2.0', args = '--cflags --libs', uselib_store = 'GLIB', mandatory = True)
@@@ -192,27 -195,17 +195,17 @@@ def build(bld)
      bld.recurse('src')
      bld.recurse('test')
      if bld.env.TARGET_WINDOWS:
-         bld.recurse('windows')
-     d = { 'PREFIX' : '${PREFIX' }
-     obj = bld(features = 'subst')
-     obj.source = 'dcpomatic.desktop.in'
-     obj.target = 'dcpomatic.desktop'
-     obj.dict = d
-     obj = bld(features = 'subst')
-     obj.source = 'dcpomatic_batch.desktop.in'
-     obj.target = 'dcpomatic_batch.desktop'
-     obj.dict = d
-     bld.install_files('${PREFIX}/share/applications', ['dcpomatic.desktop', 'dcpomatic_batch.desktop'])
+         bld.recurse('platform/windows')
+     if bld.env.TARGET_LINUX:
+         bld.recurse('platform/linux')
+     if bld.env.TARGET_OSX:
+         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)
  
@@@ -234,8 -227,8 +227,8 @@@ def create_version_cc(version)
  
      try:
          text =  '#include "version.h"\n'
 -        text += 'char const * dvdomatic_git_commit = \"%s\";\n' % commit
 -        text += 'char const * dvdomatic_version = \"%s\";\n' % version
 +        text += 'char const * dcpomatic_git_commit = \"%s\";\n' % commit
 +        text += 'char const * dcpomatic_version = \"%s\";\n' % version
          print('Writing version information to src/lib/version.cc')
          o = open('src/lib/version.cc', 'w')
          o.write(text)