Merge master.
authorCarl Hetherington <cth@carlh.net>
Sat, 15 Jun 2013 16:05:58 +0000 (17:05 +0100)
committerCarl Hetherington <cth@carlh.net>
Sat, 15 Jun 2013 16:05:58 +0000 (17:05 +0100)
30 files changed:
1  2 
cscript
icons/make_icns.sh
platform/linux/dvdomatic.desktop.in
platform/linux/dvdomatic_batch.desktop.in
platform/linux/servomatic.desktop.in
platform/linux/wscript
platform/osx/Info.plist.in
platform/osx/make_dmg.sh
platform/windows/installer.nsi.32.in
platform/windows/installer.nsi.64.in
run/dvdomatic-osx
run/dvdomatic_batch
src/lib/ab_transcode_job.cc
src/lib/cross.cc
src/lib/cross.h
src/lib/film.cc
src/lib/image.cc
src/lib/imagemagick_content.h
src/lib/player.cc
src/lib/util.cc
src/lib/util.h
src/tools/dcpomatic.cc
src/tools/dcpomatic_batch.cc
src/tools/po/es_ES.po
src/wx/about_dialog.cc
src/wx/po/es_ES.po
src/wx/wscript
test/make_black_test.cc
test/test.cc
wscript

diff --combined cscript
index 97e8dbd9239583517ead27d8e1806dbaf10e50c7,4ba4eadb918b6c9ab512c98e9a9583b4a78cd8ca..c51f3a033fb3aa22e54cc998e52e85da62858291
+++ b/cscript
@@@ -7,15 -7,14 +7,15 @@@ def dependencies(target)
          return ()
      else:
          return (('openjpeg-cdist', None),
 +                ('libcxml', None),
                  ('ffmpeg-cdist', '7a23ec9c771184ab563cfe24ad9b427f38368961'),
 -                ('libdcp', 'v0.53'))
 +                ('libdcp', None))
  
  def build(env, target):
      cmd = './waf configure --prefix=%s' % env.work_dir_cscript()
      if target.platform == 'windows':
          cmd += ' --target-windows'
-     else:
+     elif target.platform == 'linux':
          cmd += ' --static'
      env.command(cmd)
  
@@@ -43,14 -42,14 +43,14 @@@ def package(env, target, version)
          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'))
@@@ -68,9 -67,9 +68,9 @@@
  
  def make_pot(env):
      env.command('./waf pot')
 -    return [os.path.abspath('build/src/lib/libdvdomatic.pot'),
 -            os.path.abspath('build/src/wx/libdvdomatic-wx.pot'),
 -          os.path.abspath('build/src/tools/dvdomatic.pot')]
 +    return [os.path.abspath('build/src/lib/libdcpomatic.pot'),
 +            os.path.abspath('build/src/wx/libdcpomatic-wx.pot'),
 +          os.path.abspath('build/src/tools/dcpomatic.pot')]
  
  def make_manual(env):
      os.chdir('doc/manual')
diff --combined icons/make_icns.sh
index 983379ea40f54916a232cd5bb7e14fc296da065f,983379ea40f54916a232cd5bb7e14fc296da065f..522e907a6731c50eea691115cbf3a42b5092220d
@@@ -1,4 -1,4 +1,4 @@@
  #!/bin/bash
  
--iconutil --convert icns --output dvdomatic.icns dvdomatic.iconset/
++iconutil --convert icns --output dcpomatic.icns dcpomatic.iconset/
  
diff --combined platform/linux/dvdomatic.desktop.in
index 65067eb3b100431c73eec14a73dec24b08c5c17c,65067eb3b100431c73eec14a73dec24b08c5c17c..0000000000000000000000000000000000000000
deleted file mode 100644,100644
+++ /dev/null
@@@ -1,10 -1,10 +1,0 @@@
--[Desktop Entry]
--Encoding=UTF-8
--Version=1.0
--Type=Application
--Terminal=false
--Exec=@PREFIX@/bin/dvdomatic
--Name=DVD-o-matic
--Icon=dvdomatic
--Comment=DCP generator
--Categories=AudioVideo;Video
diff --combined platform/linux/dvdomatic_batch.desktop.in
index 8150fe84925439728d01591842db373a23a81ca4,8150fe84925439728d01591842db373a23a81ca4..0000000000000000000000000000000000000000
deleted file mode 100644,100644
+++ /dev/null
@@@ -1,10 -1,10 +1,0 @@@
--[Desktop Entry]
--Encoding=UTF-8
--Version=1.0
--Type=Application
--Terminal=false
--Exec=@PREFIX@/bin/dvdomatic_batch
--Name=DVD-o-matic Batch Converter
--Icon=dvdomatic
--Comment=Batch DCP generator
--Categories=AudioVideo;Video
diff --combined platform/linux/servomatic.desktop.in
index 572b4c64cbef0282ed4206b6c0fc0ee851a16d95,572b4c64cbef0282ed4206b6c0fc0ee851a16d95..0000000000000000000000000000000000000000
deleted file mode 100644,100644
+++ /dev/null
@@@ -1,10 -1,10 +1,0 @@@
--[Desktop Entry]
--Encoding=UTF-8
--Version=1.0
--Type=Application
--Terminal=false
--Exec=@PREFIX@/bin/servomatic_gui
--Name=DVD-o-matic Encode Server
--Icon=dvdomatic
--Comment=DCP generator
--Categories=AudioVideo;Video
diff --combined platform/linux/wscript
index 1d9054b326c3d7e4b120f889030a5b5ee0b954dd,1d9054b326c3d7e4b120f889030a5b5ee0b954dd..53a6efeac7ca687765c9504e57c8069449b50eac
@@@ -2,18 -2,18 +2,18 @@@ def build(bld)
      d = { 'PREFIX' : '${PREFIX' }
  
      obj = bld(features = 'subst')
--    obj.source = 'dvdomatic.desktop.in'
--    obj.target = 'dvdomatic.desktop'
++    obj.source = 'dcpomatic.desktop.in'
++    obj.target = 'dcpomatic.desktop'
      obj.dict = d
  
      obj = bld(features = 'subst')
--    obj.source = 'dvdomatic_batch.desktop.in'
--    obj.target = 'dvdomatic_batch.desktop'
++    obj.source = 'dcpomatic_batch.desktop.in'
++    obj.target = 'dcpomatic_batch.desktop'
      obj.dict = d
  
      obj = bld(features = 'subst')
--    obj.source = 'servomatic.desktop.in'
--    obj.target = 'servomatic.desktop'
++    obj.source = 'dcpomatic_server.desktop.in'
++    obj.target = 'dcpomatic_server.desktop'
      obj.dict = d
  
--    bld.install_files('${PREFIX}/share/applications', ['dvdomatic.desktop', 'dvdomatic_batch.desktop', 'servomatic.desktop'])
++    bld.install_files('${PREFIX}/share/applications', ['dcpomatic.desktop', 'dcpomatic_batch.desktop', 'dcpomatic_server.desktop'])
index c904d91ddafa05675fd58b37e5574a90044f24d9,c904d91ddafa05675fd58b37e5574a90044f24d9..0f67741386f5cf18aa587f7f65ff53ea31da3c16
@@@ -5,17 -5,17 +5,17 @@@
        <key>CFBundleDevelopmentRegion</key>
        <string>English</string>
        <key>CFBundleExecutable</key>
--      <string>dvdomatic</string>
++      <string>dcpomatic</string>
        <key>CFBundleGetInfoString</key>
        <string>DCP generator</string>
        <key>CFBundleIconFile</key>
--      <string>DVD-o-matic.icns</string>
++      <string>DCP-o-matic.icns</string>
        <key>CFBundleIdentifier</key>
--      <string>net.carlh.dvdomatic</string>
++      <string>net.carlh.dcpomatic</string>
        <key>CFBundleInfoDictionaryVersion</key>
        <string>6.0</string>
        <key>CFBundleName</key>
--      <string>DVD-o-matic</string>
++      <string>DCP-o-matic</string>
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>CFBundleShortVersions</key>
diff --combined platform/osx/make_dmg.sh
index d9e36c390d34e7e553521210f4b441fe0794cee7,f757eda34e39adaccaf383364994c700e3825c64..1fcdc6d0629782eccce9aa8c7176d95ca7b30fb5
@@@ -19,9 -19,9 +19,9 @@@ mkdir -p $WORK/$maco
  mkdir -p $WORK/$libs
  mkdir -p $WORK/$resources
  
--cp build/src/tools/dvdomatic $WORK/$macos/
--cp build/src/lib/libdvdomatic.dylib $WORK/$libs/
--cp build/src/wx/libdvdomatic-wx.dylib $WORK/$libs/
++cp build/src/tools/dcpomatic $WORK/$macos/
++cp build/src/lib/libdcpomatic.dylib $WORK/$libs/
++cp build/src/wx/libdcpomatic-wx.dylib $WORK/$libs/
  cp $DEPS/lib/libdcp.dylib $WORK/$libs/
  cp $DEPS/lib/libasdcp-libdcp.dylib $WORK/$libs/
  cp $DEPS/lib/libkumu-libdcp.dylib $WORK/$libs/
@@@ -58,7 -58,7 +58,7 @@@ cp $ENV/lib/libfontconfig*.dylib $WORK/
  cp $ENV/lib/libfreetype*.dylib $WORK/$libs/
  cp $ENV/lib/libexpat*.dylib $WORK/$libs/
  
--for obj in $WORK/$macos/dvdomatic $WORK/$libs/*.dylib; do
++for obj in $WORK/$macos/dcpomatic $WORK/$libs/*.dylib; do
    deps=`otool -L $obj | awk '{print $1}' | egrep "(/Users/carl|libboost|libssh)"`
    changes=""
    for dep in $deps; do
    fi  
  done
  
+ pwd
  cp build/platform/osx/Info.plist $WORK/$approot
--cp icons/dvdomatic.icns $WORK/$resources/DVD-o-matic.icns
++cp icons/dcpomatic.icns $WORK/$resources/DVD-o-matic.icns
  
--tmp_dmg=$WORK/dvdomatic_tmp.dmg
--dmg="$WORK/DVD-o-matic $version.dmg"
--vol_name=DVD-o-matic-$version
++tmp_dmg=$WORK/dcpomatic_tmp.dmg
++dmg="$WORK/DCP-o-matic $version.dmg"
++vol_name=DCP-o-matic-$version
  
  mkdir -p $WORK/$vol_name
  
@@@ -95,13 -95,13 +95,13 @@@ echo 
             set current view of container window to icon view
             set toolbar visible of container window to false
             set statusbar visible of container window to false
-            set the bounds of container window to {400, 200, 800, 440}
+            set the bounds of container window to {400, 200, 790, 410}
             set theViewOptions to the icon view options of container window
             set arrangement of theViewOptions to not arranged
             set icon size of theViewOptions to 64
             make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"}
-            set position of item "DVD-o-matic.app" of container window to {90, 100}
-            set position of item "Applications" of container window to {310, 100}
 -           set position of item "DVD-o-matic.app" of container window to {90, 80}
++           set position of item "DCP-o-matic.app" of container window to {90, 80}
+            set position of item "Applications" of container window to {310, 80}
             close
             open
             update without registering applications
@@@ -117,8 -117,8 +117,8 @@@ syn
  umount $device
  hdiutil eject $device
  hdiutil convert -format UDZO $tmp_dmg -imagekey zlib-level=9 -o "$dmg"
--sips -i $WORK/$resources/DVD-o-matic.icns
--DeRez -only icns $WORK/$resources/DVD-o-matic.icns > $WORK/$resources/DVD-o-matic.rsrc
--Rez -append $WORK/$resources/DVD-o-matic.rsrc -o "$dmg"
++sips -i $WORK/$resources/DCP-o-matic.icns
++DeRez -only icns $WORK/$resources/DCP-o-matic.icns > $WORK/$resources/DCP-o-matic.rsrc
++Rez -append $WORK/$resources/DCP-o-matic.rsrc -o "$dmg"
  SetFile -a C "$dmg"
  
index 6649047672fafe3198f94d8dbf71c8f8a313fa8f,dbe408564c17500c10e77e081c0a412d2b33206e..3a2cdb9e824800600683fe441db37085a7cdbe40
@@@ -1,14 -1,14 +1,14 @@@
  !include "MUI2.nsh"
 -Name "DVD-o-matic"
 +Name "DCP-o-matic"
  
  RequestExecutionLevel admin
  
 -outFile "DVD-o-matic @version@ 32-bit Installer.exe"
 -!define MUI_ICON "%resources%/dvdomatic.ico"
 -!define MUI_UNICON "%resources%/dvdomatic.ico"
 -!define MUI_SPECIALBITMAP "%resources%/dvdomatic.bmp"
 +outFile "DCP-o-matic @version@ 32-bit Installer.exe"
 +!define MUI_ICON "%resources%/dcpomatic.ico"
 +!define MUI_UNICON "%resources%/dcpomatic.ico"
 +!define MUI_SPECIALBITMAP "%resources%/dcpomatic.bmp"
  
 -InstallDir "$PROGRAMFILES\DVD-o-matic"
 +InstallDir "$PROGRAMFILES\DCP-o-matic"
  
  !insertmacro MUI_PAGE_WELCOME
  !insertmacro MUI_PAGE_LICENSE "../../../COPYING"
@@@ -79,14 -79,14 +79,15 @@@ 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/wx/dvdomatic-wx.dll"
 -File "%binaries%/src/lib/dvdomatic.dll"
 -File "%binaries%/src/tools/dvdomatic.exe"
 -File "%binaries%/src/tools/dvdomatic_batch.exe"
 -File "%binaries%/src/tools/makedcp.exe"
 -File "%binaries%/src/tools/servomatic_cli.exe"
 -File "%binaries%/src/tools/servomatic_gui.exe"
 +File "%binaries%/src/wx/dcpomatic-wx.dll"
 +File "%binaries%/src/lib/dcpomatic.dll"
 +File "%binaries%/src/tools/dcpomatic.exe"
 +File "%binaries%/src/tools/dcpomatic_batch.exe"
++File "%binaries%/src/tools/dcpomatic_cli.exe"
 +File "%binaries%/src/tools/dcpomatic_server_cli.exe"
 +File "%binaries%/src/tools/dcpomatic_server.exe"
  
  # I don't know why, but sometimes it seems that 
  # delegates.xml must be in with the binaries, and
@@@ -96,34 -96,34 +97,34 @@@ SetOutPath "$PROFILE\.magick
  File "%deps%/etc/ImageMagick/delegates.xml"
  
  SetOutPath "$INSTDIR\locale\fr\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/fr_FR/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/fr_FR/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/fr_FR/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/fr_FR/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/fr_FR/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/fr_FR/dcpomatic.mo"
  SetOutPath "$INSTDIR\locale\it\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/it_IT/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/it_IT/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/it_IT/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/it_IT/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/it_IT/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/it_IT/dcpomatic.mo"
  SetOutPath "$INSTDIR\locale\es\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/es_ES/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/es_ES/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/es_ES/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/es_ES/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/es_ES/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/es_ES/dcpomatic.mo"
  SetOutPath "$INSTDIR\locale\sv\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/sv_SE/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/sv_SE/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/sv_SE/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/sv_SE/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/sv_SE/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/sv_SE/dcpomatic.mo"
  
 -CreateShortCut "$DESKTOP\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" ""
 -CreateShortCut "$DESKTOP\DVD-o-matic batch converter.lnk" "$INSTDIR\bin\dvdomatic_batch.exe" ""
 -CreateShortCut "$DESKTOP\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" ""
 +CreateShortCut "$DESKTOP\DCP-o-matic.lnk" "$INSTDIR\bin\dcpomatic.exe" ""
 +CreateShortCut "$DESKTOP\DCP-o-matic batch converter.lnk" "$INSTDIR\bin\dcpomatic_batch.exe" ""
 +CreateShortCut "$DESKTOP\DCP-o-matic encode server.lnk" "$INSTDIR\bin\dcpomatic_server.exe" ""
   
 -CreateDirectory "$SMPROGRAMS\DVD-o-matic"
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall DVD-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" "" "$INSTDIR\bin\dvdomatic.exe" 0
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic batch converter.lnk" "$INSTDIR\bin\dvdomatic_batch.exe" "" "$INSTDIR\bin\dvdomatic_batch.exe" 0
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" "" "$INSTDIR\bin\servomatic_gui.exe" 0
 +CreateDirectory "$SMPROGRAMS\DCP-o-matic"
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\Uninstall DCP-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic.lnk" "$INSTDIR\bin\dcpomatic.exe" "" "$INSTDIR\bin\dcpomatic.exe" 0
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic batch converter.lnk" "$INSTDIR\bin\dcpomatic.exe" "" "$INSTDIR\bin\dcpomatic_batch.exe" 0
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic encode server.lnk" "$INSTDIR\bin\dcpomatic_server.exe" "" "$INSTDIR\bin\dcpomatic_server.exe" 0
   
 -WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "DisplayName" "DVD-o-matic (remove only)"
 -WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
 +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "DisplayName" "DCP-o-matic (remove only)"
 +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
   
  WriteUninstaller "$INSTDIR\Uninstall.exe"
   
@@@ -134,12 -134,12 +135,22 @@@ Section "Uninstall
   
  RMDir /r "$INSTDIR\*.*"    
  RMDir "$INSTDIR"
++<<<<<<< HEAD
 +Delete "$DESKTOP\DCP-o-matic.lnk"
 +Delete "$DESKTOP\DCP-o-matic batch converter.lnk"
 +Delete "$DESKTOP\DCP-o-matic encode server.lnk"
 +Delete "$SMPROGRAMS\DCP-o-matic\*.*"
 +RmDir  "$SMPROGRAMS\DCP-o-matic"
 +DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DCP-o-matic"
 +DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic"
++=======
+ Delete "$DESKTOP\DVD-o-matic.lnk"
+ Delete "$DESKTOP\DVD-o-matic batch converter.lnk"
+ Delete "$DESKTOP\DVD-o-matic encode server.lnk"
+ Delete "$SMPROGRAMS\DVD-o-matic\*.*"
+ RmDir  "$SMPROGRAMS\DVD-o-matic"
+ DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DVD-o-matic"
+ DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic"
++>>>>>>> master
   
  SectionEnd
index fd1237727ae2bc7fff9628649c605a918b849e2d,7727207d88fc8156925520874d412e6323c21eea..f4f1e90684b546e121285b489137f019b02fd5a4
@@@ -1,16 -1,16 +1,16 @@@
  !include "MUI2.nsh"
  !include "x64.nsh"
  
 -Name "DVD-o-matic"
 +Name "DCP-o-matic"
  
  RequestExecutionLevel admin
  
 -outFile "DVD-o-matic @version@ 64-bit Installer.exe"
 -!define MUI_ICON "%resources%/dvdomatic.ico"
 -!define MUI_UNICON "%resources%/dvdomatic.ico"
 -!define MUI_SPECIALBITMAP "%resources%/dvdomatic.bmp"
 +outFile "DCP-o-matic @version@ 64-bit Installer.exe"
 +!define MUI_ICON "%resources%/dcpomatic.ico"
 +!define MUI_UNICON "%resources%/dcpomatic.ico"
 +!define MUI_SPECIALBITMAP "%resources%/dcpomatic.bmp"
  
 -InstallDir "$PROGRAMFILES\DVD-o-matic"
 +InstallDir "$PROGRAMFILES\DCP-o-matic"
  
  !insertmacro MUI_PAGE_WELCOME
  !insertmacro MUI_PAGE_LICENSE "../../../COPYING"
@@@ -32,7 -32,7 +32,7 @@@ ${If} ${RunningX64
     ; disable registry redirection (enable access to 64-bit portion of registry)
     SetRegView 64
     ; change install dir
 -   StrCpy $INSTDIR "$PROGRAMFILES64\DVD-o-matic"
 +   StrCpy $INSTDIR "$PROGRAMFILES64\DCP-o-matic"
  ${EndIf}
  
  SetOutPath "$INSTDIR\bin"
@@@ -89,14 -89,14 +89,15 @@@ 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/wx/dvdomatic-wx.dll"
 -File "%binaries%/src/lib/dvdomatic.dll"
 -File "%binaries%/src/tools/dvdomatic.exe"
 -File "%binaries%/src/tools/dvdomatic_batch.exe"
 -File "%binaries%/src/tools/makedcp.exe"
 -File "%binaries%/src/tools/servomatic_cli.exe"
 -File "%binaries%/src/tools/servomatic_gui.exe"
 +File "%binaries%/src/wx/dcpomatic-wx.dll"
 +File "%binaries%/src/lib/dcpomatic.dll"
 +File "%binaries%/src/tools/dcpomatic.exe"
 +File "%binaries%/src/tools/dcpomatic_batch.exe"
++File "%binaries%/src/tools/dcpomatic_cli.exe"
 +File "%binaries%/src/tools/dcpomatic_server_cli.exe"
 +File "%binaries%/src/tools/dcpomatic_server.exe"
  
  # I don't know why, but sometimes it seems that 
  # delegates.xml must be in with the binaries, and
@@@ -106,34 -106,34 +107,34 @@@ SetOutPath "$PROFILE\.magick
  File "%deps%/etc/ImageMagick/delegates.xml"
  
  SetOutPath "$INSTDIR\locale\fr\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/fr_FR/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/fr_FR/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/fr_FR/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/fr_FR/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/fr_FR/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/fr_FR/dcpomatic.mo"
  SetOutPath "$INSTDIR\locale\it\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/it_IT/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/it_IT/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/it_IT/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/it_IT/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/it_IT/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/it_IT/dcpomatic.mo"
  SetOutPath "$INSTDIR\locale\es\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/es_ES/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/es_ES/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/es_ES/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/es_ES/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/es_ES/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/es_ES/dcpomatic.mo"
  SetOutPath "$INSTDIR\locale\sv\LC_MESSAGES"
 -File "%binaries%/src/lib/mo/sv_SE/libdvdomatic.mo"
 -File "%binaries%/src/wx/mo/sv_SE/libdvdomatic-wx.mo"
 -File "%binaries%/src/tools/mo/sv_SE/dvdomatic.mo"
 +File "%binaries%/src/lib/mo/sv_SE/libdcpomatic.mo"
 +File "%binaries%/src/wx/mo/sv_SE/libdcpomatic-wx.mo"
 +File "%binaries%/src/tools/mo/sv_SE/dcpomatic.mo"
  
 -CreateShortCut "$DESKTOP\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" ""
 -CreateShortCut "$DESKTOP\DVD-o-matic batch converter.lnk" "$INSTDIR\bin\dvdomatic_batch.exe" ""
 -CreateShortCut "$DESKTOP\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" ""
 +CreateShortCut "$DESKTOP\DCP-o-matic.lnk" "$INSTDIR\bin\dcpomatic.exe" ""
 +CreateShortCut "$DESKTOP\DCP-o-matic batch converter.lnk" "$INSTDIR\bin\dcpomatic_batch.exe" ""
 +CreateShortCut "$DESKTOP\DCP-o-matic encode server.lnk" "$INSTDIR\bin\dcpomatic_server.exe" ""
   
 -CreateDirectory "$SMPROGRAMS\DVD-o-matic"
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall DVD-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" "" "$INSTDIR\bin\dvdomatic.exe" 0
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic batch converter.lnk" "$INSTDIR\bin\dvdomatic.exe" "" "$INSTDIR\bin\dvdomatic_batch.exe" 0
 -CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" "" "$INSTDIR\bin\servomatic_gui.exe" 0
 +CreateDirectory "$SMPROGRAMS\DCP-o-matic"
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\Uninstall DCP-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic.lnk" "$INSTDIR\bin\dcpomatic.exe" "" "$INSTDIR\bin\dcpomatic.exe" 0
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic batch converter.lnk" "$INSTDIR\bin\dcpomatic.exe" "" "$INSTDIR\bin\dcpomatic_batch.exe" 0
 +CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic encode server.lnk" "$INSTDIR\bin\dcpomatic_server.exe" "" "$INSTDIR\bin\dcpomatic_server.exe" 0
   
 -WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "DisplayName" "DVD-o-matic (remove only)"
 -WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
 +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "DisplayName" "DCP-o-matic (remove only)"
 +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
   
  WriteUninstaller "$INSTDIR\Uninstall.exe"
   
@@@ -144,12 -144,12 +145,12 @@@ Section "Uninstall
   
  RMDir /r "$INSTDIR\*.*"    
  RMDir "$INSTDIR"
 -Delete "$DESKTOP\DVD-o-matic.lnk"
 -Delete "$DESKTOP\DVD-o-matic batch converter.lnk"
 -Delete "$DESKTOP\DVD-o-matic encode server.lnk"
 -Delete "$SMPROGRAMS\DVD-o-matic\*.*"
 -RmDir  "$SMPROGRAMS\DVD-o-matic"
 -DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DVD-o-matic"
 -DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic"
 +Delete "$DESKTOP\DCP-o-matic.lnk"
 +Delete "$DESKTOP\DCP-o-matic batch converter.lnk"
 +Delete "$DESKTOP\DCP-o-matic encode server.lnk"
 +Delete "$SMPROGRAMS\DCP-o-matic\*.*"
 +RmDir  "$SMPROGRAMS\DCP-o-matic"
 +DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DCP-o-matic"
 +DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic"
   
  SectionEnd
diff --combined run/dvdomatic-osx
index ac42c31868b16d17c2730720d27dd4b83a701ef2,ac42c31868b16d17c2730720d27dd4b83a701ef2..0000000000000000000000000000000000000000
deleted file mode 100755,100755
+++ /dev/null
@@@ -1,15 -1,15 +1,0 @@@
--#!/bin/bash
--
--export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:build/src/lib:build/src:/Users/carl/Environments/osx/10.8/lib
--if [ "$1" == "--debug" ]; then
--    shift
--    gdb --args build/src/tools/dvdomatic "$*"
--elif [ "$1" == "--valgrind" ]; then
--    shift
--    valgrind --tool="memcheck" build/src/tools/dvdomatic $*
--elif [ "$1" == "--i18n" ]; then
--    shift
--    LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 build/src/tools/dvdomatic "$*"
--else
--    build/src/tools/dvdomatic "$*"
--fi
diff --combined run/dvdomatic_batch
index 7b6ef93aefe373e37999d34d49d97aa5d7139b0e,7b6ef93aefe373e37999d34d49d97aa5d7139b0e..0000000000000000000000000000000000000000
deleted file mode 100755,100755
+++ /dev/null
@@@ -1,15 -1,15 +1,0 @@@
--#!/bin/bash
--
--export LD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:$LD_LIBRARY_PATH
--if [ "$1" == "--debug" ]; then
--    shift
--    gdb --args build/src/tools/dvdomatic_batch "$*"
--elif [ "$1" == "--valgrind" ]; then
--    shift
--    valgrind --tool="memcheck" build/src/tools/dvdomatic_batch $*
--elif [ "$1" == "--i18n" ]; then
--    shift
--    LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 build/src/tools/dvdomatic_batch "$*"
--else
--    build/src/tools/dvdomatic_batch
--fi
index bdde8a40576459d06328a8c5ca0a0563b787e3dc,a204677db34910be180bfd9a94fa22bd20857ab5..a29e7877666fc3c18ac82812f9f7247120dda0c0
  #include <stdexcept>
  #include "ab_transcode_job.h"
  #include "film.h"
 -#include "format.h"
 -#include "filter.h"
  #include "ab_transcoder.h"
  #include "config.h"
 -#include "encoder.h"
+ #include "log.h"
  
  #include "i18n.h"
  
@@@ -29,14 -33,15 +30,14 @@@ using std::string
  using boost::shared_ptr;
  
  /** @param f Film to compare.
 - *  @param o Decode options.
   */
 -ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, DecodeOptions o)
 +ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f)
        : Job (f)
 -      , _decode_opt (o)
  {
        _film_b.reset (new Film (*_film));
        _film_b->set_scaler (Config::instance()->reference_scaler ());
 -      _film_b->set_filters (Config::instance()->reference_filters ());
 +      /* XXX */
 +//    _film_b->set_filters (Config::instance()->reference_filters ());
  }
  
  string
@@@ -50,7 -55,7 +51,7 @@@ ABTranscodeJob::run (
  {
        try {
                /* _film_b is the one with reference filters */
 -              ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film)));
 +              ABTranscoder w (_film_b, _film, shared_from_this ());
                w.go ();
                set_progress (1);
                set_state (FINISHED_OK);
diff --combined src/lib/cross.cc
index f232f1779dabc432445c5f1a6d44240f43c611bd,7fbba86f9a46195f59857d7d42ac47d83493eb08..ffd44eb02810cd0107145e8a3e5eaeedb917509f
  
  */
  
+ #include <fstream>
+ #include <boost/algorithm/string.hpp>
  #include "cross.h"
 -#ifdef DVDOMATIC_POSIX
 +#ifdef DCPOMATIC_POSIX
  #include <unistd.h>
  #endif
 -#ifdef DVDOMATIC_WINDOWS
 +#ifdef DCPOMATIC_WINDOWS
  #include "windows.h"
  #endif
 -#ifdef DVDOMATIC_OSX
++#ifdef DCPOMATIC_OSX
+ #include <sys/sysctl.h>
+ #endif
+ using std::pair;
+ using std::ifstream;
+ using std::string;
  
  void
 -dvdomatic_sleep (int s)
 +dcpomatic_sleep (int s)
  {
 -#ifdef DVDOMATIC_POSIX
 +#ifdef DCPOMATIC_POSIX
        sleep (s);
  #endif
 -#ifdef DVDOMATIC_WINDOWS
 +#ifdef DCPOMATIC_WINDOWS
        Sleep (s * 1000);
  #endif
  }
 -#ifdef DVDOMATIC_LINUX
+ /** @return A pair containing CPU model name and the number of processors */
+ pair<string, int>
+ cpu_info ()
+ {
+       pair<string, int> info;
+       info.second = 0;
+       
 -#ifdef DVDOMATIC_OSX
++#ifdef DCPOMATIC_LINUX
+       ifstream f ("/proc/cpuinfo");
+       while (f.good ()) {
+               string l;
+               getline (f, l);
+               if (boost::algorithm::starts_with (l, "model name")) {
+                       string::size_type const c = l.find (':');
+                       if (c != string::npos) {
+                               info.first = l.substr (c + 2);
+                       }
+               } else if (boost::algorithm::starts_with (l, "processor")) {
+                       ++info.second;
+               }
+       }
+ #endif
++#ifdef DCPOMATIC_OSX
+       size_t N = sizeof (info.second);
+       sysctlbyname ("hw.ncpu", &info.second, &N, 0, 0);
+       char buffer[64];
+       N = sizeof (buffer);
+       if (sysctlbyname ("machdep.cpu.brand_string", buffer, &N, 0, 0) == 0) {
+               info.first = buffer;
+         }
+ #endif                
+       return info;
+ }
diff --combined src/lib/cross.h
index 00457c9681800a6950596b6cf7044418018eb587,970bf3e9dc00b4b60486c330d6c077322614ea9a..d185286b1f381d02395ba36261313eaac3c10432
  
  */
  
 -#include <string>
 -
 -#ifdef DVDOMATIC_WINDOWS
 +#ifdef DCPOMATIC_WINDOWS
  #define WEXITSTATUS(w) (w)
  #endif
  
 -void dvdomatic_sleep (int);
 +void dcpomatic_sleep (int);
+ extern std::pair<std::string, int> cpu_info ();
diff --combined src/lib/film.cc
index ef29d35fdde57806a2d0ef8f0a5f9a3def734789,8aedd76399899bef18b95523e2cf2ef0a577e4a6..75ec700e0092e5bb35487f43556e4c93b1600b4e
  #include <boost/algorithm/string.hpp>
  #include <boost/lexical_cast.hpp>
  #include <boost/date_time.hpp>
 +#include <libxml++/libxml++.h>
 +#include <libcxml/cxml.h>
  #include "film.h"
 -#include "format.h"
  #include "job.h"
  #include "filter.h"
 -#include "transcoder.h"
  #include "util.h"
  #include "job_manager.h"
  #include "ab_transcode_job.h"
  #include "transcode_job.h"
  #include "scp_dcp_job.h"
  #include "log.h"
 -#include "options.h"
  #include "exceptions.h"
  #include "examine_content_job.h"
  #include "scaler.h"
 -#include "decoder_factory.h"
  #include "config.h"
  #include "version.h"
  #include "ui_signaller.h"
 -#include "video_decoder.h"
 -#include "audio_decoder.h"
 -#include "sndfile_decoder.h"
  #include "analyse_audio_job.h"
 +#include "playlist.h"
 +#include "player.h"
 +#include "ffmpeg_content.h"
 +#include "imagemagick_content.h"
 +#include "sndfile_content.h"
 +#include "dcp_content_type.h"
 +#include "ratio.h"
+ #include "cross.h"
  
  #include "i18n.h"
  
@@@ -69,11 -68,8 +70,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 -78,39 +83,32 @@@ using libdcp::Size
  
  int const Film::state_version = 4;
  
 -/** Construct a Film object in a given directory, reading any metadata
 - *  file that exists in that directory.  An exception will be thrown if
 - *  must_exist is true and the specified directory does not exist.
 +/** Construct a Film object in a given directory.
   *
   *  @param d Film directory.
 - *  @param must_exist true to throw an exception if does not exist.
   */
  
 -Film::Film (string d, bool must_exist)
 -      : _use_dci_name (true)
 -      , _trust_content_header (true)
 +Film::Film (string d)
 +      : _playlist (new Playlist)
 +      , _use_dci_name (true)
        , _dcp_content_type (Config::instance()->default_dcp_content_type ())
 -      , _format (Config::instance()->default_format ())
 +      , _container (Config::instance()->default_container ())
        , _scaler (Scaler::from_id ("bicubic"))
 -      , _trim_start (0)
 -      , _trim_end (0)
 -      , _trim_type (CPL)
 -      , _dcp_ab (false)
 -      , _use_content_audio (true)
 -      , _audio_gain (0)
 -      , _audio_delay (0)
 -      , _still_duration (10)
 +      , _ab (false)
        , _with_subtitles (false)
        , _subtitle_offset (0)
        , _subtitle_scale (1)
        , _colour_lut (0)
        , _j2k_bandwidth (200000000)
        , _dci_metadata (Config::instance()->default_dci_metadata ())
 -      , _dcp_frame_rate (0)
 -      , _source_frame_rate (0)
 +      , _dcp_video_frame_rate (0)
 +      , _dcp_audio_channels (MAX_AUDIO_CHANNELS)
        , _dirty (false)
  {
        set_dci_date_today ();
 +
 +      _playlist->Changed.connect (bind (&Film::playlist_changed, this));
 +      _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
        
        /* Make state.directory a complete path without ..s (where possible)
           (Code swiped from Adam Bowen on stackoverflow)
        }
  
        set_directory (result.string ());
 -      
 -      if (!boost::filesystem::exists (directory())) {
 -              if (must_exist) {
 -                      throw OpenFileError (directory());
 -              } else {
 -                      boost::filesystem::create_directory (directory());
 -              }
 -      }
 -
 -      _sndfile_stream = SndfileStream::create ();
 -      
 -      if (must_exist) {
 -              read_metadata ();
 -      } else {
 -              write_metadata ();
 -      }
 -
        _log.reset (new FileLog (file ("log")));
  }
  
@@@ -135,42 -155,75 +136,42 @@@ Film::Film (Film const & o
        : boost::enable_shared_from_this<Film> (o)
        /* note: the copied film shares the original's log */
        , _log               (o._log)
 +      , _playlist          (new Playlist (o._playlist))
        , _directory         (o._directory)
        , _name              (o._name)
        , _use_dci_name      (o._use_dci_name)
 -      , _content           (o._content)
 -      , _trust_content_header (o._trust_content_header)
        , _dcp_content_type  (o._dcp_content_type)
 -      , _format            (o._format)
 -      , _crop              (o._crop)
 -      , _filters           (o._filters)
 +      , _container         (o._container)
        , _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 (trim_type() == ENCODE) {
 -              s << "_" << trim_start() << "_" << trim_end();
 -      }
 -
 -      if (dcp_ab()) {
 +      if (ab()) {
                pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
                s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
        }
@@@ -234,7 -287,7 +235,7 @@@ Film::audio_analysis_path () cons
  {
        boost::filesystem::path p;
        p /= "analysis";
 -      p /= content_digest();
 +      p /= _playlist->audio_digest();
        return file (p.string ());
  }
  
@@@ -248,7 -301,7 +249,7 @@@ Film::make_dcp (
                throw BadSettingError (_("name"), _("cannot contain slashes"));
        }
        
 -      log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary()));
 +      log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
  
        {
                char buffer[128];
                log()->log (String::compose ("Starting to make DCP on %1", buffer));
        }
        
 -      log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
 -      if (length()) {
 -              log()->log (String::compose ("Content length %1", length().get()));
 -      }
 -      log()->log (String::compose ("Content digest %1", content_digest()));
 -      log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
 +//    log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
 +//    if (length()) {
 +//            log()->log (String::compose ("Content length %1", length().get()));
 +//    }
 +//    log()->log (String::compose ("Content digest %1", content_digest()));
 +//    log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
        log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
        log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
 -#ifdef DVDOMATIC_DEBUG
 -      log()->log ("DVD-o-matic built in debug mode.");
 +#ifdef DCPOMATIC_DEBUG
 +      log()->log ("DCP-o-matic built in debug mode.");
  #else
 -      log()->log ("DVD-o-matic built in optimised mode.");
 +      log()->log ("DCP-o-matic built in optimised mode.");
  #endif
  #ifdef LIBDCP_DEBUG
        log()->log ("libdcp built in debug mode.");
        pair<string, int> const c = cpu_info ();
        log()->log (String::compose ("CPU: %1, %2 processors", c.first, c.second));
        
 -      if (format() == 0) {
 -              throw MissingSettingError (_("format"));
 +      if (container() == 0) {
 +              throw MissingSettingError (_("container"));
        }
  
 -      if (content().empty ()) {
 -              throw MissingSettingError (_("content"));
 +      if (_playlist->content().empty ()) {
 +              throw StringError (_("You must add some content to the DCP before creating it"));
        }
  
        if (dcp_content_type() == 0) {
                throw MissingSettingError (_("name"));
        }
  
 -      DecodeOptions od;
 -      od.decode_subtitles = with_subtitles ();
 -
        shared_ptr<Job> r;
  
 -      if (dcp_ab()) {
 -              r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od)));
 +      if (ab()) {
 +              r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this())));
        } else {
 -              r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od)));
 +              r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
        }
  }
  
 -/** Start a job to analyse the audio of our content file */
 +/** Start a job to analyse the audio in our Playlist */
  void
  Film::analyse_audio ()
  {
        JobManager::instance()->add (_analyse_audio_job);
  }
  
 -/** Start a job to examine our content file */
 -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;
 +              root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
        }
 -      f << "use_content_audio " << (_use_content_audio ? "1" : "0") << endl;
 -      f << "audio_gain " << _audio_gain << endl;
 -      f << "audio_delay " << _audio_delay << endl;
 -      f << "still_duration " << _still_duration << endl;
 -      if (_subtitle_stream) {
 -              f << "selected_subtitle_stream " << _subtitle_stream->to_string() << endl;
 -      }
 -      f << "with_subtitles " << _with_subtitles << endl;
 -      f << "subtitle_offset " << _subtitle_offset << endl;
 -      f << "subtitle_scale " << _subtitle_scale << endl;
 -      f << "colour_lut " << _colour_lut << endl;
 -      f << "j2k_bandwidth " << _j2k_bandwidth << endl;
 -      _dci_metadata.write (f);
 -      f << "dci_date " << boost::gregorian::to_iso_string (_dci_date) << endl;
 -      f << "dcp_frame_rate " << _dcp_frame_rate << endl;
 -      f << "width " << _size.width << endl;
 -      f << "height " << _size.height << endl;
 -      f << "length " << _length.get_value_or(0) << endl;
 -      f << "content_digest " << _content_digest << endl;
 -
 -      for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
 -              f << "content_audio_stream " << (*i)->to_string () << endl;
 -      }
 -
 -      f << "external_audio_stream " << _sndfile_stream->to_string() << endl;
  
 -      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 -524,177 +408,46 @@@ Film::read_metadata (
        boost::mutex::scoped_lock lm (_state_mutex);
        LocaleGuard lg;
  
 -      _external_audio.clear ();
 -      _content_audio_streams.clear ();
 -      _subtitle_streams.clear ();
 -
 -      boost::optional<int> version;
 -
 -      /* Backward compatibility things */
 -      boost::optional<int> audio_sample_rate;
 -      boost::optional<int> audio_stream_index;
 -      boost::optional<int> subtitle_stream_index;
 -
 -      ifstream f (file ("metadata").c_str());
 -      if (!f.good()) {
 -              throw OpenFileError (file ("metadata"));
 +      if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
 +              throw StringError (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version.  You will need to create a new Film, re-add your content and set it up again.  Sorry!"));
        }
 -      
 -      multimap<string, string> kv = read_key_value (f);
  
 -      /* We need version before anything else */
 -      multimap<string, string>::iterator v = kv.find ("version");
 -      if (v != kv.end ()) {
 -              version = atoi (v->second.c_str());
 -      }
 +      cxml::File f (file ("metadata.xml"), "Metadata");
        
 -      for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
 -              string const k = i->first;
 -              string const v = i->second;
 +      _name = f.string_child ("Name");
 +      _use_dci_name = f.bool_child ("UseDCIName");
  
 -              if (k == "audio_sample_rate") {
 -                      audio_sample_rate = atoi (v.c_str());
 -              }
 -
 -              /* User-specified stuff */
 -              if (k == "name") {
 -                      _name = v;
 -              } else if (k == "use_dci_name") {
 -                      _use_dci_name = (v == "1");
 -              } else if (k == "content") {
 -                      _content = v;
 -              } else if (k == "trust_content_header") {
 -                      _trust_content_header = (v == "1");
 -              } else if (k == "dcp_content_type") {
 -                      if (version < 3) {
 -                              _dcp_content_type = DCPContentType::from_pretty_name (v);
 -                      } else {
 -                              _dcp_content_type = DCPContentType::from_dci_name (v);
 -                      }
 -              } else if (k == "format") {
 -                      _format = Format::from_metadata (v);
 -              } else if (k == "left_crop") {
 -                      _crop.left = atoi (v.c_str ());
 -              } else if (k == "right_crop") {
 -                      _crop.right = atoi (v.c_str ());
 -              } else if (k == "top_crop") {
 -                      _crop.top = atoi (v.c_str ());
 -              } else if (k == "bottom_crop") {
 -                      _crop.bottom = atoi (v.c_str ());
 -              } else if (k == "filter") {
 -                      _filters.push_back (Filter::from_id (v));
 -              } else if (k == "scaler") {
 -                      _scaler = Scaler::from_id (v);
 -              } else if ( ((!version || version < 2) && k == "dcp_trim_start") || k == "trim_start") {
 -                      _trim_start = atoi (v.c_str ());
 -              } else if ( ((!version || version < 2) && k == "dcp_trim_end") || k == "trim_end") {
 -                      _trim_end = atoi (v.c_str ());
 -              } else if (k == "trim_type") {
 -                      if (v == "cpl") {
 -                              _trim_type = CPL;
 -                      } else if (v == "encode") {
 -                              _trim_type = ENCODE;
 -                      }
 -              } else if (k == "dcp_ab") {
 -                      _dcp_ab = (v == "1");
 -              } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
 -                      if (!version) {
 -                              audio_stream_index = atoi (v.c_str ());
 -                      } else {
 -                              _content_audio_stream = audio_stream_factory (v, version);
 -                      }
 -              } else if (k == "external_audio") {
 -                      _external_audio.push_back (v);
 -              } else if (k == "use_content_audio") {
 -                      _use_content_audio = (v == "1");
 -              } else if (k == "audio_gain") {
 -                      _audio_gain = atof (v.c_str ());
 -              } else if (k == "audio_delay") {
 -                      _audio_delay = atoi (v.c_str ());
 -              } else if (k == "still_duration") {
 -                      _still_duration = atoi (v.c_str ());
 -              } else if (k == "selected_subtitle_stream") {
 -                      if (!version) {
 -                              subtitle_stream_index = atoi (v.c_str ());
 -                      } else {
 -                              _subtitle_stream = subtitle_stream_factory (v, version);
 -                      }
 -              } else if (k == "with_subtitles") {
 -                      _with_subtitles = (v == "1");
 -              } else if (k == "subtitle_offset") {
 -                      _subtitle_offset = atoi (v.c_str ());
 -              } else if (k == "subtitle_scale") {
 -                      _subtitle_scale = atof (v.c_str ());
 -              } else if (k == "colour_lut") {
 -                      _colour_lut = atoi (v.c_str ());
 -              } else if (k == "j2k_bandwidth") {
 -                      _j2k_bandwidth = atoi (v.c_str ());
 -              } else if (k == "dci_date") {
 -                      _dci_date = boost::gregorian::from_undelimited_string (v);
 -              } else if (k == "dcp_frame_rate") {
 -                      _dcp_frame_rate = atoi (v.c_str ());
 +      {
 +              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 -730,67 +483,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 -915,107 +592,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 -1124,120 +623,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)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _use_content_audio = e;
 -      }
 -
 -      signal_changed (USE_CONTENT_AUDIO);
 -}
 -
 -void
 -Film::set_audio_gain (float g)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _audio_gain = g;
 -      }
 -      signal_changed (AUDIO_GAIN);
 -}
 -
 -void
 -Film::set_audio_delay (int d)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _audio_delay = d;
 -      }
 -      signal_changed (AUDIO_DELAY);
 -}
 -
 -void
 -Film::set_still_duration (int d)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _still_duration = d;
 -      }
 -      signal_changed (STILL_DURATION);
 -}
 -
 -void
 -Film::set_subtitle_stream (shared_ptr<SubtitleStream> s)
 +Film::set_ab (bool a)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _subtitle_stream = s;
 +              _ab = a;
        }
 -      signal_changed (SUBTITLE_STREAM);
 +      signal_changed (AB);
  }
  
  void
@@@ -693,15 -1302,85 +694,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)
 +Film::set_dcp_video_frame_rate (int f)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_digest = d;
 +              _dcp_video_frame_rate = f;
        }
 -      _dirty = true;
 +      signal_changed (DCP_VIDEO_FRAME_RATE);
  }
  
 -void
 -Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_audio_streams = s;
 -      }
 -      signal_changed (CONTENT_AUDIO_STREAMS);
 -}
 -
 -void
 -Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _subtitle_streams = s;
 -      }
 -      signal_changed (SUBTITLE_STREAMS);
 -}
 -
 -void
 -Film::set_source_frame_rate (float f)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _source_frame_rate = f;
 -      }
 -      signal_changed (SOURCE_FRAME_RATE);
 -}
 -      
  void
  Film::signal_changed (Property p)
  {
                _dirty = true;
        }
  
 -      if (ui_signaller) {
 -              ui_signaller->emit (boost::bind (boost::ref (Changed), p));
 +      switch (p) {
 +      case Film::CONTENT:
 +              set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ());
 +              break;
 +      default:
 +              break;
        }
 -}
  
 -int
 -Film::audio_channels () const
 -{
 -      shared_ptr<AudioStream> s = audio_stream ();
 -      if (!s) {
 -              return 0;
 +      if (ui_signaller) {
 +              ui_signaller->emit (boost::bind (boost::ref (Changed), p));
        }
 -
 -      return s->channels ();
  }
  
  void
@@@ -729,6 -1411,16 +730,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 -1476,20 +785,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/image.cc
index bba7d6be5d0762efba32338ba8e158209e4ed17b,f28652d4e2cdac846773df4e202e69902d0c67cf..17c969cf2d280fe04e067526066063adb8e59e86
@@@ -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;
@@@ -211,8 -236,8 +211,8 @@@ Image::crop (Crop crop, bool aligned) c
                /* Start of the source line, cropped from the top but not the left */
                uint8_t* in_p = data()[c] + crop.top * stride()[c];
                uint8_t* out_p = out->data()[c];
 -              
 -              for (int y = 0; y < cropped_size.height; ++y) {
 +
 +              for (int y = 0; y < out->lines(c); ++y) {
                        memcpy (out_p, in_p + crop_left_in_bytes, cropped_width_in_bytes);
                        in_p += stride()[c];
                        out_p += out->stride()[c];
@@@ -248,11 -273,11 +248,11 @@@ voi
  Image::make_black ()
  {
        /* U/V black value for 8-bit colour */
-       static uint8_t const eight_bit_uv = (1 << 7) - 1;
+       static uint8_t const eight_bit_uv =     (1 << 7) - 1;
        /* U/V black value for 9-bit colour */
-       static uint16_t const nine_bit_uv = (1 << 8) - 1;
+       static uint16_t const nine_bit_uv =     (1 << 8) - 1;
        /* U/V black value for 10-bit colour */
-       static uint16_t const ten_bit_uv =  (1 << 9) - 1;
+       static uint16_t const ten_bit_uv =      (1 << 9) - 1;
        /* U/V black value for 16-bit colour */
        static uint16_t const sixteen_bit_uv =  (1 << 15) - 1;
        
                memset (data()[2], eight_bit_uv, lines(2) * stride()[2]);
                break;
  
+       case PIX_FMT_YUVJ420P:
+       case PIX_FMT_YUVJ422P:
+       case PIX_FMT_YUVJ444P:
+               memset (data()[0], 0, lines(0) * stride()[0]);
+               memset (data()[1], eight_bit_uv + 1, lines(1) * stride()[1]);
+               memset (data()[2], eight_bit_uv + 1, lines(2) * stride()[2]);
+               break;
        case PIX_FMT_YUV422P9LE:
        case PIX_FMT_YUV444P9LE:
                yuv_16_black (nine_bit_uv);
@@@ -351,21 -384,6 +359,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)
  {
index 8ed6b08734ad31d34e4c8b75e368510b20d77c07,0000000000000000000000000000000000000000..d7673d870730ec3dd411e3b875a386f454652c6f
mode 100644,000000..100644
--- /dev/null
@@@ -1,51 -1,0 +1,51 @@@
- #ifndef DVDOMATIC_IMAGEMAGICK_CONTENT_H
- #define DVDOMATIC_IMAGEMAGICK_CONTENT_H
 +/*
 +    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +*/
 +
++#ifndef DCPOMATIC_IMAGEMAGICK_CONTENT_H
++#define DCPOMATIC_IMAGEMAGICK_CONTENT_H
 +
 +#include <boost/enable_shared_from_this.hpp>
 +#include "video_content.h"
 +
 +namespace cxml {
 +      class Node;
 +}
 +
 +class ImageMagickContent : public VideoContent
 +{
 +public:
 +      ImageMagickContent (boost::shared_ptr<const Film>, boost::filesystem::path);
 +      ImageMagickContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
 +
 +      boost::shared_ptr<ImageMagickContent> shared_from_this () {
 +              return boost::dynamic_pointer_cast<ImageMagickContent> (Content::shared_from_this ());
 +      };
 +
 +      void examine (boost::shared_ptr<Job>);
 +      std::string summary () const;
 +      void as_xml (xmlpp::Node *) const;
 +      boost::shared_ptr<Content> clone () const;
 +      Time length () const;
 +
 +      void set_video_length (ContentVideoFrame);
 +
 +      static bool valid_file (boost::filesystem::path);
 +};
 +
 +#endif
diff --combined src/lib/player.cc
index 757f9bbcecfe8f6df35fdc421783f2af0bdabacd,0000000000000000000000000000000000000000..85b4cbd4f77a1c016605bee7274545c7a9d520af
mode 100644,000000..100644
--- /dev/null
@@@ -1,383 -1,0 +1,384 @@@
 +/*
 +    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 <stdint.h>
 +#include "player.h"
 +#include "film.h"
 +#include "ffmpeg_decoder.h"
 +#include "ffmpeg_content.h"
 +#include "imagemagick_decoder.h"
 +#include "imagemagick_content.h"
 +#include "sndfile_decoder.h"
 +#include "sndfile_content.h"
 +#include "playlist.h"
 +#include "job.h"
 +#include "image.h"
 +#include "null_content.h"
 +#include "black_decoder.h"
 +#include "silence_decoder.h"
 +
 +using std::list;
 +using std::cout;
 +using std::min;
 +using std::max;
 +using std::vector;
 +using boost::shared_ptr;
 +using boost::weak_ptr;
 +using boost::dynamic_pointer_cast;
 +
 +struct Piece
 +{
 +      Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
 +              : content (c)
 +              , decoder (d)
 +      {}
 +      
 +      shared_ptr<Content> content;
 +      shared_ptr<Decoder> decoder;
 +};
 +
 +Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
 +      : _film (f)
 +      , _playlist (p)
 +      , _video (true)
 +      , _audio (true)
 +      , _subtitles (true)
 +      , _have_valid_pieces (false)
 +      , _position (0)
 +      , _audio_buffers (f->dcp_audio_channels(), 0)
 +      , _next_audio (0)
 +{
 +      _playlist->Changed.connect (bind (&Player::playlist_changed, this));
 +      _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2));
 +}
 +
 +void
 +Player::disable_video ()
 +{
 +      _video = false;
 +}
 +
 +void
 +Player::disable_audio ()
 +{
 +      _audio = false;
 +}
 +
 +void
 +Player::disable_subtitles ()
 +{
 +      _subtitles = false;
 +}
 +
 +bool
 +Player::pass ()
 +{
 +      if (!_have_valid_pieces) {
 +              setup_pieces ();
 +              _have_valid_pieces = true;
 +      }
 +
 +        /* Here we are just finding the active decoder with the earliest last emission time, then
 +           calling pass on it.
 +        */
 +
 +        Time earliest_t = TIME_MAX;
 +        shared_ptr<Piece> earliest;
 +
 +      for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
 +              cout << "check " << (*i)->content->file()
 +                   << " start=" << (*i)->content->start()
 +                   << ", next=" << (*i)->decoder->next()
 +                   << ", end=" << (*i)->content->end() << "\n";
 +              
 +              if ((*i)->decoder->done ()) {
 +                      continue;
 +              }
 +
 +              if (!_audio && dynamic_pointer_cast<AudioDecoder> ((*i)->decoder) && !dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
 +                      continue;
 +              }
 +              
 +              Time const t = (*i)->content->start() + (*i)->decoder->next();
 +              if (t < earliest_t) {
 +                      cout << "\t candidate; " << t << " " << (t / TIME_HZ) << ".\n";
 +                      earliest_t = t;
 +                      earliest = *i;
 +              }
 +      }
 +
 +      if (!earliest) {
 +              flush ();
 +              return true;
 +      }
 +
 +      cout << "PASS:\n";
 +      cout << "\tpass " << earliest->content->file() << " ";
 +      if (dynamic_pointer_cast<FFmpegContent> (earliest->content)) {
 +              cout << " FFmpeg.\n";
 +      } else if (dynamic_pointer_cast<ImageMagickContent> (earliest->content)) {
 +              cout << " ImageMagickContent.\n";
 +      } else if (dynamic_pointer_cast<SndfileContent> (earliest->content)) {
 +              cout << " SndfileContent.\n";
 +      } else if (dynamic_pointer_cast<BlackDecoder> (earliest->decoder)) {
 +              cout << " Black.\n";
 +      } else if (dynamic_pointer_cast<SilenceDecoder> (earliest->decoder)) {
 +              cout << " Silence.\n";
 +      }
 +      
 +      earliest->decoder->pass ();
 +      _position = earliest->content->start() + earliest->decoder->next ();
 +      cout << "\tpassed to " << _position << " " << (_position / TIME_HZ) << "\n";
 +
 +        return false;
 +}
 +
 +void
 +Player::process_video (weak_ptr<Content> weak_content, shared_ptr<const Image> image, bool same, Time time)
 +{
 +      shared_ptr<Content> content = weak_content.lock ();
 +      if (!content) {
 +              return;
 +      }
 +      
 +      time += content->start ();
 +      
 +        Video (image, same, time);
 +}
 +
 +void
 +Player::process_audio (weak_ptr<Content> weak_content, shared_ptr<const AudioBuffers> audio, Time time)
 +{
 +      shared_ptr<Content> content = weak_content.lock ();
 +      if (!content) {
 +              return;
 +      }
 +      
 +        /* The time of this audio may indicate that some of our buffered audio is not going to
 +           be added to any more, so it can be emitted.
 +        */
 +
 +      time += content->start ();
 +
 +        if (time > _next_audio) {
 +                /* We can emit some audio from our buffers */
 +                OutputAudioFrame const N = _film->time_to_audio_frames (time - _next_audio);
 +              assert (N <= _audio_buffers.frames());
 +                shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), N));
 +                emit->copy_from (&_audio_buffers, N, 0, 0);
 +                Audio (emit, _next_audio);
 +                _next_audio += _film->audio_frames_to_time (N);
 +
 +                /* And remove it from our buffers */
 +                if (_audio_buffers.frames() > N) {
 +                        _audio_buffers.move (N, 0, _audio_buffers.frames() - N);
 +                }
 +                _audio_buffers.set_frames (_audio_buffers.frames() - N);
 +        }
 +
 +        /* Now accumulate the new audio into our buffers */
 +        _audio_buffers.ensure_size (_audio_buffers.frames() + audio->frames());
 +        _audio_buffers.accumulate_frames (audio.get(), 0, 0, audio->frames ());
 +      _audio_buffers.set_frames (_audio_buffers.frames() + audio->frames());
 +}
 +
 +void
 +Player::flush ()
 +{
 +      if (_audio_buffers.frames() > 0) {
 +              shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), _audio_buffers.frames()));
 +              emit->copy_from (&_audio_buffers, _audio_buffers.frames(), 0, 0);
 +              Audio (emit, _next_audio);
 +              _next_audio += _film->audio_frames_to_time (_audio_buffers.frames ());
 +              _audio_buffers.set_frames (0);
 +      }
 +}
 +
 +/** @return true on error */
 +void
 +Player::seek (Time t)
 +{
 +      if (!_have_valid_pieces) {
 +              setup_pieces ();
 +              _have_valid_pieces = true;
 +      }
 +
 +      if (_pieces.empty ()) {
 +              return;
 +      }
 +
 +//    cout << "seek to " << t << " " << (t / TIME_HZ) << "\n";
 +
 +      for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
 +              Time s = t - (*i)->content->start ();
 +              s = max (static_cast<Time> (0), s);
 +              s = min ((*i)->content->length(), s);
 +//            cout << "seek [" << (*i)->content->file() << "," << (*i)->content->start() << "," << (*i)->content->end() << "] to " << s << "\n";
 +              (*i)->decoder->seek (s);
 +      }
 +
 +      /* XXX: don't seek audio because we don't need to... */
 +}
 +
 +
 +void
 +Player::seek_back ()
 +{
 +
 +}
 +
 +void
 +Player::seek_forward ()
 +{
 +
 +}
 +
 +void
 +Player::add_black_piece (Time s, Time len)
 +{
 +      shared_ptr<NullContent> nc (new NullContent (_film, s, len));
 +      nc->set_ratio (_film->container ());
 +      shared_ptr<BlackDecoder> bd (new BlackDecoder (_film, nc));
 +      bd->Video.connect (bind (&Player::process_video, this, nc, _1, _2, _3));
 +      _pieces.push_back (shared_ptr<Piece> (new Piece (nc, bd)));
 +      cout << "\tblack @ " << s << " -- " << (s + len) << "\n";
 +}
 +
 +void
 +Player::add_silent_piece (Time s, Time len)
 +{
 +      shared_ptr<NullContent> nc (new NullContent (_film, s, len));
 +      shared_ptr<SilenceDecoder> sd (new SilenceDecoder (_film, nc));
 +      sd->Audio.connect (bind (&Player::process_audio, this, nc, _1, _2));
 +      _pieces.push_back (shared_ptr<Piece> (new Piece (nc, sd)));
 +      cout << "\tsilence @ " << s << " -- " << (s + len) << "\n";
 +}
 +
 +
 +void
 +Player::setup_pieces ()
 +{
 +      cout << "----- Player SETUP PIECES.\n";
 +
 +      list<shared_ptr<Piece> > old_pieces = _pieces;
 +
 +      _pieces.clear ();
 +
 +      Playlist::ContentList content = _playlist->content ();
 +      sort (content.begin(), content.end(), ContentSorter ());
 +      
 +      for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) {
 +
 +              shared_ptr<Decoder> decoder;
 +              
 +                /* XXX: into content? */
 +
 +              shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
 +              if (fc) {
 +                      shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio, _subtitles));
 +                      
 +                      fd->Video.connect (bind (&Player::process_video, this, *i, _1, _2, _3));
 +                      fd->Audio.connect (bind (&Player::process_audio, this, *i, _1, _2));
 +
 +                      decoder = fd;
 +                      cout << "\tFFmpeg @ " << fc->start() << " -- " << fc->end() << "\n";
 +              }
 +              
 +              shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i);
 +              if (ic) {
 +                      shared_ptr<ImageMagickDecoder> id;
 +                      
 +                      /* See if we can re-use an old ImageMagickDecoder */
 +                      for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
 +                              shared_ptr<ImageMagickDecoder> imd = dynamic_pointer_cast<ImageMagickDecoder> ((*j)->decoder);
 +                              if (imd && imd->content() == ic) {
 +                                      id = imd;
 +                              }
 +                      }
 +
 +                      if (!id) {
 +                              id.reset (new ImageMagickDecoder (_film, ic));
 +                              id->Video.connect (bind (&Player::process_video, this, *i, _1, _2, _3));
 +                      }
 +
 +                      decoder = id;
 +                      cout << "\tImageMagick @ " << ic->start() << " -- " << ic->end() << "\n";
 +              }
 +
 +              shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
 +              if (sc) {
 +                      shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
 +                      sd->Audio.connect (bind (&Player::process_audio, this, *i, _1, _2));
 +
 +                      decoder = sd;
 +                      cout << "\tSndfile @ " << sc->start() << " -- " << sc->end() << "\n";
 +              }
 +
 +              _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder)));
 +      }
 +
 +      /* Fill in visual gaps with black and audio gaps with silence */
 +
 +      Time video_pos = 0;
 +      Time audio_pos = 0;
 +      list<shared_ptr<Piece> > pieces_copy = _pieces;
 +      for (list<shared_ptr<Piece> >::iterator i = pieces_copy.begin(); i != pieces_copy.end(); ++i) {
 +              if (dynamic_pointer_cast<VideoContent> ((*i)->content)) {
 +                      Time const diff = (*i)->content->start() - video_pos;
 +                      if (diff > 0) {
 +                              add_black_piece (video_pos, diff);
 +                      }
 +                      video_pos = (*i)->content->end();
 +              }
 +
 +              shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> ((*i)->content);
 +              if (ac && ac->audio_channels()) {
 +                      Time const diff = (*i)->content->start() - audio_pos;
 +                      if (diff > 0) {
 +                              add_silent_piece (video_pos, diff);
 +                      }
 +                      audio_pos = (*i)->content->end();
 +              }
 +      }
 +
 +      if (video_pos < audio_pos) {
 +              add_black_piece (video_pos, audio_pos - video_pos);
 +      } else if (audio_pos < video_pos) {
 +              add_silent_piece (audio_pos, video_pos - audio_pos);
 +      }
 +}
 +
 +void
 +Player::content_changed (weak_ptr<Content> w, int p)
 +{
 +      shared_ptr<Content> c = w.lock ();
 +      if (!c) {
 +              return;
 +      }
 +
 +      if (p == ContentProperty::START || p == ContentProperty::LENGTH) {
 +              _have_valid_pieces = false;
 +      }
 +}
 +
 +void
 +Player::playlist_changed ()
 +{
 +      _have_valid_pieces = false;
 +}
diff --combined src/lib/util.cc
index 71a21105b6ec2f6e9eb38096fbbf0ce36bb2dcba,4cf57368a7aea6718302c21441e719cdeeb7eb96..eda0d0236767300f349c146660d29a1f24a24595
@@@ -27,7 -27,7 +27,7 @@@
  #include <iostream>
  #include <fstream>
  #include <climits>
 -#ifdef DVDOMATIC_POSIX
 +#ifdef DCPOMATIC_POSIX
  #include <execinfo.h>
  #include <cxxabi.h>
  #endif
@@@ -56,37 -56,31 +56,37 @@@ extern "C" 
  #include "util.h"
  #include "exceptions.h"
  #include "scaler.h"
 -#include "format.h"
  #include "dcp_content_type.h"
  #include "filter.h"
  #include "sound_processor.h"
  #include "config.h"
 -#ifdef DVDOMATIC_WINDOWS
 +#include "ratio.h"
- #ifdef DVDOMATIC_WINDOWS
++#ifdef DCPOMATIC_WINDOWS
  #include "stack.hpp"
  #endif
  
  #include "i18n.h"
  
 -using std::cout;
  using std::string;
  using std::stringstream;
 -using std::list;
 +using std::setfill;
  using std::ostream;
 +using std::endl;
  using std::vector;
 +using std::hex;
 +using std::setw;
  using std::ifstream;
 -using std::istream;
 +using std::ios;
  using std::min;
  using std::max;
 +using std::list;
  using std::multimap;
 +using std::istream;
 +using std::numeric_limits;
  using std::pair;
  using std::ofstream;
  using boost::shared_ptr;
 +using boost::thread;
  using boost::lexical_cast;
  using boost::optional;
  using libdcp::Size;
@@@ -119,12 -113,6 +119,12 @@@ seconds_to_hms (int s
        return hms.str ();
  }
  
 +string
 +time_to_hms (Time t)
 +{
 +      return seconds_to_hms (t / TIME_HZ);
 +}
 +
  /** @param s Number of seconds.
   *  @return String containing an approximate description of s (e.g. "about 2 hours")
   */
@@@ -161,7 -149,7 +161,7 @@@ seconds_to_approximate_hms (int s
        return ap.str ();
  }
  
 -#ifdef DVDOMATIC_POSIX
 +#ifdef DCPOMATIC_POSIX
  /** @param l Mangled C++ identifier.
   *  @return Demangled version.
   */
@@@ -260,7 -248,7 +260,7 @@@ seconds (struct timeval t
        return t.tv_sec + (double (t.tv_usec) / 1e6);
  }
  
--#ifdef DVDOMATIC_WINDOWS
++#ifdef DCPOMATIC_WINDOWS
  LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *)
  {
        dbg::stack s;
   *  Must be called from the UI thread, if there is one.
   */
  void
 -dvdomatic_setup ()
 +dcpomatic_setup ()
  {
--#ifdef DVDOMATIC_WINDOWS
++#ifdef DCPOMATIC_WINDOWS
        backtrace_file /= g_get_user_config_dir ();
        backtrace_file /= "backtrace.txt";
        SetUnhandledExceptionFilter(exception_handler);
        
        avfilter_register_all ();
        
 -      Format::setup_formats ();
 +      Ratio::setup_ratios ();
        DCPContentType::setup_dcp_content_types ();
        Scaler::setup_scalers ();
        Filter::setup_filters ();
        ui_thread = boost::this_thread::get_id ();
  }
  
 -#ifdef DVDOMATIC_WINDOWS
 +#ifdef DCPOMATIC_WINDOWS
  boost::filesystem::path
  mo_path ()
  {
  #endif
  
  void
 -dvdomatic_setup_gettext_i18n (string lang)
 +dcpomatic_setup_gettext_i18n (string lang)
  {
 -#ifdef DVDOMATIC_POSIX
 +#ifdef DCPOMATIC_POSIX
        lang += ".UTF8";
  #endif
  
        }
  
        setlocale (LC_ALL, "");
 -      textdomain ("libdvdomatic");
 +      textdomain ("libdcpomatic");
  
 -#ifdef DVDOMATIC_WINDOWS
 -      bindtextdomain ("libdvdomatic", mo_path().string().c_str());
 -      bind_textdomain_codeset ("libdvdomatic", "UTF8");
 +#ifdef DCPOMATIC_WINDOWS
 +      bindtextdomain ("libdcpomatic", mo_path().string().c_str());
 +      bind_textdomain_codeset ("libdcpomatic", "UTF8");
  #endif        
  
 -#ifdef DVDOMATIC_POSIX
 -      bindtextdomain ("libdvdomatic", POSIX_LOCALE_PREFIX);
 +#ifdef DCPOMATIC_POSIX
 +      bindtextdomain ("libdcpomatic", POSIX_LOCALE_PREFIX);
  #endif
  }
  
@@@ -395,11 -383,11 +395,11 @@@ md5_digest (void const * data, int size
   *  @return MD5 digest of file's contents.
   */
  string
 -md5_digest (string file)
 +md5_digest (boost::filesystem::path file)
  {
 -      ifstream f (file.c_str(), std::ios::binary);
 +      ifstream f (file.string().c_str(), std::ios::binary);
        if (!f.good ()) {
 -              throw OpenFileError (file);
 +              throw OpenFileError (file.string());
        }
        
        f.seekg (0, std::ios::end);
@@@ -456,11 -444,66 +456,11 @@@ about_equal (float a, float b
        return (fabs (a - b) < 1e-4);
  }
  
 -class FrameRateCandidate
 -{
 -public:
 -      FrameRateCandidate (float source_, int dcp_)
 -              : source (source_)
 -              , dcp (dcp_)
 -      {}
 -
 -      float source;
 -      int dcp;
 -};
 -
 -int
 -best_dcp_frame_rate (float source_fps)
 -{
 -      list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates ();
 -
 -      /* Work out what rates we could manage, including those achieved by using skip / repeat. */
 -      list<FrameRateCandidate> candidates;
 -
 -      /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */
 -      for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
 -              candidates.push_back (FrameRateCandidate (*i, *i));
 -      }
 -
 -      /* Then the skip/repeat ones */
 -      for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
 -              candidates.push_back (FrameRateCandidate (float (*i) / 2, *i));
 -              candidates.push_back (FrameRateCandidate (float (*i) * 2, *i));
 -      }
 -
 -      /* Pick the best one, bailing early if we hit an exact match */
 -      float error = std::numeric_limits<float>::max ();
 -      optional<FrameRateCandidate> best;
 -      list<FrameRateCandidate>::iterator i = candidates.begin();
 -      while (i != candidates.end()) {
 -              
 -              if (about_equal (i->source, source_fps)) {
 -                      best = *i;
 -                      break;
 -              }
 -
 -              float const e = fabs (i->source - source_fps);
 -              if (e < error) {
 -                      error = e;
 -                      best = *i;
 -              }
 -
 -              ++i;
 -      }
 -
 -      assert (best);
 -      return best->dcp;
 -}
 -
 -/** @param An arbitrary sampling rate.
 - *  @return The appropriate DCP-approved sampling rate (48kHz or 96kHz).
 +/** @param An arbitrary audio frame rate.
 + *  @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
   */
  int
 -dcp_audio_sample_rate (int fs)
 +dcp_audio_frame_rate (int fs)
  {
        if (fs <= 48000) {
                return 48000;
        return 96000;
  }
  
 -bool operator== (Crop const & a, Crop const & b)
 -{
 -      return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom);
 -}
 -
 -bool operator!= (Crop const & a, Crop const & b)
 -{
 -      return !(a == b);
 -}
 -
  /** @param index Colour LUT index.
   *  @return Human-readable name.
   */
@@@ -581,6 -634,22 +581,6 @@@ Socket::read_uint32 (
        return ntohl (v);
  }
  
 -/** @param other A Rect.
 - *  @return The intersection of this with `other'.
 - */
 -dvdomatic::Rect
 -dvdomatic::Rect::intersection (Rect const & other) const
 -{
 -      int const tx = max (x, other.x);
 -      int const ty = max (y, other.y);
 -      
 -      return Rect (
 -              tx, ty,
 -              min (x + width, other.x + other.width) - tx,
 -              min (y + height, other.y + other.height) - ty
 -              );
 -}
 -
  /** Round a number up to the nearest multiple of another number.
   *  @param c Index.
   *  @param s Array of numbers to round, indexed by c.
@@@ -697,6 -766,151 +697,6 @@@ get_optional_int (multimap<string, stri
        return lexical_cast<int> (i->second);
  }
  
 -/** Construct an AudioBuffers.  Audio data is undefined after this constructor.
 - *  @param channels Number of channels.
 - *  @param frames Number of frames to reserve space for.
 - */
 -AudioBuffers::AudioBuffers (int channels, int frames)
 -      : _channels (channels)
 -      , _frames (frames)
 -      , _allocated_frames (frames)
 -{
 -      _data = new float*[_channels];
 -      for (int i = 0; i < _channels; ++i) {
 -              _data[i] = new float[frames];
 -      }
 -}
 -
 -/** Copy constructor.
 - *  @param other Other AudioBuffers; data is copied.
 - */
 -AudioBuffers::AudioBuffers (AudioBuffers const & other)
 -      : _channels (other._channels)
 -      , _frames (other._frames)
 -      , _allocated_frames (other._frames)
 -{
 -      _data = new float*[_channels];
 -      for (int i = 0; i < _channels; ++i) {
 -              _data[i] = new float[_frames];
 -              memcpy (_data[i], other._data[i], _frames * sizeof (float));
 -      }
 -}
 -
 -/* XXX: it's a shame that this is a copy-and-paste of the above;
 -   probably fixable with c++0x.
 -*/
 -AudioBuffers::AudioBuffers (boost::shared_ptr<const AudioBuffers> other)
 -      : _channels (other->_channels)
 -      , _frames (other->_frames)
 -      , _allocated_frames (other->_frames)
 -{
 -      _data = new float*[_channels];
 -      for (int i = 0; i < _channels; ++i) {
 -              _data[i] = new float[_frames];
 -              memcpy (_data[i], other->_data[i], _frames * sizeof (float));
 -      }
 -}
 -
 -/** AudioBuffers destructor */
 -AudioBuffers::~AudioBuffers ()
 -{
 -      for (int i = 0; i < _channels; ++i) {
 -              delete[] _data[i];
 -      }
 -
 -      delete[] _data;
 -}
 -
 -/** @param c Channel index.
 - *  @return Buffer for this channel.
 - */
 -float*
 -AudioBuffers::data (int c) const
 -{
 -      assert (c >= 0 && c < _channels);
 -      return _data[c];
 -}
 -
 -/** Set the number of frames that these AudioBuffers will report themselves
 - *  as having.
 - *  @param f Frames; must be less than or equal to the number of allocated frames.
 - */
 -void
 -AudioBuffers::set_frames (int f)
 -{
 -      assert (f <= _allocated_frames);
 -      _frames = f;
 -}
 -
 -/** Make all samples on all channels silent */
 -void
 -AudioBuffers::make_silent ()
 -{
 -      for (int i = 0; i < _channels; ++i) {
 -              make_silent (i);
 -      }
 -}
 -
 -/** Make all samples on a given channel silent.
 - *  @param c Channel.
 - */
 -void
 -AudioBuffers::make_silent (int c)
 -{
 -      assert (c >= 0 && c < _channels);
 -      
 -      for (int i = 0; i < _frames; ++i) {
 -              _data[c][i] = 0;
 -      }
 -}
 -
 -/** Copy data from another AudioBuffers to this one.  All channels are copied.
 - *  @param from AudioBuffers to copy from; must have the same number of channels as this.
 - *  @param frames_to_copy Number of frames to copy.
 - *  @param read_offset Offset to read from in `from'.
 - *  @param write_offset Offset to write to in `to'.
 - */
 -void
 -AudioBuffers::copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset)
 -{
 -      assert (from->channels() == channels());
 -
 -      assert (from);
 -      assert (read_offset >= 0 && (read_offset + frames_to_copy) <= from->_allocated_frames);
 -      assert (write_offset >= 0 && (write_offset + frames_to_copy) <= _allocated_frames);
 -
 -      for (int i = 0; i < _channels; ++i) {
 -              memcpy (_data[i] + write_offset, from->_data[i] + read_offset, frames_to_copy * sizeof(float));
 -      }
 -}
 -
 -/** Move audio data around.
 - *  @param from Offset to move from.
 - *  @param to Offset to move to.
 - *  @param frames Number of frames to move.
 - */
 -    
 -void
 -AudioBuffers::move (int from, int to, int frames)
 -{
 -      if (frames == 0) {
 -              return;
 -      }
 -      
 -      assert (from >= 0);
 -      assert (from < _frames);
 -      assert (to >= 0);
 -      assert (to < _frames);
 -      assert (frames > 0);
 -      assert (frames <= _frames);
 -      assert ((from + frames) <= _frames);
 -      assert ((to + frames) <= _frames);
 -      
 -      for (int i = 0; i < _channels; ++i) {
 -              memmove (_data[i] + to, _data[i] + from, frames * sizeof(float));
 -      }
 -}
 -
  /** Trip an assert if the caller is not in the UI thread */
  void
  ensure_ui_thread ()
        assert (boost::this_thread::get_id() == ui_thread);
  }
  
 -/** @param v Source video frame.
 +/** @param v Content video frame.
   *  @param audio_sample_rate Source audio sample rate.
   *  @param frames_per_second Number of video frames per second.
   *  @return Equivalent number of audio frames for `v'.
   */
  int64_t
 -video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second)
 +video_frames_to_audio_frames (ContentVideoFrame v, float audio_sample_rate, float frames_per_second)
  {
        return ((int64_t) v * audio_sample_rate / frames_per_second);
  }
  
- /** @return A pair containing CPU model name and the number of processors */
- pair<string, int>
- cpu_info ()
 -/** @param f Filename.
 - *  @return true if this file is a still image, false if it is something else.
 - */
 -bool
 -still_image_file (string f)
--{
-       pair<string, int> info;
-       info.second = 0;
-       
- #ifdef DCPOMATIC_POSIX
-       ifstream f (N_("/proc/cpuinfo"));
-       while (f.good ()) {
-               string l;
-               getline (f, l);
-               if (boost::algorithm::starts_with (l, N_("model name"))) {
-                       string::size_type const c = l.find (':');
-                       if (c != string::npos) {
-                               info.first = l.substr (c + 2);
-                       }
-               } else if (boost::algorithm::starts_with (l, N_("processor"))) {
-                       ++info.second;
-               }
-       }
- #endif        
 -      string ext = boost::filesystem::path(f).extension().string();
--
-       return info;
 -      transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
 -      
 -      return (ext == N_(".tif") || ext == N_(".tiff") || ext == N_(".jpg") || ext == N_(".jpeg") || ext == N_(".png") || ext == N_(".bmp"));
--}
--
  string
  audio_channel_name (int c)
  {
        return channels[c];
  }
  
 -AudioMapping::AudioMapping (int c)
 -      : _source_channels (c)
 -{
 -
 -}
 -
 -optional<libdcp::Channel>
 -AudioMapping::source_to_dcp (int c) const
 -{
 -      if (c >= _source_channels) {
 -              return optional<libdcp::Channel> ();
 -      }
 -
 -      if (_source_channels == 1) {
 -              /* mono sources to centre */
 -              return libdcp::CENTRE;
 -      }
 -      
 -      return static_cast<libdcp::Channel> (c);
 -}
 -
 -optional<int>
 -AudioMapping::dcp_to_source (libdcp::Channel c) const
 -{
 -      if (_source_channels == 1) {
 -              if (c == libdcp::CENTRE) {
 -                      return 0;
 -              } else {
 -                      return optional<int> ();
 -              }
 -      }
 -
 -      if (static_cast<int> (c) >= _source_channels) {
 -              return optional<int> ();
 -      }
 -      
 -      return static_cast<int> (c);
 -}
 -
 -int
 -AudioMapping::dcp_channels () const
 -{
 -      if (_source_channels == 1) {
 -              /* The source is mono, so to put the mono channel into
 -                 the centre we need to generate a 5.1 soundtrack.
 -              */
 -              return 6;
 -      }
 -
 -      return _source_channels;
 -}
 -
  FrameRateConversion::FrameRateConversion (float source, int dcp)
        : skip (false)
        , repeat (false)
diff --combined src/lib/util.h
index be70eb25997ca23b6473483bdc1e9df2c37a9e8f,0d745e50c5f3152c3984758c2260d0acf65a7fd1..42514a12c92f9f863944ab0018d16b5b8e573533
@@@ -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,9 -37,8 +37,9 @@@ 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(...)
  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);
@@@ -118,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.
@@@ -152,8 -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/tools/dcpomatic.cc
index ebd647861817ea98cacdf3289ae189e378796aaf,0000000000000000000000000000000000000000..77800b5fdfcfe9d8101c252fa007f012b059cb56
mode 100644,000000..100644
--- /dev/null
@@@ -1,551 -1,0 +1,527 @@@
- #include <wx/aboutdlg.h>
 +/*
 +    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
-       add_item (help, _("About DVD-o-matic"), wxID_ABOUT, ALWAYS);
++#include <wx/generic/aboutdlgg.h>
 +#include <wx/stdpaths.h>
 +#include <wx/cmdline.h>
 +#include "wx/film_viewer.h"
 +#include "wx/film_editor.h"
 +#include "wx/job_manager_view.h"
 +#include "wx/config_dialog.h"
 +#include "wx/job_wrapper.h"
 +#include "wx/wx_util.h"
 +#include "wx/new_film_dialog.h"
 +#include "wx/properties_dialog.h"
 +#include "wx/wx_ui_signaller.h"
++#include "wx/about_dialog.h"
 +#include "lib/film.h"
 +#include "lib/config.h"
 +#include "lib/util.h"
 +#include "lib/version.h"
 +#include "lib/ui_signaller.h"
 +#include "lib/log.h"
 +
 +using std::cout;
 +using std::string;
 +using std::wstring;
 +using std::stringstream;
 +using std::map;
 +using std::make_pair;
 +using std::exception;
 +using std::ofstream;
 +using boost::shared_ptr;
 +
 +static FilmEditor* film_editor = 0;
 +static FilmViewer* film_viewer = 0;
 +static shared_ptr<Film> film;
 +static std::string log_level;
 +static std::string film_to_load;
 +static std::string film_to_create;
 +static wxMenu* jobs_menu = 0;
 +
 +static void set_menu_sensitivity ();
 +
 +class FilmChangedDialog
 +{
 +public:
 +      FilmChangedDialog ()
 +      {
 +              _dialog = new wxMessageDialog (
 +                      0,
 +                      wxString::Format (_("Save changes to film \"%s\" before closing?"), std_to_wx (film->name ()).data()),
 +                      _("Film changed"),
 +                      wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION
 +                      );
 +      }
 +
 +      ~FilmChangedDialog ()
 +      {
 +              _dialog->Destroy ();
 +      }
 +
 +      int run ()
 +      {
 +              return _dialog->ShowModal ();
 +      }
 +
 +private:      
 +      wxMessageDialog* _dialog;
 +};
 +
 +
 +void
 +maybe_save_then_delete_film ()
 +{
 +      if (!film) {
 +              return;
 +      }
 +                      
 +      if (film->dirty ()) {
 +              FilmChangedDialog d;
 +              switch (d.run ()) {
 +              case wxID_NO:
 +                      break;
 +              case wxID_YES:
 +                      film->write_metadata ();
 +                      break;
 +              }
 +      }
 +      
 +      film.reset ();
 +}
 +
 +enum Sensitivity {
 +      ALWAYS,
 +      NEEDS_FILM
 +};
 +
 +map<wxMenuItem*, Sensitivity> menu_items;
 +      
 +void
 +add_item (wxMenu* menu, wxString text, int id, Sensitivity sens)
 +{
 +      wxMenuItem* item = menu->Append (id, text);
 +      menu_items.insert (make_pair (item, sens));
 +}
 +
 +void
 +set_menu_sensitivity ()
 +{
 +      for (map<wxMenuItem*, Sensitivity>::iterator i = menu_items.begin(); i != menu_items.end(); ++i) {
 +              if (i->second == NEEDS_FILM) {
 +                      i->first->Enable (film != 0);
 +              } else {
 +                      i->first->Enable (true);
 +              }
 +      }
 +}
 +
 +enum {
 +      ID_file_new = 1,
 +      ID_file_open,
 +      ID_file_save,
 +      ID_file_properties,
 +      ID_jobs_make_dcp,
 +      ID_jobs_send_dcp_to_tms,
 +      ID_jobs_show_dcp,
 +      ID_jobs_analyse_audio,
 +};
 +
 +void
 +setup_menu (wxMenuBar* m)
 +{
 +      wxMenu* file = new wxMenu;
 +      add_item (file, _("New..."), ID_file_new, ALWAYS);
 +      add_item (file, _("&Open..."), ID_file_open, ALWAYS);
 +      file->AppendSeparator ();
 +      add_item (file, _("&Save"), ID_file_save, NEEDS_FILM);
 +      file->AppendSeparator ();
 +      add_item (file, _("&Properties..."), ID_file_properties, NEEDS_FILM);
 +#ifndef __WXOSX__     
 +      file->AppendSeparator ();
 +#endif        
 +      add_item (file, _("&Exit"), wxID_EXIT, ALWAYS);
 +
 +#ifdef __WXOSX__      
 +      add_item (file, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
 +#else
 +      wxMenu* edit = new wxMenu;
 +      add_item (edit, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
 +#endif        
 +
 +      jobs_menu = new wxMenu;
 +      add_item (jobs_menu, _("&Make DCP"), ID_jobs_make_dcp, NEEDS_FILM);
 +      add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM);
 +      add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM);
 +      jobs_menu->AppendSeparator ();
 +      add_item (jobs_menu, _("&Analyse audio"), ID_jobs_analyse_audio, NEEDS_FILM);
 +
 +      wxMenu* help = new wxMenu;
 +#ifdef __WXOSX__      
-               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);
++      add_item (help, _("About DCP-o-matic"), wxID_ABOUT, ALWAYS);
 +#else 
 +      add_item (help, _("About"), wxID_ABOUT, ALWAYS);
 +#endif        
 +
 +      m->Append (file, _("&File"));
 +#ifndef __WXOSX__     
 +      m->Append (edit, _("&Edit"));
 +#endif        
 +      m->Append (jobs_menu, _("&Jobs"));
 +      m->Append (help, _("&Help"));
 +}
 +
 +bool
 +window_closed (wxCommandEvent &)
 +{
 +      maybe_save_then_delete_film ();
 +      return false;
 +}
 +
 +class Frame : public wxFrame
 +{
 +public:
 +      Frame (wxString const & title)
 +              : wxFrame (NULL, -1, title)
 +      {
 +              wxMenuBar* bar = new wxMenuBar;
 +              setup_menu (bar);
 +              SetMenuBar (bar);
 +
 +              Connect (ID_file_new, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_new));
 +              Connect (ID_file_open, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_open));
 +              Connect (ID_file_save, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_save));
 +              Connect (ID_file_properties, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_properties));
 +              Connect (wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_exit));
 +              Connect (wxID_PREFERENCES, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::edit_preferences));
 +              Connect (ID_jobs_make_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_make_dcp));
 +              Connect (ID_jobs_send_dcp_to_tms, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_send_dcp_to_tms));
 +              Connect (ID_jobs_show_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_show_dcp));
 +              Connect (ID_jobs_analyse_audio, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_analyse_audio));
 +              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 &)
 +      {
- #ifdef DCPOMATIC_POSIX                
++              AboutDialog* d = new AboutDialog (this);
++              d->ShowModal ();
++              d->Destroy ();
 +      }
 +};
 +
 +#if wxMINOR_VERSION == 9
 +static const wxCmdLineEntryDesc command_line_description[] = {
 +      { wxCMD_LINE_OPTION, "l", "log", "set log level (silent, verbose or timing)", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
 +      { wxCMD_LINE_SWITCH, "n", "new", "create new film", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
 +        { wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
 +      { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
 +};
 +#else
 +static const wxCmdLineEntryDesc command_line_description[] = {
 +      { wxCMD_LINE_OPTION, wxT("l"), wxT("log"), wxT("set log level (silent, verbose or timing)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
 +      { wxCMD_LINE_SWITCH, wxT("n"), wxT("new"), wxT("create new film"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
 +        { wxCMD_LINE_PARAM, 0, 0, wxT("film to load or create"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
 +      { wxCMD_LINE_NONE, wxT(""), wxT(""), wxT(""), wxCmdLineParamType (0), 0 }
 +};
 +#endif
 +
 +class App : public wxApp
 +{
 +      bool OnInit ()
 +      {
 +              if (!wxApp::OnInit()) {
 +                      return false;
 +              }
 +              
++#ifdef DCPOMATIC_LINUX        
 +              unsetenv ("UBUNTU_MENUPROXY");
 +#endif
 +
 +#ifdef __WXOSX__              
 +              ProcessSerialNumber serial;
 +              GetCurrentProcess (&serial);
 +              TransformProcessType (&serial, kProcessTransformToForegroundApplication);
 +#endif                
 +
 +              wxInitAllImageHandlers ();
 +
 +              /* Enable i18n; this will create a Config object
 +                 to look for a force-configured language.  This Config
 +                 object will be wrong, however, because dcpomatic_setup
 +                 hasn't yet been called and there aren't any scalers, filters etc.
 +                 set up yet.
 +              */
 +              dcpomatic_setup_i18n ();
 +
 +              /* Set things up, including scalers / filters etc.
 +                 which will now be internationalised correctly.
 +              */
 +              dcpomatic_setup ();
 +
 +              /* Force the configuration to be re-loaded correctly next
 +                 time it is needed.
 +              */
 +              Config::drop ();
 +
 +              if (!film_to_load.empty() && boost::filesystem::is_directory (film_to_load)) {
 +                      try {
 +                              film.reset (new Film (film_to_load));
 +                              film->read_metadata ();
 +                              film->log()->set_level (log_level);
 +                      } catch (exception& e) {
 +                              error_dialog (0, std_to_wx (String::compose (wx_to_std (_("Could not load film %1 (%2)")), film_to_load, e.what())));
 +                      }
 +              }
 +
 +              if (!film_to_create.empty ()) {
 +                      film.reset (new Film (film_to_create));
 +                      film->write_metadata ();
 +                      film->log()->set_level (log_level);
 +                      film->set_name (boost::filesystem::path (film_to_create).filename().generic_string ());
 +              }
 +
 +              Frame* f = new Frame (_("DCP-o-matic"));
 +              SetTopWindow (f);
 +              f->Maximize ();
 +              f->Show ();
 +
 +              ui_signaller = new wxUISignaller (this);
 +              this->Connect (-1, wxEVT_IDLE, wxIdleEventHandler (App::idle));
 +
 +              return true;
 +      }
 +
 +      void OnInitCmdLine (wxCmdLineParser& parser)
 +      {
 +              parser.SetDesc (command_line_description);
 +              parser.SetSwitchChars (wxT ("-"));
 +      }
 +
 +      bool OnCmdLineParsed (wxCmdLineParser& parser)
 +      {
 +              if (parser.GetParamCount() > 0) {
 +                      if (parser.Found (wxT ("new"))) {
 +                              film_to_create = wx_to_std (parser.GetParam (0));
 +                      } else {
 +                              film_to_load = wx_to_std (parser.GetParam(0));
 +                      }
 +              }
 +
 +              wxString log;
 +              if (parser.Found (wxT ("log"), &log)) {
 +                      log_level = wx_to_std (log);
 +              }
 +
 +              return true;
 +      }
 +
 +      void idle (wxIdleEvent &)
 +      {
 +              ui_signaller->ui_idle ();
 +      }
 +};
 +
 +IMPLEMENT_APP (App)
index 403c1c21b7273556a805c6eb4950249fd0fec1f9,0000000000000000000000000000000000000000..b4ab054fda92c9c01b287cac72ad115bdebed65a
mode 100644,000000..100644
--- /dev/null
@@@ -1,240 -1,0 +1,240 @@@
- #ifdef DCPOMATIC_POSIX                
 +/*
 +    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +*/
 +
 +#include <wx/aboutdlg.h>
 +#include <wx/stdpaths.h>
 +#include <wx/wx.h>
 +#include "lib/version.h"
 +#include "lib/compose.hpp"
 +#include "lib/config.h"
 +#include "lib/util.h"
 +#include "lib/film.h"
 +#include "lib/job_manager.h"
 +#include "wx/wx_util.h"
 +#include "wx/wx_ui_signaller.h"
 +#include "wx/job_manager_view.h"
 +
 +using boost::shared_ptr;
 +
 +enum {
 +      ID_file_add_film = 1,
 +      ID_file_quit,
 +      ID_help_about
 +};
 +
 +void
 +setup_menu (wxMenuBar* m)
 +{
 +      wxMenu* file = new wxMenu;
 +      file->Append (ID_file_add_film, _("&Add Film..."));
 +      file->Append (ID_file_quit, _("&Quit"));
 +
 +      wxMenu* help = new wxMenu;
 +      help->Append (ID_help_about, _("About"));
 +
 +      m->Append (file, _("&File"));
 +      m->Append (help, _("&Help"));
 +}
 +
 +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_add_film, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_add_film));
 +              Connect (ID_file_quit, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_quit));
 +              Connect (ID_help_about, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::help_about));
 +
 +              wxPanel* panel = new wxPanel (this);
 +              wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 +              s->Add (panel, 1, wxEXPAND);
 +              SetSizer (s);
 +
 +              wxSizer* sizer = new wxBoxSizer (wxVERTICAL);
 +
 +              JobManagerView* job_manager_view = new JobManagerView (panel, JobManagerView::PAUSE);
 +              sizer->Add (job_manager_view, 1, wxALL | wxEXPAND, 6);
 +
 +              wxSizer* buttons = new wxBoxSizer (wxHORIZONTAL);
 +              wxButton* add = new wxButton (panel, wxID_ANY, _("Add Film..."));
 +              add->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (Frame::add_film));
 +              buttons->Add (add, 1, wxALL, 6);
 +
 +              sizer->Add (buttons, 0, wxALL, 6);
 +
 +              panel->SetSizer (sizer);
 +
 +              Connect (wxID_ANY, wxEVT_CLOSE_WINDOW, wxCloseEventHandler (Frame::close));
 +      }
 +
 +private:
 +      bool should_close ()
 +      {
 +              if (!JobManager::instance()->work_to_do ()) {
 +                      return true;
 +              }
 +
 +              wxMessageDialog* d = new wxMessageDialog (
 +                      0,
 +                      _("There are unfinished jobs; are you sure you want to quit?"),
 +                      _("Unfinished jobs"),
 +                      wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION
 +                      );
 +
 +              bool const r = d->ShowModal() == wxID_YES;
 +              d->Destroy ();
 +              return r;
 +      }
 +              
 +      void close (wxCloseEvent& ev)
 +      {
 +              if (!should_close ()) {
 +                      ev.Veto ();
 +                      return;
 +              }
 +
 +              ev.Skip ();
 +      }
 +
 +      void file_add_film (wxCommandEvent& ev)
 +      {
 +              add_film (ev);
 +      }
 +      
 +      void file_quit (wxCommandEvent &)
 +      {
 +              if (should_close ()) {
 +                      Close (true);
 +              }
 +      }
 +
 +      void help_about (wxCommandEvent &)
 +      {
 +              wxAboutDialogInfo info;
 +              info.SetName (_("DCP-o-matic Batch Converter"));
 +              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);
 +      }
 +
 +      void add_film (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) {
 +                      try {
 +                              shared_ptr<Film> film (new Film (wx_to_std (c->GetPath ())));
 +                              film->read_metadata ();
 +                              film->make_dcp ();
 +                      } 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 ();
 +      }
 +};
 +
 +class App : public wxApp
 +{
 +      bool OnInit ()
 +      {
 +              if (!wxApp::OnInit()) {
 +                      return false;
 +              }
 +              
++#ifdef DCPOMATIC_LINUX                
 +              unsetenv ("UBUNTU_MENUPROXY");
 +#endif                
 +
 +              /* 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 ();
 +
 +              Frame* f = new Frame (_("DCP-o-matic Batch Converter"));
 +              SetTopWindow (f);
 +              f->Maximize ();
 +              f->Show ();
 +
 +              ui_signaller = new wxUISignaller (this);
 +              this->Connect (-1, wxEVT_IDLE, wxIdleEventHandler (App::idle));
 +
 +              return true;
 +      }
 +
 +      void idle (wxIdleEvent &)
 +      {
 +              ui_signaller->ui_idle ();
 +      }
 +};
 +
 +IMPLEMENT_APP (App)
diff --combined src/tools/po/es_ES.po
index 01d7c1ad29a574931252c2b71a299a5c208fda77,bbae550d77718971513035634ed63378b835b1e6..43c9b12f116d9aff5b968a58fefb32c09a4dc7fc
@@@ -5,7 -5,7 +5,7 @@@
  #
  msgid ""
  msgstr ""
 -"Project-Id-Version: DVDOMATIC\n"
 +"Project-Id-Version: DCPOMATIC\n"
  "Report-Msgid-Bugs-To: \n"
  "POT-Creation-Date: 2013-06-04 23:59+0100\n"
  "PO-Revision-Date: 2013-03-23 21:08-0500\n"
@@@ -90,22 -90,16 +90,16 @@@ msgstr "No se pudo cargar la película 
  msgid "Could not open film at %s (%s)"
  msgstr "No se pudo cargar la película en %s (%s)"
  
 -#: src/tools/dvdomatic.cc:300 src/tools/dvdomatic.cc:431
 -#: src/tools/dvdomatic.cc:524
 -msgid "DVD-o-matic"
 -msgstr "DVD-o-matic"
 +#: src/tools/dcpomatic.cc:287 src/tools/dcpomatic.cc:410
 +#: src/tools/dcpomatic.cc:531
 +msgid "DCP-o-matic"
 +msgstr "DCP-o-matic"
  
 -#: src/tools/dvdomatic.cc:79
 +#: src/tools/dcpomatic.cc:75
  msgid "Film changed"
  msgstr "Película cambiada"
  
- #: src/tools/dcpomatic.cc:416
- #: src/tools/dvdomatic.cc:288 src/tools/dvdomatic.cc:419
- #: src/tools/dvdomatic.cc:506
- msgid "DCP-o-matic"
- msgstr "DCP-o-matic"
 -#: src/tools/dvdomatic.cc:437
 +#: src/tools/dvdomatic.cc:425
  msgid "Free, open-source DCP generation from almost anything."
  msgstr ""
  "Generación de DCP a partir de casi cualquier fuente, libre y de código "
diff --combined src/wx/about_dialog.cc
index 0000000000000000000000000000000000000000,27644a00f1e001a4a07a9290ec9961e2d1d64520..7844180fa25b6ecb63ac003b848fd756f34cbeda
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,155 +1,151 @@@
 -      : wxDialog (parent, wxID_ANY, _("About DVD-o-matic"))
+ /*
+     Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+     This program is free software; you can redistribute it and/or modify
+     it under the terms of the GNU General Public License as published by
+     the Free Software Foundation; either version 2 of the License, or
+     (at your option) any later version.
+     This program is distributed in the hope that it will be useful,
+     but WITHOUT ANY WARRANTY; without even the implied warranty of
+     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     GNU General Public License for more details.
+     You should have received a copy of the GNU General Public License
+     along with this program; if not, write to the Free Software
+     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+ #include <wx/notebook.h>
+ #include <wx/hyperlink.h>
+ #include "lib/version.h"
+ #include "lib/compose.hpp"
+ #include "about_dialog.h"
+ #include "wx_util.h"
+ using std::vector;
+ AboutDialog::AboutDialog (wxWindow* parent)
 -      wxStaticText* t = new wxStaticText (this, wxID_ANY, _("DVD-o-matic"));
++      : wxDialog (parent, wxID_ANY, _("About DCP-o-matic"))
+ {
+       wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+       
+       wxFont title_font (*wxNORMAL_FONT);
+       title_font.SetPointSize (title_font.GetPointSize() + 4);
+       title_font.SetWeight (wxFONTWEIGHT_BOLD);
+       wxFont version_font (*wxNORMAL_FONT);
+       version_font.SetWeight (wxFONTWEIGHT_BOLD);
+       
 -      if (strcmp (dvdomatic_git_commit, "release") == 0) {
 -              t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1", dvdomatic_version)));
++      wxStaticText* t = new wxStaticText (this, wxID_ANY, _("DCP-o-matic"));
+       t->SetFont (title_font);
+       sizer->Add (t, wxSizerFlags().Centre().Border());
+       wxString s;
 -              t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1 git %2", dvdomatic_version, dvdomatic_git_commit)));
++      if (strcmp (dcpomatic_git_commit, "release") == 0) {
++              t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1", dcpomatic_version)));
+       } else {
 -              wxT ("www.carlh.net/software/dvdomatic"),
 -              wxT ("http://www.carlh.net/software/dvdomatic")
++              t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1 git %2", dcpomatic_version, dcpomatic_git_commit)));
+       }
+       t->SetFont (version_font);
+       sizer->Add (t, wxSizerFlags().Centre().Border());
+       sizer->AddSpacer (12);
+       t = new wxStaticText (
+               this, wxID_ANY,
+               _("Free, open-source DCP generation from almost anything."),
+               wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER
+               );
+       
+       sizer->Add (t, wxSizerFlags().Centre().Border());
+       wxHyperlinkCtrl* h = new wxHyperlinkCtrl (
+               this, wxID_ANY,
 -#if 0 
 -      info.SetWebSite (wxT ("http://carlh.net/software/dvdomatic"));
 -#endif
 -
++              wxT ("www.carlh.net/software/dcpomatic"),
++              wxT ("http://www.carlh.net/software/dcpomatic")
+               );
+       sizer->Add (h, wxSizerFlags().Centre().Border());
+       t = new wxStaticText (
+               this, wxID_ANY,
+               _("(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen"),
+               wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER
+               );
+       
+       sizer->Add (t, wxSizerFlags().Centre().Border());
+       _notebook = new wxNotebook (this, wxID_ANY);
+       wxArrayString written_by;
+       written_by.Add (wxT ("Carl Hetherington"));
+       written_by.Add (wxT ("Terrence Meiczinger"));
+       written_by.Add (wxT ("Paul Davis"));
+       written_by.Add (wxT ("Ole Laursen"));
+       add_section (_("Written by"), written_by);
+       wxArrayString translated_by;
+       translated_by.Add (wxT ("Olivier Perriere"));
+       translated_by.Add (wxT ("Lilian Lefranc"));
+       translated_by.Add (wxT ("Thierry Journet"));
+       translated_by.Add (wxT ("Massimiliano Broggi"));
+       translated_by.Add (wxT ("Manuel AC"));
+       translated_by.Add (wxT ("Adam Klotblixt"));
+       add_section (_("Translated by"), translated_by);
+       wxArrayString supported_by;
+       supported_by.Add (wxT ("Carsten Kurz"));
+       supported_by.Add (wxT ("Wolfgang Woehl"));
+       supported_by.Add (wxT ("Manual AC"));
+       supported_by.Add (wxT ("Theo Lipfert"));
+       supported_by.Add (wxT ("Olivier Lemaire"));
+       supported_by.Add (wxT ("Andrä Steiner"));
+       supported_by.Add (wxT ("Jonathan Jensen"));
+       supported_by.Add (wxT ("Kjarten Michaelsen"));
+       supported_by.Add (wxT ("Jussi Siponen"));
+       supported_by.Add (wxT ("Cinema Clarici"));
+       supported_by.Add (wxT ("Evan Freeze"));
+       supported_by.Add (wxT ("Flor Guillaume"));
+       supported_by.Add (wxT ("Adam Klotblixt "));
+       supported_by.Add (wxT ("Lilian Lefranc"));
+       supported_by.Add (wxT ("Gavin Lewarne"));
+       supported_by.Add (wxT ("Lasse Salling"));
+       supported_by.Add (wxT ("Andres Fink"));
+       supported_by.Add (wxT ("Kieran Carroll"));
+       add_section (_("Supported by"), supported_by);
+       sizer->Add (_notebook, wxSizerFlags().Centre().Border().Expand());
+       
+       SetSizerAndFit (sizer);
+ }
+ void
+ AboutDialog::add_section (wxString name, wxArrayString credits)
+ {
+       static bool first = true;
+       int const N = 3;
+       wxPanel* panel = new wxPanel (_notebook, wxID_ANY);
+       wxSizer* overall_sizer = new wxBoxSizer (wxHORIZONTAL);
+       vector<wxSizer*> sizers;
+       
+       for (int i = 0; i < N; ++i) {
+               sizers.push_back (new wxBoxSizer (wxVERTICAL));
+               overall_sizer->Add (sizers.back (), 1, wxEXPAND | wxALL, 6);
+       }
+       int c = 0;
+       for (size_t i = 0; i < credits.Count(); ++i) {
+               add_label_to_sizer (sizers[c], panel, credits[i]);
+               ++c;
+               if (c == N) {
+                       c = 0;
+               }
+       }
+       panel->SetSizerAndFit (overall_sizer);
+       _notebook->AddPage (panel, name, first);
+       first = false;
+ }
diff --combined src/wx/po/es_ES.po
index 3ff32280d7c3b4afb606ab7d04c8be082efb571d,8bc80675561edb758520f3875bab0909d2fe37b2..34646c2b7ea99f70af6d49e36009d8bcd3d9db5d
@@@ -5,7 -5,7 +5,7 @@@
  #
  msgid ""
  msgstr ""
 -"Project-Id-Version: libdvdomatic-wx\n"
 +"Project-Id-Version: libdcpomatic-wx\n"
  "Report-Msgid-Bugs-To: \n"
  "POT-Creation-Date: 2013-06-04 23:59+0100\n"
  "PO-Revision-Date: 2013-04-02 19:08-0500\n"
  msgid "%"
  msgstr "%"
  
- msgid "(restart DCP-o-matic to see language changes)"
 -#: src/wx/config_dialog.cc:98
 -msgid "(restart DVD-o-matic to see language changes)"
 -msgstr ""
--
  #: src/wx/film_editor.cc:1276
  msgid "1 channel"
  msgstr "1 canal"
@@@ -147,13 -149,9 +145,13 @@@ msgstr "Velocidad DCP
  msgid "DCP Name"
  msgstr "Nombre DCP"
  
 -#: src/wx/wx_util.cc:63 src/wx/wx_util.cc:71
 -msgid "DVD-o-matic"
 -msgstr "DVD-o-matic"
 +#: src/wx/wx_util.cc:61
 +msgid "DCP-o-matic"
 +msgstr "DCP-o-matic"
 +
 +#: src/wx/config_dialog.cc:44
 +msgid "DCP-o-matic Preferences"
 +msgstr "Preferencias DCP-o-matic"
  
  #: src/wx/config_dialog.cc:46
  #, fuzzy
@@@ -162,8 -160,8 +160,8 @@@ msgstr "Preferencias DVD-o-matic
  
  #: src/wx/audio_dialog.cc:101
  #, fuzzy, c-format
 -msgid "DVD-o-matic audio - %s"
 -msgstr "Audio DVD-o-matic - %1"
 +msgid "DCP-o-matic audio - %s"
 +msgstr "Audio DCP-o-matic - %1"
  
  #: src/wx/config_dialog.cc:120
  msgid "Default DCI name details"
diff --combined src/wx/wscript
index d915f5899a5065ff8e66cff0ff040fabe11b606e,1c5e3b8cc33e1b22c9895704bde484697e6ed354..992f31175eb0e27cfb36ed1209f7c411d53567a8
@@@ -4,8 -4,8 +4,9 @@@ from waflib import Log
  import i18n
  
  sources = """
+           about_dialog.cc
            audio_dialog.cc
 +          audio_mapping_view.cc
            audio_plot.cc
            config_dialog.cc
            dci_metadata_dialog.cc
            filter_dialog.cc
            filter_view.cc
            gain_calculator_dialog.cc
 +          imagemagick_content_dialog.cc
            job_manager_view.cc
            job_wrapper.cc
            new_film_dialog.cc
            properties_dialog.cc
            server_dialog.cc
 +          timecode.cc
 +          timeline.cc
 +          timeline_dialog.cc
            wx_util.cc
            wx_ui_signaller.cc
            """
@@@ -37,18 -33,18 +38,18 @@@ def build(bld)
      else:
          obj = bld(features = 'cxx cxxshlib')
  
 -    obj.name   = 'libdvdomatic-wx'
 +    obj.name   = 'libdcpomatic-wx'
      obj.includes = [ '..' ]
      obj.export_includes = ['.']
      obj.uselib = 'WXWIDGETS'
 -    obj.use = 'libdvdomatic'
 +    obj.use = 'libdcpomatic'
      obj.source = sources
 -    obj.target = 'dvdomatic-wx'
 +    obj.target = 'dcpomatic-wx'
  
 -    i18n.po_to_mo(os.path.join('src', 'wx'), 'libdvdomatic-wx', bld)
 +    i18n.po_to_mo(os.path.join('src', 'wx'), 'libdcpomatic-wx', bld)
  
  def pot(bld):
 -    i18n.pot(os.path.join('src', 'wx'), sources, 'libdvdomatic-wx')
 +    i18n.pot(os.path.join('src', 'wx'), sources, 'libdcpomatic-wx')
  
  def pot_merge(bld):
 -    i18n.pot_merge(os.path.join('src', 'wx'), 'libdvdomatic-wx')
 +    i18n.pot_merge(os.path.join('src', 'wx'), 'libdcpomatic-wx')
diff --combined test/make_black_test.cc
index c708709150405abd62027b63b2919c48f5037a77,714621762bbf435e01838c7fabf07cd51fc5d67d..9bec006516d67c6d6ede9174d787e2da53de4cce
@@@ -35,17 -35,23 +35,23 @@@ BOOST_AUTO_TEST_CASE (make_black_test
        pix_fmts.push_back (AV_PIX_FMT_YUV444P10LE);
        pix_fmts.push_back (AV_PIX_FMT_YUV444P10BE);
        pix_fmts.push_back (AV_PIX_FMT_UYVY422);
+       pix_fmts.push_back (AV_PIX_FMT_YUVJ420P);
+       pix_fmts.push_back (AV_PIX_FMT_YUVJ422P);
+       pix_fmts.push_back (AV_PIX_FMT_YUVJ444P);
  
        int N = 0;
        for (list<AVPixelFormat>::const_iterator i = pix_fmts.begin(); i != pix_fmts.end(); ++i) {
                boost::shared_ptr<Image> foo (new SimpleImage (*i, in_size, true));
                foo->make_black ();
 -              boost::shared_ptr<Image> bar = foo->scale_and_convert_to_rgb (out_size, 0, Scaler::from_id ("bicubic"), true);
 +              boost::shared_ptr<Image> bar = foo->scale_and_convert_to_rgb (out_size, Scaler::from_id ("bicubic"), true);
                
                uint8_t* p = bar->data()[0];
                for (int y = 0; y < bar->size().height; ++y) {
                        uint8_t* q = p;
                        for (int x = 0; x < bar->line_size()[0]; ++x) {
+                               if (*q != 0) {
+                                       std::cerr << "x=" << x << ", (x%3)=" << (x%3) << "\n";
+                               }
                                BOOST_CHECK_EQUAL (*q++, 0);
                        }
                        p += bar->stride()[0];
diff --combined test/test.cc
index 89cfa56dfafc39e79df4ef744f3a54a5dfef2fd3,65b1f9056d8ec8cf76bdb32190c278e781681a26..b33c06be4443830691cc80234de91d225c6483d0
@@@ -22,8 -22,7 +22,8 @@@
  #include <boost/filesystem.hpp>
  #include <boost/algorithm/string/predicate.hpp>
  #include <boost/date_time.hpp>
 -#include "format.h"
 +#include <libdcp/dcp.h>
 +#include "ratio.h"
  #include "film.h"
  #include "filter.h"
  #include "job_manager.h"
  #include "scaler.h"
  #include "ffmpeg_decoder.h"
  #include "sndfile_decoder.h"
 -#include "trimmer.h"
 +#include "dcp_content_type.h"
  #define BOOST_TEST_DYN_LINK
 -#define BOOST_TEST_MODULE dvdomatic_test
 +#define BOOST_TEST_MODULE dcpomatic_test
  #include <boost/test/unit_test.hpp>
  
  using std::string;
  using std::list;
  using std::stringstream;
  using std::vector;
 +using std::min;
 +using std::cout;
  using boost::shared_ptr;
  using boost::thread;
  using boost::dynamic_pointer_cast;
@@@ -59,14 -56,14 +59,14 @@@ struct TestConfi
  {
        TestConfig()
        {
 -              dvdomatic_setup();
 +              dcpomatic_setup();
  
                Config::instance()->set_num_local_encoding_threads (1);
                Config::instance()->set_servers (vector<ServerDescription*> ());
                Config::instance()->set_server_port (61920);
                Config::instance()->set_default_dci_metadata (DCIMetadata ());
 -              Config::instance()->set_default_format (static_cast<Format*> (0));
 -              Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
 +              Config::instance()->set_default_container (0);
 +              Config::instance()->set_default_dcp_content_type (0);
        }
  };
  
@@@ -90,77 -87,20 +90,78 @@@ new_test_film (string name
                boost::filesystem::remove_all (p);
        }
        
 -      return shared_ptr<Film> (new Film (p.string(), false));
 +      shared_ptr<Film> f = shared_ptr<Film> (new Film (p.string()));
 +      f->write_metadata ();
 +      return f;
  }
  
 +void
 +check_file (string ref, string check)
 +{
 +      uintmax_t N = boost::filesystem::file_size (ref);
 +      BOOST_CHECK_EQUAL (N, boost::filesystem::file_size(check));
 +      FILE* ref_file = fopen (ref.c_str(), "rb");
 +      BOOST_CHECK (ref_file);
 +      FILE* check_file = fopen (check.c_str(), "rb");
 +      BOOST_CHECK (check_file);
 +      
 +      int const buffer_size = 65536;
 +      uint8_t* ref_buffer = new uint8_t[buffer_size];
 +      uint8_t* check_buffer = new uint8_t[buffer_size];
 +
 +      while (N) {
 +              uintmax_t this_time = min (uintmax_t (buffer_size), N);
 +              size_t r = fread (ref_buffer, 1, this_time, ref_file);
 +              BOOST_CHECK_EQUAL (r, this_time);
 +              r = fread (check_buffer, 1, this_time, check_file);
 +              BOOST_CHECK_EQUAL (r, this_time);
 +
 +              BOOST_CHECK_EQUAL (memcmp (ref_buffer, check_buffer, this_time), 0);
 +              N -= this_time;
 +      }
 +
 +      delete[] ref_buffer;
 +      delete[] check_buffer;
 +
 +      fclose (ref_file);
 +      fclose (check_file);
 +}
 +
 +static void
 +note (libdcp::NoteType, string n)
 +{
 +      cout << n << "\n";
 +}
 +
 +void
 +check_dcp (string ref, string check)
 +{
 +      libdcp::DCP ref_dcp (ref);
 +      ref_dcp.read ();
 +      libdcp::DCP check_dcp (check);
 +      check_dcp.read ();
 +
 +      libdcp::EqualityOptions options;
 +      options.max_mean_pixel_error = 5;
 +      options.max_std_dev_pixel_error = 5;
 +      options.max_audio_sample_error = 255;
 +      options.cpl_names_can_differ = true;
 +      options.mxf_names_can_differ = true;
 +      
 +      BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2)));
 +}
 +
 +#include "black_fill_test.cc"
 +#include "scaling_test.cc"
 +#include "ratio_test.cc"
  #include "pixel_formats_test.cc"
  #include "make_black_test.cc"
 -#include "trimmer_test.cc"
  #include "film_metadata_test.cc"
  #include "stream_test.cc"
 -#include "format_test.cc"
  #include "util_test.cc"
 -#include "film_test.cc"
 -#include "dcp_test.cc"
 +#include "ffmpeg_dcp_test.cc"
  #include "frame_rate_test.cc"
  #include "job_test.cc"
  #include "client_server_test.cc"
  #include "image_test.cc"
diff --combined wscript
index 21599e171fe689b1d1d71d040f360f6ca124edfc,5bf0f1cde79ae680b60a8161d1c46d6413d91768..b1d7eafe2a155fa134fe2298db66060f52ba412c
+++ b/wscript
@@@ -2,8 -2,8 +2,8 @@@ import subproces
  import os
  import sys
  
 -APPNAME = 'dvdomatic'
 -VERSION = '0.100pre'
 +APPNAME = 'dcpomatic'
 +VERSION = '1.00pre'
  
  def options(opt):
      opt.load('compiler_cxx')
      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.TARGET_WINDOWS = conf.options.target_windows
+     conf.env.DISABLE_GUI = conf.options.disable_gui
+     conf.env.STATIC = conf.options.static
+     conf.env.VERSION = VERSION
+     conf.env.TARGET_OSX = sys.platform == 'darwin'
+     conf.env.TARGET_LINUX = not conf.env.TARGET_WINDOWS and not conf.env.TARGET_OSX
 +    conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS', '-D__STDC_LIMIT_MACROS', '-msse', '-mfpmath=sse', '-ffast-math', '-fno-strict-aliasing',
                                         '-Wall', '-Wno-attributes', '-Wextra'])
  
-     if conf.options.target_windows:
+     if conf.env.TARGET_WINDOWS:
 -        conf.env.append_value('CXXFLAGS', ['-DDVDOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE'])
 +        conf.env.append_value('CXXFLAGS', ['-DDCPOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE'])
          wxrc = os.popen('wx-config --rescomp').read().split()[1:]
          conf.env.append_value('WINRCFLAGS', wxrc)
          if conf.options.enable_debug:
          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')
-         # libxml2 seems to be linked against this on Ubuntu, but it doesn't mention it in its .pc file
+     if conf.env.TARGET_LINUX:
+         # libxml2 seems to be linked against this on Ubuntu but it doesn't mention it in its .pc file
          conf.env.append_value('LIB', 'lzma')
 -        conf.env.append_value('CXXFLAGS', '-DDVDOMATIC_LINUX')
++        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_LINUX')
  
-     conf.env.TARGET_WINDOWS = conf.options.target_windows
-     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.env.TARGET_OSX:
 -        conf.env.append_value('CXXFLAGS', '-DDVDOMATIC_OSX')
++        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_OSX')
  
      if conf.options.enable_debug:
 -        conf.env.append_value('CXXFLAGS', ['-g', '-DDVDOMATIC_DEBUG'])
 +        conf.env.append_value('CXXFLAGS', ['-g', '-DDCPOMATIC_DEBUG'])
      else:
          conf.env.append_value('CXXFLAGS', '-O2')
  
      if not conf.options.static:
          conf.check_cfg(package = 'libdcp', atleast_version = '0.52', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
 +        conf.check_cfg(package = 'libcxml', atleast_version = '0.01', args = '--cflags --libs', uselib_store = 'CXML', mandatory = True)
          conf.check_cfg(package = 'libavformat', args = '--cflags --libs', uselib_store = 'AVFORMAT', mandatory = True)
          conf.check_cfg(package = 'libavfilter', args = '--cflags --libs', uselib_store = 'AVFILTER', mandatory = True)
          conf.check_cfg(package = 'libavcodec', args = '--cflags --libs', uselib_store = 'AVCODEC', mandatory = True)
          conf.check_cfg(package = 'libavutil', args = '--cflags --libs', uselib_store = 'AVUTIL', mandatory = True)
          conf.check_cfg(package = 'libswscale', args = '--cflags --libs', uselib_store = 'SWSCALE', mandatory = True)
 -        conf.check_cfg(package = 'libswresample', args = '--cflags --libs', uselib_store = 'SWRESAMPLE', mandatory = False)
 +        conf.check_cfg(package = 'libswresample', args = '--cflags --libs', uselib_store = 'SWRESAMPLE', mandatory = True)
          conf.check_cfg(package = 'libpostproc', args = '--cflags --libs', uselib_store = 'POSTPROC', mandatory = True)
      else:
          # This is hackio grotesquio for static builds (ie for .deb packages).  We need to link some things
@@@ -79,9 -83,6 +84,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)
-     if conf.options.target_windows is False:
-         conf.check_cfg(package = 'liblzma', args = '--cflags --libs', uselib_store = 'LZMA', mandatory = True)
+     if conf.env.TARGET_LINUX:
+         conf.check_cfg(package='liblzma', args='--cflags --libs', uselib_store='LZMA', mandatory=True)
      conf.check_cfg(package = '', path = conf.options.magickpp_config, args = '--cppflags --cxxflags --libs', uselib_store = 'MAGICK', mandatory = True)
  
      if conf.options.static:
@@@ -202,10 -209,10 +209,10 @@@ def build(bld)
          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)
  
@@@ -227,8 -234,8 +234,8 @@@ def create_version_cc(version)
  
      try:
          text =  '#include "version.h"\n'
 -        text += 'char const * dvdomatic_git_commit = \"%s\";\n' % commit
 -        text += 'char const * dvdomatic_version = \"%s\";\n' % version
 +        text += 'char const * dcpomatic_git_commit = \"%s\";\n' % commit
 +        text += 'char const * dcpomatic_version = \"%s\";\n' % version
          print('Writing version information to src/lib/version.cc')
          o = open('src/lib/version.cc', 'w')
          o.write(text)