Merge master.
authorCarl Hetherington <cth@carlh.net>
Tue, 9 Jul 2013 19:35:39 +0000 (20:35 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 9 Jul 2013 19:35:39 +0000 (20:35 +0100)
282 files changed:
.gitignore
Doxyfile
README
branch-notes [new file with mode: 0644]
cscript
debian/changelog
debian/copyright
debian/files
debian/rules
doc/design/content.tex [new file with mode: 0644]
doc/design/timing.tex [new file with mode: 0644]
doc/mainpage.txt
doc/manual/Makefile
doc/manual/dcpomatic-html.xsl [new file with mode: 0644]
doc/manual/dcpomatic-pdf.xml [new file with mode: 0644]
doc/manual/dcpomatic.css [new file with mode: 0644]
doc/manual/dcpomatic.sty [new file with mode: 0644]
doc/manual/dcpomatic.xml [new file with mode: 0644]
doc/manual/dvdomatic-html.xsl [deleted file]
doc/manual/dvdomatic-pdf.xsl [deleted file]
doc/manual/dvdomatic.css [deleted file]
doc/manual/dvdomatic.sty [deleted file]
doc/manual/dvdomatic.xml [deleted file]
hacks/python-playback/config.py [deleted file]
hacks/python-playback/dvdomatic [deleted file]
hacks/python-playback/film.py [deleted file]
hacks/python-playback/film_view.py [deleted file]
hacks/python-playback/player.py [deleted file]
hacks/python-playback/ratio.py [deleted file]
hacks/python-playback/screens [deleted file]
hacks/python-playback/screens.py [deleted file]
hacks/python-playback/thumbs.py [deleted file]
hacks/python-playback/util.py [deleted file]
hacks/python-playback/xrandr-notes [deleted file]
icons/128x128/dcpomatic.png [new file with mode: 0644]
icons/128x128/dvdomatic.png [deleted file]
icons/16x16/dcpomatic.png [new file with mode: 0644]
icons/16x16/dvdomatic.png [deleted file]
icons/22x22/dcpomatic.png [new file with mode: 0644]
icons/22x22/dvdomatic.png [deleted file]
icons/32x32/dcpomatic.png [new file with mode: 0644]
icons/32x32/dvdomatic.png [deleted file]
icons/48x48/dcpomatic.png [new file with mode: 0644]
icons/48x48/dvdomatic.png [deleted file]
icons/64x64/dcpomatic.png [new file with mode: 0644]
icons/64x64/dvdomatic.png [deleted file]
icons/make_icns.sh
platform/linux/control-12.04-32
platform/linux/control-12.04-64
platform/linux/control-12.10-32
platform/linux/control-12.10-64
platform/linux/dcpomatic.desktop.in [new file with mode: 0644]
platform/linux/dcpomatic_batch.desktop.in [new file with mode: 0644]
platform/linux/dcpomatic_server.desktop.in [new file with mode: 0644]
platform/linux/dvdomatic.desktop.in [deleted file]
platform/linux/dvdomatic_batch.desktop.in [deleted file]
platform/linux/servomatic.desktop.in [deleted file]
platform/linux/wscript
platform/osx/Info.plist.in
platform/osx/make_dmg.sh
platform/windows/dcpomatic.bmp [new file with mode: 0644]
platform/windows/dcpomatic.ico [new file with mode: 0644]
platform/windows/dcpomatic.rc [new file with mode: 0644]
platform/windows/dcpomatic_taskbar.ico [new file with mode: 0644]
platform/windows/dvdomatic.bmp [deleted file]
platform/windows/dvdomatic.ico [deleted file]
platform/windows/dvdomatic.rc [deleted file]
platform/windows/dvdomatic_taskbar.ico [deleted file]
platform/windows/installer.nsi.32.in
platform/windows/installer.nsi.64.in
run/dcpomatic [new file with mode: 0755]
run/dcpomatic.bat [new file with mode: 0644]
run/dcpomatic_cli [new file with mode: 0755]
run/dvdomatic [deleted file]
run/dvdomatic_batch [deleted file]
run/makedcp [deleted file]
src/lib/ab_transcode_job.cc [deleted file]
src/lib/ab_transcode_job.h [deleted file]
src/lib/ab_transcoder.cc [deleted file]
src/lib/ab_transcoder.h [deleted file]
src/lib/analyse_audio_job.cc
src/lib/analyse_audio_job.h
src/lib/audio_analysis.cc
src/lib/audio_analysis.h
src/lib/audio_buffers.cc [new file with mode: 0644]
src/lib/audio_buffers.h [new file with mode: 0644]
src/lib/audio_content.cc [new file with mode: 0644]
src/lib/audio_content.h [new file with mode: 0644]
src/lib/audio_decoder.cc
src/lib/audio_decoder.h
src/lib/audio_mapping.cc [new file with mode: 0644]
src/lib/audio_mapping.h [new file with mode: 0644]
src/lib/audio_sink.h [deleted file]
src/lib/audio_source.cc [deleted file]
src/lib/audio_source.h [deleted file]
src/lib/combiner.h [deleted file]
src/lib/config.cc
src/lib/config.h
src/lib/content.cc [new file with mode: 0644]
src/lib/content.h [new file with mode: 0644]
src/lib/cross.cc
src/lib/cross.h
src/lib/dci_metadata.cc
src/lib/dci_metadata.h
src/lib/dcp_content_type.h
src/lib/dcp_video_frame.cc
src/lib/dcp_video_frame.h
src/lib/decoder.cc
src/lib/decoder.h
src/lib/decoder_factory.cc [deleted file]
src/lib/decoder_factory.h [deleted file]
src/lib/delay_line.cc [deleted file]
src/lib/delay_line.h [deleted file]
src/lib/encoder.cc
src/lib/encoder.h
src/lib/examine_content_job.cc
src/lib/examine_content_job.h
src/lib/exceptions.h
src/lib/ffmpeg.cc [new file with mode: 0644]
src/lib/ffmpeg.h [new file with mode: 0644]
src/lib/ffmpeg_content.cc [new file with mode: 0644]
src/lib/ffmpeg_content.h [new file with mode: 0644]
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_decoder.h
src/lib/ffmpeg_examiner.cc [new file with mode: 0644]
src/lib/ffmpeg_examiner.h [new file with mode: 0644]
src/lib/film.cc
src/lib/film.h
src/lib/filter.h
src/lib/filter_graph.cc
src/lib/filter_graph.h
src/lib/format.cc [deleted file]
src/lib/format.h [deleted file]
src/lib/gain.cc [deleted file]
src/lib/gain.h [deleted file]
src/lib/i18n.h
src/lib/image.cc
src/lib/image.h
src/lib/imagemagick.h [new file with mode: 0644]
src/lib/imagemagick_content.cc [new file with mode: 0644]
src/lib/imagemagick_content.h [new file with mode: 0644]
src/lib/imagemagick_decoder.cc
src/lib/imagemagick_decoder.h
src/lib/imagemagick_examiner.cc [new file with mode: 0644]
src/lib/imagemagick_examiner.h [new file with mode: 0644]
src/lib/job.cc
src/lib/job.h
src/lib/job_manager.cc
src/lib/log.h
src/lib/matcher.cc [deleted file]
src/lib/matcher.h [deleted file]
src/lib/options.h [deleted file]
src/lib/player.cc [new file with mode: 0644]
src/lib/player.h [new file with mode: 0644]
src/lib/playlist.cc [new file with mode: 0644]
src/lib/playlist.h [new file with mode: 0644]
src/lib/po/es_ES.po
src/lib/po/fr_FR.po
src/lib/po/it_IT.po
src/lib/po/sv_SE.po
src/lib/processor.h [deleted file]
src/lib/ratio.cc [new file with mode: 0644]
src/lib/ratio.h [new file with mode: 0644]
src/lib/resampler.cc [new file with mode: 0644]
src/lib/resampler.h [new file with mode: 0644]
src/lib/scaler.h
src/lib/scp_dcp_job.cc
src/lib/scp_dcp_job.h
src/lib/server.cc
src/lib/server.h
src/lib/sndfile_content.cc [new file with mode: 0644]
src/lib/sndfile_content.h [new file with mode: 0644]
src/lib/sndfile_decoder.cc
src/lib/sndfile_decoder.h
src/lib/sound_processor.h
src/lib/stack.cpp
src/lib/stack.hpp
src/lib/stream.cc [deleted file]
src/lib/stream.h [deleted file]
src/lib/subtitle.cc
src/lib/subtitle.h
src/lib/timer.h
src/lib/transcode_job.cc
src/lib/transcode_job.h
src/lib/transcoder.cc
src/lib/transcoder.h
src/lib/trimmer.h [deleted file]
src/lib/types.cc [new file with mode: 0644]
src/lib/types.h [new file with mode: 0644]
src/lib/ui_signaller.h
src/lib/util.cc
src/lib/util.h
src/lib/version.h
src/lib/video_content.cc [new file with mode: 0644]
src/lib/video_content.h [new file with mode: 0644]
src/lib/video_decoder.cc
src/lib/video_decoder.h
src/lib/video_examiner.h [new file with mode: 0644]
src/lib/video_sink.h [deleted file]
src/lib/video_source.cc [deleted file]
src/lib/video_source.h [deleted file]
src/lib/writer.cc
src/lib/writer.h
src/lib/wscript
src/tools/dcpomatic.cc [new file with mode: 0644]
src/tools/dcpomatic_batch.cc [new file with mode: 0644]
src/tools/dcpomatic_cli.cc [new file with mode: 0644]
src/tools/dcpomatic_server.cc [new file with mode: 0644]
src/tools/dcpomatic_server_cli.cc [new file with mode: 0644]
src/tools/dvdomatic.cc [deleted file]
src/tools/dvdomatic_batch.cc [deleted file]
src/tools/makedcp.cc [deleted file]
src/tools/po/es_ES.po
src/tools/po/fr_FR.po
src/tools/po/sv_SE.po
src/tools/servomatic_cli.cc [deleted file]
src/tools/servomatic_gui.cc [deleted file]
src/tools/servomatictest.cc
src/tools/wscript
src/wx/about_dialog.cc
src/wx/audio_dialog.cc
src/wx/audio_dialog.h
src/wx/audio_mapping_view.cc [new file with mode: 0644]
src/wx/audio_mapping_view.h [new file with mode: 0644]
src/wx/audio_plot.cc
src/wx/config_dialog.cc
src/wx/config_dialog.h
src/wx/dci_metadata_dialog.cc
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/film_viewer.cc
src/wx/film_viewer.h
src/wx/gain_calculator_dialog.cc
src/wx/imagemagick_content_dialog.cc [new file with mode: 0644]
src/wx/imagemagick_content_dialog.h [new file with mode: 0644]
src/wx/job_manager_view.cc
src/wx/job_manager_view.h
src/wx/new_film_dialog.cc
src/wx/new_film_dialog.h
src/wx/po/es_ES.po
src/wx/po/fr_FR.po
src/wx/po/it_IT.po
src/wx/po/sv_SE.po
src/wx/properties_dialog.cc
src/wx/server_dialog.cc
src/wx/timecode.cc [new file with mode: 0644]
src/wx/timecode.h [new file with mode: 0644]
src/wx/timeline.cc [new file with mode: 0644]
src/wx/timeline.h [new file with mode: 0644]
src/wx/timeline_dialog.cc [new file with mode: 0644]
src/wx/timeline_dialog.h [new file with mode: 0644]
src/wx/wscript
src/wx/wx_util.cc
src/wx/wx_util.h
test/black_fill_test.cc [new file with mode: 0644]
test/client_server_test.cc
test/dcp_test.cc [deleted file]
test/ffmpeg_dcp_test.cc [new file with mode: 0644]
test/ffmpeg_examiner_test.cc [new file with mode: 0644]
test/ffmpeg_pts_offset.cc [new file with mode: 0644]
test/film_metadata_test.cc
test/film_test.cc [deleted file]
test/format_test.cc [deleted file]
test/frame_rate_test.cc
test/image_test.cc
test/job_test.cc
test/make_black_test.cc
test/md5.test [deleted file]
test/metadata.ref [deleted file]
test/pixel_formats_test.cc
test/ratio_test.cc [new file with mode: 0644]
test/scaling_test.cc [new file with mode: 0644]
test/stream_test.cc
test/test.cc
test/test.mp4 [deleted file]
test/util_test.cc
test/wscript
windows/dcpomatic.bmp [new file with mode: 0644]
windows/dcpomatic.ico [new file with mode: 0644]
windows/dcpomatic.rc [new file with mode: 0644]
windows/dcpomatic_taskbar.ico [new file with mode: 0644]
wscript

index cc3351558baabbdbcfe257501c77ad3c333056cf..70738a18f475189d9df36642a39ba509060e226c 100644 (file)
@@ -15,6 +15,9 @@ sync
 doc/manual/html
 doc/manual/pdf
 doc/manual/extensions.ent
+doc/design/*.pdf
+doc/design/*.log
+doc/design/*.aux
 .be/id-cache
 *.pyc
 GPATH
index 56f7e1d3c334c7a085aa50517b638f1ee5aad790..4ed65e4f14c5bb940b5eb4bfa894339dcbab1908 100644 (file)
--- a/Doxyfile
+++ b/Doxyfile
@@ -26,7 +26,7 @@ DOXYFILE_ENCODING      = UTF-8
 # identify the project. Note that if you do not use Doxywizard you need
 # to put quotes around the project name if it contains spaces.
 
-PROJECT_NAME           = DVD-o-matic
+PROJECT_NAME           = DCP-o-matic
 
 # The PROJECT_NUMBER tag can be used to enter a project or revision number.
 # This could be handy for archiving the generated documentation or
diff --git a/README b/README
index fd3983c29e04f02d36f219aeb61f151eedeee0f3..c218ed1a52521ff71fdb357040f4ec1fd04a8a1b 100644 (file)
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-dvd-o-matic
+DCP-o-matic
 -----------
 
 Hello!
@@ -33,27 +33,22 @@ You will need these libraries:
     libsndfile
     libssh
 
-and also the command line tool:
-
-    vobcopy (if you want to rip DVDs straight into DVD-o-matic)
-
 
 Documentation
 -------------
 
-There is a manual available at http://carlh.net/software/dvdomatic
+There is a manual available at http://carlh.net/software/dcpomatic
 The DocBook source for this is in doc/manual.
 
 
 In a nutshell
 -------------
 
-The `dvdomatic' program is a GTK front-end which is probably easiest
+The `dcpomatic' program is a GTK front-end which is probably easiest
 to use.  It will create a directory for a particular project, and write
 its data to that directory.  The basic approach is:
 
 "File->New"; specify a directory.
-Choose "Jobs->Copy from DVD" to read a DVD from your drive, if you have one.
 Fill in the fields in the window (most importantly the `content' field:
   specify your video, and the `Name' field: give your project [and hence DCP]
   a name.)
@@ -76,7 +71,7 @@ Server/client
 -------------
 
 Running the `servomatic' program on a remote machine will make it
-listen on port 6192 (by default) and process requests from a dvdomatic
+listen on port 6192 (by default) and process requests from a dcpomatic
 instance.  This has been written with no thought to security, so don't
 do it over the public internet!  The connection will probably need to
 be 1 Gb/s to make it worthwhile.
diff --git a/branch-notes b/branch-notes
new file mode 100644 (file)
index 0000000..f713f5d
--- /dev/null
@@ -0,0 +1,7 @@
+things to put back
+       frame rate description  
+       trust content header?
+       overall length?
+       trim method (trim in general)
+       A/B
+
diff --git a/cscript b/cscript
index 6a9b48a891368493a33371dc326c0babdbed6320..e7ef219d4b24f233413010d2e4462b361043ebf8 100644 (file)
--- a/cscript
+++ b/cscript
@@ -42,14 +42,14 @@ def package(target, version):
         shutil.copyfile('platform/linux/control-%s-%d' % (target.version, target.bits), 'debian/control')
         target.command('./waf dist')
         f = open('debian/files', 'w')
-        print >>f,'dvdomatic_%s-1_%s.deb video extra' % (version, cpu)
+        print >>f,'dcpomatic_%s-1_%s.deb video extra' % (version, cpu)
         shutil.rmtree('build/deb', ignore_errors=True)
 
         os.makedirs('build/deb')
         os.chdir('build/deb')
-        shutil.move('../../dvdomatic-%s.tar.bz2' % version, 'dvdomatic_%s.orig.tar.bz2' % version)
-        target.command('tar xjf dvdomatic_%s.orig.tar.bz2' % version)
-        os.chdir('dvdomatic-%s' % version)
+        shutil.move('../../dcpomatic-%s.tar.bz2' % version, 'dcpomatic_%s.orig.tar.bz2' % version)
+        target.command('tar xjf dcpomatic_%s.orig.tar.bz2' % version)
+        os.chdir('dcpomatic-%s' % version)
         target.command('dch -b -v %s-1 "New upstream release."' % version)
         target.set('CDIST_LINKFLAGS', target.get('LINKFLAGS'))
         target.set('CDIST_CXXFLAGS', target.get('CXXFLAGS'))
@@ -67,9 +67,9 @@ def package(target, version):
 
 def make_pot(target):
     target.command('./waf pot')
-    return [os.path.abspath('build/src/lib/libdvdomatic.pot'),
-            os.path.abspath('build/src/wx/libdvdomatic-wx.pot'),
-           os.path.abspath('build/src/tools/dvdomatic.pot')]
+    return [os.path.abspath('build/src/lib/libdcpomatic.pot'),
+            os.path.abspath('build/src/wx/libdcpomatic-wx.pot'),
+           os.path.abspath('build/src/tools/dcpomatic.pot')]
 
 def make_manual(target):
     os.chdir('doc/manual')
index d07a26ad89b12e947688877e04e2792cb69c861d..1cb0a7d31e62066b7f927109de57a775a3c91e6d 100644 (file)
-dvdomatic (0.108-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Mon, 08 Jul 2013 23:53:25 +0100
-
-dvdomatic (0.107-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Mon, 08 Jul 2013 23:35:11 +0100
-
-dvdomatic (0.107beta1-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Thu, 04 Jul 2013 00:28:04 +0100
-
-dvdomatic (0.106-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Mon, 01 Jul 2013 23:42:59 +0100
-
-dvdomatic (0.106beta1-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Mon, 01 Jul 2013 16:18:29 +0100
-
-dvdomatic (0.105-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Fri, 28 Jun 2013 22:01:06 +0100
-
-dvdomatic (0.104-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Thu, 27 Jun 2013 01:20:18 +0100
-
-dvdomatic (0.103-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Mon, 24 Jun 2013 23:39:57 +0100
-
-dvdomatic (0.102-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Thu, 20 Jun 2013 19:07:07 +0100
-
-dvdomatic (0.101-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Wed, 19 Jun 2013 18:55:41 +0100
-
-dvdomatic (0.101beta5-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Wed, 19 Jun 2013 16:56:16 +0100
-
-dvdomatic (0.101beta4-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Wed, 19 Jun 2013 10:21:55 +0100
-
-dvdomatic (0.101beta3-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Wed, 19 Jun 2013 10:11:26 +0100
-
-dvdomatic (0.101beta2-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Wed, 19 Jun 2013 10:03:36 +0100
-
-dvdomatic (0.101beta1-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Wed, 19 Jun 2013 10:00:20 +0100
-
-dvdomatic (0.100-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Fri, 14 Jun 2013 00:10:28 +0100
-
-dvdomatic (0.99-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Thu, 13 Jun 2013 21:29:35 +0100
-
-dvdomatic (0.98-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Sun, 09 Jun 2013 20:54:08 +0100
-
-dvdomatic (0.97-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Fri, 07 Jun 2013 16:28:13 +0100
-
-dvdomatic (0.96-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Fri, 07 Jun 2013 10:41:57 +0100
-
-dvdomatic (0.95-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Wed, 05 Jun 2013 14:29:24 +0100
-
-dvdomatic (0.94-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Wed, 05 Jun 2013 07:48:39 +0100
-
-dvdomatic (0.94beta2-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Tue, 04 Jun 2013 22:59:39 +0100
-
-dvdomatic (0.94beta1-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Fri, 31 May 2013 00:59:14 +0100
-
-dvdomatic (0.93-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Mon, 20 May 2013 13:32:29 +0100
-
-dvdomatic (0.92-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Sun, 19 May 2013 12:35:52 +0100
-
-dvdomatic (0.91-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Sun, 19 May 2013 00:11:52 +0100
-
-dvdomatic (0.90-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Fri, 17 May 2013 16:29:58 +0100
-
-dvdomatic (0.89-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Mon, 06 May 2013 02:03:25 +0100
-
-dvdomatic (0.89beta1-1) UNRELEASED; urgency=low
-
-  * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan>  Sat, 04 May 2013 21:15:34 +0100
-
-dvdomatic (0.88-1) UNRELEASED; urgency=low
+dcpomatic (0.88-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 28 Apr 2013 16:28:17 +0100
 
-dvdomatic (0.87-1) UNRELEASED; urgency=low
+dcpomatic (0.87-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Fri, 26 Apr 2013 09:53:27 +0100
 
-dvdomatic (0.86-1) UNRELEASED; urgency=low
+dcpomatic (0.86-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 23 Apr 2013 08:13:13 +0100
 
-dvdomatic (0.85-1) UNRELEASED; urgency=low
+dcpomatic (0.85-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 23 Apr 2013 00:08:20 +0100
 
-dvdomatic (0.84-1) UNRELEASED; urgency=low
+dcpomatic (0.84-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 21 Apr 2013 17:49:54 +0100
 
-dvdomatic (0.84beta5-1) UNRELEASED; urgency=low
+dcpomatic (0.84beta5-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 21 Apr 2013 00:06:12 +0100
 
-dvdomatic (0.84beta4-1) UNRELEASED; urgency=low
+dcpomatic (0.84beta4-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Fri, 19 Apr 2013 17:41:58 +0100
 
-dvdomatic (0.84beta3-1) UNRELEASED; urgency=low
+dcpomatic (0.84beta3-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Fri, 19 Apr 2013 11:36:37 +0100
 
-dvdomatic (0.84beta2-1) UNRELEASED; urgency=low
+dcpomatic (0.84beta2-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Fri, 19 Apr 2013 11:12:09 +0100
 
-dvdomatic (0.84beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.84beta1-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 18 Apr 2013 23:32:17 +0100
 
-dvdomatic (0.83-1) UNRELEASED; urgency=low
+dcpomatic (0.83-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Wed, 10 Apr 2013 12:48:25 +0100
 
-dvdomatic (0.82-1) UNRELEASED; urgency=low
+dcpomatic (0.82-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 09 Apr 2013 23:43:35 +0100
 
-dvdomatic (0.82beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.82beta1-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 09 Apr 2013 21:48:56 +0100
 
-dvdomatic (0.81-1) UNRELEASED; urgency=low
+dcpomatic (0.81-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 09 Apr 2013 19:48:04 +0100
 
-dvdomatic (0.81beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.81beta1-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 09 Apr 2013 15:37:32 +0100
 
-dvdomatic (0.80-1) UNRELEASED; urgency=low
+dcpomatic (0.80-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 07 Apr 2013 23:48:12 +0100
 
-dvdomatic (0.80beta4-1) UNRELEASED; urgency=low
+dcpomatic (0.80beta4-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 07 Apr 2013 23:08:49 +0100
 
-dvdomatic (0.80beta3-1) UNRELEASED; urgency=low
+dcpomatic (0.80beta3-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 07 Apr 2013 22:44:29 +0100
 
-dvdomatic (0.80beta2-1) UNRELEASED; urgency=low
+dcpomatic (0.80beta2-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 07 Apr 2013 22:19:34 +0100
 
-dvdomatic (0.80beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.80beta1-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 07 Apr 2013 18:21:33 +0100
 
-dvdomatic (0.79-1) UNRELEASED; urgency=low
+dcpomatic (0.79-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Mon, 01 Apr 2013 22:37:03 +0100
 
-dvdomatic (0.78-1) UNRELEASED; urgency=low
+dcpomatic (0.78-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 31 Mar 2013 02:43:03 +0100
 
-dvdomatic (0.78beta16-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta16-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 28 Mar 2013 16:28:05 +0000
 
-dvdomatic (0.78beta15-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta15-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 28 Mar 2013 14:25:56 +0000
 
-dvdomatic (0.78beta14-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta14-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 28 Mar 2013 10:38:07 +0000
 
-dvdomatic (0.78beta13-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta13-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Wed, 27 Mar 2013 12:26:55 +0000
 
-dvdomatic (0.78beta12-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta12-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 26 Mar 2013 21:13:54 +0000
 
-dvdomatic (0.78beta11-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta11-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 26 Mar 2013 17:34:49 +0000
 
-dvdomatic (0.78beta10-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta10-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 26 Mar 2013 11:35:15 +0000
 
-dvdomatic (0.78beta9-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta9-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 26 Mar 2013 10:36:05 +0000
 
-dvdomatic (0.78beta8-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta8-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 26 Mar 2013 00:59:36 +0000
 
-dvdomatic (0.78beta7-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta7-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 26 Mar 2013 00:19:21 +0000
 
-dvdomatic (0.78beta6-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta6-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Mon, 25 Mar 2013 00:08:10 +0000
 
-dvdomatic (0.78beta5-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta5-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 21 Mar 2013 16:32:21 +0000
 
-dvdomatic (0.78beta4-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta4-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Wed, 20 Mar 2013 15:01:10 +0000
 
-dvdomatic (0.78beta3-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta3-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Wed, 20 Mar 2013 10:49:17 +0000
 
-dvdomatic (0.78beta2-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta2-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 19 Mar 2013 21:35:50 +0000
 
-dvdomatic (0.78beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta1-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 19 Mar 2013 20:50:54 +0000
 
-dvdomatic (0.77-1) UNRELEASED; urgency=low
+dcpomatic (0.77-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 14 Mar 2013 17:12:03 +0000
 
-dvdomatic (0.77beta2-1) UNRELEASED; urgency=low
+dcpomatic (0.77beta2-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 14 Mar 2013 15:50:43 +0000
 
-dvdomatic (0.77beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.77beta1-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 14 Mar 2013 15:14:01 +0000
 
-dvdomatic (0.76-1) UNRELEASED; urgency=low
+dcpomatic (0.76-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 05 Mar 2013 13:30:28 +0000
 
-dvdomatic (0.76beta3-1) UNRELEASED; urgency=low
+dcpomatic (0.76beta3-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 05 Mar 2013 12:47:20 +0000
 
-dvdomatic (0.76beta2-1) UNRELEASED; urgency=low
+dcpomatic (0.76beta2-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Fri, 01 Mar 2013 18:32:16 +0000
 
-dvdomatic (0.76beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.76beta1-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Fri, 01 Mar 2013 17:36:55 +0000
 
-dvdomatic (0.75-1) UNRELEASED; urgency=low
+dcpomatic (0.75-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Wed, 27 Feb 2013 11:03:07 +0000
 
-dvdomatic (0.75beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.75beta1-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Wed, 27 Feb 2013 08:20:42 +0000
 
-dvdomatic (0.74-1) UNRELEASED; urgency=low
+dcpomatic (0.74-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sat, 23 Feb 2013 22:57:20 +0000
 
-dvdomatic (0.74beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.74beta1-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sat, 23 Feb 2013 21:44:22 +0000
 
-dvdomatic (0.73-1) UNRELEASED; urgency=low
+dcpomatic (0.73-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 21 Feb 2013 00:43:40 +0000
 
-dvdomatic (0.73beta9-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta9-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Wed, 20 Feb 2013 23:40:24 +0000
 
-dvdomatic (0.73beta8-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta8-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Mon, 18 Feb 2013 22:35:51 +0000
 
-dvdomatic (0.73beta7-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta7-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Mon, 18 Feb 2013 20:38:51 +0000
 
-dvdomatic (0.73beta6-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta6-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 17 Feb 2013 23:05:56 +0000
 
-dvdomatic (0.73beta3-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta3-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 17 Feb 2013 23:05:05 +0000
 
-dvdomatic (0.73beta2-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta2-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sat, 16 Feb 2013 22:42:32 +0000
 
-dvdomatic (0.73beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta1-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sat, 16 Feb 2013 21:19:24 +0000
 
-dvdomatic (0.72-1) UNRELEASED; urgency=low
+dcpomatic (0.72-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 24 Jan 2013 15:31:57 +0000
 
-dvdomatic (0.71-1) UNRELEASED; urgency=low
+dcpomatic (0.71-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 24 Jan 2013 11:36:04 +0000
 
-dvdomatic (0.70-1) UNRELEASED; urgency=low
+dcpomatic (0.70-1) UNRELEASED; urgency=low
 
   * New upstream release.
   * New upstream release.
@@ -540,7 +360,7 @@ dvdomatic (0.70-1) UNRELEASED; urgency=low
 
  -- Carl Hetherington <cth@carlh.net>  Sat, 12 Jan 2013 23:07:15 +0000
 
-dvdomatic (0.70beta3-1) UNRELEASED; urgency=low
+dcpomatic (0.70beta3-1) UNRELEASED; urgency=low
 
   * New upstream release.
   * New upstream release.
@@ -549,13 +369,13 @@ dvdomatic (0.70beta3-1) UNRELEASED; urgency=low
 
  -- Carl Hetherington <cth@carlh.net>  Sun, 06 Jan 2013 23:44:24 +0000
 
-dvdomatic (0.68-1) UNRELEASED; urgency=low
+dcpomatic (0.68-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 23 Dec 2012 01:43:44 +0000
 
-dvdomatic (0.68beta10-1) UNRELEASED; urgency=low
+dcpomatic (0.68beta10-1) UNRELEASED; urgency=low
 
   * New upstream release.
   * New upstream release.
@@ -565,91 +385,91 @@ dvdomatic (0.68beta10-1) UNRELEASED; urgency=low
 
  -- Carl Hetherington <cth@carlh.net>  Sat, 22 Dec 2012 13:27:27 +0000
 
-dvdomatic (0.68beta5-1) unstable; urgency=low
+dcpomatic (0.68beta5-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 20 Dec 2012 07:53:46 +0000
 
-dvdomatic (0.68beta4-1) unstable; urgency=low
+dcpomatic (0.68beta4-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 20 Dec 2012 07:48:45 +0000
 
-dvdomatic (0.68beta3-1) unstable; urgency=low
+dcpomatic (0.68beta3-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 20 Dec 2012 00:35:45 +0000
 
-dvdomatic (0.68beta2-1) unstable; urgency=low
+dcpomatic (0.68beta2-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Wed, 19 Dec 2012 11:22:58 +0000
 
-dvdomatic (0.68beta1-1) unstable; urgency=low
+dcpomatic (0.68beta1-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Wed, 19 Dec 2012 10:11:13 +0000
 
-dvdomatic (0.67-1) unstable; urgency=low
+dcpomatic (0.67-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 18 Dec 2012 23:49:27 +0000
 
-dvdomatic (0.66-1) unstable; urgency=low
+dcpomatic (0.66-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 18 Dec 2012 11:29:04 +0000
 
-dvdomatic (0.65-1) unstable; urgency=low
+dcpomatic (0.65-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 18 Dec 2012 09:24:56 +0000
 
-dvdomatic (0.64-1) unstable; urgency=low
+dcpomatic (0.64-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Thu, 13 Dec 2012 21:52:09 +0000
 
-dvdomatic (0.63pre-1) unstable; urgency=low
+dcpomatic (0.63pre-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 11 Dec 2012 23:15:52 +0000
 
-dvdomatic (0.60-1) unstable; urgency=low
+dcpomatic (0.60-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Tue, 11 Dec 2012 22:46:04 +0000
 
-dvdomatic (0.59-1) unstable; urgency=low
+dcpomatic (0.59-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Mon, 10 Dec 2012 20:58:19 +0000
 
-dvdomatic (0.59beta5-1) unstable; urgency=low
+dcpomatic (0.59beta5-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 09 Dec 2012 23:51:55 +0000
 
-dvdomatic (0.59beta4-1) unstable; urgency=low
+dcpomatic (0.59beta4-1) unstable; urgency=low
 
   * New upstream release.
 
  -- Carl Hetherington <carl@houllier.lan>  Sun, 09 Dec 2012 21:38:00 +0000
 
-dvdomatic (0.59beta1-1) unstable; urgency=low
+dcpomatic (0.59beta1-1) unstable; urgency=low
 
   * Initial release.
 
index 2579947e42aef1323b810440fa6d2359a7bddb6b..0cf23aacdc653b18050991a21180b5a7aedd2c90 100644 (file)
@@ -1,6 +1,6 @@
 Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: dvdomatic
-Source: <url://carlh.net/software/dvdomatic>
+Upstream-Name: dcpomatic
+Source: <url://carlh.net/software/dcpomatic>
 
 Files: *
 Copyright: 2012 Carl Hetherington <cth@carlh.net>
index 7639f05acb94a9d0812a47f05db181e0b9651e4c..ca46cf438cb3f2b6bf19ac9b8d0adece97e62611 100644 (file)
@@ -1 +1 @@
-dvdomatic_0.59beta1-1_i386.deb video extra
+dcpomatic_0.59beta1-1_i386.deb video extra
index f2b2219beb7c56829e770256c1810a3a68db03a7..29f926c31651a2feb2b76792320f0a4fce70198e 100755 (executable)
@@ -20,8 +20,8 @@ override_dh_auto_build:
        ./waf --nocache build
 
 override_dh_auto_install:
-       ./waf --nocache install --destdir=debian/dvdomatic
+       ./waf --nocache install --destdir=debian/dcpomatic
 
 .PHONY: override_dh_strip
 override_dh_strip:
-       dh_strip --dbg-package=dvdomatic-dbg
+       dh_strip --dbg-package=dcpomatic-dbg
diff --git a/doc/design/content.tex b/doc/design/content.tex
new file mode 100644 (file)
index 0000000..0f5f170
--- /dev/null
@@ -0,0 +1,195 @@
+\documentclass{article}
+\begin{document}
+
+\section{Status quo}
+
+As at 0.78 there is an unfortunate mish-mash of code to handle the
+input `content' the goes into a DCP.
+
+The Film has a `content' file name.  This is guessed to be either a
+movie (for FFmpeg) or a still-image (for ImageMagick) based on its
+extension.  We also have `external audio', which is a set of WAV files
+for libsndfile, and a flag to enable that.
+
+The `content' file is badly named and limiting.  We can't have
+multiple content files, and it's not really the `content' as such (it
+used to be, but increasingly it's only a part of the content, on equal
+footing with `external' audio).
+
+The choice of sources for sound is expressed clumsily by the
+AudioStream class hierarchy.
+
+
+\section{Targets}
+
+We want to be able to implement the following:
+
+\begin{itemize}
+\item Immediately:
+\begin{itemize}
+\item Multiple still images, each with their own duration, made into a `slide-show'
+\item Lack of bugs in adding WAV-file audio to still images.
+\item External subtitle files (either XML or SRT) to be converted to XML subtitles in the DCP.
+\end{itemize}
+
+\item In the future:
+\begin{itemize}
+\item Playlist-style multiple video / audio (perhaps).
+\end{itemize}
+\end{itemize}
+
+
+\section{Content hierarchy}
+
+One idea is to have a hierarchy of Content classes (\texttt{Content},
+\texttt{\{Video/Audio\}Content}, \texttt{FFmpegContent}, \texttt{ImageMagickContent},
+\texttt{SndfileContent}).
+
+Then the Film has a list of these, and decides what to put into the
+DCP based on some rules.  These rules would probably be fixed (for
+now), with the possibility to expand later into some kind of playlist.
+
+
+\section{Immediate questions}
+
+\subsection{What Film attributes are video-content specific, and which are general?}
+
+Questionable attributes:
+
+\begin{itemize}
+\item Trust content header
+\item Crop
+\item Filters
+
+Post-processing (held as part of the filters description) is done in
+the encoder, by which time all knowledge of the source is lost.
+
+\item Scaler
+\item Trim start/end
+
+Messily tied in with the encoding side.  We want to implement this
+using start/end point specifications in the DCP reel, otherwise
+modifying the trim points requires a complete re-encode.
+
+\item Audio gain
+\item Audio delay
+\item With subtitles
+\item Subtitle offset/scale
+\item Colour LUT
+\end{itemize}
+
+Attributes that I think must remain in Film:
+\begin{itemize}
+\item DCP content type
+\item Format
+\item A/B
+\item J2K bandwidth
+\end{itemize}
+
+Part of the consideration here is that per-content attributes need to
+be represented in the GUI differently to how things are represented
+now.
+
+Bear in mind also that, as it stands, the only options for video are:
+
+\begin{enumerate}
+\item An FFmpeg video
+\item A set of stills
+\end{enumerate}
+
+and so the need for multiple scalers, crop and filters is
+questionable.  Also, there is one set of audio (either from WAVs or
+from the FFMpeg file), so per-content audio gain/delay is also
+questionable.  Trust content header is only applicable for FFmpeg
+content, really.  Similarly trim, with-subtitles, subtitle details,
+colour LUT; basically none of it is really important right now.
+
+Hence it may be sensible to keep everything in Film and move it later
+along YAGNI lines.
+
+
+\subsection{Who answers questions like: ``what is the length of video?''?}
+
+If we have FFmpeg video, the question is easy to answer.  For a set of
+stills, it is less easy.  Who knows that we are sticking them all
+together end-to-end, with different durations for each?
+
+If we have one-content-object equalling one file, the content objects
+will presumably know how long their file should be displayed for.
+There would appear to be two options following this:
+
+\begin{enumerate}
+\item There is one \texttt{ImageMagickDecoder} which is fed all the
+  files, and outputs them in order.  The magic knowledge is then
+  within this class, really.
+\item There are multiple \texttt{ImageMagickDecoder} classes, one per
+  \texttt{..Content}, and some controlling (`playlist') class to manage
+  them.  The `playlist' is then itself a
+  \texttt{\{Video/Audio\}Source}, and has the magic knowledge.
+\end{enumerate}
+
+
+\section{Playlist approach}
+
+Let's try the playlist approach.  We define a hierarchy of content classes:
+
+\begin{verbatim}
+
+class Content
+{
+public:
+  boost::filesystem::path file () const;
+};
+
+class VideoContent : virtual public Content
+{
+public:
+  VideoContentFrame video_length () const;
+  float video_frame_rate () const;
+  libdcp::Size size () const;
+
+};
+
+class AudioContent : virtual public Content
+{
+
+};
+
+class FFmpegContent : public VideoContent, public AudioContent
+{
+public:
+  .. stream stuff ..
+};
+
+class ImageMagickContent : public VideoContent
+{
+
+};
+
+class SndfileContent : public AudioContent
+{
+public:
+  .. channel allocation for this file ..
+};
+\end{verbatim}
+
+Then Film has a \texttt{Playlist} which has a
+\texttt{vector<shared\_ptr<Content> >}.  It can answer questions
+about audio/video length, frame rate, audio channels and so on.
+
+\texttt{Playlist} can also be a source of video and audio, so clients can do:
+
+\begin{verbatim}
+shared_ptr<Playlist> p = film->playlist ();
+p->Video.connect (foo);
+p->Audio.connect (foo);
+while (!p->pass ()) {
+  /* carry on */
+}
+\end{verbatim}
+
+Playlist could be created on-demand for all the difference it would
+make.  And perhaps it should, since it will hold Decoders which are
+probably run-once.
+
+\end{document}
diff --git a/doc/design/timing.tex b/doc/design/timing.tex
new file mode 100644 (file)
index 0000000..567ba02
--- /dev/null
@@ -0,0 +1,42 @@
+\documentclass{article}
+\begin{document}
+
+We are trying to implement full-ish playlist based content specification.  The timing is awkward.
+
+\section{Reference timing}
+
+Frame rates of things can vary a lot; content can be in pretty much
+anything, and DCP video and audio frame rates may change on a whim
+depending on what is best for a given set of content.  This suggests
+(albeit without strong justification) the need for a frame-rate-independent unit of time.
+
+So far we've been using a time type called \texttt{Time} expressed in
+$\mathtt{TIME\_HZ}^{-1}$; e.g. \texttt{TIME\_HZ} units is 1 second.
+\texttt{TIME\_HZ} is chosen to be divisible by lots of frame and
+sample rates.
+
+We express content start time as a \texttt{Time}.
+
+
+\section{Timing at different stages of the chain}
+
+Let's try this: decoders produce sequences of (perhaps) video frames
+and (perhaps) audio frames.  There are no gaps.  They are at the
+content's native frame rates and are synchronised (meaning that if
+they are played together, at the content's frame rates, they will be
+in sync).  The decoders give timestamps for each piece of their
+output, which are \emph{simple indices} (\texttt{ContentVideoFrame}
+and \texttt{ContentAudioFrame}).  Decoders know nothing of \texttt{Time}.
+
+
+\section{Split of stuff between decoders and player}
+
+In some ways it seems nice to have decoders which produce the rawest
+possible data and make the player sort it out (e.g.\ cropping and
+scaling video, resampling audio).  The resampling is awkward, though,
+as you really need one resampler per source.  So it might make more sense
+to put stuff in the decoder.  But then, what's one map of resamplers between friends?
+
+
+
+\end{document}
index 59c5788999c1c0d761bd7ae46cf4bd21c17579fc..649c9c60913506e43cb4c4084ce9dc4209edf453 100644 (file)
@@ -1,37 +1,37 @@
-/** @mainpage DVD-o-matic
+/** @mainpage DCP-o-matic
  *
- *  DVD-o-matic is a tool to create digital cinema packages (DCPs) from
+ *  DCP-o-matic is a tool to create digital cinema packages (DCPs) from
  *  video files, or from sets of TIFF image files.  It is written in C++
  *  and distributed under the GPL.
  *
  *  Video files are decoded using FFmpeg (http://ffmpeg.org), so any video
- *  supported by FFmpeg should be usable with DVD-o-matic.  DVD-o-matic's output has been
+ *  supported by FFmpeg should be usable with DCP-o-matic.  DCP-o-matic's output has been
  *  tested on numerous digital projectors.
  *
- *  DVD-o-matic allows you to crop black borders from movies, scale them to the correct
+ *  DCP-o-matic allows you to crop black borders from movies, scale them to the correct
  *  aspect ratio and apply FFmpeg filters.  The time-consuming encoding of JPEG2000 files
  *  can be parallelised amongst any number of processors on the local host and any number
  *  of servers over a network.
  *
- *  DVD-o-matic can also make DCPs from still images, for advertisements and such-like.
+ *  DCP-o-matic can also make DCPs from still images, for advertisements and such-like.
  * 
- *  Parts of DVD-o-matic are based on OpenDCP (http://code.google.com/p/opendcp),
+ *  Parts of DCP-o-matic are based on OpenDCP (http://code.google.com/p/opendcp),
  *  written by Terrence Meiczinger.
  *
- *  DVD-o-matic uses libopenjpeg (http://code.google.com/p/openjpeg/) for JPEG2000 encoding
+ *  DCP-o-matic uses libopenjpeg (http://code.google.com/p/openjpeg/) for JPEG2000 encoding
  *  and libsndfile (http://www.mega-nerd.com/libsndfile/) for WAV file manipulation.  It
  *  also makes heavy use of the boost libraries (http://www.boost.org/).  ImageMagick
  *  (http://www.imagemagick.org/) is used for still-image encoding and decoding, and the GUI is
  *  built using wxWidgets (http://wxwidgets.org/).  It also uses libmhash (http://mhash.sourceforge.net/)
  *  for debugging purposes.
  *
- *  Thanks are due to the authors and communities of all DVD-o-matic's dependencies.
+ *  Thanks are due to the authors and communities of all DCP-o-matic's dependencies.
  * 
- *  DVD-o-matic is distributed in the hope that there are still cinemas with projectionists
+ *  DCP-o-matic is distributed in the hope that there are still cinemas with projectionists
  *  who might want to use it.  As Mark Kermode says, "if it doesn't have a projectionist
  *  it's not a cinema - it's a sweetshop with a video-screen."
  *
  *  Email correspondance is welcome to cth@carlh.net
  *
- *  More details can be found at http://carlh.net/software/dvdomatic
+ *  More details can be found at http://carlh.net/software/dcpomatic
  */
index 94abc8516a672e4c6b4a99a566404a693e87d03c..115d7c3c8c9b6b45efc4b34aff0c3e3e181a635f 100644 (file)
@@ -1,4 +1,4 @@
-# DVD-o-matic manual makefile
+# DCP-o-matic manual makefile
 
 all:   html pdf
 
@@ -8,7 +8,7 @@ SCREENSHOTS := file-new.png video-new-film.png still-new-film.png click-content-
                still-select-content-file.png examine-thumbs.png \
                calculate-audio-gain.png prefs.png making-dcp.png filters.png film-tab.png video-tab.png audio-tab.png subtitles-tab.png
 
-XML := dvdomatic.xml
+XML := dcpomatic.xml
 
 GRAPHICS := 
 
@@ -70,7 +70,7 @@ diagrams/%.pdf:       diagrams/%.svg
 # HTML
 #
 
-html:  $(XML) dvdomatic-html.xsl extensions-html.ent dvdomatic.css \
+html:  $(XML) dcpomatic-html.xsl extensions-html.ent dcpomatic.css \
        $(addprefix html/screenshots/,$(SCREENSHOTS)) \
        $(subst .svg,.png,$(addprefix diagrams/,$(DIAGRAMS))) \
        $(subst .svg,.png,$(addprefix graphics/,$(GRAPHICS))) \
@@ -80,19 +80,19 @@ html:       $(XML) dvdomatic-html.xsl extensions-html.ent dvdomatic.css \
        cp extensions-html.ent extensions.ent
 
 #      DocBoox -> html
-       xmlto html -m dvdomatic-html.xsl dvdomatic.xml --skip-validation -o html
+       xmlto html -m dcpomatic-html.xsl dcpomatic.xml --skip-validation -o html
 
 #      Copy graphics and CSS in
 #      mkdir -p html/diagrams html/graphics
 #      cp diagrams/*.png html/diagrams
 #      cp graphics/*.png html/graphics
-       cp dvdomatic.css html
+       cp dcpomatic.css html
 
 #
 # PDF
 #
 
-pdf:   $(XML) dvdomatic-pdf.xsl extensions-pdf.ent screenshots/*.png $(subst .svg,.pdf,$(addprefix diagrams/,$(DIAGRAMS)))
+pdf:   $(XML) dcpomatic-pdf.xsl extensions-pdf.ent screenshots/*.png $(subst .svg,.pdf,$(addprefix diagrams/,$(DIAGRAMS)))
 
 #      The DocBook needs to know what file extensions to look for
 #      for screenshots and diagrams; use the correct file to tell it.
@@ -100,14 +100,14 @@ pdf:      $(XML) dvdomatic-pdf.xsl extensions-pdf.ent screenshots/*.png $(subst .svg,
 
        mkdir -p pdf
 
-       dblatex -p dvdomatic-pdf.xsl -s dvdomatic.sty -r pptex.py -T native dvdomatic.xml -t pdf -o pdf/dvdomatic.pdf
+       dblatex -p dcpomatic-pdf.xsl -s dcpomatic.sty -r pptex.py -T native dcpomatic.xml -t pdf -o pdf/dcpomatic.pdf
 
 
 #
 # LaTeX (handy for debugging)
 #
 
-tex:   $(XML) dvdomatic-pdf.xsl extensions-pdf.ent
+tex:   $(XML) dcpomatic-pdf.xsl extensions-pdf.ent
 
 #      The DocBook needs to know what file extensions to look for
 #      for screenshots and diagrams; use the correct file to tell it.
@@ -116,8 +116,8 @@ tex:        $(XML) dvdomatic-pdf.xsl extensions-pdf.ent
        mkdir -p tex
 
 #      -P <foo> removes the revhistory table
-       dblatex -P doc.collab.show=0 -P latex.output.revhistory=0 -p dvdomatic-pdf.xsl -s dvdomatic.sty -r pptex.py -T native dvdomatic.xml -t tex -o tex/dvdomatic.tex
+       dblatex -P doc.collab.show=0 -P latex.output.revhistory=0 -p dcpomatic-pdf.xsl -s dcpomatic.sty -r pptex.py -T native dcpomatic.xml -t tex -o tex/dcpomatic.tex
 
 
-clean:;        rm -rf html pdf diagrams/*.pdf diagrams/*.png graphics/*.png *.aux dvdomatic.cb dvdomatic.cb2 dvdomatic.glo dvdomatic.idx dvdomatic.ilg
-       rm -rf dvdomatic.ind dvdomatic.lof dvdomatic.log dvdomatic.tex dvdomatic.toc extensions.ent dvdomatic.out
+clean:;        rm -rf html pdf diagrams/*.pdf diagrams/*.png graphics/*.png *.aux dcpomatic.cb dcpomatic.cb2 dcpomatic.glo dcpomatic.idx dcpomatic.ilg
+       rm -rf dcpomatic.ind dcpomatic.lof dcpomatic.log dcpomatic.tex dcpomatic.toc extensions.ent dcpomatic.out
diff --git a/doc/manual/dcpomatic-html.xsl b/doc/manual/dcpomatic-html.xsl
new file mode 100644 (file)
index 0000000..144675d
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version='1.0'?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:fo="http://www.w3.org/1999/XSL/Format"
+                version="1.0">
+
+<!-- Our CSS -->
+<xsl:param name="html.stylesheet" select="'dcpomatic.css'"/>
+
+<!-- I can't fathom xmlto's logic with image scaling, so I've turned it off -->
+<xsl:param name="ignore.image.scaling" select="1"/>
+
+</xsl:stylesheet>
diff --git a/doc/manual/dcpomatic-pdf.xml b/doc/manual/dcpomatic-pdf.xml
new file mode 100644 (file)
index 0000000..414fb64
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version='1.0' encoding="iso-8859-1"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
+
+<!-- colour links in black -->
+<xsl:param name="latex.hyperparam">colorlinks,linkcolor=black,urlcolor=black</xsl:param>
+
+<!-- no revhistory table -->
+<xsl:param name="doc.collab.show">0</xsl:param>
+<xsl:param name="latex.output.revhistory">0</xsl:param>
+
+<!-- hack images to vaguely the right size -->
+<xsl:param name="imagedata.default.scale">scale=0.6</xsl:param>
+
+<!-- don't make too-ridiculous section numbers -->
+<xsl:param name="doc.section.depth">3</xsl:param>
+
+</xsl:stylesheet>
diff --git a/doc/manual/dcpomatic.css b/doc/manual/dcpomatic.css
new file mode 100644 (file)
index 0000000..0e4982f
--- /dev/null
@@ -0,0 +1,19 @@
+body {
+    font-family: luxi sans, sans-serif;
+    margin-left: 4em;
+    margin-right: 4em;
+    margin-top: 1em;
+    margin-bottom: 1em;
+    background-color: #E2E8EE;
+}
+
+div.sidebar {
+    margin-left: 1em;
+    margin-right: 1em;
+    padding-left: 1em;
+    padding-right: 1em;
+    border-color: #000000;
+    border-width: 2px;
+    border-style: solid;
+    background-color: #E2E8EE;
+}
diff --git a/doc/manual/dcpomatic.sty b/doc/manual/dcpomatic.sty
new file mode 100644 (file)
index 0000000..834e581
--- /dev/null
@@ -0,0 +1,68 @@
+%%
+%% This style is derivated from the docbook one
+%%
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{ardour}[2007/04/04 My DocBook Style]
+
+%% Just use the original package and pass the options
+\RequirePackageWithOptions{docbook}
+
+% Use a nice font
+\usepackage{lmodern}
+
+% Define \dbend as the dangerous bend sign
+\font\manual=manfnt
+\def\dbend{{\manual\char127}}
+
+% Redefine sidebar environment to use the dangerous bend style
+% Danger, Will Robinson!
+\def\sidebar{\begin{trivlist}\item[]\noindent%
+\begingroup\hangindent=2pc\hangafter=-2%\clubpenalty=10000%
+\def\par{\endgraf\endgroup}%
+\hbox to0pt{\hskip-\hangindent\dbend\hfill}\ignorespaces}
+\def\endsidebar{\par\end{trivlist}}
+
+
+% Futz with the title page; basically a copy of
+% /usr/share/texmf/tex/latex/dblatex/style/dbk_title.sty
+% with authors added.
+
+\def\DBKcover{
+\ifthenelse{\equal{\DBKedition}{}}{\def\edhead{}}{\def\edhead{Ed. \DBKedition}}
+
+\pagestyle{empty}
+
+% interligne double
+\setlength{\oldbaselineskip}{\baselineskip}
+\setlength{\baselineskip}{2\oldbaselineskip}
+\textsf{
+\vfill
+\vspace{2.5cm}
+\begin{center}
+  \huge{\textbf{\DBKtitle}}\\ %
+  \ \\ %
+  \ \\ %
+  \Large{\DBKauthor}\\ %
+  \ifx\DBKsubtitle\relax\else%
+    \underline{\ \ \ \ \ \ \ \ \ \ \ }\\ %
+    \ \\ %
+    \huge{\textbf{\DBKsubtitle}}\\ %
+  \fi
+\end{center}
+\vfill
+\setlength{\baselineskip}{\oldbaselineskip}
+\hspace{1cm}
+\vspace{1cm}
+\begin{center}
+\begin{tabular}{p{7cm} p{7cm}}
+\Large{\DBKreference{} \edhead} & \\
+\end{tabular}
+\end{center}
+}
+
+% Format for the other pages
+\newpage
+\setlength{\baselineskip}{\oldbaselineskip}
+%\chead[]{\DBKcheadfront}
+\lfoot[]{}
+}
diff --git a/doc/manual/dcpomatic.xml b/doc/manual/dcpomatic.xml
new file mode 100644 (file)
index 0000000..ee7b960
--- /dev/null
@@ -0,0 +1,932 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE book [
+<!ENTITY % sgml.features "IGNORE">
+<!ENTITY % xml.features "INCLUDE">
+<!ENTITY % dbcent PUBLIC "-//OASIS//ENTITIES DocBook Character Entities V4.5//EN"
+   "/usr/share/xml/docbook/schema/dtd/4.5/dbcentx.mod">
+%dbcent;
+<!ENTITY % extensions SYSTEM "extensions.ent">
+%extensions;
+]>
+<book xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
+
+<bookinfo>
+<title>DCP-o-matic</title>
+<author><firstname>Carl</firstname><surname>Hetherington</surname></author>
+</bookinfo>
+
+<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
+<title>Introduction</title>
+
+<para>
+Hello, and welcome to DCP-o-matic!
+</para>
+
+<section>
+<title>What is DCP-o-matic?</title>
+
+<para>
+DCP-o-matic is a program to generate <ulink
+url="http://en.wikipedia.org/wiki/Digital_Cinema_Package">Digital
+Cinema Packages</ulink> (DCPs) from DVDs, Blu-Rays, video files such as MP4
+and AVI, or still images.  The resulting DCPs will play on modern digital
+cinema projectors.
+</para>
+
+<para>
+You might find it useful to make DVDs easier to present, to encode
+independently-shot feature films, or to generate local advertising for
+your cinema.
+</para>
+
+</section>
+
+<section>
+<title>Licence</title>
+
+<para>
+DCP-o-matic is licensed under the <ulink url="http://www.gnu.org/licenses/old-licenses/gpl-2.0.html">GNU GPL</ulink>.
+</para>
+
+</section>
+
+</chapter>
+
+<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
+<title>Installation</title>
+
+<section>
+<title>Windows</title>
+
+<para>
+To install DCP-o-matic on Windows, simply download the installer from
+<ulink url="http://carlh.net/software/dcpomatic">http://carlh.net</ulink>
+and double-click it.  Click through the installer wizard, and
+DCP-o-matic will be installed onto your machine.
+</para>
+
+<para>
+If you are using a 32-bit version of Windows, you will need the 32-bit
+installer.  For 64-bit Windows, either installer will work, but I
+suggest you used the 64-bit version as it will allow DCP-o-matic to
+use more memory.  You may find that DCP-o-matic crashes if you run
+many parallel encoding threads (more than 4) on the 32-bit
+version.
+</para>
+
+</section>
+
+<section>
+<title>Ubuntu Linux</title>
+
+<para>
+You can install DCP-o-matic on Ubuntu 12.04 (&lsquo;Precise
+Pangolin&rsquo;) or 12.10 (&lsquo;Quantal Quetzal&rsquo;) using
+<code>.deb</code> packages: download the appropriate package from
+<ulink
+url="http://carlh.net/software/dcpomatic">http://carlh.net</ulink> and
+double-click it.  Ubuntu will install the necessary bits and pieces
+and set DCP-o-matic up for you.
+</para>
+
+</section>
+
+<section>
+<title>Other Linux distributions</title>
+
+<para>
+Installation on non-Ubuntu Linux is currently a little involved, as
+there are no packages available (yet); you will have to compile it
+from source.  If you are using a non-Ubuntu distribution, do let me
+know via the <ulink url="mailto:dcpomatic@carlh.net">mailing
+list</ulink> and I will see about building some packages.
+</para>
+
+<para>
+The following dependencies are required:
+<itemizedlist>
+<listitem><ulink url="http://ffmpeg.org/">FFmpeg</ulink></listitem>
+<listitem><ulink url="http://www.mega-nerd.com/libsndfile/">libsndfile</ulink></listitem>
+<listitem><ulink url="http://www.openssl.org/">OpenSSL</ulink></listitem>
+<listitem><ulink url="http://www.openjpeg.org/">libopenjpeg</ulink></listitem>
+<listitem><ulink url="http://www.imagemagick.org/script/index.php">ImageMagick</ulink></listitem>
+<listitem><ulink url="http://www.boost.org/">Boost</ulink></listitem>
+<listitem><ulink url="http://www.libssh.org/">libssh</ulink></listitem>
+<listitem><ulink url="http://www.gtk.org/">GTK</ulink></listitem>
+<listitem><ulink url="http://www.wxwidgets.org/">wxWidgets</ulink></listitem>
+<listitem><ulink url="http://carlh.net/software/libdcp/">libdcp</ulink></listitem>
+</itemizedlist>
+</para>
+
+<para>
+Once you have installed the development packages for the dependencies,
+download the source code from <ulink
+url="http://carlh.net/software/dcpomatic">http://carlh.net</ulink>,
+unpack it and run the following commands from inside the source
+directory:
+</para>
+
+<programlisting>
+./waf configure
+./waf build
+sudo ./waf install
+</programlisting>
+
+<para>
+With any luck, this will build and install DCP-o-matic on your system.  To run it, enter:
+</para>
+
+<programlisting>
+dcpomatic
+</programlisting>
+
+<para>
+in a shell.
+</para>
+
+</section>
+</chapter>
+
+<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
+<title>Creating a video DCP</title>
+
+<para>
+In this chapter we will see how to create a video DCP using
+DCP-o-matic.  We will gloss over some of the finer details, which are
+explained in later chapters.
+</para>
+
+<section>
+<title>Creating a new film</title>
+
+<para>
+Let's make a very simple DCP to see how DCP-o-matic works.  First, we
+need some content.  Download the low-resolution trailer for the open
+movie <ulink url="http://sintel.org/">Sintel</ulink> from <ulink
+url="http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/Sintel_Trailer1.480p.DivX_Plus_HD.mkv">their
+website</ulink>.  Generally, of course, one would want to use the
+highest-resolution material available, but for this test we will use
+the low-resolution version to save everyone's bandwidth bills.
+</para>
+
+<para>
+Now, start DCP-o-matic and its window will open.  First, we will
+create a new &lsquo;film&rsquo;.  A &lsquo;film&rsquo; is how DCP-o-matic refers to
+a piece of content, along with some settings, which we will make into
+a DCP.  DCP-o-matic stores its data in a folder on your disk while it
+creates the DCP.  You can create a new film by selecting
+<guilabel>New</guilabel> from the <guilabel>File</guilabel> menu, as
+shown in <xref linkend="fig-file-new"/>.
+</para>
+
+<figure id="fig-file-new"> 
+  <title>Creating a new film</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/file-new&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+This will open a dialogue box for the new film, as shown in <xref
+linkend="fig-video-new-film"/>.
+</para>
+
+<figure id="fig-video-new-film"> 
+  <title>Dialogue box for creating a new film</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/video-new-film&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+In this dialogue box you can choose a name for the film.  This will be
+used to name the folder to store its data in, and also as the initial
+name for the DCP itself.  You can also choose whereabouts you want to create
+the film.  In the example from the figure, DCP-o-matic will create a
+folder called &lsquo;DCP Test&rsquo; inside my home folder (carl) into which it
+will write its working files.
+</para>
+
+<para>
+If you always create your DCPs in a particular folder, you can use
+DCP-o-matic's <guilabel>Preferences</guilabel> to make life a little
+easier by setting the default folder that DCP-o-matic will offer in this dialogue.
+See <xref linkend="ch-preferences"/>.
+</para>
+
+</section>
+
+<section>
+<title>Selecting content</title>
+
+<para>
+The next step is to set the content that you want to use.  Click the
+content selector, as shown in <xref
+linkend="fig-click-content-selector"/>, and a file chooser will
+open for you to select the content file to use, as shown in <xref
+linkend="fig-video-select-content-file"/>.
+</para>
+
+<figure id="fig-click-content-selector">
+  <title>Opening the content selector</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/click-content-selector&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<figure id="fig-video-select-content-file"> 
+  <title>Selecting a video content file</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/video-select-content-file&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+Select your content file and click <guilabel>Open</guilabel>.  In this
+case we are using the Sintel trailer that we downloaded earlier.
+</para>
+
+<para>
+When you do this, DCP-o-matic will take a look at your file.  After a
+short while (when the progress bar at the bottom right of the window
+has finished), you can look through your content using the slider to
+the right of the window, as shown in <xref linkend="fig-examine-thumbs"/>.
+</para>
+
+<figure id="fig-examine-thumbs"> 
+  <title>Examining the content</title>
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/examine-thumbs&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+Dragging the slider will move through your video.  You can also click
+the <guilabel>Play</guilabel> button to play the content back.  Note
+that there will be no sound, and playback might not be entirely
+accurate (it may be slightly slower or faster than it should be, for
+example).  This player is really only intended for brief inspection of
+content; if you need to check it more thoroughly, use another player
+such as <ulink url="http://projects.gnome.org/totem/index.html">Totem</ulink>, <ulink url="http://www.mplayerhq.hu/design7/news.html">mplayer</ulink> or <ulink url="http://www.videolan.org/vlc/index.html">VLC</ulink>.
+</para>
+
+</section>
+
+<section>
+<title>Setting up</title>
+
+<para>
+Now there are a few things to set up to describe how the DCP should be
+created.  The settings are divided into four tabs: film, video, audio and subtitles.
+</para>
+
+<section>
+<title>Film tab</title>
+
+<para>
+The &lsquo;film&rsquo; tab contains settings that pertain to the whole film, as shown in <xref linkend="fig-film-tab"/>.
+</para>
+
+<figure id="fig-film-tab"> 
+  <title>Film settings tab</title>
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/film-tab&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+The first thing here is the name.  This is generally set to the title
+of the film that is being encoded.  If <guilabel>Use DCI
+name</guilabel> is not ticked, the name that you specify will be used
+as-is for the name of the DCP.  If <guilabel>Use DCI name</guilabel>
+is ticked, the name that you enter will be used as part of a
+DCI-compliant name.
+</para>
+
+<para>
+Underneath the name field is a preview of the name that the DCP will
+get.  To use a DCI-compliant name, tick the <guilabel>Use DCI
+name</guilabel> checkbox.  The DCI name will be composed using details
+of your content's soundtrack, the current date and other things that
+can be specified in the DCI name details dialogue box, which you can
+open by clicking on the <guilabel>Details</guilabel> button.
+</para>
+
+<para>
+If the DCP name is long, it may not all be visible.  You can see the
+full name by hovering the mouse pointer over the partial name.
+</para>
+
+<para>
+The <guilabel>Trust content's header</guilabel> button starts off
+checked, and this means that DCP-o-matic will use the content's header
+information to determine its length.  If, for some reason, this header
+length is wrong, uncheck the <guilabel>Trust content's
+header</guilabel> button and DCP-o-matic will run through the content
+to find its exact length.  This may take a while for large pieces of content.
+</para>
+
+<para>
+Next up is the content type.  This can be
+&lsquo;feature&rsquo;, &lsquo;trailer&rsquo; or whatever; select the
+required type from the drop-down list.
+</para>
+
+<para>
+The <guilabel>trim frames</guilabel> settings allow you to trim frames
+from the beginning and end of the content; any trimmed frames will not
+be included in the DCP.
+</para>
+
+</section>
+
+<section>
+<title>Video tab</title>
+
+<para>
+This tab contains settings related to the picture in your DCP, as shown in <xref linkend="fig-video-tab"/>.
+</para>
+
+<figure id="fig-video-tab"> 
+  <title>Video settings tab</title>
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/video-tab&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+The first option on this tab is the format.  This will govern the
+shape that DCP-o-matic will make your image into.  Select the aspect
+ratio that your content should be presented in.  The &lsquo;4:3 within
+Flat&rsquo; and &lsquo;16:9 within Flat&rsquo; settings will put the
+image at the specified ratio within a Flat (1.85:1) frame, so that you
+can project the DCP using your projector's Flat preset.
+</para>
+
+<para>
+The remaining options can often be left alone, but may sometimes be
+useful.  The &lsquo;crop&rsquo; settings can be used to crop your
+content, which can be used to remove black borders from round the
+edges of DVD images, for example.  The specified number of pixels will
+be trimmed from each edge, and the content image in the right of the
+window will be updated to show the effect of the crop.
+</para>
+
+<para>
+The &lsquo;filters&rsquo; settings allow you to apply various video
+filters to the image.  These may be useful to try to improve
+poor-quality sources like DVDs.  We will discuss filtering later in the manual.
+<!-- XXX: link -->
+</para>
+
+<para>
+The &lsquo;scaler&rsquo; is the method that will be used to scale up
+your content to the required size for the DCP, if required.  We will
+discuss the options in more detail later; Bicubic is a fine choice in
+most situations.
+<!-- XXX: link -->
+</para>
+
+<para>
+The &lsquo;colour look-up table&rsquo; specifies the colour space that
+your input content will be expected to be in.  If in doubt, leave it
+set to &lsquo;sRGB&rsquo;.
+</para>
+
+<para>
+Finally, the &lsquo;JPEG2000 bandwidth&rsquo; setting changes how big the final
+image files used within the DCP will be.  Larger numbers will give
+better quality, but correspondingly larger DCPs.  The bandwidth can be
+between 50 and 250 megabits per second (MBps).
+</para>
+
+</section>
+
+<section>
+<title>Audio tab</title>
+
+<para>
+This tab contains settings related to the sound in your DCP, as shown in <xref linkend="fig-audio-tab"/>.
+</para>
+
+<figure id="fig-audio-tab"> 
+  <title>Audio settings tab</title>
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/audio-tab&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+
+<para>
+&lsquo;Audio Gain&rsquo; is used to alter the volume of the
+soundtrack.  The specified gain (in dB) will be applied to each sound
+channel before it is written to the DCP.
+</para>
+
+<para>
+If you use a sound processor that DCP-o-matic knows about, it can help
+you calculate changes in gain that you should apply.  Say, for
+example, that you make a test DCP and find that you have to run it at
+volume 5 instead of volume 7 to get a good sound level in the screen.
+If this is the case, click the <guilabel>Calculate...</guilabel>
+button next to the audio gain entry, and the dialogue box in <xref
+linkend="fig-calculate-audio-gain"/> will open.
+</para>
+
+<figure id="fig-calculate-audio-gain"> 
+  <title>Calculating audio gain</title>
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/calculate-audio-gain&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+For our example, put 5 in the first box and 7 in the second and click
+<guilabel>OK</guilabel>.  DCP-o-matic will calculate the audio gain
+that it should apply to make this happen.  Then you can re-make the
+DCP (this will be reasonably fast, as the video data will already have
+been done) and it should play back at the correct volume with 7 on
+your sound-rack fader.
+</para>
+
+<para>
+Current versions of DCP-o-matic only know about the Dolby CP750.  If
+you use a different sound processor, and know the gain curve of its
+volume control, <ulink url="mailto:cth@carlh.net">get in
+touch</ulink>.
+</para>
+
+<para>
+&lsquo;Audio Delay&rsquo; is used to adjust the synchronisation
+between audio and video.  A positive delay will move the audio later
+with respect to the video, and a negative delay will move it earlier.
+</para>
+
+<para>
+By default the <guilabel>Use content&lsquo;s audio</guilabel> button
+will be selected.  This means that the DCP will use one of the
+soundtracks from your content file; you can select the soundtrack that
+you wish to use from the drop-down box.
+</para>
+
+<para>
+Note that if your content's audio is mono, DCP-o-matic will place it
+in the centre channel in the DCP.
+</para>
+
+<para>
+Alternatively, you can supply different sound files by clicking the
+<guilabel>Use external audio</guilabel> button and choosing a WAV file
+for any channels that you want to appear in the DCP.  These files can
+be any bit depth and sampling rate, and will be re-sampled and
+bit-depth converted if required.
+</para>
+
+</section>
+<section>
+<title>Subtitles tab</title>
+
+<para>
+This tab contains settings related to subtitles in your DCP, as shown in <xref linkend="fig-subtitles-tab"/>.
+</para>
+
+<figure id="fig-subtitles-tab"> 
+  <title>Subtitle settings tab</title>
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/subtitles-tab&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+DCP-o-matic will extract subtitles from the content, if present, and
+they can be &lsquo;burnt into&rsquo; the DCP (that is, they are
+included in the image and not overlaid by the projector).  Note that
+DVD and Blu-Ray subtitles are stored as bitmaps, so it is not possible
+(automatically) to use non-burnt-in subtitles with these sources.
+Select the <guilabel>With Subtitles</guilabel> checkbox to enable
+subtitles.  The <guilabel>offset</guilabel> control moves the
+subtitles up and down the image, and the <guilabel>scale</guilabel>
+control changes their size.
+</para>
+
+<para>
+Future versions of DCP-o-matic will hopefully include the option to
+use text subtitles (as is the norm with most professionally-mastered
+DCPs).
+</para>
+
+</section>
+</section>
+
+<section>
+<title>Making the DCP</title>
+
+<para>
+Now that we have set everything up, choose <guilabel>Make
+DCP</guilabel> from the <guilabel>Jobs</guilabel> menu.  DCP-o-matic
+will encode your DCP.  This may take some time (many hours in some
+cases).  While the job is in progress, DCP-o-matic will update you on
+how it is getting on with the progress bar in the bottom of its window, as shown in <xref linkend="fig-making-dcp"/>.
+</para>
+
+<figure id="fig-making-dcp">
+  <title>Making the DCP</title>
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/making-dcp&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+When it has finished, the DCP will end up on your disk inside the
+film's directory.  You can then copy this to a projector via a USB
+stick, hard-drive or network connection.
+</para>
+
+<para>
+Alternatively, if you have a projector or TMS that is accessible via
+SCP across your network, you can upload the content directly from
+DCP-o-matic.  See <xref linkend="sec-tms-upload"/>.
+</para>
+
+</section>
+</chapter>
+
+
+<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
+<title>Creating a still-image DCP</title>
+
+<para>
+DCP-o-matic can also be used to create DCPs of a still image, perhaps
+for an advertisement or an on-screen announcement.  This chapter shows you
+how to do it.
+</para>
+
+<para>
+As with video DCPs, the first step is to create a new
+&lsquo;Film&rsquo;; select <guilabel>New</guilabel> from the
+<guilabel>File</guilabel> menu and the new film dialogue will open as
+shown in <xref linkend="fig-still-new-film"/>.
+</para>
+
+<figure id="fig-still-new-film"> 
+  <title>Dialogue box for creating a new film</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/still-new-film&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+Enter a name and click <guilabel>OK</guilabel>.  Then we set up the
+content; click the content selector as before, and this time we will
+choose an image file, as shown in <xref
+linkend="fig-still-select-content-file"/>.
+</para>
+
+<figure id="fig-still-select-content-file"> 
+  <title>Selecting a still content file</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/still-select-content-file&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+Setting up for a still image DCP is somewhat simpler than for a video;
+the tabs are all the same, but many options are removed and a few are added.
+</para>
+
+<para>
+As with video, you can select a content type and the format (ratio)
+that your image should be presented in.  It will be scaled and padded
+to fit the selected ratio, but in such a way that the pixel aspect
+ratio is preserved.  In other words, the image will not be stretched,
+merely scaled; if you want to stretch your image, you will need to do
+so in a separate program before importing it into DCP-o-matic.  You
+can also crop your image, if you so choose, and then set a duration
+(in seconds) that the image should appear on screen.
+</para>
+
+<para>
+Still-image DCPs can include sound; this can be added from the
+<guilabel>Audio</guilabel> tab.  If your specified duration is shorter
+than the audio, the audio will be cut off at the duration; if it is
+longer, silence will be added after your audio.
+</para>
+
+<para>
+Finally, as with video, you can choose <guilabel>Make DCP</guilabel>
+from the <guilabel>Jobs</guilabel> menu to create your DCP.  This will
+be much quicker than creating a video DCP, as DCP-o-matic only needs
+to encode a single frame which it can then repeat.
+</para>
+
+</chapter>
+
+
+<chapter xml:id="ch-preferences" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
+<title>Preferences</title>
+
+<para>
+DCP-o-matic provides a few preferences which can be used to modify its
+behaviour.  This chapter explains those options.
+</para>
+
+<section>
+<title>The preferences dialogue</title>
+
+<para>
+The preferences dialogue is opened by choosing
+<guilabel>Preferences...</guilabel> from the <guilabel>Edit</guilabel>
+menu.  The dialogue is shown in <xref linkend="fig-prefs"/>.
+</para>
+
+<figure id="fig-prefs"> 
+  <title>Preferences</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/prefs&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<section>
+<title>TMS setup</title>
+
+<para>
+The first part of the dialogue gives some options for specifying
+details about your TMS.  If you do this, and your TMS accepts SSH
+connections, you can upload DCPs directly from DCP-o-matic to the TMS.
+This is discussed in <xref linkend="sec-tms-upload"/>.
+</para>
+
+<para>
+<guilabel>TMS IP address</guilabel> should be set to the IP address of
+your TMS, <guilabel>TMS target path</guilabel> to the place that DCPs
+should be uploaded to (which will be relative to the home directory of
+the SSH user).  Finally, the user name and password are the
+credentials required to log into the TMS via SSH.
+</para>
+</section>
+
+<section>
+<title>Threads</title>
+
+<para>
+When DCP-o-matic is encoding DCPs it can use multiple parallel threads
+to speed things up.  Set this value to the number of threads
+DCP-o-matic should use.  This would typically be set to the number of
+processors (or processor cores) in your machine.
+</para>
+
+</section>
+
+<section>
+<title>Default directory for new films</title>
+
+<para>
+This is the directory which DCP-o-matic will suggest initially as a place to put new films.
+</para>
+
+</section>
+
+<section>
+<title>A/B options</title>
+
+<para>
+These options are for DCP-o-matic's special mode of making A/B
+comparison DCPs for checking the performance of video filters.  Their
+use is described in <xref linkend="sec-ab"/>.
+</para>
+
+</section>
+
+<section>
+<title>Encoding servers</title>
+
+<para>
+If you have spare machines sitting around on your network not doing
+much, they can be pressed into service to speed up DCP encodes.  This
+is done by running a small server program on the machine, which will
+encode video sent to it by the &lsquo;master&rsquo; DCP-o-matic.  This
+option is described in more detail in <xref linkend="sec-servers"/>.
+Use these preferences to specify the encoding servers that should be
+used.
+</para>
+
+</section>
+
+</section>
+</chapter>
+
+<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
+<title>Advanced topics</title>
+
+<para>This chapter describes some parts of DCP-o-matic that are
+probably not essential, but which you might find useful in some
+circumstances.
+</para>
+
+<section>
+<title>Filtering</title>
+
+<para>
+DCP-o-matic offers a variety of filters that can be applied to your
+video content.  You can set up the filters by clicking the
+<guilabel>Edit</guilabel> button next to the filters entry in the
+setup area of the DCP-o-matic window; this opens the filters selector
+as shown in <xref linkend="fig-filters"/>.
+</para>
+
+<figure id="fig-filters"> 
+  <title>Filters selector</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/filters&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+After changing the filters setup, you will need to regenerate the DCP
+to see the effect on the cinema screen.  The preview in DCP-o-matic
+will update itself whenever filters are changed, though of course this
+image is much smaller and of lower resolution than a projected image!
+</para>
+
+</section>
+
+<section>
+<title>Scaling</title>
+
+<para>
+If your source material is not of the DCI-specified size, or if it
+uses non-square pixels, DCP-o-matic will need to scale it.  The
+algorithm used to scale is set up by the <guilabel>Scaler</guilabel>
+entry in the film setup area.  We think &lsquo;Bicubic&rsquo; is the
+best all-round option, but tests are ongoing.
+</para>
+
+</section>
+
+<section xml:id="sec-tms-upload">
+<title>TMS upload</title>
+
+<para>
+If you have configured details of a TMS in the preferences dialogue
+(<xref linkend="ch-preferences"/>) you can upload a completed DCP
+straight to your TMS buy choosing <guilabel>Send DCP to TMS</guilabel>
+from the <guilabel>Jobs</guilabel> menu.
+</para>
+
+</section>
+
+
+<section xml:id="sec-ab">
+<title>A/B comparison</title>
+
+<para>
+When evaluating the effects of different filters or scalers on the
+image quality, A/B mode might be useful.  In this mode, DCP-o-matic
+will generate a DCP where the left half of the image uses some
+&lsquo;reference&rsquo; filtering and scaling, and the right half of
+the image uses a different set of filters and a different scaler.
+This DCP can then be played back on a projector and the image quality
+evaluated.
+</para>
+
+<para>
+To enable A/B mode, click the A/B checkbox in the setup area of the
+DCP-o-matic window.  When you generate your DCP, the left half of the
+screen will use the filters and scaler specified in the <xref
+linkend="ch-preferences">preferences</xref> dialogue, and the right
+half will use the filters and scaler specified in the film setup.
+</para>
+
+</section>
+
+<section xml:id="sec-servers">
+<title>Encoding servers</title>
+
+<para>
+One way to increase the speed of DCP encoding is to use more
+than one machine at the same time.  An instance of DCP-o-matic can
+offload some of the time-consuming JPEG2000 encoding to any number of
+other machines on a network.  To do this, one &lsquo;master&rsquo;
+machine runs DCP-o-matic, and the &lsquo;server&rsquo; machines run
+a small program called &lsquo;servomatic&rsquo;.
+</para>
+
+<section>
+<title>Running the servers</title>
+
+<para>
+There are two options for the encoding server;
+<code>servomatic_cli</code>, which runs on the command line, and
+<code>servomatic_gui</code>, which has a simple GUI.  The command line
+version is well-suited to headless servers, especially on Linux, and
+the GUI version works best on Windows where it will put an icon in the
+system tray.
+</para>
+
+<para>
+To run the command line version, simply enter:
+</para>
+
+<programlisting>
+servomatic_cli
+</programlisting>
+
+<para>
+at a command prompt.  If you are running the program on a machine with
+a multi-core processor, you can run multiple parallel encoding threads
+by doing something like:
+</para>
+
+<programlisting>
+servomatic_cli -t 4
+</programlisting>
+
+<para>
+to run 4 threads in parallel.
+</para>
+
+<para>
+To run the GUI version on windows, run the &lsquo;DCP-o-matic encode
+server&rsquo; from the start menu.  An icon will appear in the system
+tray; right-click it to open a menu from whence you can quit the
+server or open a window to show its status.
+</para>
+
+</section>
+<section>
+<title>Setting up DCP-o-matic</title>
+
+<para>
+Once your servers are running, you need to tell your master
+DCP-o-matic instance about them.  Start DCP-o-matic and open the
+<guilabel>Preferences</guilabel> dialog from the
+<guilabel>Edit</guilabel> menu.  At the bottom of this dialog is a
+section where you can add, edit and remove encoding servers.  For each
+encoding server you need only specify its IP address and the number of
+threads that it is running, so that DCP-o-matic knows how many
+parallel encode jobs to send to the server.
+</para>
+
+<para>
+Once this is done, any encodes that you start will split the workload
+up between the master machine and the servers.
+</para>
+
+</section>
+<section>
+<title>Some notes about encode servers</title>
+
+<para>
+DCP-o-matic does not mind if servers come and go; if a server
+disappears, DCP-o-matic will stop sending work to it, and will check
+it every minute or so in case it has come back online.
+</para>
+
+<para>
+You will probably find that using a 1Gb/s or faster network will
+provide a significant speed-up compared to a 100Mb/s network.
+</para>
+
+<para>
+Making changes to the server configuration in the master DCP-o-matic
+will have no effect while an encode is running; the changes will only
+be noticed when a new encode is started.
+</para>
+
+</section>
+</section>
+
+</chapter>
+
+
+</book>
diff --git a/doc/manual/dvdomatic-html.xsl b/doc/manual/dvdomatic-html.xsl
deleted file mode 100644 (file)
index 059d7ea..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version='1.0'?>
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-                xmlns:fo="http://www.w3.org/1999/XSL/Format"
-                version="1.0">
-
-<!-- Our CSS -->
-<xsl:param name="html.stylesheet" select="'dvdomatic.css'"/>
-
-<!-- I can't fathom xmlto's logic with image scaling, so I've turned it off -->
-<xsl:param name="ignore.image.scaling" select="1"/>
-
-</xsl:stylesheet>
diff --git a/doc/manual/dvdomatic-pdf.xsl b/doc/manual/dvdomatic-pdf.xsl
deleted file mode 100644 (file)
index 414fb64..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version='1.0' encoding="iso-8859-1"?>
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
-
-<!-- colour links in black -->
-<xsl:param name="latex.hyperparam">colorlinks,linkcolor=black,urlcolor=black</xsl:param>
-
-<!-- no revhistory table -->
-<xsl:param name="doc.collab.show">0</xsl:param>
-<xsl:param name="latex.output.revhistory">0</xsl:param>
-
-<!-- hack images to vaguely the right size -->
-<xsl:param name="imagedata.default.scale">scale=0.6</xsl:param>
-
-<!-- don't make too-ridiculous section numbers -->
-<xsl:param name="doc.section.depth">3</xsl:param>
-
-</xsl:stylesheet>
diff --git a/doc/manual/dvdomatic.css b/doc/manual/dvdomatic.css
deleted file mode 100644 (file)
index 0e4982f..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-body {
-    font-family: luxi sans, sans-serif;
-    margin-left: 4em;
-    margin-right: 4em;
-    margin-top: 1em;
-    margin-bottom: 1em;
-    background-color: #E2E8EE;
-}
-
-div.sidebar {
-    margin-left: 1em;
-    margin-right: 1em;
-    padding-left: 1em;
-    padding-right: 1em;
-    border-color: #000000;
-    border-width: 2px;
-    border-style: solid;
-    background-color: #E2E8EE;
-}
diff --git a/doc/manual/dvdomatic.sty b/doc/manual/dvdomatic.sty
deleted file mode 100644 (file)
index 834e581..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-%%
-%% This style is derivated from the docbook one
-%%
-\NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{ardour}[2007/04/04 My DocBook Style]
-
-%% Just use the original package and pass the options
-\RequirePackageWithOptions{docbook}
-
-% Use a nice font
-\usepackage{lmodern}
-
-% Define \dbend as the dangerous bend sign
-\font\manual=manfnt
-\def\dbend{{\manual\char127}}
-
-% Redefine sidebar environment to use the dangerous bend style
-% Danger, Will Robinson!
-\def\sidebar{\begin{trivlist}\item[]\noindent%
-\begingroup\hangindent=2pc\hangafter=-2%\clubpenalty=10000%
-\def\par{\endgraf\endgroup}%
-\hbox to0pt{\hskip-\hangindent\dbend\hfill}\ignorespaces}
-\def\endsidebar{\par\end{trivlist}}
-
-
-% Futz with the title page; basically a copy of
-% /usr/share/texmf/tex/latex/dblatex/style/dbk_title.sty
-% with authors added.
-
-\def\DBKcover{
-\ifthenelse{\equal{\DBKedition}{}}{\def\edhead{}}{\def\edhead{Ed. \DBKedition}}
-
-\pagestyle{empty}
-
-% interligne double
-\setlength{\oldbaselineskip}{\baselineskip}
-\setlength{\baselineskip}{2\oldbaselineskip}
-\textsf{
-\vfill
-\vspace{2.5cm}
-\begin{center}
-  \huge{\textbf{\DBKtitle}}\\ %
-  \ \\ %
-  \ \\ %
-  \Large{\DBKauthor}\\ %
-  \ifx\DBKsubtitle\relax\else%
-    \underline{\ \ \ \ \ \ \ \ \ \ \ }\\ %
-    \ \\ %
-    \huge{\textbf{\DBKsubtitle}}\\ %
-  \fi
-\end{center}
-\vfill
-\setlength{\baselineskip}{\oldbaselineskip}
-\hspace{1cm}
-\vspace{1cm}
-\begin{center}
-\begin{tabular}{p{7cm} p{7cm}}
-\Large{\DBKreference{} \edhead} & \\
-\end{tabular}
-\end{center}
-}
-
-% Format for the other pages
-\newpage
-\setlength{\baselineskip}{\oldbaselineskip}
-%\chead[]{\DBKcheadfront}
-\lfoot[]{}
-}
diff --git a/doc/manual/dvdomatic.xml b/doc/manual/dvdomatic.xml
deleted file mode 100644 (file)
index 58315ec..0000000
+++ /dev/null
@@ -1,932 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE book [
-<!ENTITY % sgml.features "IGNORE">
-<!ENTITY % xml.features "INCLUDE">
-<!ENTITY % dbcent PUBLIC "-//OASIS//ENTITIES DocBook Character Entities V4.5//EN"
-   "/usr/share/xml/docbook/schema/dtd/4.5/dbcentx.mod">
-%dbcent;
-<!ENTITY % extensions SYSTEM "extensions.ent">
-%extensions;
-]>
-<book xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
-
-<bookinfo>
-<title>DVD-o-matic</title>
-<author><firstname>Carl</firstname><surname>Hetherington</surname></author>
-</bookinfo>
-
-<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
-<title>Introduction</title>
-
-<para>
-Hello, and welcome to DVD-o-matic!
-</para>
-
-<section>
-<title>What is DVD-o-matic?</title>
-
-<para>
-DVD-o-matic is a program to generate <ulink
-url="http://en.wikipedia.org/wiki/Digital_Cinema_Package">Digital
-Cinema Packages</ulink> (DCPs) from DVDs, Blu-Rays, video files such as MP4
-and AVI, or still images.  The resulting DCPs will play on modern digital
-cinema projectors.
-</para>
-
-<para>
-You might find it useful to make DVDs easier to present, to encode
-independently-shot feature films, or to generate local advertising for
-your cinema.
-</para>
-
-</section>
-
-<section>
-<title>Licence</title>
-
-<para>
-DVD-o-matic is licensed under the <ulink url="http://www.gnu.org/licenses/old-licenses/gpl-2.0.html">GNU GPL</ulink>.
-</para>
-
-</section>
-
-</chapter>
-
-<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
-<title>Installation</title>
-
-<section>
-<title>Windows</title>
-
-<para>
-To install DVD-o-matic on Windows, simply download the installer from
-<ulink url="http://carlh.net/software/dvdomatic">http://carlh.net</ulink>
-and double-click it.  Click through the installer wizard, and
-DVD-o-matic will be installed onto your machine.
-</para>
-
-<para>
-If you are using a 32-bit version of Windows, you will need the 32-bit
-installer.  For 64-bit Windows, either installer will work, but I
-suggest you used the 64-bit version as it will allow DVD-o-matic to
-use more memory.  You may find that DVD-o-matic crashes if you run
-many parallel encoding threads (more than 4) on the 32-bit
-version.
-</para>
-
-</section>
-
-<section>
-<title>Ubuntu Linux</title>
-
-<para>
-You can install DVD-o-matic on Ubuntu 12.04 (&lsquo;Precise
-Pangolin&rsquo;) or 12.10 (&lsquo;Quantal Quetzal&rsquo;) using
-<code>.deb</code> packages: download the appropriate package from
-<ulink
-url="http://carlh.net/software/dvdomatic">http://carlh.net</ulink> and
-double-click it.  Ubuntu will install the necessary bits and pieces
-and set DVD-o-matic up for you.
-</para>
-
-</section>
-
-<section>
-<title>Other Linux distributions</title>
-
-<para>
-Installation on non-Ubuntu Linux is currently a little involved, as
-there are no packages available (yet); you will have to compile it
-from source.  If you are using a non-Ubuntu distribution, do let me
-know via the <ulink url="mailto:dvdomatic@carlh.net">mailing
-list</ulink> and I will see about building some packages.
-</para>
-
-<para>
-The following dependencies are required:
-<itemizedlist>
-<listitem><ulink url="http://ffmpeg.org/">FFmpeg</ulink></listitem>
-<listitem><ulink url="http://www.mega-nerd.com/libsndfile/">libsndfile</ulink></listitem>
-<listitem><ulink url="http://www.openssl.org/">OpenSSL</ulink></listitem>
-<listitem><ulink url="http://www.openjpeg.org/">libopenjpeg</ulink></listitem>
-<listitem><ulink url="http://www.imagemagick.org/script/index.php">ImageMagick</ulink></listitem>
-<listitem><ulink url="http://www.boost.org/">Boost</ulink></listitem>
-<listitem><ulink url="http://www.libssh.org/">libssh</ulink></listitem>
-<listitem><ulink url="http://www.gtk.org/">GTK</ulink></listitem>
-<listitem><ulink url="http://www.wxwidgets.org/">wxWidgets</ulink></listitem>
-<listitem><ulink url="http://carlh.net/software/libdcp/">libdcp</ulink></listitem>
-</itemizedlist>
-</para>
-
-<para>
-Once you have installed the development packages for the dependencies,
-download the source code from <ulink
-url="http://carlh.net/software/dvdomatic">http://carlh.net</ulink>,
-unpack it and run the following commands from inside the source
-directory:
-</para>
-
-<programlisting>
-./waf configure
-./waf build
-sudo ./waf install
-</programlisting>
-
-<para>
-With any luck, this will build and install DVD-o-matic on your system.  To run it, enter:
-</para>
-
-<programlisting>
-dvdomatic
-</programlisting>
-
-<para>
-in a shell.
-</para>
-
-</section>
-</chapter>
-
-<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
-<title>Creating a video DCP</title>
-
-<para>
-In this chapter we will see how to create a video DCP using
-DVD-o-matic.  We will gloss over some of the finer details, which are
-explained in later chapters.
-</para>
-
-<section>
-<title>Creating a new film</title>
-
-<para>
-Let's make a very simple DCP to see how DVD-o-matic works.  First, we
-need some content.  Download the low-resolution trailer for the open
-movie <ulink url="http://sintel.org/">Sintel</ulink> from <ulink
-url="http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/Sintel_Trailer1.480p.DivX_Plus_HD.mkv">their
-website</ulink>.  Generally, of course, one would want to use the
-highest-resolution material available, but for this test we will use
-the low-resolution version to save everyone's bandwidth bills.
-</para>
-
-<para>
-Now, start DVD-o-matic and its window will open.  First, we will
-create a new &lsquo;film&rsquo;.  A &lsquo;film&rsquo; is how DVD-o-matic refers to
-a piece of content, along with some settings, which we will make into
-a DCP.  DVD-o-matic stores its data in a folder on your disk while it
-creates the DCP.  You can create a new film by selecting
-<guilabel>New</guilabel> from the <guilabel>File</guilabel> menu, as
-shown in <xref linkend="fig-file-new"/>.
-</para>
-
-<figure id="fig-file-new"> 
-  <title>Creating a new film</title> 
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/file-new&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<para>
-This will open a dialogue box for the new film, as shown in <xref
-linkend="fig-video-new-film"/>.
-</para>
-
-<figure id="fig-video-new-film"> 
-  <title>Dialogue box for creating a new film</title> 
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/video-new-film&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<para>
-In this dialogue box you can choose a name for the film.  This will be
-used to name the folder to store its data in, and also as the initial
-name for the DCP itself.  You can also choose whereabouts you want to create
-the film.  In the example from the figure, DVD-o-matic will create a
-folder called &lsquo;DCP Test&rsquo; inside my home folder (carl) into which it
-will write its working files.
-</para>
-
-<para>
-If you always create your DCPs in a particular folder, you can use
-DVD-o-matic's <guilabel>Preferences</guilabel> to make life a little
-easier by setting the default folder that DVD-o-matic will offer in this dialogue.
-See <xref linkend="ch-preferences"/>.
-</para>
-
-</section>
-
-<section>
-<title>Selecting content</title>
-
-<para>
-The next step is to set the content that you want to use.  Click the
-content selector, as shown in <xref
-linkend="fig-click-content-selector"/>, and a file chooser will
-open for you to select the content file to use, as shown in <xref
-linkend="fig-video-select-content-file"/>.
-</para>
-
-<figure id="fig-click-content-selector">
-  <title>Opening the content selector</title> 
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/click-content-selector&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<figure id="fig-video-select-content-file"> 
-  <title>Selecting a video content file</title> 
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/video-select-content-file&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<para>
-Select your content file and click <guilabel>Open</guilabel>.  In this
-case we are using the Sintel trailer that we downloaded earlier.
-</para>
-
-<para>
-When you do this, DVD-o-matic will take a look at your file.  After a
-short while (when the progress bar at the bottom right of the window
-has finished), you can look through your content using the slider to
-the right of the window, as shown in <xref linkend="fig-examine-thumbs"/>.
-</para>
-
-<figure id="fig-examine-thumbs"> 
-  <title>Examining the content</title>
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/examine-thumbs&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<para>
-Dragging the slider will move through your video.  You can also click
-the <guilabel>Play</guilabel> button to play the content back.  Note
-that there will be no sound, and playback might not be entirely
-accurate (it may be slightly slower or faster than it should be, for
-example).  This player is really only intended for brief inspection of
-content; if you need to check it more thoroughly, use another player
-such as <ulink url="http://projects.gnome.org/totem/index.html">Totem</ulink>, <ulink url="http://www.mplayerhq.hu/design7/news.html">mplayer</ulink> or <ulink url="http://www.videolan.org/vlc/index.html">VLC</ulink>.
-</para>
-
-</section>
-
-<section>
-<title>Setting up</title>
-
-<para>
-Now there are a few things to set up to describe how the DCP should be
-created.  The settings are divided into four tabs: film, video, audio and subtitles.
-</para>
-
-<section>
-<title>Film tab</title>
-
-<para>
-The &lsquo;film&rsquo; tab contains settings that pertain to the whole film, as shown in <xref linkend="fig-film-tab"/>.
-</para>
-
-<figure id="fig-film-tab"> 
-  <title>Film settings tab</title>
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/film-tab&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<para>
-The first thing here is the name.  This is generally set to the title
-of the film that is being encoded.  If <guilabel>Use DCI
-name</guilabel> is not ticked, the name that you specify will be used
-as-is for the name of the DCP.  If <guilabel>Use DCI name</guilabel>
-is ticked, the name that you enter will be used as part of a
-DCI-compliant name.
-</para>
-
-<para>
-Underneath the name field is a preview of the name that the DCP will
-get.  To use a DCI-compliant name, tick the <guilabel>Use DCI
-name</guilabel> checkbox.  The DCI name will be composed using details
-of your content's soundtrack, the current date and other things that
-can be specified in the DCI name details dialogue box, which you can
-open by clicking on the <guilabel>Details</guilabel> button.
-</para>
-
-<para>
-If the DCP name is long, it may not all be visible.  You can see the
-full name by hovering the mouse pointer over the partial name.
-</para>
-
-<para>
-The <guilabel>Trust content's header</guilabel> button starts off
-checked, and this means that DVD-o-matic will use the content's header
-information to determine its length.  If, for some reason, this header
-length is wrong, uncheck the <guilabel>Trust content's
-header</guilabel> button and DVD-o-matic will run through the content
-to find its exact length.  This may take a while for large pieces of content.
-</para>
-
-<para>
-Next up is the content type.  This can be
-&lsquo;feature&rsquo;, &lsquo;trailer&rsquo; or whatever; select the
-required type from the drop-down list.
-</para>
-
-<para>
-The <guilabel>trim frames</guilabel> settings allow you to trim frames
-from the beginning and end of the content; any trimmed frames will not
-be included in the DCP.
-</para>
-
-</section>
-
-<section>
-<title>Video tab</title>
-
-<para>
-This tab contains settings related to the picture in your DCP, as shown in <xref linkend="fig-video-tab"/>.
-</para>
-
-<figure id="fig-video-tab"> 
-  <title>Video settings tab</title>
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/video-tab&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<para>
-The first option on this tab is the format.  This will govern the
-shape that DVD-o-matic will make your image into.  Select the aspect
-ratio that your content should be presented in.  The &lsquo;4:3 within
-Flat&rsquo; and &lsquo;16:9 within Flat&rsquo; settings will put the
-image at the specified ratio within a Flat (1.85:1) frame, so that you
-can project the DCP using your projector's Flat preset.
-</para>
-
-<para>
-The remaining options can often be left alone, but may sometimes be
-useful.  The &lsquo;crop&rsquo; settings can be used to crop your
-content, which can be used to remove black borders from round the
-edges of DVD images, for example.  The specified number of pixels will
-be trimmed from each edge, and the content image in the right of the
-window will be updated to show the effect of the crop.
-</para>
-
-<para>
-The &lsquo;filters&rsquo; settings allow you to apply various video
-filters to the image.  These may be useful to try to improve
-poor-quality sources like DVDs.  We will discuss filtering later in the manual.
-<!-- XXX: link -->
-</para>
-
-<para>
-The &lsquo;scaler&rsquo; is the method that will be used to scale up
-your content to the required size for the DCP, if required.  We will
-discuss the options in more detail later; Bicubic is a fine choice in
-most situations.
-<!-- XXX: link -->
-</para>
-
-<para>
-The &lsquo;colour look-up table&rsquo; specifies the colour space that
-your input content will be expected to be in.  If in doubt, leave it
-set to &lsquo;sRGB&rsquo;.
-</para>
-
-<para>
-Finally, the &lsquo;JPEG2000 bandwidth&rsquo; setting changes how big the final
-image files used within the DCP will be.  Larger numbers will give
-better quality, but correspondingly larger DCPs.  The bandwidth can be
-between 50 and 250 megabits per second (MBps).
-</para>
-
-</section>
-
-<section>
-<title>Audio tab</title>
-
-<para>
-This tab contains settings related to the sound in your DCP, as shown in <xref linkend="fig-audio-tab"/>.
-</para>
-
-<figure id="fig-audio-tab"> 
-  <title>Audio settings tab</title>
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/audio-tab&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-
-<para>
-&lsquo;Audio Gain&rsquo; is used to alter the volume of the
-soundtrack.  The specified gain (in dB) will be applied to each sound
-channel before it is written to the DCP.
-</para>
-
-<para>
-If you use a sound processor that DVD-o-matic knows about, it can help
-you calculate changes in gain that you should apply.  Say, for
-example, that you make a test DCP and find that you have to run it at
-volume 5 instead of volume 7 to get a good sound level in the screen.
-If this is the case, click the <guilabel>Calculate...</guilabel>
-button next to the audio gain entry, and the dialogue box in <xref
-linkend="fig-calculate-audio-gain"/> will open.
-</para>
-
-<figure id="fig-calculate-audio-gain"> 
-  <title>Calculating audio gain</title>
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/calculate-audio-gain&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<para>
-For our example, put 5 in the first box and 7 in the second and click
-<guilabel>OK</guilabel>.  DVD-o-matic will calculate the audio gain
-that it should apply to make this happen.  Then you can re-make the
-DCP (this will be reasonably fast, as the video data will already have
-been done) and it should play back at the correct volume with 7 on
-your sound-rack fader.
-</para>
-
-<para>
-Current versions of DVD-o-matic only know about the Dolby CP750.  If
-you use a different sound processor, and know the gain curve of its
-volume control, <ulink url="mailto:cth@carlh.net">get in
-touch</ulink>.
-</para>
-
-<para>
-&lsquo;Audio Delay&rsquo; is used to adjust the synchronisation
-between audio and video.  A positive delay will move the audio later
-with respect to the video, and a negative delay will move it earlier.
-</para>
-
-<para>
-By default the <guilabel>Use content&lsquo;s audio</guilabel> button
-will be selected.  This means that the DCP will use one of the
-soundtracks from your content file; you can select the soundtrack that
-you wish to use from the drop-down box.
-</para>
-
-<para>
-Note that if your content's audio is mono, DVD-o-matic will place it
-in the centre channel in the DCP.
-</para>
-
-<para>
-Alternatively, you can supply different sound files by clicking the
-<guilabel>Use external audio</guilabel> button and choosing a WAV file
-for any channels that you want to appear in the DCP.  These files can
-be any bit depth and sampling rate, and will be re-sampled and
-bit-depth converted if required.
-</para>
-
-</section>
-<section>
-<title>Subtitles tab</title>
-
-<para>
-This tab contains settings related to subtitles in your DCP, as shown in <xref linkend="fig-subtitles-tab"/>.
-</para>
-
-<figure id="fig-subtitles-tab"> 
-  <title>Subtitle settings tab</title>
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/subtitles-tab&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<para>
-DVD-o-matic will extract subtitles from the content, if present, and
-they can be &lsquo;burnt into&rsquo; the DCP (that is, they are
-included in the image and not overlaid by the projector).  Note that
-DVD and Blu-Ray subtitles are stored as bitmaps, so it is not possible
-(automatically) to use non-burnt-in subtitles with these sources.
-Select the <guilabel>With Subtitles</guilabel> checkbox to enable
-subtitles.  The <guilabel>offset</guilabel> control moves the
-subtitles up and down the image, and the <guilabel>scale</guilabel>
-control changes their size.
-</para>
-
-<para>
-Future versions of DVD-o-matic will hopefully include the option to
-use text subtitles (as is the norm with most professionally-mastered
-DCPs).
-</para>
-
-</section>
-</section>
-
-<section>
-<title>Making the DCP</title>
-
-<para>
-Now that we have set everything up, choose <guilabel>Make
-DCP</guilabel> from the <guilabel>Jobs</guilabel> menu.  DVD-o-matic
-will encode your DCP.  This may take some time (many hours in some
-cases).  While the job is in progress, DVD-o-matic will update you on
-how it is getting on with the progress bar in the bottom of its window, as shown in <xref linkend="fig-making-dcp"/>.
-</para>
-
-<figure id="fig-making-dcp">
-  <title>Making the DCP</title>
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/making-dcp&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<para>
-When it has finished, the DCP will end up on your disk inside the
-film's directory.  You can then copy this to a projector via a USB
-stick, hard-drive or network connection.
-</para>
-
-<para>
-Alternatively, if you have a projector or TMS that is accessible via
-SCP across your network, you can upload the content directly from
-DVD-o-matic.  See <xref linkend="sec-tms-upload"/>.
-</para>
-
-</section>
-</chapter>
-
-
-<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
-<title>Creating a still-image DCP</title>
-
-<para>
-DVD-o-matic can also be used to create DCPs of a still image, perhaps
-for an advertisement or an on-screen announcement.  This chapter shows you
-how to do it.
-</para>
-
-<para>
-As with video DCPs, the first step is to create a new
-&lsquo;Film&rsquo;; select <guilabel>New</guilabel> from the
-<guilabel>File</guilabel> menu and the new film dialogue will open as
-shown in <xref linkend="fig-still-new-film"/>.
-</para>
-
-<figure id="fig-still-new-film"> 
-  <title>Dialogue box for creating a new film</title> 
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/still-new-film&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<para>
-Enter a name and click <guilabel>OK</guilabel>.  Then we set up the
-content; click the content selector as before, and this time we will
-choose an image file, as shown in <xref
-linkend="fig-still-select-content-file"/>.
-</para>
-
-<figure id="fig-still-select-content-file"> 
-  <title>Selecting a still content file</title> 
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/still-select-content-file&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<para>
-Setting up for a still image DCP is somewhat simpler than for a video;
-the tabs are all the same, but many options are removed and a few are added.
-</para>
-
-<para>
-As with video, you can select a content type and the format (ratio)
-that your image should be presented in.  It will be scaled and padded
-to fit the selected ratio, but in such a way that the pixel aspect
-ratio is preserved.  In other words, the image will not be stretched,
-merely scaled; if you want to stretch your image, you will need to do
-so in a separate program before importing it into DVD-o-matic.  You
-can also crop your image, if you so choose, and then set a duration
-(in seconds) that the image should appear on screen.
-</para>
-
-<para>
-Still-image DCPs can include sound; this can be added from the
-<guilabel>Audio</guilabel> tab.  If your specified duration is shorter
-than the audio, the audio will be cut off at the duration; if it is
-longer, silence will be added after your audio.
-</para>
-
-<para>
-Finally, as with video, you can choose <guilabel>Make DCP</guilabel>
-from the <guilabel>Jobs</guilabel> menu to create your DCP.  This will
-be much quicker than creating a video DCP, as DVD-o-matic only needs
-to encode a single frame which it can then repeat.
-</para>
-
-</chapter>
-
-
-<chapter xml:id="ch-preferences" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
-<title>Preferences</title>
-
-<para>
-DVD-o-matic provides a few preferences which can be used to modify its
-behaviour.  This chapter explains those options.
-</para>
-
-<section>
-<title>The preferences dialogue</title>
-
-<para>
-The preferences dialogue is opened by choosing
-<guilabel>Preferences...</guilabel> from the <guilabel>Edit</guilabel>
-menu.  The dialogue is shown in <xref linkend="fig-prefs"/>.
-</para>
-
-<figure id="fig-prefs"> 
-  <title>Preferences</title> 
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/prefs&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<section>
-<title>TMS setup</title>
-
-<para>
-The first part of the dialogue gives some options for specifying
-details about your TMS.  If you do this, and your TMS accepts SSH
-connections, you can upload DCPs directly from DVD-o-matic to the TMS.
-This is discussed in <xref linkend="sec-tms-upload"/>.
-</para>
-
-<para>
-<guilabel>TMS IP address</guilabel> should be set to the IP address of
-your TMS, <guilabel>TMS target path</guilabel> to the place that DCPs
-should be uploaded to (which will be relative to the home directory of
-the SSH user).  Finally, the user name and password are the
-credentials required to log into the TMS via SSH.
-</para>
-</section>
-
-<section>
-<title>Threads</title>
-
-<para>
-When DVD-o-matic is encoding DCPs it can use multiple parallel threads
-to speed things up.  Set this value to the number of threads
-DVD-o-matic should use.  This would typically be set to the number of
-processors (or processor cores) in your machine.
-</para>
-
-</section>
-
-<section>
-<title>Default directory for new films</title>
-
-<para>
-This is the directory which DVD-o-matic will suggest initially as a place to put new films.
-</para>
-
-</section>
-
-<section>
-<title>A/B options</title>
-
-<para>
-These options are for DVD-o-matic's special mode of making A/B
-comparison DCPs for checking the performance of video filters.  Their
-use is described in <xref linkend="sec-ab"/>.
-</para>
-
-</section>
-
-<section>
-<title>Encoding servers</title>
-
-<para>
-If you have spare machines sitting around on your network not doing
-much, they can be pressed into service to speed up DCP encodes.  This
-is done by running a small server program on the machine, which will
-encode video sent to it by the &lsquo;master&rsquo; DVD-o-matic.  This
-option is described in more detail in <xref linkend="sec-servers"/>.
-Use these preferences to specify the encoding servers that should be
-used.
-</para>
-
-</section>
-
-</section>
-</chapter>
-
-<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
-<title>Advanced topics</title>
-
-<para>This chapter describes some parts of DVD-o-matic that are
-probably not essential, but which you might find useful in some
-circumstances.
-</para>
-
-<section>
-<title>Filtering</title>
-
-<para>
-DVD-o-matic offers a variety of filters that can be applied to your
-video content.  You can set up the filters by clicking the
-<guilabel>Edit</guilabel> button next to the filters entry in the
-setup area of the DVD-o-matic window; this opens the filters selector
-as shown in <xref linkend="fig-filters"/>.
-</para>
-
-<figure id="fig-filters"> 
-  <title>Filters selector</title> 
-  <mediaobject>
-    <imageobject> 
-      <imagedata fileref="screenshots/filters&scs;"/>
-    </imageobject> 
-  </mediaobject>
-</figure>
-
-<para>
-After changing the filters setup, you will need to regenerate the DCP
-to see the effect on the cinema screen.  The preview in DVD-o-matic
-will update itself whenever filters are changed, though of course this
-image is much smaller and of lower resolution than a projected image!
-</para>
-
-</section>
-
-<section>
-<title>Scaling</title>
-
-<para>
-If your source material is not of the DCI-specified size, or if it
-uses non-square pixels, DVD-o-matic will need to scale it.  The
-algorithm used to scale is set up by the <guilabel>Scaler</guilabel>
-entry in the film setup area.  We think &lsquo;Bicubic&rsquo; is the
-best all-round option, but tests are ongoing.
-</para>
-
-</section>
-
-<section xml:id="sec-tms-upload">
-<title>TMS upload</title>
-
-<para>
-If you have configured details of a TMS in the preferences dialogue
-(<xref linkend="ch-preferences"/>) you can upload a completed DCP
-straight to your TMS buy choosing <guilabel>Send DCP to TMS</guilabel>
-from the <guilabel>Jobs</guilabel> menu.
-</para>
-
-</section>
-
-
-<section xml:id="sec-ab">
-<title>A/B comparison</title>
-
-<para>
-When evaluating the effects of different filters or scalers on the
-image quality, A/B mode might be useful.  In this mode, DVD-o-matic
-will generate a DCP where the left half of the image uses some
-&lsquo;reference&rsquo; filtering and scaling, and the right half of
-the image uses a different set of filters and a different scaler.
-This DCP can then be played back on a projector and the image quality
-evaluated.
-</para>
-
-<para>
-To enable A/B mode, click the A/B checkbox in the setup area of the
-DVD-o-matic window.  When you generate your DCP, the left half of the
-screen will use the filters and scaler specified in the <xref
-linkend="ch-preferences">preferences</xref> dialogue, and the right
-half will use the filters and scaler specified in the film setup.
-</para>
-
-</section>
-
-<section xml:id="sec-servers">
-<title>Encoding servers</title>
-
-<para>
-One way to increase the speed of DCP encoding is to use more
-than one machine at the same time.  An instance of DVD-o-matic can
-offload some of the time-consuming JPEG2000 encoding to any number of
-other machines on a network.  To do this, one &lsquo;master&rsquo;
-machine runs DVD-o-matic, and the &lsquo;server&rsquo; machines run
-a small program called &lsquo;servomatic&rsquo;.
-</para>
-
-<section>
-<title>Running the servers</title>
-
-<para>
-There are two options for the encoding server;
-<code>servomatic_cli</code>, which runs on the command line, and
-<code>servomatic_gui</code>, which has a simple GUI.  The command line
-version is well-suited to headless servers, especially on Linux, and
-the GUI version works best on Windows where it will put an icon in the
-system tray.
-</para>
-
-<para>
-To run the command line version, simply enter:
-</para>
-
-<programlisting>
-servomatic_cli
-</programlisting>
-
-<para>
-at a command prompt.  If you are running the program on a machine with
-a multi-core processor, you can run multiple parallel encoding threads
-by doing something like:
-</para>
-
-<programlisting>
-servomatic_cli -t 4
-</programlisting>
-
-<para>
-to run 4 threads in parallel.
-</para>
-
-<para>
-To run the GUI version on windows, run the &lsquo;DVD-o-matic encode
-server&rsquo; from the start menu.  An icon will appear in the system
-tray; right-click it to open a menu from whence you can quit the
-server or open a window to show its status.
-</para>
-
-</section>
-<section>
-<title>Setting up DVD-o-matic</title>
-
-<para>
-Once your servers are running, you need to tell your master
-DVD-o-matic instance about them.  Start DVD-o-matic and open the
-<guilabel>Preferences</guilabel> dialog from the
-<guilabel>Edit</guilabel> menu.  At the bottom of this dialog is a
-section where you can add, edit and remove encoding servers.  For each
-encoding server you need only specify its IP address and the number of
-threads that it is running, so that DVD-o-matic knows how many
-parallel encode jobs to send to the server.
-</para>
-
-<para>
-Once this is done, any encodes that you start will split the workload
-up between the master machine and the servers.
-</para>
-
-</section>
-<section>
-<title>Some notes about encode servers</title>
-
-<para>
-DVD-o-matic does not mind if servers come and go; if a server
-disappears, DVD-o-matic will stop sending work to it, and will check
-it every minute or so in case it has come back online.
-</para>
-
-<para>
-You will probably find that using a 1Gb/s or faster network will
-provide a significant speed-up compared to a 100Mb/s network.
-</para>
-
-<para>
-Making changes to the server configuration in the master DVD-o-matic
-will have no effect while an encode is running; the changes will only
-be noticed when a new encode is started.
-</para>
-
-</section>
-</section>
-
-</chapter>
-
-
-</book>
diff --git a/hacks/python-playback/config.py b/hacks/python-playback/config.py
deleted file mode 100644 (file)
index fecf261..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-
-LEFT_SCREEN_WIDTH = 1366
diff --git a/hacks/python-playback/dvdomatic b/hacks/python-playback/dvdomatic
deleted file mode 100755 (executable)
index ce405f3..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-#!/usr/bin/python
-
-import os
-import operator
-import traceback
-import pygtk
-pygtk.require('2.0')
-import gtk
-import glib
-import gobject
-import film
-import film_view
-import player
-import screens
-import thumbs
-import ratio
-import util
-
-FILM_DIRECTORY = '/home/carl/DVD'
-
-current_player = None
-films = []
-inhibit_selection_update = False
-
-def find_films():
-    global films
-    films = []
-    for root, dirs, files in os.walk(FILM_DIRECTORY):
-        for name in files:
-            if os.path.basename(name) == 'info':
-                films.append(film.Film(os.path.join(root, os.path.dirname(name))))
-
-    films.sort(key = operator.attrgetter('name'))
-
-def update_film_store():
-    global film_store
-    global films
-    global inhibit_selection_update
-    inhibit_selection_update = True
-    film_store.clear()
-    for f in films:
-        film_store.append([f.name])
-    inhibit_selection_update = False
-
-def update_screen_store(screen_store, screens):
-    screen_store.clear()
-    for s in screens.screens:
-        screen_store.append([s.name])
-
-def create_film_tree_view(film_store):
-    view = gtk.TreeView(film_store)
-    column = gtk.TreeViewColumn()
-    view.append_column(column)
-    cell = gtk.CellRendererText()
-    column.pack_start(cell)
-    column.add_attribute(cell, 'text', 0)
-    view.get_selection().set_mode(gtk.SELECTION_SINGLE)
-    return view
-
-def create_screen_view(screen_store):
-    view = gtk.TreeView(screen_store)
-    column = gtk.TreeViewColumn()
-    view.append_column(column)
-    cell = gtk.CellRendererText()
-    column.pack_start(cell)
-    column.add_attribute(cell, 'text', 0)
-    view.get_selection().set_mode(gtk.SELECTION_SINGLE)
-    return view
-
-def get_selected_film():
-    (model, iter) = film_tree_view.get_selection().get_selected()
-
-    for f in films:
-        if f.name == model.get(iter, 0)[0]:
-            return f
-
-    return None
-
-# @return Selected screen name
-def get_selected_screen():
-    (model, iter) = screen_view.get_selection().get_selected()
-    return model.get(iter, 0)[0]
-
-def film_selected(selection):
-    if inhibit_selection_update:
-        return
-
-    film_view.set(get_selected_film())
-    check_for_playability()
-
-def screen_selected(selection):
-    check_for_playability()
-
-def check_for_playability():
-    f = get_selected_film()
-    if screens.get_format(get_selected_screen(), f.ratio) is not None:
-        play_button.set_label("Play")
-        play_button.set_sensitive(True)
-    else:
-        play_button.set_label("Cannot play: no setting for %s on screen %s" % (ratio.find(f.ratio).name(), get_selected_screen()))
-        play_button.set_sensitive(False)
-
-def update_status(s):
-    global current_player
-    if current_player is None:
-        s.set_text("Not playing")
-        return True
-
-    position = current_player.time_pos
-    if position is None:
-        return True
-    position_hms = util.s_to_hms(position)
-
-    length = current_player.length
-    if length is None:
-        return True
-
-    remaining = length - position
-    remaining_hms = util.s_to_hms(remaining)
-    s.set_text("Playing: %d:%02d:%02d, %d:%02d:%02d remaining" % (position_hms[0], position_hms[1], position_hms[2], remaining_hms[0], remaining_hms[1], remaining_hms[2]))
-    return True
-
-def play_clicked(b):
-    global current_player
-    f = get_selected_film()
-    current_player = player.get_player(f, screens.get_format(get_selected_screen(), f.ratio))
-    print current_player.args
-
-def stop_clicked(b):
-    global current_player
-    if current_player is not None:
-        current_player.stop()
-        current_player = None
-
-def add_film_clicked(b):
-    global films
-    c = gtk.FileChooserDialog("New Film", main_window, gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER, (("Add", gtk.RESPONSE_OK)))
-    c.set_current_folder(FILM_DIRECTORY)
-    if c.run() == gtk.RESPONSE_OK:
-        f = film.Film()
-        f.data = c.get_filename()
-        f.name = os.path.basename(c.get_filename())
-        f.write()
-        find_films()
-        update_film_store()
-        c.hide()
-
-        for i in range(0, len(films)):
-            if films[i].name == f.name:
-                film_tree_view.get_selection().select_path((i, ))
-
-main_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
-main_window.set_title("DVD-o-matic")
-main_window.maximize()
-
-main_hbox = gtk.HBox()
-main_hbox.set_spacing(12)
-main_hbox.set_border_width(12)
-main_window.add(main_hbox)
-
-find_films()
-film_view = film_view.FilmView(main_window)
-screens = screens.Screens("screens")
-
-left_vbox = gtk.VBox()
-left_vbox.set_spacing(12)
-main_hbox.pack_start(left_vbox, False, False)
-right_vbox = gtk.VBox()
-right_vbox.set_spacing(12)
-main_hbox.pack_start(right_vbox)
-
-film_store = gtk.ListStore(gobject.TYPE_STRING)
-update_film_store()
-
-film_tree_view = create_film_tree_view(film_store)
-left_vbox.pack_start(film_tree_view, True, True)
-film_tree_view.get_selection().select_path((0, ))
-film_tree_view.get_selection().connect("changed", film_selected)
-
-add_film_button = gtk.Button(stock = gtk.STOCK_ADD)
-left_vbox.pack_start(add_film_button, False, False)
-add_film_button.connect("clicked", add_film_clicked)
-
-screen_store = gtk.ListStore(gobject.TYPE_STRING)
-update_screen_store(screen_store, screens)
-
-screen_view = create_screen_view(screen_store)
-left_vbox.pack_start(screen_view, False, False)
-screen_view.get_selection().select_path((0, ))
-screen_view.get_selection().connect("changed", screen_selected)
-
-right_vbox.pack_start(film_view, False, False)
-film_view.set(films[0])
-
-play_button = gtk.Button("Play")
-right_vbox.pack_start(play_button)
-play_button.connect("clicked", play_clicked)
-
-stop_button = gtk.Button("Stop")
-right_vbox.pack_start(stop_button)
-stop_button.connect("clicked", stop_clicked)
-
-status = gtk.Label()
-right_vbox.pack_start(status, False, False)
-glib.timeout_add_seconds(1, update_status, status)
-
-check_for_playability()
-main_window.show_all()
-gtk.main()
diff --git a/hacks/python-playback/film.py b/hacks/python-playback/film.py
deleted file mode 100644 (file)
index 3ad1280..0000000
+++ /dev/null
@@ -1,188 +0,0 @@
-import os
-import subprocess
-import shlex
-import shutil
-import player
-
-class Film:
-    def __init__(self, data = None):
-        # File or directory containing content
-        self.content = None
-        # True if content is in DVD format
-        self.dvd = False
-        # DVD title number
-        self.dvd_title = 1
-        # Directory containing metadata
-        self.data = None
-        # Film name
-        self.name = None
-        # Number of pixels by which to crop the content from each edge
-        self.left_crop = 0
-        self.top_crop = 0
-        self.right_crop = 0
-        self.bottom_crop = 0
-        # Use deinterlacing filter
-        self.deinterlace = False
-        # Target ratio
-        self.ratio = 1.85
-        # Audio stream ID to play
-        self.aid = None
-
-        self.width = None
-        self.height = None
-        self.fps = None
-        self.length = None
-
-        if data is not None:
-            self.data = data
-            f = open(os.path.join(self.data, 'info'), 'r')
-            while 1:
-                l = f.readline()
-                if l == '':
-                    break
-
-                d = l.strip()
-            
-                s = d.find(' ')
-                if s != -1:
-                    key = d[:s]
-                    value = d[s+1:]
-                
-                    if key == 'name':
-                        self.name = value
-                    elif key == 'content':
-                        self.content = value
-                    elif key == 'dvd':
-                        self.dvd = int(value) == 1
-                    elif key == 'dvd_title':
-                        self.dvd_title = int(value)
-                    elif key == 'left_crop':
-                        self.left_crop = int(value)
-                    elif key == 'top_crop':
-                        self.top_crop = int(value)
-                    elif key == 'right_crop':
-                        self.right_crop = int(value)
-                    elif key == 'bottom_crop':
-                        self.bottom_crop = int(value)
-                    elif key == 'deinterlace':
-                        self.deinterlace = int(value) == 1
-                    elif key == 'ratio':
-                        self.ratio = float(value)
-                    elif key == 'aid':
-                        self.aid = int(value)
-                    elif key == 'width':
-                        self.width = int(value)
-                    elif key == 'height':
-                        self.height = int(value)
-                    elif key == 'fps':
-                        self.fps = float(value)
-                    elif key == 'length':
-                        self.length = float(value)
-
-        if self.width is None or self.height is None or self.fps is None or self.length is None:
-            self.update_content_metadata()
-
-    def write(self):
-        try:
-            os.mkdir(self.data)
-        except OSError:
-            pass
-
-        f = open(os.path.join(self.data, 'info'), 'w')
-        self.write_datum(f, 'name', self.name)
-        self.write_datum(f, 'content', self.content)
-        self.write_datum(f, 'dvd', int(self.dvd))
-        self.write_datum(f, 'dvd_title', self.dvd_title)
-        self.write_datum(f, 'left_crop', self.left_crop)
-        self.write_datum(f, 'top_crop', self.top_crop)
-        self.write_datum(f, 'right_crop', self.right_crop)
-        self.write_datum(f, 'bottom_crop', self.bottom_crop)
-        self.write_datum(f, 'deinterlace', int(self.deinterlace))
-        self.write_datum(f, 'ratio', self.ratio)
-        self.write_datum(f, 'aid', self.aid)
-        self.write_datum(f, 'width', self.width)
-        self.write_datum(f, 'height', self.height)
-        self.write_datum(f, 'fps', self.fps)
-        self.write_datum(f, 'length', self.length)
-
-    def write_datum(self, f, key, value):
-        if value is not None:
-            print >>f,'%s %s' % (key, str(value))
-
-    def thumbs_dir(self):
-        t = os.path.join(self.data, 'thumbs')
-
-        try:
-            os.mkdir(t)
-        except OSError:
-            pass
-
-        return t
-
-    def thumb(self, n):
-        return os.path.join(self.thumbs_dir(), str('%08d.png' % (n + 1)))
-
-    def thumbs(self):
-        return len(os.listdir(self.thumbs_dir()))
-
-    def remove_thumbs(self):
-        shutil.rmtree(self.thumbs_dir())
-
-    def make_thumbs(self):
-        num_thumbs = 128
-        cl = self.player_command_line()
-        if self.length is not None:
-            sstep = self.length / num_thumbs
-        else:
-            sstep = 100
-        cl.extra = '-vo png -frames %d -sstep %d -nosound' % (num_thumbs, sstep)
-        os.chdir(self.thumbs_dir())
-        os.system(cl.get(True))
-
-    def set_dvd(self, d):
-        self.dvd = d
-        self.remove_thumbs()
-
-    def set_dvd_title(self, t):
-        self.dvd_title = t
-        self.remove_thumbs()
-
-    def set_content(self, c):
-        if c == self.content:
-            return
-
-        self.content = c
-        self.update_content_metadata()
-
-    def player_command_line(self):
-        cl = player.CommandLine()
-        cl.dvd = self.dvd
-        cl.dvd_title = self.dvd_title
-        cl.content = self.content
-        return cl
-    
-    def update_content_metadata(self):
-        if self.content is None:
-            return
-
-        self.width = None
-        self.height = None
-        self.fps = None
-        self.length = None
-
-        cl = self.player_command_line()
-        cl.extra = '-identify -vo null -ao null -frames 0'
-        text = subprocess.check_output(shlex.split(cl.get(True))).decode('utf-8')
-        lines = text.split('\n')
-        for l in lines:
-            s = l.strip()
-            b = s.split('=')
-            if len(b) == 2:
-                if b[0] == 'ID_VIDEO_WIDTH':
-                    self.width = int(b[1])
-                elif b[0] == 'ID_VIDEO_HEIGHT':
-                    self.height = int(b[1])
-                elif b[0] == 'ID_VIDEO_FPS':
-                    self.fps = float(b[1])
-                elif b[0] == 'ID_LENGTH':
-                    self.length = float(b[1])
diff --git a/hacks/python-playback/film_view.py b/hacks/python-playback/film_view.py
deleted file mode 100644 (file)
index c11b2e6..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-import os
-import pygtk
-pygtk.require('2.0')
-import gtk
-import ratio
-import util
-import thumbs
-
-class FilmView(gtk.HBox):
-    def __init__(self, parent):
-        gtk.HBox.__init__(self)
-
-        self.parent_window = parent
-
-        self.inhibit_save = True
-        
-        self.table = gtk.Table()
-        self.pack_start(self.table, True, True)
-
-        self.table.set_row_spacings(4)
-        self.table.set_col_spacings(12)
-        self.film_name = gtk.Entry()
-        self.ratio = gtk.combo_box_new_text()
-        for r in ratio.ratios:
-            self.ratio.append_text(r.name())
-        self.content_file_radio = gtk.RadioButton()
-        self.content_file_radio.set_label("File")
-        self.content_file_chooser = gtk.FileChooserDialog("Content", self.parent_window, gtk.FILE_CHOOSER_ACTION_OPEN, (("Select", gtk.RESPONSE_OK)))
-        self.content_file_button = gtk.FileChooserButton(self.content_file_chooser)
-        self.content_folder_radio = gtk.RadioButton()
-        self.content_folder_radio.set_label("Folder")
-        self.content_folder_radio.set_group(self.content_file_radio)
-        self.content_folder_chooser = gtk.FileChooserDialog("Content", self.parent_window, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (("Select", gtk.RESPONSE_OK)))
-        self.content_folder_button = gtk.FileChooserButton(self.content_folder_chooser)
-        self.dvd = gtk.CheckButton("DVD")
-        self.dvd_title = gtk.SpinButton()
-        self.dvd_title.set_range(0, 32)
-        self.dvd_title.set_increments(1, 4)
-        self.deinterlace = gtk.CheckButton("Deinterlace")
-        self.left_crop = self.crop_spinbutton()
-        self.right_crop = self.crop_spinbutton()
-        self.top_crop = self.crop_spinbutton()
-        self.bottom_crop = self.crop_spinbutton()
-        
-        # Information about the content (immutable)
-        self.source_size = self.label()
-        self.fps = self.label()
-        self.length = self.label()
-
-        # Buttons
-        self.thumbs_button = gtk.Button("Show Thumbnails")
-
-        self.film_name.connect("changed", self.changed, self)
-        self.ratio.connect("changed", self.changed, self)
-        self.deinterlace.connect("toggled", self.changed, self)
-        self.thumbs_button.connect("clicked", self.thumbs_clicked, self)
-        self.content_file_radio.connect("toggled", self.content_radio_toggled, self)
-        self.content_folder_radio.connect("toggled", self.content_radio_toggled, self)
-        self.content_file_button.connect("file-set", self.content_file_chooser_file_set, self)
-        self.content_folder_button.connect("file-set", self.content_folder_chooser_file_set, self)
-        self.dvd.connect("toggled", self.changed, self)
-        self.dvd_title.connect("value-changed", self.changed, self)
-
-        n = 0
-        self.table.attach(self.label("Film"), 0, 1, n, n + 1)
-        self.table.attach(self.film_name, 1, 2, n, n + 1)
-        n += 1
-        self.table.attach(self.label("Ratio"), 0, 1, n, n + 1)
-        self.table.attach(self.ratio, 1, 2, n, n + 1)
-        n += 1
-        self.table.attach(self.label("Content"), 0, 1, n, n + 1)
-        b = gtk.HBox()
-        b.set_spacing(4)
-        b.pack_start(self.content_file_radio, False, False)
-        b.pack_start(self.content_file_button, True, True)
-        b.pack_start(self.content_folder_radio, False, False)
-        b.pack_start(self.content_folder_button, True, True)
-        self.table.attach(b, 1, 2, n, n + 1)
-        n += 1
-        self.table.attach(self.dvd, 0, 2, n, n + 1)
-        n += 1
-        self.table.attach(self.label("DVD title"), 0, 1, n, n + 1)
-        self.table.attach(self.dvd_title, 1, 2, n, n + 1)
-        n += 1
-        self.table.attach(self.deinterlace, 0, 2, n, n + 1)
-        n += 1
-        self.table.attach(self.label("Left Crop"), 0, 1, n, n + 1)
-        self.table.attach(self.left_crop, 1, 2, n, n + 1)
-        n += 1
-        self.table.attach(self.label("Right Crop"), 0, 1, n, n + 1)
-        self.table.attach(self.right_crop, 1, 2, n, n + 1)
-        n += 1
-        self.table.attach(self.label("Top Crop"), 0, 1, n, n + 1)
-        self.table.attach(self.top_crop, 1, 2, n, n + 1)
-        n += 1
-        self.table.attach(self.label("Bottom Crop"), 0, 1, n, n + 1)
-        self.table.attach(self.bottom_crop, 1, 2, n, n + 1)
-        n += 1
-        self.table.attach(self.label("Source size"), 0, 1, n, n + 1)
-        self.table.attach(self.source_size, 1, 2, n, n + 1)
-        n += 1
-        self.table.attach(self.label("Frames per second"), 0, 1, n, n + 1)
-        self.table.attach(self.fps, 1, 2, n, n + 1)
-        n += 1
-        self.table.attach(self.label("Length"), 0, 1, n, n + 1)
-        self.table.attach(self.length, 1, 2, n, n + 1)
-
-        self.right_vbox = gtk.VBox()
-        self.pack_start(self.right_vbox, False, False)
-
-        self.right_vbox.pack_start(self.thumbs_button, False, False)
-
-        self.inhibit_save = False
-
-    def set(self, film):
-        self.inhibit_save = True
-
-        self.film = film
-        self.film_name.set_text(film.name)
-        self.ratio.set_active(ratio.ratio_to_index(film.ratio))
-        if film.content is not None:
-            if os.path.isfile(film.content):
-                self.set_content_is_file(True)
-                self.content_file_button.set_filename(film.content)
-            else:
-                self.set_content_is_file(False)
-                self.content_folder_button.set_filename(film.content)
-        self.dvd.set_active(film.dvd)
-        self.dvd_title.set_value(film.dvd_title)
-        self.dvd_title.set_sensitive(film.dvd)
-        self.deinterlace.set_active(film.deinterlace)
-        self.left_crop.set_value(film.left_crop)
-        self.right_crop.set_value(film.right_crop)
-        self.top_crop.set_value(film.top_crop)
-        self.bottom_crop.set_value(film.bottom_crop)
-
-        # Content information
-        if film.width is not None and film.height is not None:
-            self.source_size.set_text("%dx%d" % (film.width, film.height))
-        else:
-            self.source_size.set_text("Unknown")
-        if film.fps is not None:
-            self.fps.set_text(str(film.fps))
-        if film.length is not None:
-            self.length.set_text("%d:%02d:%02d" % util.s_to_hms(film.length))
-
-        self.inhibit_save = False
-
-    def set_content_is_file(self, f):
-        self.content_file_button.set_sensitive(f)
-        self.content_folder_button.set_sensitive(not f)
-        self.content_file_radio.set_active(f)
-        self.content_folder_radio.set_active(not f)
-    
-    def label(self, text = ""):
-        l = gtk.Label(text)
-        l.set_alignment(0, 0.5)
-        return l
-
-    def changed(self, a, b):
-        self.dvd_title.set_sensitive(self.dvd.get_active())
-        self.save_film()
-
-    def crop_spinbutton(self):
-        s = gtk.SpinButton()
-        s.set_range(0, 1024)
-        s.set_increments(1, 16)
-        s.connect("value-changed", self.changed, self)
-        return s
-
-    def save_film(self):
-        if self.inhibit_save:
-            return
-
-        self.film.name = self.film_name.get_text()
-        self.film.ratio = ratio.index_to_ratio(self.ratio.get_active()).ratio
-
-        if self.content_file_radio.get_active():
-            self.film.set_content(self.content_file_button.get_filename())
-        else:
-            self.film.set_content(self.content_folder_button.get_filename())
-        self.film.set_dvd(self.dvd.get_active())
-        self.film.set_dvd_title(self.dvd_title.get_value_as_int())
-        self.film.deinterlace = self.deinterlace.get_active()
-        self.film.left_crop = self.left_crop.get_value_as_int()
-        self.film.right_crop = self.right_crop.get_value_as_int()
-        self.film.top_crop = self.top_crop.get_value_as_int()
-        self.film.bottom_crop = self.bottom_crop.get_value_as_int()
-        self.film.write()
-
-    def thumbs_clicked(self, a, b):
-        if self.film.thumbs() == 0:
-            self.film.make_thumbs()
-
-        t = thumbs.Thumbs(self.film)
-        t.run()
-        t.hide()
-        self.left_crop.set_value(t.left_crop())
-        self.right_crop.set_value(t.right_crop())
-        self.top_crop.set_value(t.top_crop())
-        self.bottom_crop.set_value(t.bottom_crop())
-
-    def content_file_chooser_file_set(self, a, b):
-        self.changed(a, b)
-
-    def content_folder_chooser_file_set(self, a, b):
-        self.changed(a, b)
-
-    def content_radio_toggled(self, a, b):
-        self.set_content_is_file(self.content_file_radio.get_active())
-        self.changed(a, b)
-
diff --git a/hacks/python-playback/player.py b/hacks/python-playback/player.py
deleted file mode 100644 (file)
index 5cc8da7..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-import os
-import threading
-import subprocess
-import shlex
-import select
-import film
-import config
-import mplayer
-
-class CommandLine:
-    def __init__(self):
-        self.position_x = 0
-        self.position_y = 0
-        self.output_width = None
-        self.output_height = None
-        self.mov = False
-        self.delay = None
-        self.dvd = False
-        self.dvd_title = 1
-        self.content = None
-        self.extra = ''
-        self.crop_x = None
-        self.crop_y = None
-        self.crop_w = None
-        self.crop_h = None
-        self.deinterlace = False
-        self.aid = None
-        
-    def get(self, with_binary):
-        # hqdn3d?
-        # nr, unsharp?
-        # -vo x11 appears to be necessary to prevent unwanted hardware scaling
-        # -noaspect stops mplayer rescaling to the movie's specified aspect ratio
-        args = '-vo x11 -noaspect -ao pulse -noborder -noautosub -nosub -sws 10 '
-        args += '-geometry %d:%d ' % (self.position_x, self.position_y)
-
-        # Video filters (passed to -vf)
-
-        filters = []
-
-        if self.crop_x is not None or self.crop_y is not None or self.crop_w is not None or self.crop_h is not None:
-            crop = 'crop='
-            if self.crop_w is not None and self.crop_h is not None:
-                crop += '%d:%d' % (self.crop_w, self.crop_h)
-                if self.crop_x is not None and self.crop_x is not None:
-                    crop += ':%d:%d' % (self.crop_x, self.crop_y)
-                filters.append(crop)
-
-        if self.output_width is not None or self.output_height is not None:
-            filters.append('scale=%d:%d' % (self.output_width, self.output_height))
-
-        # Post processing
-        pp = []
-        if self.deinterlace:
-            pp.append('lb')
-
-        # Deringing filter
-        pp.append('dr')
-
-        if len(pp) > 0:
-            pp_string = 'pp='
-            for i in range(0, len(pp)):
-                pp_string += pp[i]
-                if i < len(pp) - 1:
-                    pp += ','
-
-            filters.append(pp_string)
-
-        if len(filters) > 0:
-            args += '-vf '
-            for i in range(0, len(filters)):
-                args += filters[i]
-                if i < len(filters) - 1:
-                    args += ','
-            args += ' '
-
-        if self.mov:
-            args += '-demuxer mov '
-        if self.delay is not None:
-            args += '-delay %f ' % float(args.delay)
-        if self.aid is not None:
-            args += '-aid %s ' % self.aid
-
-        args += self.extra
-        
-        if self.dvd:
-            data_specifier = 'dvd://%d -dvd-device \"%s\"' % (self.dvd_title, self.content)
-        else:
-            data_specifier = '\"%s\"' % self.content
-
-        if with_binary:
-            return 'mplayer %s %s' % (args, data_specifier)
-        
-        return '%s %s' % (args, data_specifier)
-  
-def get_player(film, format):
-    cl = CommandLine()
-    cl.dvd = film.dvd
-    cl.dvd_title = film.dvd_title
-    cl.content = film.content
-    cl.crop_w = film.width - film.left_crop - film.right_crop
-    cl.crop_h = film.height - film.top_crop - film.bottom_crop
-    cl.position_x = format.x
-    if format.external:
-        cl.position_x = format.x + config.LEFT_SCREEN_WIDTH
-        cl.position_y = format.y
-    cl.output_width = format.width
-    cl.output_height = format.height
-    cl.deinterlace = film.deinterlace
-    cl.aid = film.aid
-    return mplayer.Player(cl.get(False))
-
diff --git a/hacks/python-playback/ratio.py b/hacks/python-playback/ratio.py
deleted file mode 100644 (file)
index 62320dc..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-# Class to describe a Ratio, and a collection of common
-# (and not-so-common) film ratios collected from Wikipedia.
-
-class Ratio:
-    def __init__(self, ratio, nickname = None):
-        self.nickname = nickname
-        self.ratio = ratio
-
-    # @return presentation name of this ratio
-    def name(self):
-        if self.nickname is not None:
-            return "%.2f (%s)" % (self.ratio, self.nickname)
-        
-        return "%.2f" % self.ratio
-
-ratios = []
-ratios.append(Ratio(1.33, '4:3'))
-ratios.append(Ratio(1.37, 'Academy'))
-ratios.append(Ratio(1.78, '16:9'))
-ratios.append(Ratio(1.85, 'Flat / widescreen'))
-ratios.append(Ratio(2.39, 'CinemaScope / Panavision'))
-ratios.append(Ratio(1.15, 'Movietone'))
-ratios.append(Ratio(1.43, 'IMAX'))
-ratios.append(Ratio(1.5))
-ratios.append(Ratio(1.56, '14:9'))
-ratios.append(Ratio(1.6, '16:10'))
-ratios.append(Ratio(1.67))
-ratios.append(Ratio(2, 'SuperScope'))
-ratios.append(Ratio(2.2, 'Todd-AO'))
-ratios.append(Ratio(2.35, 'Early CinemaScope / Panavision'))
-ratios.append(Ratio(2.37, '21:9'))
-ratios.append(Ratio(2.55, 'CinemaScope 55'))
-ratios.append(Ratio(2.59, 'Cinerama'))
-ratios.append(Ratio(2.76, 'Ultra Panavision'))
-ratios.append(Ratio(2.93, 'MGM Camera 65'))
-ratios.append(Ratio(4, 'Polyvision'))
-
-# Find a Ratio object from a fractional ratio
-def find(ratio):
-    for r in ratios:
-        if r.ratio == ratio:
-            return r
-
-    return None
-
-# @return the ith ratio
-def index_to_ratio(i):
-    return ratios[i]
-
-# @return the index within the ratios list of a given fractional ratio
-def ratio_to_index(r):
-    for i in range(0, len(ratios)):
-        if ratios[i].ratio == r:
-            return i
-
-    return None
diff --git a/hacks/python-playback/screens b/hacks/python-playback/screens
deleted file mode 100644 (file)
index f389cb1..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-# Screen 1 (untested)
-screen 1
-ratio 1.85
-x 175
-y 100
-width 1550
-height 950
-external 1
-ratio 2.39
-x 0
-y 200
-width 2000
-height 860
-external 1
-
-# Screen 2
-screen 2
-ratio 1.85
-x 175
-y 100
-width 1550
-height 950
-external 1
-ratio 2.39
-x 0
-y 200
-width 2000
-height 860
-external 1
-
-# Screen 3 (untested)
-screen 3
-ratio 1.85
-x 175
-y 100
-width 1550
-height 950
-external 1
-ratio 2.39
-x 0
-y 200
-width 2000
-height 860
-external 1
-
-# Carl's Laptop
-screen laptop
-ratio 1.85
-x 0
-y 0
-width 1366
-height 738
-ratio 2.39
-x 0
-y 0
-width 1366
-height 572
-ratio 1.78
-x 0
-y 0 
-width 1366
-height 767
diff --git a/hacks/python-playback/screens.py b/hacks/python-playback/screens.py
deleted file mode 100644 (file)
index 4230a4c..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/usr/bin/python
-
-class Screen:
-    def __init__(self):
-        self.name = None
-        self.formats = []
-
-class Format:
-    def __init__(self):
-        self.ratio = None
-        self.x = None
-        self.y = None
-        self.width = None
-        self.height = None
-        self.external = False
-
-class Screens:
-    def __init__(self, file):
-
-        self.screens = []
-
-        f = open(file, 'r')
-        current_screen = None
-        current_format = None
-        while 1:
-            l = f.readline()
-            if l == '':
-                break
-            if len(l) > 0 and l[0] == '#':
-                continue
-
-            s = l.strip()
-
-            if len(s) == 0:
-                continue
-
-            b = s.split()
-
-            if len(b) != 2:
-                print "WARNING: ignored line `%s' in screens file" % (s)
-                continue
-
-            if b[0] == 'screen':
-                if current_format is not None:
-                    current_screen.formats.append(current_format)
-                    current_format = None
-
-                if current_screen is not None:
-                    self.screens.append(current_screen)
-                    current_screen = None
-                
-                current_screen = Screen()
-                current_screen.name = b[1]
-            elif b[0] == 'ratio':
-                if current_format is not None:
-                    current_screen.formats.append(current_format)
-                    current_format = None
-                    
-                current_format = Format()
-                current_format.ratio = float(b[1])
-            elif b[0] == 'x':
-                current_format.x = int(b[1])
-            elif b[0] == 'y':
-                current_format.y = int(b[1])
-            elif b[0] == 'width':
-                current_format.width = int(b[1])
-            elif b[0] == 'height':
-                current_format.height = int(b[1])
-            elif b[0] == 'external':
-                current_format.external = int(b[1]) == 1
-
-        if current_format is not None:
-            current_screen.formats.append(current_format)
-
-        if current_screen is not None:
-            self.screens.append(current_screen)
-
-    def get_format(self, screen, ratio):
-        for s in self.screens:
-            if s.name == screen:
-                for f in s.formats:
-                    if f.ratio == ratio:
-                        return f
-
-        return None
diff --git a/hacks/python-playback/thumbs.py b/hacks/python-playback/thumbs.py
deleted file mode 100644 (file)
index 921f82f..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-# GUI to display thumbnails and allow cropping
-# to be set up
-
-import os
-import sys
-import pygtk
-pygtk.require('2.0')
-import gtk
-import film
-import player
-
-class Thumbs(gtk.Dialog):
-    def __init__(self, film):
-        gtk.Dialog.__init__(self)
-        self.film = film
-        self.controls = gtk.Table()
-        self.controls.set_col_spacings(4)
-        self.thumb_adj = gtk.Adjustment(0, 0, self.film.thumbs() - 1, 1, 10)
-        self.add_control("Thumbnail", self.thumb_adj, 0)
-        self.left_crop_adj = gtk.Adjustment(self.film.left_crop, 0, 1024, 1, 128)
-        self.add_control("Left crop", self.left_crop_adj, 1)
-        self.right_crop_adj = gtk.Adjustment(self.film.right_crop, 0, 1024, 1, 128)
-        self.add_control("Right crop", self.right_crop_adj, 2)
-        self.top_crop_adj = gtk.Adjustment(self.film.top_crop, 0, 1024, 1, 128)
-        self.add_control("Top crop", self.top_crop_adj, 3)
-        self.bottom_crop_adj = gtk.Adjustment(self.film.bottom_crop, 0, 1024, 1, 128)
-        self.add_control("Bottom crop", self.bottom_crop_adj, 4)
-        self.display_image = gtk.Image()
-        self.update_display()
-        window_box = gtk.HBox()
-        window_box.set_spacing(12)
-
-        controls_vbox = gtk.VBox()
-        controls_vbox.set_spacing(4)
-        controls_vbox.pack_start(self.controls, False, False)
-        
-        window_box.pack_start(controls_vbox, True, True)
-        window_box.pack_start(self.display_image)
-        
-        self.set_title("%s Thumbnails" % film.name)
-        self.get_content_area().add(window_box)
-        self.add_button("Close", gtk.RESPONSE_ACCEPT)
-        self.show_all()
-
-        for a in [self.thumb_adj, self.left_crop_adj, self.right_crop_adj, self.top_crop_adj, self.bottom_crop_adj]:
-            a.connect('value-changed', self.update_display, self)
-
-    def add_control(self, name, adj, n):
-        l = gtk.Label(name)
-        l.set_alignment(1, 0.5)
-        self.controls.attach(l, 0, 1, n, n + 1)
-        s = gtk.SpinButton(adj)
-        self.controls.attach(s, 1, 2, n, n + 1)
-
-    def update_display(self, a = None, b = None):
-        thumb_pixbuf = gtk.gdk.pixbuf_new_from_file(self.film.thumb(self.thumb_adj.get_value()))
-        self.width = thumb_pixbuf.get_width()
-        self.height = thumb_pixbuf.get_height()
-        left = self.left_crop()
-        right = self.right_crop()
-        top = self.top_crop()
-        bottom = self.bottom_crop()
-        pixbuf = thumb_pixbuf.subpixbuf(left, top, self.width - left - right, self.height - top - bottom)
-        self.display_image.set_from_pixbuf(pixbuf)
-
-    def top_crop(self):
-        return int(self.top_crop_adj.get_value())
-
-    def bottom_crop(self):
-        return int(self.bottom_crop_adj.get_value())
-
-    def left_crop(self):
-        return int(self.left_crop_adj.get_value())
-    
-    def right_crop(self):
-        return int(self.right_crop_adj.get_value())
diff --git a/hacks/python-playback/util.py b/hacks/python-playback/util.py
deleted file mode 100644 (file)
index d78abdd..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-
-def s_to_hms(s):
-    m = int(s / 60)
-    s -= (m * 60)
-    h = int(m / 60)
-    m -= (h * 60)
-    return (h, m, s)
diff --git a/hacks/python-playback/xrandr-notes b/hacks/python-playback/xrandr-notes
deleted file mode 100644 (file)
index eeabf14..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-Recommended 1680 x 1050, 60 fps
-xrandr --output HDMI1 --mode 0xbc
-
-List modes
-xrandr --verbose -q
-
-2048 x 1024, 24 fps
-xrandr --output HDMI1 --mode 0xd1
-
-cvt <xres> <yres> <fps>
-to give modeline, then
-xrandr --newmode modeline
-then add
-xrandr --verbose --addmode HDMI1 modename
-then activate
-xrandr --output HDMI1 --mode foo
-
diff --git a/icons/128x128/dcpomatic.png b/icons/128x128/dcpomatic.png
new file mode 100644 (file)
index 0000000..9936b39
Binary files /dev/null and b/icons/128x128/dcpomatic.png differ
diff --git a/icons/128x128/dvdomatic.png b/icons/128x128/dvdomatic.png
deleted file mode 100644 (file)
index 9936b39..0000000
Binary files a/icons/128x128/dvdomatic.png and /dev/null differ
diff --git a/icons/16x16/dcpomatic.png b/icons/16x16/dcpomatic.png
new file mode 100644 (file)
index 0000000..3c5a10f
Binary files /dev/null and b/icons/16x16/dcpomatic.png differ
diff --git a/icons/16x16/dvdomatic.png b/icons/16x16/dvdomatic.png
deleted file mode 100644 (file)
index 3c5a10f..0000000
Binary files a/icons/16x16/dvdomatic.png and /dev/null differ
diff --git a/icons/22x22/dcpomatic.png b/icons/22x22/dcpomatic.png
new file mode 100644 (file)
index 0000000..dddb862
Binary files /dev/null and b/icons/22x22/dcpomatic.png differ
diff --git a/icons/22x22/dvdomatic.png b/icons/22x22/dvdomatic.png
deleted file mode 100644 (file)
index dddb862..0000000
Binary files a/icons/22x22/dvdomatic.png and /dev/null differ
diff --git a/icons/32x32/dcpomatic.png b/icons/32x32/dcpomatic.png
new file mode 100644 (file)
index 0000000..8cecf08
Binary files /dev/null and b/icons/32x32/dcpomatic.png differ
diff --git a/icons/32x32/dvdomatic.png b/icons/32x32/dvdomatic.png
deleted file mode 100644 (file)
index 8cecf08..0000000
Binary files a/icons/32x32/dvdomatic.png and /dev/null differ
diff --git a/icons/48x48/dcpomatic.png b/icons/48x48/dcpomatic.png
new file mode 100644 (file)
index 0000000..07bf2d1
Binary files /dev/null and b/icons/48x48/dcpomatic.png differ
diff --git a/icons/48x48/dvdomatic.png b/icons/48x48/dvdomatic.png
deleted file mode 100644 (file)
index 07bf2d1..0000000
Binary files a/icons/48x48/dvdomatic.png and /dev/null differ
diff --git a/icons/64x64/dcpomatic.png b/icons/64x64/dcpomatic.png
new file mode 100644 (file)
index 0000000..35564a8
Binary files /dev/null and b/icons/64x64/dcpomatic.png differ
diff --git a/icons/64x64/dvdomatic.png b/icons/64x64/dvdomatic.png
deleted file mode 100644 (file)
index 35564a8..0000000
Binary files a/icons/64x64/dvdomatic.png and /dev/null differ
index 983379ea40f54916a232cd5bb7e14fc296da065f..522e907a6731c50eea691115cbf3a42b5092220d 100755 (executable)
@@ -1,4 +1,4 @@
 #!/bin/bash
 
-iconutil --convert icns --output dvdomatic.icns dvdomatic.iconset/
+iconutil --convert icns --output dcpomatic.icns dcpomatic.iconset/
 
index 753df49315ee5f39aa9ce80d55943543c470608d..a337944c3a2ab060d7aa804f7db42cbf86be4942 100644 (file)
@@ -1,24 +1,24 @@
-Source: dvdomatic
+Source: dcpomatic
 Section: video
 Priority: extra
 Maintainer: Carl Hetherington <cth@carlh.net>
 Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7), libgtk2.0-dev (>= 2.24.10)
 Standards-Version: 3.9.3
-Homepage: http://carlh.net/software/dvdomatic
+Homepage: http://carlh.net/software/dcpomatic
 
-Package: dvdomatic
+Package: dcpomatic
 Architecture: i386
 Depends: libc6 (>= 2.15), libssh-4 (>= 0.5.2), libboost-filesystem1.46.1 (>= 1.46.1), libboost-thread1.46.1 (>= 1.46.1), libsndfile1 (>= 1.0.25), libmagick++4 (>= 8:6.6.9.7), libxml++2.6-2 (>= 2.34.1), libgtk2.0-0 (>= 2.24.10)
 Description: Generator of Digital Cinema Packages (DCPs)
-  DVD-o-matic generates Digital Cinema Packages (DCPs) from video and audio
+  DCP-o-matic generates Digital Cinema Packages (DCPs) from video and audio
   files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant
   digital projectors.
 
-Package: dvdomatic-dbg
+Package: dcpomatic-dbg
 Architecture: i386
 Section: debug
 Priority: extra
-Depends: ${dvdomatic:Depends}, ${misc:Depends}
-Description: debugging symbols for dvdomatic
-  This package contains the debugging symbols for dvdomatic.
+Depends: ${dcpomatic:Depends}, ${misc:Depends}
+Description: debugging symbols for dcpomatic
+  This package contains the debugging symbols for dcpomatic.
 
index bb7672f5340937d17ea830b8833ee83e14e3e593..c2cfdf5d7a04150e4a17e965c8ad05653a4bcc30 100644 (file)
@@ -1,24 +1,24 @@
-Source: dvdomatic
+Source: dcpomatic
 Section: video
 Priority: extra
 Maintainer: Carl Hetherington <cth@carlh.net>
 Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7), libgtk2.0-dev (>= 2.4.10)
 Standards-Version: 3.9.3
-Homepage: http://carlh.net/software/dvdomatic
+Homepage: http://carlh.net/software/dcpomatic
 
-Package: dvdomatic
+Package: dcpomatic
 Architecture: amd64
 Depends: libc6 (>= 2.15), libwxgtk2.8-0 (>= 2.8.12.1), libssh-4 (>= 0.5.2), libboost-filesystem1.46.1 (>= 1.46.1), libboost-thread1.46.1 (>= 1.46.1), libsndfile1 (>= 1.0.25), libmagick++4 (>= 8:6.6.9.7), libxml++2.6-2 (>= 2.34.1)
 Description: Generator of Digital Cinema Packages (DCPs)
-  DVD-o-matic generates Digital Cinema Packages (DCPs) from video and audio
+  DCP-o-matic generates Digital Cinema Packages (DCPs) from video and audio
   files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant
   digital projectors.
 
-Package: dvdomatic-dbg
+Package: dcpomatic-dbg
 Architecture: amd64
 Section: debug
 Priority: extra
-Depends: ${dvdomatic:Depends}, ${misc:Depends}
-Description: debugging symbols for dvdomatic
-  This package contains the debugging symbols for dvdomatic.
+Depends: ${dcpomatic:Depends}, ${misc:Depends}
+Description: debugging symbols for dcpomatic
+  This package contains the debugging symbols for dcpomatic.
 
index 116e26b759125ada1cdb4a35d21ff554b372f013..14dc5a0dc4907d251c9df6bc18b260c2dcccb71e 100644 (file)
@@ -1,23 +1,23 @@
-Source: dvdomatic
+Source: dcpomatic
 Section: video
 Priority: extra
 Maintainer: Carl Hetherington <cth@carlh.net>
 Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7), libgtk2.0-dev (>= 2.24.13)
 Standards-Version: 3.9.3
-Homepage: http://carlh.net/software/dvdomatic
+Homepage: http://carlh.net/software/dcpomatic
 
-Package: dvdomatic
+Package: dcpomatic
 Architecture: i386
 Depends: libc6 (>= 2.15), libssh-4 (>= 0.5.2), libboost-filesystem1.49.0 (>= 1.49.0), libboost-thread1.49.0 (>= 1.49.0), libsndfile1 (>= 1.0.25), libmagick++5 (>= 8:6.7.7.10), libxml++2.6-2 (>= 2.34.2), libgtk2.0-0 (>= 2.24.13)
 Description: Generator of Digital Cinema Packages (DCPs)
-  DVD-o-matic generates Digital Cinema Packages (DCPs) from video and audio
+  DCP-o-matic generates Digital Cinema Packages (DCPs) from video and audio
   files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant
   digital projectors.
 
-Package: dvdomatic-dbg
+Package: dcpomatic-dbg
 Architecture: i386
 Section: debug
 Priority: extra
-Depends: ${dvdomatic:Depends}, ${misc:Depends}
-Description: debugging symbols for dvdomatic
-  This package contains the debugging symbols for dvdomatic.
+Depends: ${dcpomatic:Depends}, ${misc:Depends}
+Description: debugging symbols for dcpomatic
+  This package contains the debugging symbols for dcpomatic.
index 486d1f22579113a2ff6314942c556defdc7f2f63..8a8019f013e3db3303db8a01b5a1cae129c2b7a0 100644 (file)
@@ -1,24 +1,24 @@
-Source: dvdomatic
+Source: dcpomatic
 Section: video
 Priority: extra
 Maintainer: Carl Hetherington <cth@carlh.net>
 Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7), libgtk2.0-dev (>= 2.24.13)
 Standards-Version: 3.9.3
-Homepage: http://carlh.net/software/dvdomatic
+Homepage: http://carlh.net/software/dcpomatic
 
-Package: dvdomatic
+Package: dcpomatic
 Architecture: amd64
 Depends: libc6 (>= 2.15), libssh-4 (>= 0.5.2), libboost-filesystem1.49.0 (>= 1.49.0), libboost-thread1.49.0 (>= 1.49.0), libsndfile1 (>= 1.0.25), libmagick++5 (>= 8:6.7.7.10), libxml++2.6-2 (>= 2.34.2), libgtk2.0-0 (>= 2.24.13)
 Description: Generator of Digital Cinema Packages (DCPs)
-  DVD-o-matic generates Digital Cinema Packages (DCPs) from video and audio
+  DCP-o-matic generates Digital Cinema Packages (DCPs) from video and audio
   files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant
   digital projectors.
 
-Package: dvdomatic-dbg
+Package: dcpomatic-dbg
 Architecture: amd64
 Section: debug
 Priority: extra
-Depends: ${dvdomatic:Depends}, ${misc:Depends}
-Description: debugging symbols for dvdomatic
-  This package contains the debugging symbols for dvdomatic.
+Depends: ${dcpomatic:Depends}, ${misc:Depends}
+Description: debugging symbols for dcpomatic
+  This package contains the debugging symbols for dcpomatic.
 
diff --git a/platform/linux/dcpomatic.desktop.in b/platform/linux/dcpomatic.desktop.in
new file mode 100644 (file)
index 0000000..aabd992
--- /dev/null
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Encoding=UTF-8
+Version=1.0
+Type=Application
+Terminal=false
+Exec=@PREFIX@/bin/dcpomatic
+Name=DCP-o-matic
+Icon=dcpomatic
+Comment=DCP generator
+Categories=AudioVideo;Video
diff --git a/platform/linux/dcpomatic_batch.desktop.in b/platform/linux/dcpomatic_batch.desktop.in
new file mode 100644 (file)
index 0000000..bab136e
--- /dev/null
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Encoding=UTF-8
+Version=1.0
+Type=Application
+Terminal=false
+Exec=@PREFIX@/bin/dcpomatic_batch
+Name=DCP-o-matic Batch Converter
+Icon=dcpomatic
+Comment=DCP generator
+Categories=AudioVideo;Video
diff --git a/platform/linux/dcpomatic_server.desktop.in b/platform/linux/dcpomatic_server.desktop.in
new file mode 100644 (file)
index 0000000..7b8215e
--- /dev/null
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Encoding=UTF-8
+Version=1.0
+Type=Application
+Terminal=false
+Exec=@PREFIX@/bin/dcpomatic_server
+Name=DCP-o-matic Encode Server
+Icon=dcpomatic
+Comment=DCP generator
+Categories=AudioVideo;Video
diff --git a/platform/linux/dvdomatic.desktop.in b/platform/linux/dvdomatic.desktop.in
deleted file mode 100644 (file)
index 65067eb..0000000
+++ /dev/null
@@ -1,10 +0,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 --git a/platform/linux/dvdomatic_batch.desktop.in b/platform/linux/dvdomatic_batch.desktop.in
deleted file mode 100644 (file)
index 8150fe8..0000000
+++ /dev/null
@@ -1,10 +0,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 --git a/platform/linux/servomatic.desktop.in b/platform/linux/servomatic.desktop.in
deleted file mode 100644 (file)
index 572b4c6..0000000
+++ /dev/null
@@ -1,10 +0,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
index 1d9054b326c3d7e4b120f889030a5b5ee0b954dd..53a6efeac7ca687765c9504e57c8069449b50eac 100644 (file)
@@ -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..0f67741386f5cf18aa587f7f65ff53ea31da3c16 100644 (file)
@@ -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>
index a409d82fe7511ab6fdd773eecb4e5cc12ae360ed..debd4aaac5a5c4af41875739b365f3d5985dce08 100644 (file)
@@ -70,7 +70,7 @@ universal_copy $ENV lib/libfontconfig*.dylib $WORK/$libs
 universal_copy $ENV lib/libfreetype*.dylib $WORK/$libs
 universal_copy $ENV lib/libexpat*.dylib $WORK/$libs
 
-for obj in $WORK/$macos/dvdomatic $WORK/$libs/*.dylib; do
+for obj in $WORK/$macos/dcpomatic $WORK/$libs/*.dylib; do
   deps=`otool -L $obj | awk '{print $1}' | egrep "(/Users/carl|libboost|libssh)"`
   changes=""
   for dep in $deps; do
@@ -83,11 +83,11 @@ for obj in $WORK/$macos/dvdomatic $WORK/$libs/*.dylib; do
 done
 
 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
 
@@ -111,7 +111,7 @@ echo '
            set arrangement of theViewOptions to not arranged
            set icon size of theViewOptions to 64
            make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"}
-           set position of item "DVD-o-matic.app" of container window to {90, 80}
+           set position of item "DCP-o-matic.app" of container window to {90, 80}
            set position of item "Applications" of container window to {310, 80}
            close
            open
@@ -128,8 +128,8 @@ sync
 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"
 
diff --git a/platform/windows/dcpomatic.bmp b/platform/windows/dcpomatic.bmp
new file mode 100644 (file)
index 0000000..0a196f7
Binary files /dev/null and b/platform/windows/dcpomatic.bmp differ
diff --git a/platform/windows/dcpomatic.ico b/platform/windows/dcpomatic.ico
new file mode 100644 (file)
index 0000000..225008c
Binary files /dev/null and b/platform/windows/dcpomatic.ico differ
diff --git a/platform/windows/dcpomatic.rc b/platform/windows/dcpomatic.rc
new file mode 100644 (file)
index 0000000..17790cf
--- /dev/null
@@ -0,0 +1,3 @@
+id ICON "dvdomatic.ico"
+taskbar_icon ICON "dvdomatic_taskbar.ico"
+#include "wx-2.9/wx/msw/wx.rc"
diff --git a/platform/windows/dcpomatic_taskbar.ico b/platform/windows/dcpomatic_taskbar.ico
new file mode 100644 (file)
index 0000000..f4489fa
Binary files /dev/null and b/platform/windows/dcpomatic_taskbar.ico differ
diff --git a/platform/windows/dvdomatic.bmp b/platform/windows/dvdomatic.bmp
deleted file mode 100644 (file)
index 0a196f7..0000000
Binary files a/platform/windows/dvdomatic.bmp and /dev/null differ
diff --git a/platform/windows/dvdomatic.ico b/platform/windows/dvdomatic.ico
deleted file mode 100644 (file)
index 225008c..0000000
Binary files a/platform/windows/dvdomatic.ico and /dev/null differ
diff --git a/platform/windows/dvdomatic.rc b/platform/windows/dvdomatic.rc
deleted file mode 100644 (file)
index 17790cf..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-id ICON "dvdomatic.ico"
-taskbar_icon ICON "dvdomatic_taskbar.ico"
-#include "wx-2.9/wx/msw/wx.rc"
diff --git a/platform/windows/dvdomatic_taskbar.ico b/platform/windows/dvdomatic_taskbar.ico
deleted file mode 100644 (file)
index f4489fa..0000000
Binary files a/platform/windows/dvdomatic_taskbar.ico and /dev/null differ
index 6dd1de2d9e322247249f7f85f62cbdf1cae67ccf..314fe176ff6277edb1a9bf6e1297245b1772b47a 100644 (file)
@@ -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"
@@ -80,15 +80,16 @@ File "%deps%/bin/libpixman-1-0.dll"
 File "%deps%/bin/libfontconfig-1.dll"
 File "%deps%/bin/libexpat-1.dll"
 File "%deps%/bin/libbz2.dll"
+File "%deps%/bin/cxml.dll"
 File "%deps%/bin/ffprobe.exe"
 
-File "%binaries%/src/wx/dvdomatic-wx.dll"
-File "%binaries%/src/lib/dvdomatic.dll"
-File "%binaries%/src/tools/dvdomatic.exe"
-File "%binaries%/src/tools/dvdomatic_batch.exe"
-File "%binaries%/src/tools/makedcp.exe"
-File "%binaries%/src/tools/servomatic_cli.exe"
-File "%binaries%/src/tools/servomatic_gui.exe"
+File "%binaries%/src/wx/dcpomatic-wx.dll"
+File "%binaries%/src/lib/dcpomatic.dll"
+File "%binaries%/src/tools/dcpomatic.exe"
+File "%binaries%/src/tools/dcpomatic_batch.exe"
+File "%binaries%/src/tools/dcpomatic_cli.exe"
+File "%binaries%/src/tools/dcpomatic_server_cli.exe"
+File "%binaries%/src/tools/dcpomatic_server.exe"
 
 # I don't know why, but sometimes it seems that 
 # delegates.xml must be in with the binaries, and
@@ -98,34 +99,34 @@ SetOutPath "$PROFILE\.magick"
 File "%deps%/etc/ImageMagick/delegates.xml"
 
 SetOutPath "$INSTDIR\locale\fr\LC_MESSAGES"
-File "%binaries%/src/lib/mo/fr_FR/libdvdomatic.mo"
-File "%binaries%/src/wx/mo/fr_FR/libdvdomatic-wx.mo"
-File "%binaries%/src/tools/mo/fr_FR/dvdomatic.mo"
+File "%binaries%/src/lib/mo/fr_FR/libdcpomatic.mo"
+File "%binaries%/src/wx/mo/fr_FR/libdcpomatic-wx.mo"
+File "%binaries%/src/tools/mo/fr_FR/dcpomatic.mo"
 SetOutPath "$INSTDIR\locale\it\LC_MESSAGES"
-File "%binaries%/src/lib/mo/it_IT/libdvdomatic.mo"
-File "%binaries%/src/wx/mo/it_IT/libdvdomatic-wx.mo"
-File "%binaries%/src/tools/mo/it_IT/dvdomatic.mo"
+File "%binaries%/src/lib/mo/it_IT/libdcpomatic.mo"
+File "%binaries%/src/wx/mo/it_IT/libdcpomatic-wx.mo"
+File "%binaries%/src/tools/mo/it_IT/dcpomatic.mo"
 SetOutPath "$INSTDIR\locale\es\LC_MESSAGES"
-File "%binaries%/src/lib/mo/es_ES/libdvdomatic.mo"
-File "%binaries%/src/wx/mo/es_ES/libdvdomatic-wx.mo"
-File "%binaries%/src/tools/mo/es_ES/dvdomatic.mo"
+File "%binaries%/src/lib/mo/es_ES/libdcpomatic.mo"
+File "%binaries%/src/wx/mo/es_ES/libdcpomatic-wx.mo"
+File "%binaries%/src/tools/mo/es_ES/dcpomatic.mo"
 SetOutPath "$INSTDIR\locale\sv\LC_MESSAGES"
-File "%binaries%/src/lib/mo/sv_SE/libdvdomatic.mo"
-File "%binaries%/src/wx/mo/sv_SE/libdvdomatic-wx.mo"
-File "%binaries%/src/tools/mo/sv_SE/dvdomatic.mo"
+File "%binaries%/src/lib/mo/sv_SE/libdcpomatic.mo"
+File "%binaries%/src/wx/mo/sv_SE/libdcpomatic-wx.mo"
+File "%binaries%/src/tools/mo/sv_SE/dcpomatic.mo"
 
-CreateShortCut "$DESKTOP\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" ""
-CreateShortCut "$DESKTOP\DVD-o-matic batch converter.lnk" "$INSTDIR\bin\dvdomatic_batch.exe" ""
-CreateShortCut "$DESKTOP\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" ""
+CreateShortCut "$DESKTOP\DCP-o-matic.lnk" "$INSTDIR\bin\dcpomatic.exe" ""
+CreateShortCut "$DESKTOP\DCP-o-matic batch converter.lnk" "$INSTDIR\bin\dcpomatic_batch.exe" ""
+CreateShortCut "$DESKTOP\DCP-o-matic encode server.lnk" "$INSTDIR\bin\dcpomatic_server.exe" ""
  
-CreateDirectory "$SMPROGRAMS\DVD-o-matic"
-CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall DVD-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
-CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" "" "$INSTDIR\bin\dvdomatic.exe" 0
-CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic batch converter.lnk" "$INSTDIR\bin\dvdomatic_batch.exe" "" "$INSTDIR\bin\dvdomatic_batch.exe" 0
-CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" "" "$INSTDIR\bin\servomatic_gui.exe" 0
+CreateDirectory "$SMPROGRAMS\DCP-o-matic"
+CreateShortCut "$SMPROGRAMS\DCP-o-matic\Uninstall DCP-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
+CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic.lnk" "$INSTDIR\bin\dcpomatic.exe" "" "$INSTDIR\bin\dcpomatic.exe" 0
+CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic batch converter.lnk" "$INSTDIR\bin\dcpomatic.exe" "" "$INSTDIR\bin\dcpomatic_batch.exe" 0
+CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic encode server.lnk" "$INSTDIR\bin\dcpomatic_server.exe" "" "$INSTDIR\bin\dcpomatic_server.exe" 0
  
-WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "DisplayName" "DVD-o-matic (remove only)"
-WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
+WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "DisplayName" "DCP-o-matic (remove only)"
+WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
  
 WriteUninstaller "$INSTDIR\Uninstall.exe"
  
@@ -136,12 +137,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
index e98a3a6d8c839249e6e161fad15864e3440ab087..4bf959119579972df9d33171cbf2d26805e7dd60 100644 (file)
@@ -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 @@ ${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"
@@ -90,15 +90,16 @@ File "%deps%/bin/libpixman-1-0.dll"
 File "%deps%/bin/libfontconfig-1.dll"
 File "%deps%/bin/libexpat-1.dll"
 File "%deps%/bin/libbz2.dll"
+File "%deps%/bin/cxml.dll"
 File "%deps%/bin/ffprobe.exe"
 
-File "%binaries%/src/wx/dvdomatic-wx.dll"
-File "%binaries%/src/lib/dvdomatic.dll"
-File "%binaries%/src/tools/dvdomatic.exe"
-File "%binaries%/src/tools/dvdomatic_batch.exe"
-File "%binaries%/src/tools/makedcp.exe"
-File "%binaries%/src/tools/servomatic_cli.exe"
-File "%binaries%/src/tools/servomatic_gui.exe"
+File "%binaries%/src/wx/dcpomatic-wx.dll"
+File "%binaries%/src/lib/dcpomatic.dll"
+File "%binaries%/src/tools/dcpomatic.exe"
+File "%binaries%/src/tools/dcpomatic_batch.exe"
+File "%binaries%/src/tools/dcpomatic_cli.exe"
+File "%binaries%/src/tools/dcpomatic_server_cli.exe"
+File "%binaries%/src/tools/dcpomatic_server.exe"
 
 # I don't know why, but sometimes it seems that 
 # delegates.xml must be in with the binaries, and
@@ -108,34 +109,34 @@ SetOutPath "$PROFILE\.magick"
 File "%deps%/etc/ImageMagick/delegates.xml"
 
 SetOutPath "$INSTDIR\locale\fr\LC_MESSAGES"
-File "%binaries%/src/lib/mo/fr_FR/libdvdomatic.mo"
-File "%binaries%/src/wx/mo/fr_FR/libdvdomatic-wx.mo"
-File "%binaries%/src/tools/mo/fr_FR/dvdomatic.mo"
+File "%binaries%/src/lib/mo/fr_FR/libdcpomatic.mo"
+File "%binaries%/src/wx/mo/fr_FR/libdcpomatic-wx.mo"
+File "%binaries%/src/tools/mo/fr_FR/dcpomatic.mo"
 SetOutPath "$INSTDIR\locale\it\LC_MESSAGES"
-File "%binaries%/src/lib/mo/it_IT/libdvdomatic.mo"
-File "%binaries%/src/wx/mo/it_IT/libdvdomatic-wx.mo"
-File "%binaries%/src/tools/mo/it_IT/dvdomatic.mo"
+File "%binaries%/src/lib/mo/it_IT/libdcpomatic.mo"
+File "%binaries%/src/wx/mo/it_IT/libdcpomatic-wx.mo"
+File "%binaries%/src/tools/mo/it_IT/dcpomatic.mo"
 SetOutPath "$INSTDIR\locale\es\LC_MESSAGES"
-File "%binaries%/src/lib/mo/es_ES/libdvdomatic.mo"
-File "%binaries%/src/wx/mo/es_ES/libdvdomatic-wx.mo"
-File "%binaries%/src/tools/mo/es_ES/dvdomatic.mo"
+File "%binaries%/src/lib/mo/es_ES/libdcpomatic.mo"
+File "%binaries%/src/wx/mo/es_ES/libdcpomatic-wx.mo"
+File "%binaries%/src/tools/mo/es_ES/dcpomatic.mo"
 SetOutPath "$INSTDIR\locale\sv\LC_MESSAGES"
-File "%binaries%/src/lib/mo/sv_SE/libdvdomatic.mo"
-File "%binaries%/src/wx/mo/sv_SE/libdvdomatic-wx.mo"
-File "%binaries%/src/tools/mo/sv_SE/dvdomatic.mo"
+File "%binaries%/src/lib/mo/sv_SE/libdcpomatic.mo"
+File "%binaries%/src/wx/mo/sv_SE/libdcpomatic-wx.mo"
+File "%binaries%/src/tools/mo/sv_SE/dcpomatic.mo"
 
-CreateShortCut "$DESKTOP\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" ""
-CreateShortCut "$DESKTOP\DVD-o-matic batch converter.lnk" "$INSTDIR\bin\dvdomatic_batch.exe" ""
-CreateShortCut "$DESKTOP\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" ""
+CreateShortCut "$DESKTOP\DCP-o-matic.lnk" "$INSTDIR\bin\dcpomatic.exe" ""
+CreateShortCut "$DESKTOP\DCP-o-matic batch converter.lnk" "$INSTDIR\bin\dcpomatic_batch.exe" ""
+CreateShortCut "$DESKTOP\DCP-o-matic encode server.lnk" "$INSTDIR\bin\dcpomatic_server.exe" ""
  
-CreateDirectory "$SMPROGRAMS\DVD-o-matic"
-CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall DVD-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
-CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" "" "$INSTDIR\bin\dvdomatic.exe" 0
-CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic batch converter.lnk" "$INSTDIR\bin\dvdomatic.exe" "" "$INSTDIR\bin\dvdomatic_batch.exe" 0
-CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" "" "$INSTDIR\bin\servomatic_gui.exe" 0
+CreateDirectory "$SMPROGRAMS\DCP-o-matic"
+CreateShortCut "$SMPROGRAMS\DCP-o-matic\Uninstall DCP-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
+CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic.lnk" "$INSTDIR\bin\dcpomatic.exe" "" "$INSTDIR\bin\dcpomatic.exe" 0
+CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic batch converter.lnk" "$INSTDIR\bin\dcpomatic.exe" "" "$INSTDIR\bin\dcpomatic_batch.exe" 0
+CreateShortCut "$SMPROGRAMS\DCP-o-matic\DCP-o-matic encode server.lnk" "$INSTDIR\bin\dcpomatic_server.exe" "" "$INSTDIR\bin\dcpomatic_server.exe" 0
  
-WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "DisplayName" "DVD-o-matic (remove only)"
-WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
+WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "DisplayName" "DCP-o-matic (remove only)"
+WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
  
 WriteUninstaller "$INSTDIR\Uninstall.exe"
  
@@ -146,12 +147,12 @@ Section "Uninstall"
  
 RMDir /r "$INSTDIR\*.*"    
 RMDir "$INSTDIR"
-Delete "$DESKTOP\DVD-o-matic.lnk"
-Delete "$DESKTOP\DVD-o-matic batch converter.lnk"
-Delete "$DESKTOP\DVD-o-matic encode server.lnk"
-Delete "$SMPROGRAMS\DVD-o-matic\*.*"
-RmDir  "$SMPROGRAMS\DVD-o-matic"
-DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DVD-o-matic"
-DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic"
+Delete "$DESKTOP\DCP-o-matic.lnk"
+Delete "$DESKTOP\DCP-o-matic batch converter.lnk"
+Delete "$DESKTOP\DCP-o-matic encode server.lnk"
+Delete "$SMPROGRAMS\DCP-o-matic\*.*"
+RmDir  "$SMPROGRAMS\DCP-o-matic"
+DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DCP-o-matic"
+DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic"
  
 SectionEnd
diff --git a/run/dcpomatic b/run/dcpomatic
new file mode 100755 (executable)
index 0000000..7ea0877
--- /dev/null
@@ -0,0 +1,15 @@
+#!/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/dcpomatic $*
+elif [ "$1" == "--valgrind" ]; then
+    shift
+    valgrind --tool="memcheck" build/src/tools/dcpomatic $*
+elif [ "$1" == "--i18n" ]; then
+    shift
+    LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 build/src/tools/dcpomatic "$*"
+else
+    build/src/tools/dcpomatic $*
+fi
diff --git a/run/dcpomatic.bat b/run/dcpomatic.bat
new file mode 100644 (file)
index 0000000..abc867e
--- /dev/null
@@ -0,0 +1,4 @@
+
+set PATH=%PATH%:src\lib:..\..\Environments\64\bin
+build\src\tools\dvdomatic.exe
+pause
\ No newline at end of file
diff --git a/run/dcpomatic_cli b/run/dcpomatic_cli
new file mode 100755 (executable)
index 0000000..bf2f080
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH:build/src
+if [ "$1" == "--debug" ]; then
+    shift
+    gdb --args build/src/tools/dcpomatic_cli "$@"
+elif [ "$1" == "--valgrind" ]; then
+    shift
+    valgrind --tool="memcheck" --leak-check=full --show-reachable=yes build/src/tools/dcpomatic_cli "$@"
+else
+    build/src/tools/dcpomatic_cli "$@"
+fi
diff --git a/run/dvdomatic b/run/dvdomatic
deleted file mode 100755 (executable)
index 147c001..0000000
+++ /dev/null
@@ -1,15 +0,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 "$*"
-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 --git a/run/dvdomatic_batch b/run/dvdomatic_batch
deleted file mode 100755 (executable)
index 7b6ef93..0000000
+++ /dev/null
@@ -1,15 +0,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
diff --git a/run/makedcp b/run/makedcp
deleted file mode 100755 (executable)
index 2b95ea1..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-
-export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH:build/src
-if [ "$1" == "--debug" ]; then
-    shift
-    gdb --args build/src/tools/makedcp "$@"
-elif [ "$1" == "--memcheck" ]; then
-    shift
-    valgrind --tool="memcheck" --leak-check=full --show-reachable=yes build/src/tools/makedcp "$@"
-elif [ "$1" == "--massif" ]; then
-    shift
-    valgrind --tool="massif" build/src/tools/makedcp "$@"
-else
-    build/src/tools/makedcp "$@"
-fi
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc
deleted file mode 100644 (file)
index a204677..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#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"
-
-using std::string;
-using boost::shared_ptr;
-
-/** @param f Film to compare.
- *  @param o Decode options.
- */
-ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, DecodeOptions o)
-       : 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 ());
-}
-
-string
-ABTranscodeJob::name () const
-{
-       return String::compose (_("A/B transcode %1"), _film->name());
-}
-
-void
-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)));
-               w.go ();
-               set_progress (1);
-               set_state (FINISHED_OK);
-
-               _film->log()->log ("A/B transcode job completed successfully");
-
-       } catch (std::exception& e) {
-
-               set_progress (1);
-               set_state (FINISHED_ERROR);
-               _film->log()->log (String::compose ("A/B transcode job failed (%1)", e.what()));
-               throw;
-       }
-}
diff --git a/src/lib/ab_transcode_job.h b/src/lib/ab_transcode_job.h
deleted file mode 100644 (file)
index 8e3cbe2..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/ab_transcode_job.h
- *  @brief Job to run a transcoder which produces output for A/B comparison of various settings.
- */
-
-#include <boost/shared_ptr.hpp>
-#include "job.h"
-#include "options.h"
-
-class Film;
-
-/** @class ABTranscodeJob
- *  @brief Job to run a transcoder which produces output for A/B comparison of various settings.
- *
- *  The right half of the frame will be processed using the Film supplied;
- *  the left half will be processed using the same state but with the reference
- *  filters and scaler.
- */
-class ABTranscodeJob : public Job
-{
-public:
-       ABTranscodeJob (
-               boost::shared_ptr<Film> f,
-               DecodeOptions o
-               );
-
-       std::string name () const;
-       void run ();
-
-private:
-       DecodeOptions _decode_opt;
-       
-       /** Copy of our Film using the reference filters and scaler */
-       boost::shared_ptr<Film> _film_b;
-};
diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc
deleted file mode 100644 (file)
index c42f0d2..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <iostream>
-#include <boost/shared_ptr.hpp>
-#include "ab_transcoder.h"
-#include "film.h"
-#include "video_decoder.h"
-#include "audio_decoder.h"
-#include "encoder.h"
-#include "job.h"
-#include "options.h"
-#include "image.h"
-#include "decoder_factory.h"
-#include "matcher.h"
-#include "delay_line.h"
-#include "gain.h"
-#include "combiner.h"
-#include "trimmer.h"
-
-/** @file src/ab_transcoder.cc
- *  @brief A transcoder which uses one Film for the left half of the screen, and a different one
- *  for the right half (to facilitate A/B comparisons of settings)
- */
-
-using std::string;
-using boost::shared_ptr;
-using boost::dynamic_pointer_cast;
-
-/** @param a Film to use for the left half of the screen.
- *  @param b Film to use for the right half of the screen.
- *  @param o Decoder options.
- *  @param j Job that we are associated with.
- *  @param e Encoder to use.
- */
-
-ABTranscoder::ABTranscoder (
-       shared_ptr<Film> a, shared_ptr<Film> b, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
-       : _film_a (a)
-       , _film_b (b)
-       , _job (j)
-       , _encoder (e)
-       , _combiner (new Combiner (a->log()))
-{
-       _da = decoder_factory (_film_a, o);
-       _db = decoder_factory (_film_b, o);
-
-       shared_ptr<AudioStream> st = _film_a->audio_stream();
-       if (st && st->sample_rate()) {
-               _matcher.reset (new Matcher (_film_a->log(), st->sample_rate(), _film_a->source_frame_rate()));
-       }
-       _delay_line.reset (new DelayLine (_film_a->log(), _film_a->audio_delay() / 1000.0f));
-       _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain()));
-
-       int const sr = st ? st->sample_rate() : 0;
-       int const trim_start = _film_a->trim_type() == Film::ENCODE ? _film_a->trim_start() : 0;
-       int const trim_end = _film_a->trim_type() == Film::ENCODE ? _film_a->trim_end() : 0;
-       _trimmer.reset (new Trimmer (
-                               _film_a->log(), trim_start, trim_end, _film_a->length().get(),
-                               sr, _film_a->source_frame_rate(), _film_a->dcp_frame_rate()
-                               ));
-       
-       /* Set up the decoder to use the film's set streams */
-       _da.video->set_subtitle_stream (_film_a->subtitle_stream ());
-       _db.video->set_subtitle_stream (_film_a->subtitle_stream ());
-       if (_film_a->audio_stream ()) {
-               _da.audio->set_audio_stream (_film_a->audio_stream ());
-       }
-
-       _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3, _4));
-       _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3, _4));
-
-       _combiner->connect_video (_delay_line);
-       if (_matcher) {
-               _delay_line->connect_video (_matcher);
-               _matcher->connect_video (_trimmer);
-       } else {
-               _delay_line->connect_video (_trimmer);
-       }
-       _trimmer->connect_video (_encoder);
-       
-       _da.audio->connect_audio (_delay_line);
-       if (_matcher) {
-               _delay_line->connect_audio (_matcher);
-               _matcher->connect_audio (_gain);
-       } else {
-               _delay_line->connect_audio (_gain);
-       }
-       _gain->connect_audio (_trimmer);
-       _trimmer->connect_audio (_encoder);
-}
-
-void
-ABTranscoder::go ()
-{
-       _encoder->process_begin ();
-
-       bool done[3] = { false, false, false };
-       
-       while (1) {
-               done[0] = _da.video->pass ();
-               done[1] = _db.video->pass ();
-               
-               if (!done[2] && _da.audio && dynamic_pointer_cast<Decoder> (_da.audio) != dynamic_pointer_cast<Decoder> (_da.video)) {
-                       done[2] = _da.audio->pass ();
-               } else {
-                       done[2] = true;
-               }
-               
-               if (_job) {
-                       _da.video->set_progress (_job);
-               }
-               
-               if (done[0] && done[1] && done[2]) {
-                       break;
-               }
-       }
-               
-       _delay_line->process_end ();
-       if (_matcher) {
-               _matcher->process_end ();
-       }
-       _gain->process_end ();
-       _encoder->process_end ();
-}
-                           
diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h
deleted file mode 100644 (file)
index 4f1b14e..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/ab_transcoder.h
- *  @brief A transcoder which uses one Film for the left half of the screen, and a different one
- *  for the right half (to facilitate A/B comparisons of settings)
- */
-
-#include <boost/shared_ptr.hpp>
-#include <stdint.h>
-#include "util.h"
-#include "decoder_factory.h"
-
-class Job;
-class Encoder;
-class VideoDecoder;
-class AudioDecoder;
-class Image;
-class Log;
-class Subtitle;
-class Film;
-class Matcher;
-class DelayLine;
-class Gain;
-class Combiner;
-class Trimmer;
-
-/** @class ABTranscoder
- *  @brief A transcoder which uses one Film for the left half of the screen, and a different one
- *  for the right half (to facilitate A/B comparisons of settings)
- */
-class ABTranscoder
-{
-public:
-       ABTranscoder (
-               boost::shared_ptr<Film> a,
-               boost::shared_ptr<Film> b,
-               DecodeOptions o,
-               Job* j,
-               boost::shared_ptr<Encoder> e
-               );
-       
-       void go ();
-
-private:
-       boost::shared_ptr<Film> _film_a;
-       boost::shared_ptr<Film> _film_b;
-       Job* _job;
-       boost::shared_ptr<Encoder> _encoder;
-       Decoders _da;
-       Decoders _db;
-       boost::shared_ptr<Combiner> _combiner;
-       boost::shared_ptr<Matcher> _matcher;
-       boost::shared_ptr<DelayLine> _delay_line;
-       boost::shared_ptr<Gain> _gain;
-       boost::shared_ptr<Trimmer> _trimmer;
-       boost::shared_ptr<Image> _image;
-};
index 88cd65fee64f0227d24960600b6664dc46721ae1..2848c1ed773c16dfe5b6acbf0b3c0f6b61f65aac 100644 (file)
@@ -21,9 +21,7 @@
 #include "analyse_audio_job.h"
 #include "compose.hpp"
 #include "film.h"
-#include "options.h"
-#include "decoder_factory.h"
-#include "audio_decoder.h"
+#include "player.h"
 
 #include "i18n.h"
 
@@ -35,8 +33,9 @@ using boost::shared_ptr;
 
 int const AnalyseAudioJob::_num_points = 1024;
 
-AnalyseAudioJob::AnalyseAudioJob (shared_ptr<Film> f)
+AnalyseAudioJob::AnalyseAudioJob (shared_ptr<const Film> f, shared_ptr<AudioContent> c)
        : Job (f)
+       , _content (c)
        , _done (0)
        , _samples_per_point (1)
 {
@@ -46,45 +45,47 @@ AnalyseAudioJob::AnalyseAudioJob (shared_ptr<Film> f)
 string
 AnalyseAudioJob::name () const
 {
-       return String::compose (_("Analyse audio of %1"), _film->name());
+       shared_ptr<AudioContent> content = _content.lock ();
+       if (!content) {
+               return "";
+       }
+       
+       return String::compose (_("Analyse audio of %1"), content->file().filename());
 }
 
 void
 AnalyseAudioJob::run ()
 {
-       if (!_film->audio_stream () || !_film->length()) {
-               set_progress (1);
-               set_state (FINISHED_ERROR);
+       shared_ptr<AudioContent> content = _content.lock ();
+       if (!content) {
                return;
        }
-               
-       DecodeOptions options;
-       options.decode_video = false;
 
-       Decoders decoders = decoder_factory (_film, options);
-       assert (decoders.audio);
+       shared_ptr<Playlist> playlist (new Playlist);
+       playlist->add (content);
+       shared_ptr<Player> player (new Player (_film, playlist));
+       player->disable_video ();
        
-       decoders.audio->set_audio_stream (_film->audio_stream ());
-       decoders.audio->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1));
+       player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1, _2));
 
-       int64_t total_audio_frames = video_frames_to_audio_frames (_film->length().get(), _film->audio_stream()->sample_rate(), _film->source_frame_rate());
-       _samples_per_point = max (int64_t (1), total_audio_frames / _num_points);
+       _samples_per_point = max (int64_t (1), _film->time_to_audio_frames (_film->length()) / _num_points);
 
-       _current.resize (_film->audio_stream()->channels ());
-       _analysis.reset (new AudioAnalysis (_film->audio_stream()->channels()));
-                        
-       while (!decoders.audio->pass()) {
-               set_progress (float (_done) / total_audio_frames);
+       _current.resize (_film->dcp_audio_channels ());
+       _analysis.reset (new AudioAnalysis (_film->dcp_audio_channels ()));
+
+       _done = 0;
+       while (!player->pass ()) {
+               set_progress (double (_film->audio_frames_to_time (_done)) / _film->length ());
        }
 
-       _analysis->write (_film->audio_analysis_path ());
+       _analysis->write (content->audio_analysis_path ());
        
        set_progress (1);
        set_state (FINISHED_OK);
 }
 
 void
-AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b)
+AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time)
 {
        for (int i = 0; i < b->frames(); ++i) {
                for (int j = 0; j < b->channels(); ++j) {
@@ -100,7 +101,7 @@ AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b)
                        if ((_done % _samples_per_point) == 0) {
                                _current[j][AudioPoint::RMS] = sqrt (_current[j][AudioPoint::RMS] / _samples_per_point);
                                _analysis->add_point (j, _current[j]);
-                               
+
                                _current[j] = AudioPoint ();
                        }
                }
index 5435e0a7cfb5401fe5efc2901ac367f7b23c1826..3d4881983b5234572ce40a47455c5b849a620328 100644 (file)
 
 #include "job.h"
 #include "audio_analysis.h"
+#include "types.h"
 
 class AudioBuffers;
+class AudioContent;
 
 class AnalyseAudioJob : public Job
 {
 public:
-       AnalyseAudioJob (boost::shared_ptr<Film> f);
+       AnalyseAudioJob (boost::shared_ptr<const Film>, boost::shared_ptr<AudioContent>);
 
        std::string name () const;
        void run ();
 
 private:
-       void audio (boost::shared_ptr<const AudioBuffers>);
+       void audio (boost::shared_ptr<const AudioBuffers>, Time);
 
-       int64_t _done;
+       boost::weak_ptr<AudioContent> _content;
+       OutputAudioFrame _done;
        int64_t _samples_per_point;
        std::vector<AudioPoint> _current;
 
index 9d708bbfdd07f30e07327781cc328828a4ecce06..e1251662089bd543fc3d713eadb8748ca37c8a9d 100644 (file)
@@ -62,9 +62,9 @@ AudioAnalysis::AudioAnalysis (int channels)
        _data.resize (channels);
 }
 
-AudioAnalysis::AudioAnalysis (string filename)
+AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
 {
-       ifstream f (filename.c_str ());
+       ifstream f (filename.string().c_str ());
 
        int channels;
        f >> channels;
@@ -107,10 +107,10 @@ AudioAnalysis::points (int c) const
 }
 
 void
-AudioAnalysis::write (string filename)
+AudioAnalysis::write (boost::filesystem::path filename)
 {
-       string tmp = filename + ".tmp";
-       
+       string tmp = filename.string() + ".tmp";
+
        ofstream f (tmp.c_str ());
        f << _data.size() << "\n";
        for (vector<vector<AudioPoint> >::iterator i = _data.begin(); i != _data.end(); ++i) {
index 6e0e2b78a8cf7a5a52f72586259eefb6eb238450..d57eba90a3184200a18619bcb0ac4b0727998038 100644 (file)
 
 */
 
-#ifndef DVDOMATIC_AUDIO_ANALYSIS_H
-#define DVDOMATIC_AUDIO_ANALYSIS_H
+#ifndef DCPOMATIC_AUDIO_ANALYSIS_H
+#define DCPOMATIC_AUDIO_ANALYSIS_H
 
 #include <iostream>
 #include <vector>
 #include <list>
+#include <boost/filesystem.hpp>
 
 class AudioPoint
 {
@@ -50,7 +51,7 @@ class AudioAnalysis
 {
 public:
        AudioAnalysis (int c);
-       AudioAnalysis (std::string);
+       AudioAnalysis (boost::filesystem::path);
 
        void add_point (int c, AudioPoint const & p);
        
@@ -58,7 +59,7 @@ public:
        int points (int c) const;
        int channels () const;
 
-       void write (std::string);
+       void write (boost::filesystem::path);
 
 private:
        std::vector<std::vector<AudioPoint> > _data;
diff --git a/src/lib/audio_buffers.cc b/src/lib/audio_buffers.cc
new file mode 100644 (file)
index 0000000..403baba
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+    Copyright (C) 2012-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 <cassert>
+#include <cstring>
+#include <stdexcept>
+#include "audio_buffers.h"
+
+using std::bad_alloc;
+using boost::shared_ptr;
+
+/** 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 = static_cast<float**> (malloc (_channels * sizeof (float *)));
+       if (!_data) {
+               throw bad_alloc ();
+       }
+       
+       for (int i = 0; i < _channels; ++i) {
+               _data[i] = static_cast<float*> (malloc (frames * sizeof (float)));
+               if (!_data[i]) {
+                       throw bad_alloc ();
+               }
+       }
+}
+
+/** 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 = static_cast<float**> (malloc (_channels * sizeof (float *)));
+       if (!_data) {
+               throw bad_alloc ();
+       }
+       
+       for (int i = 0; i < _channels; ++i) {
+               _data[i] = static_cast<float*> (malloc (_frames * sizeof (float)));
+               if (!_data[i]) {
+                       throw bad_alloc ();
+               }
+               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 = static_cast<float**> (malloc (_channels * sizeof (float *)));
+       if (!_data) {
+               throw bad_alloc ();
+       }
+       
+       for (int i = 0; i < _channels; ++i) {
+               _data[i] = static_cast<float*> (malloc (_frames * sizeof (float)));
+               if (!_data[i]) {
+                       throw bad_alloc ();
+               }
+               memcpy (_data[i], other->_data[i], _frames * sizeof (float));
+       }
+}
+
+/** AudioBuffers destructor */
+AudioBuffers::~AudioBuffers ()
+{
+       for (int i = 0; i < _channels; ++i) {
+               free (_data[i]);
+       }
+
+       free (_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 const * 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));
+       }
+}
+
+/** Add data from from `from', `from_channel' to our channel `to_channel' */
+void
+AudioBuffers::accumulate_channel (AudioBuffers const * from, int from_channel, int to_channel)
+{
+       int const N = frames ();
+       assert (from->frames() == N);
+
+       float* s = from->data (from_channel);
+       float* d = _data[to_channel];
+
+       for (int i = 0; i < N; ++i) {
+               *d++ += *s++;
+       }
+}
+
+/** Ensure we have space for at least a certain number of frames.  If we extend
+ *  the buffers, fill the new space with silence.
+ */
+void
+AudioBuffers::ensure_size (int frames)
+{
+       if (_allocated_frames >= frames) {
+               return;
+       }
+
+       for (int i = 0; i < _channels; ++i) {
+               _data[i] = static_cast<float*> (realloc (_data[i], frames * sizeof (float)));
+               if (!_data[i]) {
+                       throw bad_alloc ();
+               }
+               for (int j = _allocated_frames; j < frames; ++j) {
+                       _data[i][j] = 0;
+               }
+       }
+
+       _allocated_frames = frames;
+}
+
+void
+AudioBuffers::accumulate_frames (AudioBuffers const * from, int read_offset, int write_offset, int frames)
+{
+       assert (_channels == from->channels ());
+
+       for (int i = 0; i < _channels; ++i) {
+               for (int j = 0; j < frames; ++j) {
+                       _data[i][j + write_offset] += from->data()[i][j + read_offset];
+               }
+       }
+}
+
diff --git a/src/lib/audio_buffers.h b/src/lib/audio_buffers.h
new file mode 100644 (file)
index 0000000..47b8145
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+    Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/shared_ptr.hpp>
+
+/** @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 ();
+
+       void ensure_size (int);
+
+       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 const * from, int frames_to_copy, int read_offset, int write_offset);
+       void move (int from, int to, int frames);
+       void accumulate_channel (AudioBuffers const *, int, int);
+       void accumulate_frames (AudioBuffers const *, int read_offset, int write_offset, 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;
+};
diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc
new file mode 100644 (file)
index 0000000..e93f348
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "audio_content.h"
+#include "analyse_audio_job.h"
+#include "job_manager.h"
+#include "film.h"
+
+using std::string;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::dynamic_pointer_cast;
+
+int const AudioContentProperty::AUDIO_CHANNELS = 200;
+int const AudioContentProperty::AUDIO_LENGTH = 201;
+int const AudioContentProperty::AUDIO_FRAME_RATE = 202;
+int const AudioContentProperty::AUDIO_GAIN = 203;
+int const AudioContentProperty::AUDIO_DELAY = 204;
+int const AudioContentProperty::AUDIO_MAPPING = 205;
+
+AudioContent::AudioContent (shared_ptr<const Film> f, Time s)
+       : Content (f, s)
+       , _audio_gain (0)
+       , _audio_delay (0)
+{
+
+}
+
+AudioContent::AudioContent (shared_ptr<const Film> f, boost::filesystem::path p)
+       : Content (f, p)
+       , _audio_gain (0)
+       , _audio_delay (0)
+{
+
+}
+
+AudioContent::AudioContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+       : Content (f, node)
+{
+       _audio_gain = node->number_child<float> ("AudioGain");
+       _audio_delay = node->number_child<int> ("AudioDelay");
+}
+
+AudioContent::AudioContent (AudioContent const & o)
+       : Content (o)
+       , _audio_gain (o._audio_gain)
+       , _audio_delay (o._audio_delay)
+{
+
+}
+
+void
+AudioContent::as_xml (xmlpp::Node* node) const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       node->add_child("AudioGain")->add_child_text (lexical_cast<string> (_audio_gain));
+       node->add_child("AudioDelay")->add_child_text (lexical_cast<string> (_audio_delay));
+}
+
+
+void
+AudioContent::set_audio_gain (float g)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _audio_gain = g;
+       }
+       
+       signal_changed (AudioContentProperty::AUDIO_GAIN);
+}
+
+void
+AudioContent::set_audio_delay (int d)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _audio_delay = d;
+       }
+       
+       signal_changed (AudioContentProperty::AUDIO_DELAY);
+}
+
+void
+AudioContent::analyse_audio (boost::function<void()> finished)
+{
+       shared_ptr<const Film> film = _film.lock ();
+       if (!film) {
+               return;
+       }
+       
+       shared_ptr<AnalyseAudioJob> job (new AnalyseAudioJob (film, dynamic_pointer_cast<AudioContent> (shared_from_this())));
+       job->Finished.connect (finished);
+       JobManager::instance()->add (job);
+}
+
+boost::filesystem::path
+AudioContent::audio_analysis_path () const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       if (!film) {
+               return boost::filesystem::path ();
+       }
+
+       return film->audio_analysis_path (dynamic_pointer_cast<const AudioContent> (shared_from_this ()));
+}
diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h
new file mode 100644 (file)
index 0000000..9bf53e0
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+    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_AUDIO_CONTENT_H
+#define DCPOMATIC_AUDIO_CONTENT_H
+
+#include "content.h"
+#include "audio_mapping.h"
+
+namespace cxml {
+       class Node;
+}
+
+class AudioContentProperty
+{
+public:
+       static int const AUDIO_CHANNELS;
+       static int const AUDIO_LENGTH;
+       static int const AUDIO_FRAME_RATE;
+       static int const AUDIO_GAIN;
+       static int const AUDIO_DELAY;
+       static int const AUDIO_MAPPING;
+};
+
+class AudioContent : public virtual Content
+{
+public:
+       typedef int64_t Frame;
+       
+       AudioContent (boost::shared_ptr<const Film>, Time);
+       AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+       AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+       AudioContent (AudioContent const &);
+
+       void as_xml (xmlpp::Node *) const;
+
+        virtual int audio_channels () const = 0;
+        virtual AudioContent::Frame audio_length () const = 0;
+        virtual int content_audio_frame_rate () const = 0;
+       virtual int output_audio_frame_rate () const = 0;
+       virtual AudioMapping audio_mapping () const = 0;
+       virtual void set_audio_mapping (AudioMapping) = 0;
+
+       void analyse_audio (boost::function<void()>);
+       boost::filesystem::path audio_analysis_path () const;
+
+       void set_audio_gain (float);
+       void set_audio_delay (int);
+       
+       float audio_gain () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _audio_gain;
+       }
+
+       int audio_delay () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _audio_delay;
+       }
+
+private:
+       /** Gain to apply to audio in dB */
+       float _audio_gain;
+       /** Delay to apply to audio (positive moves audio later) in milliseconds */
+       int _audio_delay;
+};
+
+#endif
index a54c14843927449b4a62f00665ea83020a53bf70..dc49a1846e74ada429e20603d213666d858ecd52 100644 (file)
 */
 
 #include "audio_decoder.h"
-#include "stream.h"
+#include "audio_buffers.h"
+#include "exceptions.h"
+#include "log.h"
 
+#include "i18n.h"
+
+using std::stringstream;
+using std::list;
+using std::pair;
+using std::cout;
 using boost::optional;
 using boost::shared_ptr;
 
-AudioDecoder::AudioDecoder (shared_ptr<Film> f, DecodeOptions o)
-       : Decoder (f, o)
+AudioDecoder::AudioDecoder (shared_ptr<const Film> f)
+       : Decoder (f)
+       , _audio_position (0)
+{
+}
+
+#if 0
+void
+AudioDecoder::process_end ()
 {
+       if (_swr_context) {
+
+               shared_ptr<const Film> film = _film.lock ();
+               assert (film);
+               
+               shared_ptr<AudioBuffers> out (new AudioBuffers (film->audio_mapping().dcp_channels(), 256));
+                       
+               while (1) {
+                       int const frames = swr_convert (_swr_context, (uint8_t **) out->data(), 256, 0, 0);
+
+                       if (frames < 0) {
+                               throw EncodeError (_("could not run sample-rate converter"));
+                       }
+
+                       if (frames == 0) {
+                               break;
+                       }
+
+                       out->set_frames (frames);
+                       _writer->write (out);
+               }
 
+       }
 }
+#endif
 
 void
-AudioDecoder::set_audio_stream (shared_ptr<AudioStream> s)
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
 {
-       _audio_stream = s;
+       Audio (data, frame);
+       _audio_position = frame + data->frames ();
 }
index cfe94b5283dc48c06a83e516b526c789dae46861..ddfb296c9a9b4fffbf8e2b94928594907566fb68 100644 (file)
  *  @brief Parent class for audio decoders.
  */
 
-#ifndef DVDOMATIC_AUDIO_DECODER_H
-#define DVDOMATIC_AUDIO_DECODER_H
+#ifndef DCPOMATIC_AUDIO_DECODER_H
+#define DCPOMATIC_AUDIO_DECODER_H
 
-#include "audio_source.h"
-#include "stream.h"
 #include "decoder.h"
+#include "content.h"
+
+class AudioBuffers;
 
 /** @class AudioDecoder.
  *  @brief Parent class for audio decoders.
  */
-class AudioDecoder : public TimedAudioSource, public virtual Decoder
+class AudioDecoder : public virtual Decoder
 {
 public:
-       AudioDecoder (boost::shared_ptr<Film>, DecodeOptions);
-
-       virtual void set_audio_stream (boost::shared_ptr<AudioStream>);
+       AudioDecoder (boost::shared_ptr<const Film>);
 
-       /** @return Audio stream that we are using */
-       boost::shared_ptr<AudioStream> audio_stream () const {
-               return _audio_stream;
-       }
-
-       /** @return All available audio streams */
-       std::vector<boost::shared_ptr<AudioStream> > audio_streams () const {
-               return _audio_streams;
-       }
+       /** Emitted when some audio data is ready */
+       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
 
 protected:
-       /** Audio stream that we are using */
-       boost::shared_ptr<AudioStream> _audio_stream;
-       /** All available audio streams */
-       std::vector<boost::shared_ptr<AudioStream> > _audio_streams;
+
+       void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+       AudioContent::Frame _audio_position;
 };
 
 #endif
diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc
new file mode 100644 (file)
index 0000000..4630f17
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/lexical_cast.hpp>
+#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
+#include "audio_mapping.h"
+
+using std::list;
+using std::cout;
+using std::make_pair;
+using std::pair;
+using std::string;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::dynamic_pointer_cast;
+
+AudioMapping::AudioMapping ()
+{
+
+}
+
+/** Create a default AudioMapping for a given channel count.
+ *  @param c Number of channels.
+ */
+AudioMapping::AudioMapping (int c)
+{
+       if (c == 1) {
+               /* Mono -> Centre */
+               add (0, libdcp::CENTRE);
+       } else {
+               /* 1:1 mapping */
+               for (int i = 0; i < c; ++i) {
+                       add (i, static_cast<libdcp::Channel> (i));
+               }
+       }
+}
+
+AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node)
+{
+       list<shared_ptr<cxml::Node> > const c = node->node_children ("Map");
+       for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+               add ((*i)->number_child<int> ("ContentIndex"), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP")));
+       }
+}
+
+void
+AudioMapping::add (int c, libdcp::Channel d)
+{
+       _content_to_dcp.push_back (make_pair (c, d));
+}
+
+list<int>
+AudioMapping::dcp_to_content (libdcp::Channel d) const
+{
+       list<int> c;
+       for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+               if (i->second == d) {
+                       c.push_back (i->first);
+               }
+       }
+
+       return c;
+}
+
+list<int>
+AudioMapping::content_channels () const
+{
+       list<int> c;
+       for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+               if (find (c.begin(), c.end(), i->first) == c.end ()) {
+                       c.push_back (i->first);
+               }
+       }
+
+       return c;
+}
+
+list<libdcp::Channel>
+AudioMapping::content_to_dcp (int c) const
+{
+       list<libdcp::Channel> d;
+       for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+               if (i->first == c) {
+                       d.push_back (i->second);
+               }
+       }
+
+       return d;
+}
+
+void
+AudioMapping::as_xml (xmlpp::Node* node) const
+{
+       for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+               xmlpp::Node* t = node->add_child ("Map");
+               t->add_child ("ContentIndex")->add_child_text (lexical_cast<string> (i->first));
+               t->add_child ("DCP")->add_child_text (lexical_cast<string> (i->second));
+       }
+}
diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h
new file mode 100644 (file)
index 0000000..a2de830
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+    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_AUDIO_MAPPING_H
+#define DCPOMATIC_AUDIO_MAPPING_H
+
+#include <list>
+#include <libdcp/types.h>
+#include <boost/shared_ptr.hpp>
+
+namespace xmlpp {
+       class Node;
+}
+
+namespace cxml {
+       class Node;
+}
+
+class AudioMapping
+{
+public:
+       AudioMapping ();
+       AudioMapping (int);
+       AudioMapping (boost::shared_ptr<const cxml::Node>);
+       
+       void as_xml (xmlpp::Node *) const;
+
+       void add (int, libdcp::Channel);
+
+       std::list<int> dcp_to_content (libdcp::Channel) const;
+       std::list<std::pair<int, libdcp::Channel> > content_to_dcp () const {
+               return _content_to_dcp;
+       }
+
+       std::list<int> content_channels () const;
+       std::list<libdcp::Channel> content_to_dcp (int) const;
+
+private:
+       std::list<std::pair<int, libdcp::Channel> > _content_to_dcp;
+};
+
+#endif
diff --git a/src/lib/audio_sink.h b/src/lib/audio_sink.h
deleted file mode 100644 (file)
index 69b3a4b..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#ifndef DVDOMATIC_AUDIO_SINK_H
-#define DVDOMATIC_AUDIO_SINK_H
-
-class AudioSink
-{
-public:
-       /** Call with some audio data */
-       virtual void process_audio (boost::shared_ptr<const AudioBuffers>) = 0;
-};
-
-class TimedAudioSink
-{
-public:
-        /** Call with some audio data */
-        virtual void process_audio (boost::shared_ptr<const AudioBuffers>, double t) = 0;
-};
-
-#endif
diff --git a/src/lib/audio_source.cc b/src/lib/audio_source.cc
deleted file mode 100644 (file)
index d77e893..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "audio_source.h"
-#include "audio_sink.h"
-
-using boost::shared_ptr;
-using boost::bind;
-
-void
-AudioSource::connect_audio (shared_ptr<AudioSink> s)
-{
-       Audio.connect (bind (&AudioSink::process_audio, s, _1));
-}
-
-void
-TimedAudioSource::connect_audio (shared_ptr<TimedAudioSink> s)
-{
-       Audio.connect (bind (&TimedAudioSink::process_audio, s, _1, _2));
-}
-
-void
-TimedAudioSource::connect_audio (shared_ptr<AudioSink> s)
-{
-       Audio.connect (bind (&AudioSink::process_audio, s, _1));
-}
diff --git a/src/lib/audio_source.h b/src/lib/audio_source.h
deleted file mode 100644 (file)
index c13f163..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file  src/audio_source.h
- *  @brief Parent class for classes which emit audio data.
- */
-
-#ifndef DVDOMATIC_AUDIO_SOURCE_H
-#define DVDOMATIC_AUDIO_SOURCE_H
-
-#include <boost/signals2.hpp>
-
-class AudioBuffers;
-class AudioSink;
-class TimedAudioSink;
-
-/** A class that emits audio data */
-class AudioSource
-{
-public:
-       /** Emitted when some audio data is ready */
-       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>)> Audio;
-
-       void connect_audio (boost::shared_ptr<AudioSink>);
-};
-
-
-/** A class that emits audio data with timestamps */
-class TimedAudioSource
-{
-public:
-       /** Emitted when some audio data is ready */
-       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, double)> Audio;
-
-       void connect_audio (boost::shared_ptr<AudioSink>);
-       void connect_audio (boost::shared_ptr<TimedAudioSink>);
-};
-
-#endif
diff --git a/src/lib/combiner.h b/src/lib/combiner.h
deleted file mode 100644 (file)
index 7ed316e..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/lib/combiner.h
- *  @brief Class for combining two video streams.
- */
-
-#include "processor.h"
-
-/** @class Combiner
- *  @brief A class which can combine two video streams into one, with
- *  one image used for the left half of the screen and the other for
- *  the right.
- */
-class Combiner : public TimedVideoProcessor
-{
-public:
-       Combiner (boost::shared_ptr<Log> log);
-
-       void process_video (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s, double);
-       void process_video_b (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s, double);
-
-private:
-       /** The image that we are currently working on */
-       boost::shared_ptr<Image> _image;
-};
index d2d7fa2fd1da001059a72a440b46e5b88ee11d62..e0fbcc703f58042948cb3a1dc83aa62d58f09fe3 100644 (file)
 #include <fstream>
 #include <glib.h>
 #include <boost/filesystem.hpp>
+#include <libcxml/cxml.h>
 #include "config.h"
 #include "server.h"
 #include "scaler.h"
 #include "filter.h"
-#include "format.h"
+#include "ratio.h"
 #include "dcp_content_type.h"
 #include "sound_processor.h"
 
@@ -36,8 +37,11 @@ using std::vector;
 using std::ifstream;
 using std::string;
 using std::ofstream;
+using std::list;
 using std::max;
 using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::optional;
 
 Config* Config::_instance = 0;
 
@@ -45,10 +49,10 @@ Config* Config::_instance = 0;
 Config::Config ()
        : _num_local_encoding_threads (max (2U, boost::thread::hardware_concurrency()))
        , _server_port (6192)
-       , _reference_scaler (Scaler::from_id (N_("bicubic")))
        , _tms_path (N_("."))
        , _sound_processor (SoundProcessor::from_id (N_("dolby_cp750")))
-       , _default_format (0)
+       , _default_still_length (10)
+       , _default_container (Ratio::from_id ("185"))
        , _default_dcp_content_type (0)
 {
        _allowed_dcp_frame_rates.push_back (24);
@@ -57,8 +61,61 @@ Config::Config ()
        _allowed_dcp_frame_rates.push_back (48);
        _allowed_dcp_frame_rates.push_back (50);
        _allowed_dcp_frame_rates.push_back (60);
+}
+
+void
+Config::read ()
+{
+       if (!boost::filesystem::exists (file (false))) {
+               read_old_metadata ();
+               return;
+       }
+
+       cxml::File f (file (false), "Config");
+       optional<string> c;
+
+       _num_local_encoding_threads = f.number_child<int> ("NumLocalEncodingThreads");
+       _default_directory = f.string_child ("DefaultDirectory");
+       _server_port = f.number_child<int> ("ServerPort");
        
-       ifstream f (file().c_str ());
+       list<shared_ptr<cxml::Node> > servers = f.node_children ("Server");
+       for (list<shared_ptr<cxml::Node> >::iterator i = servers.begin(); i != servers.end(); ++i) {
+               _servers.push_back (new ServerDescription (*i));
+       }
+
+       _tms_ip = f.string_child ("TMSIP");
+       _tms_path = f.string_child ("TMSPath");
+       _tms_user = f.string_child ("TMSUser");
+       _tms_password = f.string_child ("TMSPassword");
+
+       c = f.optional_string_child ("SoundProcessor");
+       if (c) {
+               _sound_processor = SoundProcessor::from_id (c.get ());
+       }
+
+       _language = f.optional_string_child ("Language");
+
+       c = f.optional_string_child ("DefaultContainer");
+       if (c) {
+               _default_container = Ratio::from_id (c.get ());
+       }
+
+       c = f.optional_string_child ("DefaultDCPContentType");
+       if (c) {
+               _default_dcp_content_type = DCPContentType::from_dci_name (c.get ());
+       }
+
+       _dcp_metadata.issuer = f.optional_string_child ("DCPMetadataIssuer").get_value_or ("");
+       _dcp_metadata.creator = f.optional_string_child ("DCPMetadataCreator").get_value_or ("");
+
+       _default_dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
+       _default_still_length = f.optional_number_child<int>("DefaultStillLength").get_value_or (10);
+}
+
+void
+Config::read_old_metadata ()
+{
+       ifstream f (file(true).c_str ());
        string line;
        while (getline (f, line)) {
                if (line.empty ()) {
@@ -83,10 +140,6 @@ Config::Config ()
                        _default_directory = v;
                } else if (k == N_("server_port")) {
                        _server_port = atoi (v.c_str ());
-               } else if (k == N_("reference_scaler")) {
-                       _reference_scaler = Scaler::from_id (v);
-               } else if (k == N_("reference_filter")) {
-                       _reference_filters.push_back (Filter::from_id (v));
                } else if (k == N_("server")) {
                        _servers.push_back (ServerDescription::create_from_metadata (v));
                } else if (k == N_("tms_ip")) {
@@ -101,8 +154,8 @@ Config::Config ()
                        _sound_processor = SoundProcessor::from_id (v);
                } else if (k == "language") {
                        _language = v;
-               } else if (k == "default_format") {
-                       _default_format = Format::from_metadata (v);
+               } else if (k == "default_container") {
+                       _default_container = Ratio::from_id (v);
                } else if (k == "default_dcp_content_type") {
                        _default_dcp_content_type = DCPContentType::from_dci_name (v);
                } else if (k == "dcp_metadata_issuer") {
@@ -113,19 +166,23 @@ Config::Config ()
                        _dcp_metadata.issue_date = v;
                }
 
-               _default_dci_metadata.read (k, v);
+               _default_dci_metadata.read_old_metadata (k, v);
        }
 }
 
 /** @return Filename to write configuration to */
 string
-Config::file () const
+Config::file (bool old) const
 {
        boost::filesystem::path p;
        p /= g_get_user_config_dir ();
        boost::system::error_code ec;
        boost::filesystem::create_directory (p, ec);
-       p /= N_(".dvdomatic");
+       if (old) {
+               p /= ".dvdomatic";
+       } else {
+               p /= ".dcpomatic.xml";
+       }
        return p.string ();
 }
 
@@ -135,6 +192,13 @@ Config::instance ()
 {
        if (_instance == 0) {
                _instance = new Config;
+               try {
+                       _instance->read ();
+               } catch (...) {
+                       /* configuration load failed; never mind, just
+                          stick with the default.
+                       */
+               }
        }
 
        return _instance;
@@ -144,44 +208,41 @@ Config::instance ()
 void
 Config::write () const
 {
-       ofstream f (file().c_str ());
-       f << "num_local_encoding_threads " << _num_local_encoding_threads << "\n"
-         << "default_directory " << _default_directory << "\n"
-         << "server_port " << _server_port << "\n";
-
-       if (_reference_scaler) {
-               f << "reference_scaler " << _reference_scaler->id () << "\n";
-       }
+       xmlpp::Document doc;
+       xmlpp::Element* root = doc.create_root_node ("Config");
 
-       for (vector<Filter const *>::const_iterator i = _reference_filters.begin(); i != _reference_filters.end(); ++i) {
-               f << "reference_filter " << (*i)->id () << "\n";
-       }
+       root->add_child("NumLocalEncodingThreads")->add_child_text (lexical_cast<string> (_num_local_encoding_threads));
+       root->add_child("DefaultDirectory")->add_child_text (_default_directory);
+       root->add_child("ServerPort")->add_child_text (lexical_cast<string> (_server_port));
        
        for (vector<ServerDescription*>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) {
-               f << "server " << (*i)->as_metadata () << "\n";
+               (*i)->as_xml (root->add_child ("Server"));
        }
 
-       f << "tms_ip " << _tms_ip << "\n";
-       f << "tms_path " << _tms_path << "\n";
-       f << "tms_user " << _tms_user << "\n";
-       f << "tms_password " << _tms_password << "\n";
+       root->add_child("TMSIP")->add_child_text (_tms_ip);
+       root->add_child("TMSPath")->add_child_text (_tms_path);
+       root->add_child("TMSUser")->add_child_text (_tms_user);
+       root->add_child("TMSPassword")->add_child_text (_tms_password);
        if (_sound_processor) {
-               f << "sound_processor " << _sound_processor->id () << "\n";
+               root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ());
        }
        if (_language) {
-               f << "language " << _language.get() << "\n";
+               root->add_child("Language")->add_child_text (_language.get());
        }
-       if (_default_format) {
-               f << "default_format " << _default_format->as_metadata() << "\n";
+       if (_default_container) {
+               root->add_child("DefaultContainer")->add_child_text (_default_container->id ());
        }
        if (_default_dcp_content_type) {
-               f << "default_dcp_content_type " << _default_dcp_content_type->dci_name() << "\n";
+               root->add_child("DefaultDCPContentType")->add_child_text (_default_dcp_content_type->dci_name ());
        }
-       f << "dcp_metadata_issuer " << _dcp_metadata.issuer << "\n";
-       f << "dcp_metadata_creator " << _dcp_metadata.creator << "\n";
-       f << "dcp_metadata_issue_date " << _dcp_metadata.issue_date << "\n";
+       root->add_child("DCPMetadataIssuer")->add_child_text (_dcp_metadata.issuer);
+       root->add_child("DCPMetadataCreator")->add_child_text (_dcp_metadata.creator);
+
+       _default_dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+
+       root->add_child("DefaultStillLength")->add_child_text (lexical_cast<string> (_default_still_length));
 
-       _default_dci_metadata.write (f);
+       doc.write_to_file_formatted (file (false));
 }
 
 string
index a59cdcae0df448e61fb03c0e7694162e25a1600d..f3e8f1441b918bd23282f5adfc2f8bb46a20d9e6 100644 (file)
@@ -21,8 +21,8 @@
  *  @brief Class holding configuration.
  */
 
-#ifndef DVDOMATIC_CONFIG_H
-#define DVDOMATIC_CONFIG_H
+#ifndef DCPOMATIC_CONFIG_H
+#define DCPOMATIC_CONFIG_H
 
 #include <vector>
 #include <boost/shared_ptr.hpp>
@@ -34,8 +34,8 @@ class ServerDescription;
 class Scaler;
 class Filter;
 class SoundProcessor;
-class Format;
 class DCPContentType;
+class Ratio;
 
 /** @class Config
  *  @brief A singleton class holding configuration.
@@ -65,14 +65,6 @@ public:
                return _servers;
        }
 
-       Scaler const * reference_scaler () const {
-               return _reference_scaler;
-       }
-
-       std::vector<Filter const *> reference_filters () const {
-               return _reference_filters;
-       }
-
        /** @return The IP address of a TMS that we can copy DCPs to */
        std::string tms_ip () const {
                return _tms_ip;
@@ -110,8 +102,12 @@ public:
                return _language;
        }
 
-       Format const * default_format () const {
-               return _default_format;
+       int default_still_length () const {
+               return _default_still_length;
+       }
+
+       Ratio const * default_container () const {
+               return _default_container;
        }
 
        DCPContentType const * default_dcp_content_type () const {
@@ -185,8 +181,12 @@ public:
                _language = boost::none;
        }
 
-       void set_default_format (Format const * f) {
-               _default_format = f;
+       void set_default_still_length (int s) {
+               _default_still_length = s;
+       }
+
+       void set_default_container (Ratio const * c) {
+               _default_container = c;
        }
 
        void set_default_dcp_content_type (DCPContentType const * t) {
@@ -204,7 +204,9 @@ public:
 
 private:
        Config ();
-       std::string file () const;
+       std::string file (bool) const;
+       void read ();
+       void read_old_metadata ();
 
        /** number of threads to use for J2K encoding on the local machine */
        int _num_local_encoding_threads;
@@ -233,7 +235,8 @@ private:
        /** Default DCI metadata for newly-created Films */
        DCIMetadata _default_dci_metadata;
        boost::optional<std::string> _language;
-       Format const * _default_format;
+       int _default_still_length;
+       Ratio const * _default_container;
        DCPContentType const * _default_dcp_content_type;
        libdcp::XMLMetadata _dcp_metadata;
 
diff --git a/src/lib/content.cc b/src/lib/content.cc
new file mode 100644 (file)
index 0000000..6a33e9f
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/thread/mutex.hpp>
+#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
+#include "content.h"
+#include "util.h"
+
+using std::string;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+int const ContentProperty::START = 400;
+int const ContentProperty::LENGTH = 401;
+
+Content::Content (shared_ptr<const Film> f, Time s)
+       : _film (f)
+       , _start (s)
+{
+
+}
+
+Content::Content (shared_ptr<const Film> f, boost::filesystem::path p)
+       : _film (f)
+       , _file (p)
+       , _start (0)
+{
+
+}
+
+Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+       : _film (f)
+{
+       _file = node->string_child ("File");
+       _digest = node->string_child ("Digest");
+       _start = node->number_child<Time> ("Start");
+}
+
+Content::Content (Content const & o)
+       : boost::enable_shared_from_this<Content> (o)
+       , _film (o._film)
+       , _file (o._file)
+       , _digest (o._digest)
+       , _start (o._start)
+{
+
+}
+
+void
+Content::as_xml (xmlpp::Node* node) const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       node->add_child("File")->add_child_text (_file.string());
+       node->add_child("Digest")->add_child_text (_digest);
+       node->add_child("Start")->add_child_text (lexical_cast<string> (_start));
+}
+
+void
+Content::examine (shared_ptr<Job>)
+{
+       string const d = md5_digest (_file);
+       boost::mutex::scoped_lock lm (_mutex);
+       _digest = d;
+}
+
+void
+Content::signal_changed (int p)
+{
+       Changed (shared_from_this (), p);
+}
+
+void
+Content::set_start (Time s)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _start = s;
+       }
+
+       signal_changed (ContentProperty::START);
+}
diff --git a/src/lib/content.h b/src/lib/content.h
new file mode 100644 (file)
index 0000000..e33f517
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+    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_CONTENT_H
+#define DCPOMATIC_CONTENT_H
+
+#include <boost/filesystem.hpp>
+#include <boost/signals2.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <libxml++/libxml++.h>
+#include "types.h"
+
+namespace cxml {
+       class Node;
+}
+
+class Job;
+class Film;
+
+class ContentProperty
+{
+public:
+       static int const START;
+       static int const LENGTH;
+};
+
+class Content : public boost::enable_shared_from_this<Content>
+{
+public:
+       Content (boost::shared_ptr<const Film>, Time);
+       Content (boost::shared_ptr<const Film>, boost::filesystem::path);
+       Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+       Content (Content const &);
+       
+       virtual void examine (boost::shared_ptr<Job>);
+       virtual std::string summary () const = 0;
+       virtual std::string information () const = 0;
+       virtual void as_xml (xmlpp::Node *) const;
+       virtual boost::shared_ptr<Content> clone () const = 0;
+       virtual Time length () const = 0;
+
+       boost::filesystem::path file () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _file;
+       }
+
+       std::string digest () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _digest;
+       }
+
+       void set_start (Time);
+
+       Time start () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _start;
+       }
+
+       Time end () const {
+               return start() + length();
+       }
+
+       boost::signals2::signal<void (boost::weak_ptr<Content>, int)> Changed;
+
+protected:
+       void signal_changed (int);
+
+       boost::weak_ptr<const Film> _film;
+       mutable boost::mutex _mutex;
+
+private:
+       boost::filesystem::path _file;
+       std::string _digest;
+       Time _start;
+};
+
+#endif
index 124697fb446aca92ad5e89c490a67f7b51a51fd9..ee0ef89b2ea0c9394b53124b700605d5c703a3af 100644 (file)
 #include "cross.h"
 #include "compose.hpp"
 #include "log.h"
-#ifdef DVDOMATIC_LINUX
+#ifdef DCPOMATIC_LINUX
 #include <unistd.h>
 #include <mntent.h>
 #endif
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
 #include <windows.h>
 #undef DATADIR
 #include <shlwapi.h>
 #endif
-#ifdef DVDOMATIC_OSX
+#ifdef DCPOMATIC_OSX
 #include <sys/sysctl.h>
 #endif
 
@@ -43,12 +43,12 @@ using std::make_pair;
 using boost::shared_ptr;
 
 void
-dvdomatic_sleep (int s)
+dcpomatic_sleep (int s)
 {
-#ifdef DVDOMATIC_POSIX
+#ifdef DCPOMATIC_POSIX
        sleep (s);
 #endif
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
        Sleep (s * 1000);
 #endif
 }
@@ -60,7 +60,7 @@ cpu_info ()
        pair<string, int> info;
        info.second = 0;
        
-#ifdef DVDOMATIC_LINUX
+#ifdef DCPOMATIC_LINUX
        ifstream f ("/proc/cpuinfo");
        while (f.good ()) {
                string l;
@@ -76,7 +76,7 @@ cpu_info ()
        }
 #endif
 
-#ifdef DVDOMATIC_OSX
+#ifdef DCPOMATIC_OSX
        size_t N = sizeof (info.second);
        sysctlbyname ("hw.ncpu", &info.second, &N, 0, 0);
        char buffer[64];
@@ -92,7 +92,7 @@ cpu_info ()
 void
 run_ffprobe (boost::filesystem::path content, boost::filesystem::path out, shared_ptr<Log> log)
 {
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
        SECURITY_ATTRIBUTES security;
        security.nLength = sizeof (security);
        security.bInheritHandle = TRUE;
@@ -167,7 +167,7 @@ mount_info ()
 {
        list<pair<string, string> > m;
        
-#ifdef DVDOMATIC_LINUX
+#ifdef DCPOMATIC_LINUX
        FILE* f = setmntent ("/etc/mtab", "r");
        if (!f) {
                return m;
index d9cc2d12f4b2981b48378515fcbfae8d3d15dacd..a00fee67917c95031a038c7b14fdeec0b8d85f30 100644 (file)
 
 */
 
-#include <string>
 #include <boost/filesystem.hpp>
 
-class Log;
-
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
 #define WEXITSTATUS(w) (w)
 #endif
 
-extern void dvdomatic_sleep (int);
+class Log;
+
+void dcpomatic_sleep (int);
 extern std::pair<std::string, int> cpu_info ();
 extern void run_ffprobe (boost::filesystem::path, boost::filesystem::path, boost::shared_ptr<Log>);
 extern std::list<std::pair<std::string, std::string> > mount_info ();
index 758886db46596f059917fe8136aa7865547de678..f25b3ddb00508157065b682c20a626b3de965f4d 100644 (file)
 */
 
 #include <iostream>
+#include <libcxml/cxml.h>
 #include "dci_metadata.h"
 
 #include "i18n.h"
 
-using namespace std;
+using std::string;
+using boost::shared_ptr;
+
+DCIMetadata::DCIMetadata (shared_ptr<const cxml::Node> node)
+{
+       audio_language = node->string_child ("AudioLanguage");
+       subtitle_language = node->string_child ("SubtitleLanguage");
+       territory = node->string_child ("Territory");
+       rating = node->string_child ("Rating");
+       studio = node->string_child ("Studio");
+       facility = node->string_child ("Facility");
+       package_type = node->string_child ("PackageType");
+}
 
 void
-DCIMetadata::write (ostream& f) const
+DCIMetadata::as_xml (xmlpp::Node* root) const
 {
-       f << N_("audio_language ") << audio_language << N_("\n");
-       f << N_("subtitle_language ") << subtitle_language << N_("\n");
-       f << N_("territory ") << territory << N_("\n");
-       f << N_("rating ") << rating << N_("\n");
-       f << N_("studio ") << studio << N_("\n");
-       f << N_("facility ") << facility << N_("\n");
-       f << N_("package_type ") << package_type << N_("\n");
+       root->add_child("AudioLanguage")->add_child_text (audio_language);
+       root->add_child("SubtitleLanguage")->add_child_text (subtitle_language);
+       root->add_child("Territory")->add_child_text (territory);
+       root->add_child("Rating")->add_child_text (rating);
+       root->add_child("Studio")->add_child_text (studio);
+       root->add_child("Facility")->add_child_text (facility);
+       root->add_child("PackageType")->add_child_text (package_type);
 }
 
 void
-DCIMetadata::read (string k, string v)
+DCIMetadata::read_old_metadata (string k, string v)
 {
        if (k == N_("audio_language")) {
                audio_language = v;
index eecdc765511a6ac2d6fa83301eb647cd1e78fab9..b87609ed0060224e573186f109c213aa2a074911 100644 (file)
 
 */
 
-#ifndef DVDOMATIC_DCI_METADATA_H
-#define DVDOMATIC_DCI_METADATA_H
+#ifndef DCPOMATIC_DCI_METADATA_H
+#define DCPOMATIC_DCI_METADATA_H
 
 #include <string>
+#include <libxml++/libxml++.h>
+
+namespace cxml {
+       class Node;
+}
 
 class DCIMetadata
 {
 public:
-       void read (std::string, std::string);
-       void write (std::ostream &) const;
+       DCIMetadata () {}
+       DCIMetadata (boost::shared_ptr<const cxml::Node>);
+
+       void as_xml (xmlpp::Node *) const;
+       void read_old_metadata (std::string, std::string);
        
        std::string audio_language;
        std::string subtitle_language;
index 960bb012948cf8c8b8be310b5ee13622a7cd934d..14204bd722b6d167e5234e09e88eb331b97362ef 100644 (file)
@@ -17,8 +17,8 @@
 
 */
 
-#ifndef DVDOMATIC_DCP_CONTENT_TYPE_H
-#define DVDOMATIC_DCP_CONTENT_TYPE_H
+#ifndef DCPOMATIC_DCP_CONTENT_TYPE_H
+#define DCPOMATIC_DCP_CONTENT_TYPE_H
 
 /** @file src/content_type.h
  *  @brief A description of the type of content for a DCP (e.g. feature, trailer etc.)
index 77b81a658f48e9e3a5ff531bc3041879d1502aba..2f597522c3c4401fb17c476d6d2a7ffc20223450 100644 (file)
 #include "dcp_video_frame.h"
 #include "lut.h"
 #include "config.h"
-#include "options.h"
 #include "exceptions.h"
 #include "server.h"
 #include "util.h"
 #include "scaler.h"
 #include "image.h"
 #include "log.h"
-#include "subtitle.h"
 
 #include "i18n.h"
 
@@ -67,35 +65,21 @@ using libdcp::Size;
 
 /** Construct a DCP video frame.
  *  @param input Input image.
- *  @param out Required size of output, in pixels (including any padding).
- *  @param s Scaler to use.
- *  @param p Number of pixels of padding either side of the image.
  *  @param f Index of the frame within the DCP.
- *  @param fps Frames per second of the Film's source.
- *  @param pp FFmpeg post-processing string to use.
  *  @param clut Colour look-up table to use (see Config::colour_lut_index ())
  *  @param bw J2K bandwidth to use (see Config::j2k_bandwidth ())
  *  @param l Log to write to.
  */
 DCPVideoFrame::DCPVideoFrame (
-       shared_ptr<const Image> yuv, shared_ptr<Subtitle> sub,
-       Size out, int p, int subtitle_offset, float subtitle_scale,
-       Scaler const * s, int f, int dcp_fps, string pp, int clut, int bw, shared_ptr<Log> l
+       shared_ptr<const Image> image, int f, int dcp_fps, int clut, int bw, shared_ptr<Log> l
        )
-       : _input (yuv)
-       , _subtitle (sub)
-       , _out_size (out)
-       , _padding (p)
-       , _subtitle_offset (subtitle_offset)
-       , _subtitle_scale (subtitle_scale)
-       , _scaler (s)
+       : _image (image)
        , _frame (f)
        , _frames_per_second (dcp_fps)
-       , _post_process (pp)
        , _colour_lut (clut)
        , _j2k_bandwidth (bw)
        , _log (l)
-       , _image (0)
+       , _opj_image (0)
        , _parameters (0)
        , _cinfo (0)
        , _cio (0)
@@ -110,8 +94,8 @@ DCPVideoFrame::create_openjpeg_container ()
        for (int i = 0; i < 3; ++i) {
                _cmptparm[i].dx = 1;
                _cmptparm[i].dy = 1;
-               _cmptparm[i].w = _out_size.width;
-               _cmptparm[i].h = _out_size.height;
+               _cmptparm[i].w = _image->size().width;
+               _cmptparm[i].h = _image->size().height;
                _cmptparm[i].x0 = 0;
                _cmptparm[i].y0 = 0;
                _cmptparm[i].prec = 12;
@@ -119,21 +103,21 @@ DCPVideoFrame::create_openjpeg_container ()
                _cmptparm[i].sgnd = 0;
        }
 
-       _image = opj_image_create (3, &_cmptparm[0], CLRSPC_SRGB);
-       if (_image == 0) {
+       _opj_image = opj_image_create (3, &_cmptparm[0], CLRSPC_SRGB);
+       if (_opj_image == 0) {
                throw EncodeError (N_("could not create libopenjpeg image"));
        }
 
-       _image->x0 = 0;
-       _image->y0 = 0;
-       _image->x1 = _out_size.width;
-       _image->y1 = _out_size.height;
+       _opj_image->x0 = 0;
+       _opj_image->y0 = 0;
+       _opj_image->x1 = _image->size().width;
+       _opj_image->y1 = _image->size().height;
 }
 
 DCPVideoFrame::~DCPVideoFrame ()
 {
-       if (_image) {
-               opj_image_destroy (_image);
+       if (_opj_image) {
+               opj_image_destroy (_opj_image);
        }
 
        if (_cio) {
@@ -157,23 +141,6 @@ DCPVideoFrame::~DCPVideoFrame ()
 shared_ptr<EncodedData>
 DCPVideoFrame::encode_locally ()
 {
-       if (!_post_process.empty ()) {
-               _input = _input->post_process (_post_process, true);
-       }
-       
-       shared_ptr<Image> prepared = _input->scale_and_convert_to_rgb (_out_size, _padding, _scaler, true);
-
-       if (_subtitle) {
-               dvdomatic::Rect tx = subtitle_transformed_area (
-                       float (_out_size.width) / _input->size().width,
-                       float (_out_size.height) / _input->size().height,
-                       _subtitle->area(), _subtitle_offset, _subtitle_scale
-                       );
-
-               shared_ptr<Image> im = _subtitle->image()->scale (tx.size(), _scaler, true);
-               prepared->alpha_blend (im, tx.position());
-       }
-
        create_openjpeg_container ();
 
        struct {
@@ -187,9 +154,9 @@ DCPVideoFrame::encode_locally ()
        /* Copy our RGB into the openjpeg container, converting to XYZ in the process */
 
        int jn = 0;
-       for (int y = 0; y < _out_size.height; ++y) {
-               uint8_t* p = prepared->data()[0] + y * prepared->stride()[0];
-               for (int x = 0; x < _out_size.width; ++x) {
+       for (int y = 0; y < _image->size().height; ++y) {
+               uint8_t* p = _image->data()[0] + y * _image->stride()[0];
+               for (int x = 0; x < _image->size().width; ++x) {
 
                        /* In gamma LUT (converting 8-bit input to 12-bit) */
                        s.r = lut_in[_colour_lut][*p++ << 4];
@@ -215,9 +182,9 @@ DCPVideoFrame::encode_locally ()
                        d.z = d.z * DCI_COEFFICENT * (DCI_LUT_SIZE - 1);
                        
                        /* Out gamma LUT */
-                       _image->comps[0].data[jn] = lut_out[LO_DCI][(int) d.x];
-                       _image->comps[1].data[jn] = lut_out[LO_DCI][(int) d.y];
-                       _image->comps[2].data[jn] = lut_out[LO_DCI][(int) d.z];
+                       _opj_image->comps[0].data[jn] = lut_out[LO_DCI][(int) d.x];
+                       _opj_image->comps[1].data[jn] = lut_out[LO_DCI][(int) d.y];
+                       _opj_image->comps[2].data[jn] = lut_out[LO_DCI][(int) d.z];
 
                        ++jn;
                }
@@ -267,7 +234,7 @@ DCPVideoFrame::encode_locally ()
        _parameters->tcp_numlayers++;
        _parameters->cp_disto_alloc = 1;
        _parameters->cp_rsiz = CINEMA2K;
-       _parameters->cp_comment = strdup (N_("DVD-o-matic"));
+       _parameters->cp_comment = strdup (N_("DCP-o-matic"));
        _parameters->cp_cinema = CINEMA2K_24;
 
        /* 3 components, so use MCT */
@@ -275,7 +242,7 @@ DCPVideoFrame::encode_locally ()
        
        /* set max image */
        _parameters->max_comp_size = max_comp_size;
-       _parameters->tcp_rates[0] = ((float) (3 * _image->comps[0].w * _image->comps[0].h * _image->comps[0].prec)) / (max_cs_len * 8);
+       _parameters->tcp_rates[0] = ((float) (3 * _opj_image->comps[0].w * _opj_image->comps[0].h * _opj_image->comps[0].prec)) / (max_cs_len * 8);
 
        /* get a J2K compressor handle */
        _cinfo = opj_create_compress (CODEC_J2K);
@@ -287,14 +254,14 @@ DCPVideoFrame::encode_locally ()
        _cinfo->event_mgr = 0;
 
        /* Setup the encoder parameters using the current image and user parameters */
-       opj_setup_encoder (_cinfo, _parameters, _image);
+       opj_setup_encoder (_cinfo, _parameters, _opj_image);
 
        _cio = opj_cio_open ((opj_common_ptr) _cinfo, 0, 0);
        if (_cio == 0) {
                throw EncodeError (N_("could not open JPEG2000 stream"));
        }
 
-       int const r = opj_encode (_cinfo, _cio, _image, 0);
+       int const r = opj_encode (_cinfo, _cio, _opj_image, 0);
        if (r == 0) {
                throw EncodeError (N_("JPEG2000 encoding failed"));
        }
@@ -322,46 +289,24 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv)
 
        stringstream s;
        s << N_("encode please\n")
-         << N_("input_width ") << _input->size().width << N_("\n")
-         << N_("input_height ") << _input->size().height << N_("\n")
-         << N_("input_pixel_format ") << _input->pixel_format() << N_("\n")
-         << N_("output_width ") << _out_size.width << N_("\n")
-         << N_("output_height ") << _out_size.height << N_("\n")
-         << N_("padding ") <<  _padding << N_("\n")
-         << N_("subtitle_offset ") << _subtitle_offset << N_("\n")
-         << N_("subtitle_scale ") << _subtitle_scale << N_("\n")
-         << N_("scaler ") << _scaler->id () << N_("\n")
+         << N_("width ") << _image->size().width << N_("\n")
+         << N_("height ") << _image->size().height << N_("\n")
          << N_("frame ") << _frame << N_("\n")
-         << N_("frames_per_second ") << _frames_per_second << N_("\n");
-
-       if (!_post_process.empty()) {
-               s << N_("post_process ") << _post_process << N_("\n");
-       }
-       
-       s << N_("colour_lut ") << _colour_lut << N_("\n")
+         << N_("frames_per_second ") << _frames_per_second << N_("\n")
+         << N_("colour_lut ") << _colour_lut << N_("\n")
          << N_("j2k_bandwidth ") << _j2k_bandwidth << N_("\n");
 
-       if (_subtitle) {
-               s << N_("subtitle_x ") << _subtitle->position().x << N_("\n")
-                 << N_("subtitle_y ") << _subtitle->position().y << N_("\n")
-                 << N_("subtitle_width ") << _subtitle->image()->size().width << N_("\n")
-                 << N_("subtitle_height ") << _subtitle->image()->size().height << N_("\n");
-       }
-
        _log->log (String::compose (
                           N_("Sending to remote; pixel format %1, components %2, lines (%3,%4,%5), line sizes (%6,%7,%8)"),
-                          _input->pixel_format(), _input->components(),
-                          _input->lines(0), _input->lines(1), _input->lines(2),
-                          _input->line_size()[0], _input->line_size()[1], _input->line_size()[2]
+                          _image->pixel_format(), _image->components(),
+                          _image->lines(0), _image->lines(1), _image->lines(2),
+                          _image->line_size()[0], _image->line_size()[1], _image->line_size()[2]
                           ));
 
        socket->write (s.str().length() + 1);
        socket->write ((uint8_t *) s.str().c_str(), s.str().length() + 1);
 
-       _input->write_to_socket (socket);
-       if (_subtitle) {
-               _subtitle->image()->write_to_socket (socket);
-       }
+       _image->write_to_socket (socket);
 
        shared_ptr<EncodedData> e (new RemotelyEncodedData (socket->read_uint32 ()));
        socket->read (e->data(), e->size());
index 4ceb07d2649905a87a80ebf9daa6712e97fc8d96..f234b445abe00f9ba8cd169d1971b95882a76709 100644 (file)
@@ -105,12 +105,8 @@ public:
 class DCPVideoFrame
 {
 public:
-       DCPVideoFrame (
-               boost::shared_ptr<const Image>, boost::shared_ptr<Subtitle>, libdcp::Size,
-               int, int, float, Scaler const *, int, int, std::string, int, int, boost::shared_ptr<Log>
-               );
-       
-       virtual ~DCPVideoFrame ();
+       DCPVideoFrame (boost::shared_ptr<const Image>, int, int, int, int, boost::shared_ptr<Log>);
+       ~DCPVideoFrame ();
 
        boost::shared_ptr<EncodedData> encode_locally ();
        boost::shared_ptr<EncodedData> encode_remotely (ServerDescription const *);
@@ -122,23 +118,16 @@ public:
 private:
        void create_openjpeg_container ();
 
-       boost::shared_ptr<const Image> _input; ///< the input image
-       boost::shared_ptr<Subtitle> _subtitle; ///< any subtitle that should be on the image
-       libdcp::Size _out_size;                ///< the required size of the output, in pixels
-       int _padding;
-       int _subtitle_offset;
-       float _subtitle_scale;
-       Scaler const * _scaler;          ///< scaler to use
+       boost::shared_ptr<const Image> _image;
        int _frame;                      ///< frame index within the DCP's intrinsic duration
        int _frames_per_second;          ///< Frames per second that we will use for the DCP
-       std::string _post_process;       ///< FFmpeg post-processing string to use
        int _colour_lut;                 ///< Colour look-up table to use
        int _j2k_bandwidth;              ///< J2K bandwidth to use
 
        boost::shared_ptr<Log> _log; ///< log
 
        opj_image_cmptparm_t _cmptparm[3]; ///< libopenjpeg's opj_image_cmptparm_t
-       opj_image* _image;                 ///< libopenjpeg's image container 
+       opj_image* _opj_image;             ///< libopenjpeg's image container 
        opj_cparameters_t* _parameters;    ///< libopenjpeg's parameters
        opj_cinfo_t* _cinfo;               ///< libopenjpeg's opj_cinfo_t
        opj_cio_t* _cio;                   ///< libopenjpeg's opj_cio_t
index 52b22fa067955b48341b40947a728161f65c6152..637e0ddb291101af81f28115327f195a3e128116 100644 (file)
  *  @brief Parent class for decoders of content.
  */
 
-#include <iostream>
-#include <stdint.h>
-#include <boost/lexical_cast.hpp>
 #include "film.h"
-#include "format.h"
-#include "options.h"
-#include "exceptions.h"
-#include "image.h"
-#include "util.h"
-#include "log.h"
 #include "decoder.h"
-#include "delay_line.h"
-#include "subtitle.h"
-#include "filter_graph.h"
 
 #include "i18n.h"
 
-using std::string;
-using std::stringstream;
-using std::min;
-using std::pair;
-using std::list;
 using boost::shared_ptr;
-using boost::optional;
 
 /** @param f Film.
  *  @param o Decode options.
  */
-Decoder::Decoder (boost::shared_ptr<Film> f, DecodeOptions o)
+Decoder::Decoder (shared_ptr<const Film> f)
        : _film (f)
-       , _opt (o)
 {
        _film_connection = f->Changed.connect (bind (&Decoder::film_changed, this, _1));
 }
-
-/** Seek to a position as a source timestamp in seconds.
- *  @return true on error.
- */
-bool
-Decoder::seek (double)
-{
-       throw DecodeError (N_("decoder does not support seek"));
-}
-
-/** Seek so that the next frame we will produce is the same as the last one.
- *  @return true on error.
- */
-bool
-Decoder::seek_to_last ()
-{
-       throw DecodeError (N_("decoder does not support seek"));
-}
index 2bc462c33c635013c4111447f43b2610204d562f..cfca6867f652d3fc9d2d5be7320d75128060cffe 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-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
  *  @brief Parent class for decoders of content.
  */
 
-#ifndef DVDOMATIC_DECODER_H
-#define DVDOMATIC_DECODER_H
+#ifndef DCPOMATIC_DECODER_H
+#define DCPOMATIC_DECODER_H
 
 #include <vector>
 #include <string>
 #include <stdint.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/signals2.hpp>
-#include "util.h"
-#include "stream.h"
-#include "video_source.h"
-#include "audio_source.h"
 #include "film.h"
-#include "options.h"
 
 class Image;
 class Log;
@@ -45,34 +40,30 @@ class FilterGraph;
 
 /** @class Decoder.
  *  @brief Parent class for decoders of content.
- *
- *  These classes can be instructed run through their content (by
- *  calling ::go), and they emit signals when video or audio data is
- *  ready for something else to process.
  */
 class Decoder
 {
 public:
-       Decoder (boost::shared_ptr<Film>, DecodeOptions);
+       Decoder (boost::shared_ptr<const Film>);
        virtual ~Decoder () {}
 
-       virtual bool pass () = 0;
-       virtual bool seek (double);
-       virtual bool seek_to_last ();
-       virtual void seek_back () {}
-       virtual void seek_forward () {}
+       /** Perform one decode pass of the content, which may or may not
+        *  cause the object to emit some data.
+        */
+       virtual void pass () = 0;
 
-       boost::signals2::signal<void()> OutputChanged;
+       virtual bool done () const = 0;
 
 protected:
-       /** our Film */
-       boost::shared_ptr<Film> _film;
-       /** our decode options */
-       DecodeOptions _opt;
+
+       /** The Film that we are decoding in */
+       boost::weak_ptr<const Film> _film;
 
 private:
+       /** This will be called when our Film emits Changed */
        virtual void film_changed (Film::Property) {}
-       
+
+       /** Connection to our Film */
        boost::signals2::scoped_connection _film_connection;
 };
 
diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc
deleted file mode 100644 (file)
index f7f9f40..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file  src/decoder_factory.cc
- *  @brief A method to create an appropriate decoder for some content.
- */
-
-#include <boost/filesystem.hpp>
-#include "ffmpeg_decoder.h"
-#include "imagemagick_decoder.h"
-#include "film.h"
-#include "sndfile_decoder.h"
-#include "decoder_factory.h"
-
-using std::string;
-using std::pair;
-using std::make_pair;
-using boost::shared_ptr;
-using boost::dynamic_pointer_cast;
-
-Decoders
-decoder_factory (
-       shared_ptr<Film> f, DecodeOptions o
-       )
-{
-       if (f->content().empty()) {
-               return Decoders ();
-       }
-       
-       if (boost::filesystem::is_directory (f->content_path()) || f->content_type() == STILL) {
-               /* A single image file, or a directory of them */
-               return Decoders (
-                       shared_ptr<VideoDecoder> (new ImageMagickDecoder (f, o)),
-                       shared_ptr<AudioDecoder> (new SndfileDecoder (f, o))
-                       );
-       }
-
-       shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (f, o));
-       if (f->use_content_audio()) {
-               return Decoders (fd, fd);
-       }
-
-       return Decoders (fd, shared_ptr<AudioDecoder> (new SndfileDecoder (f, o)));
-}
diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h
deleted file mode 100644 (file)
index 8076b01..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#ifndef DVDOMATIC_DECODER_FACTORY_H
-#define DVDOMATIC_DECODER_FACTORY_H
-
-/** @file  src/decoder_factory.h
- *  @brief A method to create appropriate decoders for some content.
- */
-
-#include "options.h"
-
-class Film;
-class VideoDecoder;
-class AudioDecoder;
-
-struct Decoders {
-       Decoders () {}
-       
-       Decoders (boost::shared_ptr<VideoDecoder> v, boost::shared_ptr<AudioDecoder> a)
-               : video (v)
-               , audio (a)
-       {}
-
-       boost::shared_ptr<VideoDecoder> video;
-       boost::shared_ptr<AudioDecoder> audio;
-};
-
-extern Decoders decoder_factory (
-       boost::shared_ptr<Film>, DecodeOptions
-       );
-
-#endif
diff --git a/src/lib/delay_line.cc b/src/lib/delay_line.cc
deleted file mode 100644 (file)
index f6af6f9..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <stdint.h>
-#include <cstring>
-#include <algorithm>
-#include <iostream>
-#include "delay_line.h"
-#include "util.h"
-
-using std::min;
-using boost::shared_ptr;
-
-/*  @param seconds Delay in seconds, +ve to move audio later.
- */
-DelayLine::DelayLine (shared_ptr<Log> log, double seconds)
-       : TimedAudioVideoProcessor (log)
-       , _seconds (seconds)
-{
-       
-}
-
-void
-DelayLine::process_audio (shared_ptr<const AudioBuffers> data, double t)
-{
-       if (_seconds > 0) {
-               t += _seconds;
-       }
-
-       Audio (data, t);
-}
-
-void
-DelayLine::process_video (shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
-{
-       if (_seconds < 0) {
-               t -= _seconds;
-       }
-
-       Video (image, same, sub, t);
-}
diff --git a/src/lib/delay_line.h b/src/lib/delay_line.h
deleted file mode 100644 (file)
index 781dce8..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <boost/shared_ptr.hpp>
-#include "processor.h"
-
-/** A delay line */
-class DelayLine : public TimedAudioVideoProcessor
-{
-public:
-       DelayLine (boost::shared_ptr<Log> log, double);
-       
-       void process_video (boost::shared_ptr<const Image>, bool, boost::shared_ptr<Subtitle>, double);
-       void process_audio (boost::shared_ptr<const AudioBuffers>, double);
-
-private:
-       double _seconds;
-};
index 0ac32d3bfa9cd2eb099360080d9ac6e4657d715d..d3181acd9b1f7df35f0224a96be318b6203f5f1d 100644 (file)
  */
 
 #include <iostream>
-#include <boost/filesystem.hpp>
-#include <boost/lexical_cast.hpp>
-#include <libdcp/picture_asset.h>
 #include "encoder.h"
 #include "util.h"
-#include "options.h"
 #include "film.h"
 #include "log.h"
-#include "exceptions.h"
-#include "filter.h"
 #include "config.h"
 #include "dcp_video_frame.h"
 #include "server.h"
-#include "format.h"
 #include "cross.h"
 #include "writer.h"
 
@@ -49,18 +42,16 @@ using std::list;
 using std::cout;
 using std::min;
 using std::make_pair;
-using namespace boost;
+using boost::shared_ptr;
+using boost::optional;
 
 int const Encoder::_history_size = 25;
 
 /** @param f Film that we are encoding */
-Encoder::Encoder (shared_ptr<Film> f)
+Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<Job> j)
        : _film (f)
-       , _video_frames_in (0)
+       , _job (j)
        , _video_frames_out (0)
-#ifdef HAVE_SWRESAMPLE   
-       , _swr_context (0)
-#endif
        , _have_a_real_frame (false)
        , _terminate (false)
 {
@@ -78,35 +69,6 @@ Encoder::~Encoder ()
 void
 Encoder::process_begin ()
 {
-       if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate()) {
-#ifdef HAVE_SWRESAMPLE
-
-               stringstream s;
-               s << String::compose (N_("Will resample audio from %1 to %2"), _film->audio_stream()->sample_rate(), _film->target_audio_sample_rate());
-               _film->log()->log (s.str ());
-
-               /* We will be using planar float data when we call the resampler */
-               _swr_context = swr_alloc_set_opts (
-                       0,
-                       _film->audio_stream()->channel_layout(),
-                       AV_SAMPLE_FMT_FLTP,
-                       _film->target_audio_sample_rate(),
-                       _film->audio_stream()->channel_layout(),
-                       AV_SAMPLE_FMT_FLTP,
-                       _film->audio_stream()->sample_rate(),
-                       0, 0
-                       );
-               
-               swr_init (_swr_context);
-#else
-               throw EncodeError (_("Cannot resample audio as libswresample is not present"));
-#endif
-       } else {
-#ifdef HAVE_SWRESAMPLE
-               _swr_context = 0;
-#endif         
-       }
-
        for (int i = 0; i < Config::instance()->num_local_encoding_threads (); ++i) {
                _threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, (ServerDescription *) 0)));
        }
@@ -119,51 +81,13 @@ Encoder::process_begin ()
                }
        }
 
-       _writer.reset (new Writer (_film));
+       _writer.reset (new Writer (_film, _job));
 }
 
 
 void
 Encoder::process_end ()
 {
-#if HAVE_SWRESAMPLE    
-       if (_film->audio_stream() && _film->audio_stream()->channels() && _swr_context) {
-
-               shared_ptr<AudioBuffers> out (new AudioBuffers (_film->audio_stream()->channels(), 256));
-                       
-               while (1) {
-                       int const frames = swr_convert (_swr_context, (uint8_t **) out->data(), 256, 0, 0);
-
-                       if (frames < 0) {
-                               throw EncodeError (_("could not run sample-rate converter"));
-                       }
-
-                       if (frames == 0) {
-                               break;
-                       }
-
-                       out->set_frames (frames);
-                       write_audio (out);
-               }
-
-               swr_free (&_swr_context);
-       }
-#endif
-
-       if (_film->audio_channels() == 0 && _film->minimum_audio_channels() > 0) {
-               /* Put audio in where there is none at all */
-               int64_t af = video_frames_to_audio_frames (_video_frames_out, 48000, _film->dcp_frame_rate ());
-               while (af) {
-                       int64_t const this_time = min (af, static_cast<int64_t> (24000));
-                       shared_ptr<AudioBuffers> out (new AudioBuffers (_film->minimum_audio_channels(), this_time));
-                       out->make_silent ();
-                       out->set_frames (this_time);
-                       write_audio (out);
-
-                       af -= this_time;
-               }
-       }
-
        boost::mutex::scoped_lock lock (_mutex);
 
        _film->log()->log (String::compose (N_("Clearing queue of %1"), _queue.size ()));
@@ -208,7 +132,7 @@ Encoder::process_end ()
  *  or 0 if not known.
  */
 float
-Encoder::current_frames_per_second () const
+Encoder::current_encoding_rate () const
 {
        boost::mutex::scoped_lock lock (_history_mutex);
        if (int (_time_history.size()) < _history_size) {
@@ -246,15 +170,8 @@ Encoder::frame_done ()
 }
 
 void
-Encoder::process_video (shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub)
+Encoder::process_video (shared_ptr<const Image> image, bool same)
 {
-       FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate());
-       
-       if (frc.skip && (_video_frames_in % 2)) {
-               ++_video_frames_in;
-               return;
-       }
-
        boost::mutex::scoped_lock lock (_mutex);
 
        /* Wait until the queue has gone down a bit */
@@ -282,15 +199,12 @@ Encoder::process_video (shared_ptr<const Image> image, bool same, boost::shared_
                frame_done ();
        } else {
                /* Queue this new frame for encoding */
-               pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
                TIMING ("adding to queue of %1", _queue.size ());
-               _queue.push_back (boost::shared_ptr<DCPVideoFrame> (
+               /* XXX: padding */
+               _queue.push_back (shared_ptr<DCPVideoFrame> (
                                          new DCPVideoFrame (
-                                                 image, sub, _film->format()->dcp_size(), _film->format()->dcp_padding (_film),
-                                                 _film->subtitle_offset(), _film->subtitle_scale(),
-                                                 _film->scaler(), _video_frames_out, _film->dcp_frame_rate(), s.second,
-                                                 _film->colour_lut(), _film->j2k_bandwidth(),
-                                                 _film->log()
+                                                 image, _video_frames_out, _film->dcp_video_frame_rate(),
+                                                 _film->colour_lut(), _film->j2k_bandwidth(), _film->log()
                                                  )
                                          ));
                
@@ -298,49 +212,13 @@ Encoder::process_video (shared_ptr<const Image> image, bool same, boost::shared_
                _have_a_real_frame = true;
        }
 
-       ++_video_frames_in;
        ++_video_frames_out;
-
-       if (frc.repeat) {
-               _writer->repeat (_video_frames_out);
-               ++_video_frames_out;
-               frame_done ();
-       }
 }
 
 void
 Encoder::process_audio (shared_ptr<const AudioBuffers> data)
 {
-       if (!data->frames ()) {
-               return;
-       }
-       
-#if HAVE_SWRESAMPLE
-       /* Maybe sample-rate convert */
-       if (_swr_context) {
-
-               /* Compute the resampled frames count and add 32 for luck */
-               int const max_resampled_frames = ceil ((int64_t) data->frames() * _film->target_audio_sample_rate() / _film->audio_stream()->sample_rate()) + 32;
-
-               shared_ptr<AudioBuffers> resampled (new AudioBuffers (_film->audio_stream()->channels(), max_resampled_frames));
-
-               /* Resample audio */
-               int const resampled_frames = swr_convert (
-                       _swr_context, (uint8_t **) resampled->data(), max_resampled_frames, (uint8_t const **) data->data(), data->frames()
-                       );
-               
-               if (resampled_frames < 0) {
-                       throw EncodeError (_("could not run sample-rate converter"));
-               }
-
-               resampled->set_frames (resampled_frames);
-               
-               /* And point our variables at the resampled audio */
-               data = resampled;
-       }
-#endif
-
-       write_audio (data);
+       _writer->write (data);
 }
 
 void
@@ -383,7 +261,7 @@ Encoder::encoder_thread (ServerDescription* server)
                }
 
                TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size());
-               boost::shared_ptr<DCPVideoFrame> vf = _queue.front ();
+               shared_ptr<DCPVideoFrame> vf = _queue.front ();
                _film->log()->log (String::compose (N_("Encoder thread %1 pops frame %2 from queue"), boost::this_thread::get_id(), vf->frame()), Log::VERBOSE);
                _queue.pop_front ();
                
@@ -437,34 +315,10 @@ Encoder::encoder_thread (ServerDescription* server)
                }
 
                if (remote_backoff > 0) {
-                       dvdomatic_sleep (remote_backoff);
+                       dcpomatic_sleep (remote_backoff);
                }
 
                lock.lock ();
                _condition.notify_all ();
        }
 }
-
-void
-Encoder::write_audio (shared_ptr<const AudioBuffers> data)
-{
-       AudioMapping m (_film);
-       if (m.dcp_channels() != _film->audio_channels()) {
-
-               /* Remap and pad with silence */
-
-               shared_ptr<AudioBuffers> b (new AudioBuffers (m.dcp_channels(), data->frames ()));
-               for (int i = 0; i < m.dcp_channels(); ++i) {
-                       optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (i));
-                       if (!s) {
-                               b->make_silent (i);
-                       } else {
-                               memcpy (b->data()[i], data->data()[s.get()], data->frames() * sizeof(float));
-                       }
-               }
-
-               data = b;
-       }
-
-       _writer->write (data);
-}
index 70e81a7e071d483f3ae6bad45faf5b76f9c8c797..b5a641f50d40492caa008115cf294ea4ec084de5 100644 (file)
@@ -17,8 +17,8 @@
 
 */
 
-#ifndef DVDOMATIC_ENCODER_H
-#define DVDOMATIC_ENCODER_H
+#ifndef DCPOMATIC_ENCODER_H
+#define DCPOMATIC_ENCODER_H
 
 /** @file src/encoder.h
  *  @brief Encoder to J2K and WAV for DCP.
 #include <stdint.h>
 extern "C" {
 #include <libavutil/samplefmt.h>
-}
-#ifdef HAVE_SWRESAMPLE
-extern "C" {
 #include <libswresample/swresample.h>
 }
-#endif
 #include "util.h"
-#include "video_sink.h"
-#include "audio_sink.h"
 
 class Image;
-class Subtitle;
 class AudioBuffers;
 class Film;
 class ServerDescription;
 class DCPVideoFrame;
 class EncodedData;
 class Writer;
+class Job;
 
 /** @class Encoder
  *  @brief Encoder to J2K and WAV for DCP.
  *
- *  Video is supplied to process_video as YUV frames, and audio
+ *  Video is supplied to process_video as RGB frames, and audio
  *  is supplied as uncompressed PCM in blocks of various sizes.
  */
 
-class Encoder : public VideoSink, public AudioSink
+class Encoder
 {
 public:
-       Encoder (boost::shared_ptr<Film> f);
+       Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<Job>);
        virtual ~Encoder ();
 
        /** Called to indicate that a processing run is about to begin */
-       virtual void process_begin ();
+       void process_begin ();
 
        /** Call with a frame of video.
         *  @param i Video frame image.
         *  @param same true if i is the same as the last time we were called.
-        *  @param s A subtitle that should be on this frame, or 0.
         */
-       void process_video (boost::shared_ptr<const Image> i, bool same, boost::shared_ptr<Subtitle> s);
+       void process_video (boost::shared_ptr<const Image> i, bool same);
 
        /** Call with some audio data */
        void process_audio (boost::shared_ptr<const AudioBuffers>);
 
        /** Called when a processing run has finished */
-       virtual void process_end ();
+       void process_end ();
 
-       float current_frames_per_second () const;
+       float current_encoding_rate () const;
        int video_frames_out () const;
 
 private:
        
        void frame_done ();
        
-       void write_audio (boost::shared_ptr<const AudioBuffers> data);
-
        void encoder_thread (ServerDescription *);
        void terminate_threads ();
 
        /** Film that we are encoding */
-       boost::shared_ptr<Film> _film;
+       boost::shared_ptr<const Film> _film;
+       boost::shared_ptr<Job> _job;
 
        /** Mutex for _time_history and _last_frame */
        mutable boost::mutex _history_mutex;
@@ -105,15 +97,9 @@ private:
        /** Number of frames that we should keep history for */
        static int const _history_size;
 
-       /** Number of video frames received so far */
-       SourceFrame _video_frames_in;
        /** Number of video frames written for the DCP so far */
        int _video_frames_out;
 
-#if HAVE_SWRESAMPLE    
-       SwrContext* _swr_context;
-#endif
-
        bool _have_a_real_frame;
        bool _terminate;
        std::list<boost::shared_ptr<DCPVideoFrame> > _queue;
index 4b30c943136a522d3b2197794ca38ca5eb2b187d..3cab9716de3783576d92cd9e441d50e6d1765df8 100644 (file)
 
 */
 
-/** @file  src/examine_content_job.cc
- *  @brief A class to run through content at high speed to find its length.
- */
-
 #include <boost/filesystem.hpp>
 #include "examine_content_job.h"
-#include "options.h"
-#include "decoder_factory.h"
-#include "decoder.h"
-#include "transcoder.h"
 #include "log.h"
+#include "content.h"
 #include "film.h"
-#include "video_decoder.h"
 
 #include "i18n.h"
 
 using std::string;
-using std::vector;
-using std::pair;
 using boost::shared_ptr;
 
-ExamineContentJob::ExamineContentJob (shared_ptr<Film> f)
+ExamineContentJob::ExamineContentJob (shared_ptr<const Film> f, shared_ptr<Content> c)
        : Job (f)
+       , _content (c)
 {
 
 }
@@ -51,60 +42,13 @@ ExamineContentJob::~ExamineContentJob ()
 string
 ExamineContentJob::name () const
 {
-       if (_film->name().empty ()) {
-               return _("Examine content");
-       }
-       
-       return String::compose (_("Examine content of %1"), _film->name());
+       return _("Examine content");
 }
 
 void
 ExamineContentJob::run ()
 {
-       descend (0.5);
-       _film->set_content_digest (md5_digest (_film->content_path ()));
-       ascend ();
-
-       descend (0.5);
-
-       /* Set the film's length to either
-          a) a length judged by running through the content or
-          b) the length from a decoder's header.
-       */
-       if (!_film->trust_content_header()) {
-               /* Decode the content to get an accurate length */
-               
-               /* We don't want to use any existing length here, as progress
-                  will be messed up.
-               */
-               _film->unset_length ();
-               _film->set_crop (Crop ());
-               
-               DecodeOptions o;
-               o.decode_audio = false;
-               
-               Decoders decoders = decoder_factory (_film, o);
-               
-               set_progress_unknown ();
-               while (!decoders.video->pass()) {
-                       /* keep going */
-               }
-               
-               _film->set_length (decoders.video->video_frame());
-               
-               _film->log()->log (String::compose (N_("Video length examined as %1 frames"), _film->length().get()));
-               
-       } else {
-
-               /* Get a quick decoder to get the content's length from its header */
-               
-               Decoders d = decoder_factory (_film, DecodeOptions());
-               _film->set_length (d.video->length());
-       
-               _film->log()->log (String::compose (N_("Video length obtained from header as %1 frames"), _film->length().get()));
-       }
-
-       ascend ();
+       _content->examine (shared_from_this ());
        set_progress (1);
        set_state (FINISHED_OK);
 }
index 8ee4f0d608de5e8b591e68f23dd2634929065dbe..b6903b86bebf0ed9e80970a7382c0f07628301cc 100644 (file)
 
 */
 
-/** @file  src/examine_content_job.h
- *  @brief A class to obtain the length and MD5 digest of a content file.
- */
-
+#include <boost/shared_ptr.hpp>
 #include "job.h"
 
-/** @class ExamineContentJob
- *  @brief A class to obtain the length and MD5 digest of a content file.
- */
+class Content;
+class Log;
+
 class ExamineContentJob : public Job
 {
 public:
-       ExamineContentJob (boost::shared_ptr<Film>);
+       ExamineContentJob (boost::shared_ptr<const Film>, boost::shared_ptr<Content>);
        ~ExamineContentJob ();
 
        std::string name () const;
        void run ();
+
+private:
+       boost::shared_ptr<Content> _content;
 };
 
index e45a62353bf57fa7085701828ccfbc6eec1d2791..6bad7c9245bbb8ae833e4202841a08a3ccfc2896 100644 (file)
@@ -17,8 +17,8 @@
 
 */
 
-#ifndef DVDOMATIC_EXCEPTIONS_H
-#define DVDOMATIC_EXCEPTIONS_H
+#ifndef DCPOMATIC_EXCEPTIONS_H
+#define DCPOMATIC_EXCEPTIONS_H
 
 /** @file  src/exceptions.h
  *  @brief Our exceptions.
@@ -112,6 +112,7 @@ class OpenFileError : public FileError
 {
 public:
        /** @param f File that we were trying to open */
+       /* XXX: should be boost::filesystem::path */
        OpenFileError (std::string f);
 };
 
diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc
new file mode 100644 (file)
index 0000000..a39de39
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+    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.
+
+*/
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+#include <libpostproc/postprocess.h>
+}
+#include "ffmpeg.h"
+#include "ffmpeg_content.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+
+boost::mutex FFmpeg::_mutex;
+
+FFmpeg::FFmpeg (boost::shared_ptr<const FFmpegContent> c)
+       : _ffmpeg_content (c)
+       , _format_context (0)
+       , _frame (0)
+       , _video_stream (-1)
+{
+       setup_general ();
+       setup_video ();
+       setup_audio ();
+}
+
+FFmpeg::~FFmpeg ()
+{
+       boost::mutex::scoped_lock lm (_mutex);
+
+       for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
+               AVCodecContext* context = _format_context->streams[i]->codec;
+               if (context->codec_type == AVMEDIA_TYPE_VIDEO || context->codec_type == AVMEDIA_TYPE_AUDIO) {
+                       avcodec_close (context);
+               }
+       }
+
+       av_free (_frame);
+       
+       avformat_close_input (&_format_context);
+}
+
+void
+FFmpeg::setup_general ()
+{
+       av_register_all ();
+
+       if (avformat_open_input (&_format_context, _ffmpeg_content->file().string().c_str(), 0, 0) < 0) {
+               throw OpenFileError (_ffmpeg_content->file().string ());
+       }
+
+       if (avformat_find_stream_info (_format_context, 0) < 0) {
+               throw DecodeError (_("could not find stream information"));
+       }
+
+       /* Find video stream */
+
+       for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
+               AVStream* s = _format_context->streams[i];
+               if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
+                       _video_stream = i;
+               }
+       }
+
+       if (_video_stream < 0) {
+               throw DecodeError (N_("could not find video stream"));
+       }
+
+       _frame = avcodec_alloc_frame ();
+       if (_frame == 0) {
+               throw DecodeError (N_("could not allocate frame"));
+       }
+}
+
+void
+FFmpeg::setup_video ()
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       
+       AVCodecContext* context = _format_context->streams[_video_stream]->codec;
+       AVCodec* codec = avcodec_find_decoder (context->codec_id);
+
+       if (codec == 0) {
+               throw DecodeError (_("could not find video decoder"));
+       }
+
+       if (avcodec_open2 (context, codec, 0) < 0) {
+               throw DecodeError (N_("could not open video decoder"));
+       }
+}
+
+void
+FFmpeg::setup_audio ()
+{
+       boost::mutex::scoped_lock lm (_mutex);
+
+       for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
+               AVCodecContext* context = _format_context->streams[i]->codec;
+               if (context->codec_type != AVMEDIA_TYPE_AUDIO) {
+                       continue;
+               }
+               
+               AVCodec* codec = avcodec_find_decoder (context->codec_id);
+               if (codec == 0) {
+                       throw DecodeError (_("could not find audio decoder"));
+               }
+               
+               if (avcodec_open2 (context, codec, 0) < 0) {
+                       throw DecodeError (N_("could not open audio decoder"));
+               }
+       }
+}
+
+
+AVCodecContext *
+FFmpeg::video_codec_context () const
+{
+       return _format_context->streams[_video_stream]->codec;
+}
+
+AVCodecContext *
+FFmpeg::audio_codec_context () const
+{
+       return _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec;
+}
diff --git a/src/lib/ffmpeg.h b/src/lib/ffmpeg.h
new file mode 100644 (file)
index 0000000..4d1a45d
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_FFMPEG_H
+#define DCPOMATIC_FFMPEG_H
+
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <boost/thread/mutex.hpp>
+extern "C" {
+#include <libavcodec/avcodec.h>
+}
+
+struct AVFilterGraph;
+struct AVCodecContext;
+struct AVFilterContext;
+struct AVFormatContext;
+struct AVFrame;
+struct AVBufferContext;
+struct AVCodec;
+struct AVStream;
+
+class FFmpegContent;
+
+class FFmpeg
+{
+public:
+       FFmpeg (boost::shared_ptr<const FFmpegContent>);
+       virtual ~FFmpeg ();
+
+       boost::shared_ptr<const FFmpegContent> ffmpeg_content () const {
+               return _ffmpeg_content;
+       }
+
+protected:
+       AVCodecContext* video_codec_context () const;
+       AVCodecContext* audio_codec_context () const;
+       
+       boost::shared_ptr<const FFmpegContent> _ffmpeg_content;
+
+       AVFormatContext* _format_context;
+       AVPacket _packet;
+       AVFrame* _frame;
+
+       int _video_stream;
+
+       /* It would appear (though not completely verified) that one must have
+          a mutex around calls to avcodec_open* and avcodec_close... and here
+          it is.
+       */
+       static boost::mutex _mutex;
+
+private:
+       void setup_general ();
+       void setup_video ();
+       void setup_audio ();
+};
+
+#endif
diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc
new file mode 100644 (file)
index 0000000..1135cc9
--- /dev/null
@@ -0,0 +1,376 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "ffmpeg_content.h"
+#include "ffmpeg_examiner.h"
+#include "compose.hpp"
+#include "job.h"
+#include "util.h"
+#include "filter.h"
+#include "film.h"
+#include "log.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::vector;
+using std::list;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
+int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
+int const FFmpegContentProperty::AUDIO_STREAMS = 102;
+int const FFmpegContentProperty::AUDIO_STREAM = 103;
+int const FFmpegContentProperty::FILTERS = 104;
+
+FFmpegContent::FFmpegContent (shared_ptr<const Film> f, boost::filesystem::path p)
+       : Content (f, p)
+       , VideoContent (f, p)
+       , AudioContent (f, p)
+{
+
+}
+
+FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+       : Content (f, node)
+       , VideoContent (f, node)
+       , AudioContent (f, node)
+{
+       list<shared_ptr<cxml::Node> > c = node->node_children ("SubtitleStream");
+       for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+               _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (*i)));
+               if ((*i)->optional_number_child<int> ("Selected")) {
+                       _subtitle_stream = _subtitle_streams.back ();
+               }
+       }
+
+       c = node->node_children ("AudioStream");
+       for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+               _audio_streams.push_back (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (*i)));
+               if ((*i)->optional_number_child<int> ("Selected")) {
+                       _audio_stream = _audio_streams.back ();
+               }
+       }
+
+       c = node->node_children ("Filter");
+       for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
+               _filters.push_back (Filter::from_id ((*i)->content ()));
+       }
+
+       _first_video = node->optional_number_child<Time> ("FirstVideo");
+}
+
+FFmpegContent::FFmpegContent (FFmpegContent const & o)
+       : Content (o)
+       , VideoContent (o)
+       , AudioContent (o)
+       , _subtitle_streams (o._subtitle_streams)
+       , _subtitle_stream (o._subtitle_stream)
+       , _audio_streams (o._audio_streams)
+       , _audio_stream (o._audio_stream)
+{
+
+}
+
+void
+FFmpegContent::as_xml (xmlpp::Node* node) const
+{
+       node->add_child("Type")->add_child_text ("FFmpeg");
+       Content::as_xml (node);
+       VideoContent::as_xml (node);
+       AudioContent::as_xml (node);
+
+       boost::mutex::scoped_lock lm (_mutex);
+
+       for (vector<shared_ptr<FFmpegSubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
+               xmlpp::Node* t = node->add_child("SubtitleStream");
+               if (_subtitle_stream && *i == _subtitle_stream) {
+                       t->add_child("Selected")->add_child_text("1");
+               }
+               (*i)->as_xml (t);
+       }
+
+       for (vector<shared_ptr<FFmpegAudioStream> >::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
+               xmlpp::Node* t = node->add_child("AudioStream");
+               if (_audio_stream && *i == _audio_stream) {
+                       t->add_child("Selected")->add_child_text("1");
+               }
+               (*i)->as_xml (t);
+       }
+
+       for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
+               node->add_child("Filter")->add_child_text ((*i)->id ());
+       }
+
+       if (_first_video) {
+               node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get ()));
+       }
+}
+
+void
+FFmpegContent::examine (shared_ptr<Job> job)
+{
+       job->set_progress_unknown ();
+
+       Content::examine (job);
+
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+
+       shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
+
+       VideoContent::Frame video_length = 0;
+       video_length = examiner->video_length ();
+       film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
+
+        {
+                boost::mutex::scoped_lock lm (_mutex);
+
+                _video_length = video_length;
+
+                _subtitle_streams = examiner->subtitle_streams ();
+                if (!_subtitle_streams.empty ()) {
+                        _subtitle_stream = _subtitle_streams.front ();
+                }
+                
+                _audio_streams = examiner->audio_streams ();
+                if (!_audio_streams.empty ()) {
+                        _audio_stream = _audio_streams.front ();
+                }
+
+               _first_video = examiner->first_video ();
+        }
+
+        take_from_video_examiner (examiner);
+
+        signal_changed (ContentProperty::LENGTH);
+        signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
+        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
+        signal_changed (FFmpegContentProperty::AUDIO_STREAMS);
+        signal_changed (FFmpegContentProperty::AUDIO_STREAM);
+        signal_changed (AudioContentProperty::AUDIO_CHANNELS);
+}
+
+string
+FFmpegContent::summary () const
+{
+       return String::compose (_("Movie: %1"), file().filename().string());
+}
+
+string
+FFmpegContent::information () const
+{
+       if (video_length() == 0 || video_frame_rate() == 0) {
+               return "";
+       }
+       
+       stringstream s;
+       
+       s << String::compose (_("%1 frames; %2 frames per second"), video_length(), video_frame_rate()) << "\n";
+       s << VideoContent::information ();
+
+       return s.str ();
+}
+
+void
+FFmpegContent::set_subtitle_stream (shared_ptr<FFmpegSubtitleStream> s)
+{
+        {
+                boost::mutex::scoped_lock lm (_mutex);
+                _subtitle_stream = s;
+        }
+
+        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
+}
+
+void
+FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s)
+{
+        {
+                boost::mutex::scoped_lock lm (_mutex);
+                _audio_stream = s;
+        }
+
+        signal_changed (FFmpegContentProperty::AUDIO_STREAM);
+}
+
+AudioContent::Frame
+FFmpegContent::audio_length () const
+{
+       int const cafr = content_audio_frame_rate ();
+       int const vfr  = video_frame_rate ();
+       VideoContent::Frame const vl = video_length ();
+
+       boost::mutex::scoped_lock lm (_mutex);
+        if (!_audio_stream) {
+                return 0;
+        }
+        
+        return video_frames_to_audio_frames (vl, cafr, vfr);
+}
+
+int
+FFmpegContent::audio_channels () const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       
+        if (!_audio_stream) {
+                return 0;
+        }
+
+        return _audio_stream->channels;
+}
+
+int
+FFmpegContent::content_audio_frame_rate () const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+
+        if (!_audio_stream) {
+                return 0;
+        }
+
+        return _audio_stream->frame_rate;
+}
+
+int
+FFmpegContent::output_audio_frame_rate () const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       /* Resample to a DCI-approved sample rate */
+       double t = dcp_audio_frame_rate (content_audio_frame_rate ());
+
+       FrameRateConversion frc (video_frame_rate(), film->dcp_video_frame_rate());
+
+       /* Compensate if the DCP is being run at a different frame rate
+          to the source; that is, if the video is run such that it will
+          look different in the DCP compared to the source (slower or faster).
+          skip/repeat doesn't come into effect here.
+       */
+
+       if (frc.change_speed) {
+               t *= video_frame_rate() * frc.factor() / film->dcp_video_frame_rate();
+       }
+
+       return rint (t);
+}
+
+bool
+operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
+{
+        return a.id == b.id;
+}
+
+bool
+operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
+{
+        return a.id == b.id;
+}
+
+FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node)
+{
+       name = node->string_child ("Name");
+       id = node->number_child<int> ("Id");
+       frame_rate = node->number_child<int> ("FrameRate");
+       channels = node->number_child<int64_t> ("Channels");
+       mapping = AudioMapping (node->node_child ("Mapping"));
+       first_audio = node->optional_number_child<Time> ("FirstAudio");
+}
+
+void
+FFmpegAudioStream::as_xml (xmlpp::Node* root) const
+{
+       root->add_child("Name")->add_child_text (name);
+       root->add_child("Id")->add_child_text (lexical_cast<string> (id));
+       root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
+       root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
+       if (first_audio) {
+               root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio));
+       }
+       mapping.as_xml (root->add_child("Mapping"));
+}
+
+/** Construct a SubtitleStream from a value returned from to_string().
+ *  @param t String returned from to_string().
+ *  @param v State file version.
+ */
+FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
+{
+       name = node->string_child ("Name");
+       id = node->number_child<int> ("Id");
+}
+
+void
+FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
+{
+       root->add_child("Name")->add_child_text (name);
+       root->add_child("Id")->add_child_text (lexical_cast<string> (id));
+}
+
+shared_ptr<Content>
+FFmpegContent::clone () const
+{
+       return shared_ptr<Content> (new FFmpegContent (*this));
+}
+
+Time
+FFmpegContent::length () const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       FrameRateConversion frc (video_frame_rate (), film->dcp_video_frame_rate ());
+       return video_length() * frc.factor() * TIME_HZ / film->dcp_video_frame_rate ();
+}
+
+AudioMapping
+FFmpegContent::audio_mapping () const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+
+       if (!_audio_stream) {
+               return AudioMapping ();
+       }
+
+       return _audio_stream->mapping;
+}
+
+void
+FFmpegContent::set_filters (vector<Filter const *> const & filters)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _filters = filters;
+       }
+
+       signal_changed (FFmpegContentProperty::FILTERS);
+}
+
+void
+FFmpegContent::set_audio_mapping (AudioMapping m)
+{
+       audio_stream()->mapping = m;
+       signal_changed (AudioContentProperty::AUDIO_MAPPING);
+}
diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h
new file mode 100644 (file)
index 0000000..c5ccee7
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_FFMPEG_CONTENT_H
+#define DCPOMATIC_FFMPEG_CONTENT_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include "video_content.h"
+#include "audio_content.h"
+
+class Filter;
+
+class FFmpegAudioStream
+{
+public:
+        FFmpegAudioStream (std::string n, int i, int f, int c)
+                : name (n)
+                , id (i)
+                , frame_rate (f)
+               , channels (c)
+               , mapping (c)
+        {}
+
+       FFmpegAudioStream (boost::shared_ptr<const cxml::Node>);
+
+       void as_xml (xmlpp::Node *) const;
+       
+        std::string name;
+        int id;
+        int frame_rate;
+       int channels;
+       AudioMapping mapping;
+       boost::optional<double> first_audio;
+};
+
+extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
+
+class FFmpegSubtitleStream
+{
+public:
+        FFmpegSubtitleStream (std::string n, int i)
+                : name (n)
+                , id (i)
+        {}
+        
+       FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>);
+
+       void as_xml (xmlpp::Node *) const;
+       
+        std::string name;
+        int id;
+};
+
+extern bool operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
+
+class FFmpegContentProperty : public VideoContentProperty
+{
+public:
+        static int const SUBTITLE_STREAMS;
+        static int const SUBTITLE_STREAM;
+        static int const AUDIO_STREAMS;
+        static int const AUDIO_STREAM;
+        static int const FILTERS;
+};
+
+class FFmpegContent : public VideoContent, public AudioContent
+{
+public:
+       FFmpegContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+       FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+       FFmpegContent (FFmpegContent const &);
+
+       boost::shared_ptr<FFmpegContent> shared_from_this () {
+               return boost::dynamic_pointer_cast<FFmpegContent> (Content::shared_from_this ());
+       }
+       
+       void examine (boost::shared_ptr<Job>);
+       std::string summary () const;
+       std::string information () const;
+       void as_xml (xmlpp::Node *) const;
+       boost::shared_ptr<Content> clone () const;
+       Time length () const;
+
+        /* AudioContent */
+        int audio_channels () const;
+        AudioContent::Frame audio_length () const;
+        int content_audio_frame_rate () const;
+        int output_audio_frame_rate () const;
+       AudioMapping audio_mapping () const;
+       void set_audio_mapping (AudioMapping);
+
+       void set_filters (std::vector<Filter const *> const &);
+       
+        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
+                boost::mutex::scoped_lock lm (_mutex);
+                return _subtitle_streams;
+        }
+
+        boost::shared_ptr<FFmpegSubtitleStream> subtitle_stream () const {
+                boost::mutex::scoped_lock lm (_mutex);
+                return _subtitle_stream;
+        }
+
+        std::vector<boost::shared_ptr<FFmpegAudioStream> > audio_streams () const {
+                boost::mutex::scoped_lock lm (_mutex);
+                return _audio_streams;
+        }
+        
+        boost::shared_ptr<FFmpegAudioStream> audio_stream () const {
+                boost::mutex::scoped_lock lm (_mutex);
+                return _audio_stream;
+        }
+
+       std::vector<Filter const *> filters () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _filters;
+       }
+
+        void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
+        void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>);
+
+       boost::optional<Time> first_video () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _first_video;
+       }
+       
+private:
+       std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
+       boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
+       std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
+       boost::shared_ptr<FFmpegAudioStream> _audio_stream;
+       boost::optional<Time> _first_video;
+       /** Video filters that should be used when generating DCPs */
+       std::vector<Filter const *> _filters;
+};
+
+#endif
index c2143b949e7d3618d4315ee3f54a4f6bd100b671..bf094913082104a1392048f2b8003daf245c825e 100644 (file)
 #include <iostream>
 #include <stdint.h>
 #include <boost/lexical_cast.hpp>
+#include <sndfile.h>
 extern "C" {
 #include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
-#include <libswscale/swscale.h>
-#include <libpostproc/postprocess.h>
 }
-#include <sndfile.h>
 #include "film.h"
-#include "format.h"
-#include "transcoder.h"
-#include "job.h"
 #include "filter.h"
-#include "options.h"
 #include "exceptions.h"
 #include "image.h"
 #include "util.h"
@@ -48,6 +42,7 @@ extern "C" {
 #include "ffmpeg_decoder.h"
 #include "filter_graph.h"
 #include "subtitle.h"
+#include "audio_buffers.h"
 
 #include "i18n.h"
 
@@ -56,182 +51,66 @@ using std::string;
 using std::vector;
 using std::stringstream;
 using std::list;
+using std::min;
 using boost::shared_ptr;
 using boost::optional;
 using boost::dynamic_pointer_cast;
 using libdcp::Size;
 
-boost::mutex FFmpegDecoder::_mutex;
-
-FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o)
-       : Decoder (f, o)
-       , VideoDecoder (f, o)
-       , AudioDecoder (f, o)
-       , _format_context (0)
-       , _video_stream (-1)
-       , _frame (0)
-       , _video_codec_context (0)
-       , _video_codec (0)
-       , _audio_codec_context (0)
-       , _audio_codec (0)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
+       : Decoder (f)
+       , VideoDecoder (f)
+       , AudioDecoder (f)
+       , FFmpeg (c)
        , _subtitle_codec_context (0)
        , _subtitle_codec (0)
+       , _decode_video (video)
+       , _decode_audio (audio)
+       , _pts_offset (0)
 {
-       setup_general ();
-       setup_video ();
-       setup_audio ();
        setup_subtitle ();
-}
-
-FFmpegDecoder::~FFmpegDecoder ()
-{
-       boost::mutex::scoped_lock lm (_mutex);
-       
-       if (_audio_codec_context) {
-               avcodec_close (_audio_codec_context);
-       }
-       
-       if (_video_codec_context) {
-               avcodec_close (_video_codec_context);
-       }
-
-       if (_subtitle_codec_context) {
-               avcodec_close (_subtitle_codec_context);
-       }
-
-       av_free (_frame);
-       
-       avformat_close_input (&_format_context);
-}      
-
-void
-FFmpegDecoder::setup_general ()
-{
-       av_register_all ();
-
-       if (avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0) < 0) {
-               throw OpenFileError (_film->content_path ());
-       }
-
-       if (avformat_find_stream_info (_format_context, 0) < 0) {
-               throw DecodeError (_("could not find stream information"));
-       }
-
-       /* Find video, audio and subtitle streams and choose the first of each */
-
-       for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
-               AVStream* s = _format_context->streams[i];
-               if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
-                       _video_stream = i;
-               } else if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
-
-                       /* This is a hack; sometimes it seems that _audio_codec_context->channel_layout isn't set up,
-                          so bodge it here.  No idea why we should have to do this.
-                       */
-
-                       if (s->codec->channel_layout == 0) {
-                               s->codec->channel_layout = av_get_default_channel_layout (s->codec->channels);
-                       }
-                       
-                       _audio_streams.push_back (
-                               shared_ptr<AudioStream> (
-                                       new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channel_layout)
-                                       )
-                               );
-                       
-               } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
-                       _subtitle_streams.push_back (
-                               shared_ptr<SubtitleStream> (
-                                       new SubtitleStream (stream_name (s), i)
-                                       )
-                               );
-               }
-       }
-
-       if (_video_stream < 0) {
-               throw DecodeError (N_("could not find video stream"));
-       }
 
-       _frame = avcodec_alloc_frame ();
-       if (_frame == 0) {
-               throw DecodeError (N_("could not allocate frame"));
+       if (video && audio && c->audio_stream() && c->first_video() && c->audio_stream()->first_audio) {
+               _pts_offset = compute_pts_offset (c->first_video().get(), c->audio_stream()->first_audio.get(), c->video_frame_rate());
        }
 }
 
-void
-FFmpegDecoder::setup_video ()
+double
+FFmpegDecoder::compute_pts_offset (double first_video, double first_audio, float video_frame_rate)
 {
-       boost::mutex::scoped_lock lm (_mutex);
+       double const old_first_video = first_video;
        
-       _video_codec_context = _format_context->streams[_video_stream]->codec;
-       _video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
-
-       if (_video_codec == 0) {
-               throw DecodeError (_("could not find video decoder"));
+       /* Round the first video to a frame boundary */
+       if (fabs (rint (first_video * video_frame_rate) - first_video * video_frame_rate) > 1e-6) {
+               first_video = ceil (first_video * video_frame_rate) / video_frame_rate;
        }
 
-       if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
-               throw DecodeError (N_("could not open video decoder"));
-       }
+       /* Compute the required offset (also removing any common start delay) */
+       return first_video - old_first_video - min (first_video, first_audio);
 }
 
-void
-FFmpegDecoder::setup_audio ()
+FFmpegDecoder::~FFmpegDecoder ()
 {
        boost::mutex::scoped_lock lm (_mutex);
-       
-       if (!_audio_stream) {
-               return;
-       }
-
-       shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-       assert (ffa);
-       
-       _audio_codec_context = _format_context->streams[ffa->id()]->codec;
-       _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id);
 
-       if (_audio_codec == 0) {
-               throw DecodeError (_("could not find audio decoder"));
-       }
-
-       if (avcodec_open2 (_audio_codec_context, _audio_codec, 0) < 0) {
-               throw DecodeError (N_("could not open audio decoder"));
+       if (_subtitle_codec_context) {
+               avcodec_close (_subtitle_codec_context);
        }
-}
+}      
 
 void
-FFmpegDecoder::setup_subtitle ()
-{
-       boost::mutex::scoped_lock lm (_mutex);
-       
-       if (!_subtitle_stream || _subtitle_stream->id() >= int (_format_context->nb_streams)) {
-               return;
-       }
-
-       _subtitle_codec_context = _format_context->streams[_subtitle_stream->id()]->codec;
-       _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
-
-       if (_subtitle_codec == 0) {
-               throw DecodeError (_("could not find subtitle decoder"));
-       }
-       
-       if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
-               throw DecodeError (N_("could not open subtitle decoder"));
-       }
-}
-
-
-bool
 FFmpegDecoder::pass ()
 {
        int r = av_read_frame (_format_context, &_packet);
-       
+
        if (r < 0) {
                if (r != AVERROR_EOF) {
                        /* Maybe we should fail here, but for now we'll just finish off instead */
                        char buf[256];
                        av_strerror (r, buf, sizeof(buf));
-                       _film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
+                       shared_ptr<const Film> film = _film.lock ();
+                       assert (film);
+                       film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
                }
 
                /* Get any remaining frames */
@@ -241,41 +120,28 @@ FFmpegDecoder::pass ()
                
                /* XXX: should we reset _packet.data and size after each *_decode_* call? */
                
-               int frame_finished;
-               
-               if (_opt.decode_video) {
-                       while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
-                               filter_and_emit_video ();
-                       }
+               if (_decode_video) {
+                       while (decode_video_packet ());
                }
-               
-               if (_audio_stream && _opt.decode_audio) {
+
+               if (_ffmpeg_content->audio_stream() && _decode_audio) {
                        decode_audio_packet ();
                }
-                       
-               return true;
+
+               /* Stop us being asked for any more data */
+               _video_position = _ffmpeg_content->video_length ();
+               _audio_position = _ffmpeg_content->audio_length ();
+               return;
        }
 
        avcodec_get_frame_defaults (_frame);
 
-       shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-
-       if (_packet.stream_index == _video_stream && _opt.decode_video) {
-
-               int frame_finished;
-               int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet);
-               if (r >= 0 && frame_finished) {
-
-                       if (r != _packet.size) {
-                               _film->log()->log (String::compose (N_("Used only %1 bytes of %2 in packet"), r, _packet.size));
-                       }
-
-                       filter_and_emit_video ();
-               }
-
-       } else if (ffa && _packet.stream_index == ffa->id() && _opt.decode_audio) {
+       if (_packet.stream_index == _video_stream && _decode_video) {
+               decode_video_packet ();
+       } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->id && _decode_audio) {
                decode_audio_packet ();
-       } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles) {
+       } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id) {
+#if 0          
 
                int got_subtitle;
                AVSubtitle sub;
@@ -286,19 +152,19 @@ FFmpegDecoder::pass ()
                        if (sub.num_rects > 0) {
                                shared_ptr<TimedSubtitle> ts;
                                try {
-                                       emit_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub)));
+                                       subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub)));
                                } catch (...) {
                                        /* some problem with the subtitle; we probably didn't understand it */
                                }
                        } else {
-                               emit_subtitle (shared_ptr<TimedSubtitle> ());
+                               subtitle (shared_ptr<TimedSubtitle> ());
                        }
                        avsubtitle_free (&sub);
                }
+#endif         
        }
-       
+
        av_free_packet (&_packet);
-       return false;
 }
 
 /** @param data pointer to array of pointers to buffers.
@@ -307,19 +173,16 @@ FFmpegDecoder::pass ()
 shared_ptr<AudioBuffers>
 FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
 {
-       assert (_film->audio_channels());
+       assert (_ffmpeg_content->audio_channels());
        assert (bytes_per_audio_sample());
 
-       shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-       assert (ffa);
-       
        /* Deinterleave and convert to float */
 
-       assert ((size % (bytes_per_audio_sample() * ffa->channels())) == 0);
+       assert ((size % (bytes_per_audio_sample() * _ffmpeg_content->audio_channels())) == 0);
 
        int const total_samples = size / bytes_per_audio_sample();
-       int const frames = total_samples / _film->audio_channels();
-       shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), frames));
+       int const frames = total_samples / _ffmpeg_content->audio_channels();
+       shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames));
 
        switch (audio_sample_format()) {
        case AV_SAMPLE_FMT_S16:
@@ -331,7 +194,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
                        audio->data(channel)[sample] = float(*p++) / (1 << 15);
 
                        ++channel;
-                       if (channel == _film->audio_channels()) {
+                       if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
@@ -342,7 +205,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
        case AV_SAMPLE_FMT_S16P:
        {
                int16_t** p = reinterpret_cast<int16_t **> (data);
-               for (int i = 0; i < _film->audio_channels(); ++i) {
+               for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
                        for (int j = 0; j < frames; ++j) {
                                audio->data(i)[j] = static_cast<float>(p[i][j]) / (1 << 15);
                        }
@@ -359,7 +222,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
                        audio->data(channel)[sample] = static_cast<float>(*p++) / (1 << 31);
 
                        ++channel;
-                       if (channel == _film->audio_channels()) {
+                       if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
@@ -376,7 +239,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
                        audio->data(channel)[sample] = *p++;
 
                        ++channel;
-                       if (channel == _film->audio_channels()) {
+                       if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
@@ -387,7 +250,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
        case AV_SAMPLE_FMT_FLTP:
        {
                float** p = reinterpret_cast<float**> (data);
-               for (int i = 0; i < _film->audio_channels(); ++i) {
+               for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
                        memcpy (audio->data(i), p[i], frames * sizeof(float));
                }
        }
@@ -400,89 +263,14 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
        return audio;
 }
 
-float
-FFmpegDecoder::frames_per_second () const
-{
-       AVStream* s = _format_context->streams[_video_stream];
-
-       if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
-               return av_q2d (s->avg_frame_rate);
-       }
-
-       return av_q2d (s->r_frame_rate);
-}
-
 AVSampleFormat
 FFmpegDecoder::audio_sample_format () const
 {
-       if (_audio_codec_context == 0) {
+       if (!_ffmpeg_content->audio_stream()) {
                return (AVSampleFormat) 0;
        }
        
-       return _audio_codec_context->sample_fmt;
-}
-
-libdcp::Size
-FFmpegDecoder::native_size () const
-{
-       return libdcp::Size (_video_codec_context->width, _video_codec_context->height);
-}
-
-PixelFormat
-FFmpegDecoder::pixel_format () const
-{
-       return _video_codec_context->pix_fmt;
-}
-
-int
-FFmpegDecoder::time_base_numerator () const
-{
-       return _video_codec_context->time_base.num;
-}
-
-int
-FFmpegDecoder::time_base_denominator () const
-{
-       return _video_codec_context->time_base.den;
-}
-
-int
-FFmpegDecoder::sample_aspect_ratio_numerator () const
-{
-       return _video_codec_context->sample_aspect_ratio.num;
-}
-
-int
-FFmpegDecoder::sample_aspect_ratio_denominator () const
-{
-       return _video_codec_context->sample_aspect_ratio.den;
-}
-
-string
-FFmpegDecoder::stream_name (AVStream* s) const
-{
-       stringstream n;
-
-       if (s->metadata) {
-               AVDictionaryEntry const * lang = av_dict_get (s->metadata, N_("language"), 0, 0);
-               if (lang) {
-                       n << lang->value;
-               }
-               
-               AVDictionaryEntry const * title = av_dict_get (s->metadata, N_("title"), 0, 0);
-               if (title) {
-                       if (!n.str().empty()) {
-                               n << N_(" ");
-                       }
-                       n << title->value;
-               }
-       }
-
-       if (n.str().empty()) {
-               n << N_("unknown");
-       }
-
-       return n.str ();
+       return audio_codec_context()->sample_fmt;
 }
 
 int
@@ -492,91 +280,29 @@ FFmpegDecoder::bytes_per_audio_sample () const
 }
 
 void
-FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s)
+FFmpegDecoder::seek (VideoContent::Frame frame)
 {
-       AudioDecoder::set_audio_stream (s);
-       setup_audio ();
+       do_seek (frame, false, false);
 }
 
 void
-FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
-       VideoDecoder::set_subtitle_stream (s);
-       setup_subtitle ();
-       OutputChanged ();
-}
-
-void
-FFmpegDecoder::filter_and_emit_video ()
+FFmpegDecoder::seek_back ()
 {
-       int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
-       if (bet == AV_NOPTS_VALUE) {
-               _film->log()->log ("Dropping frame without PTS");
+       if (_video_position == 0) {
                return;
        }
        
-       shared_ptr<FilterGraph> graph;
-
-       {
-               boost::mutex::scoped_lock lm (_filter_graphs_mutex);
-               
-               list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
-               while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
-                       ++i;
-               }
-               
-               if (i == _filter_graphs.end ()) {
-                       graph = filter_graph_factory (_film, this, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format);
-                       _filter_graphs.push_back (graph);
-                       _film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
-               } else {
-                       graph = *i;
-               }
-       }
-
-       list<shared_ptr<Image> > images = graph->process (_frame);
-
-       for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
-               emit_video (*i, false, bet * av_q2d (_format_context->streams[_video_stream]->time_base));
-       }
-}
-
-bool
-FFmpegDecoder::seek (double p)
-{
-       return do_seek (p, false, false);
-}
-
-bool
-FFmpegDecoder::seek_to_last ()
-{
-       /* This AVSEEK_FLAG_BACKWARD in do_seek is a bit of a hack; without it, if we ask for a seek to the same place as last time
-          (used when we change decoder parameters and want to re-fetch the frame) we end up going forwards rather than
-          staying in the same place.
-       */
-       return do_seek (last_source_time(), true, false);
-}
-
-void
-FFmpegDecoder::seek_back ()
-{
-       do_seek (last_source_time() - 2.5 / frames_per_second (), true, true);
+       do_seek (_video_position - 1, true, true);
 }
 
 void
-FFmpegDecoder::seek_forward ()
-{
-       do_seek (last_source_time() - 0.5 / frames_per_second(), true, true);
-}
-
-bool
-FFmpegDecoder::do_seek (double p, bool backwards, bool accurate)
+FFmpegDecoder::do_seek (VideoContent::Frame frame, bool backwards, bool accurate)
 {
-       int64_t const vt = p / av_q2d (_format_context->streams[_video_stream]->time_base);
+       int64_t const vt = frame * _ffmpeg_content->video_frame_rate() / av_q2d (_format_context->streams[_video_stream]->time_base);
+       av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
+       _video_position = frame;
 
-       int const r = av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
-
-       avcodec_flush_buffers (_video_codec_context);
+       avcodec_flush_buffers (video_codec_context());
        if (_subtitle_codec_context) {
                avcodec_flush_buffers (_subtitle_codec_context);
        }
@@ -585,17 +311,19 @@ FFmpegDecoder::do_seek (double p, bool backwards, bool accurate)
                while (1) {
                        int r = av_read_frame (_format_context, &_packet);
                        if (r < 0) {
-                               return true;
+                               return;
                        }
                        
                        avcodec_get_frame_defaults (_frame);
                        
                        if (_packet.stream_index == _video_stream) {
                                int finished = 0;
-                               int const r = avcodec_decode_video2 (_video_codec_context, _frame, &finished, &_packet);
+                               int const r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
                                if (r >= 0 && finished) {
                                        int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
                                        if (bet > vt) {
+                                               _video_position = (bet * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset)
+                                                       * _ffmpeg_content->video_frame_rate();
                                                break;
                                        }
                                }
@@ -604,123 +332,156 @@ FFmpegDecoder::do_seek (double p, bool backwards, bool accurate)
                        av_free_packet (&_packet);
                }
        }
-               
-       return r < 0;
 }
 
-shared_ptr<FFmpegAudioStream>
-FFmpegAudioStream::create (string t, optional<int> v)
+void
+FFmpegDecoder::decode_audio_packet ()
 {
-       if (!v) {
-               /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
-               return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
-       }
+       /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
+          several times.
+       */
+       
+       AVPacket copy_packet = _packet;
 
-       stringstream s (t);
-       string type;
-       s >> type;
-       if (type != N_("ffmpeg")) {
-               return shared_ptr<FFmpegAudioStream> ();
-       }
+       while (copy_packet.size > 0) {
 
-       return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
+               int frame_finished;
+               int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
+               if (decode_result >= 0) {
+                       if (frame_finished) {
+
+                               if (_audio_position == 0) {
+                                       /* Where we are in the source, in seconds */
+                                       double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
+                                               * av_frame_get_best_effort_timestamp(_frame) - _pts_offset;
+
+                                       if (pts > 0) {
+                                               /* Emit some silence */
+                                               shared_ptr<AudioBuffers> silence (
+                                                       new AudioBuffers (
+                                                               _ffmpeg_content->audio_channels(),
+                                                               pts * _ffmpeg_content->content_audio_frame_rate()
+                                                               )
+                                                       );
+                                               
+                                               silence->make_silent ();
+                                               audio (silence, _audio_position);
+                                       }
+                               }
+                                       
+                               copy_packet.data += decode_result;
+                               copy_packet.size -= decode_result;
+                       }
+               }
+       }
 }
 
-FFmpegAudioStream::FFmpegAudioStream (string t, optional<int> version)
+bool
+FFmpegDecoder::decode_video_packet ()
 {
-       stringstream n (t);
-       
-       int name_index = 4;
-       if (!version) {
-               name_index = 2;
-               int channels;
-               n >> _id >> channels;
-               _channel_layout = av_get_default_channel_layout (channels);
-               _sample_rate = 0;
-       } else {
-               string type;
-               /* Current (marked version 1) */
-               n >> type >> _id >> _sample_rate >> _channel_layout;
-               assert (type == N_("ffmpeg"));
+       int frame_finished;
+       if (avcodec_decode_video2 (video_codec_context(), _frame, &frame_finished, &_packet) < 0 || !frame_finished) {
+               return false;
        }
+               
+       boost::mutex::scoped_lock lm (_filter_graphs_mutex);
 
-       for (int i = 0; i < name_index; ++i) {
-               size_t const s = t.find (' ');
-               if (s != string::npos) {
-                       t = t.substr (s + 1);
-               }
+       shared_ptr<FilterGraph> graph;
+       
+       list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
+       while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
+               ++i;
        }
 
-       _name = t;
-}
+       if (i == _filter_graphs.end ()) {
+               shared_ptr<const Film> film = _film.lock ();
+               assert (film);
 
-string
-FFmpegAudioStream::to_string () const
-{
-       return String::compose (N_("ffmpeg %1 %2 %3 %4"), _id, _sample_rate, _channel_layout, _name);
-}
+               graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
+               _filter_graphs.push_back (graph);
 
-void
-FFmpegDecoder::film_changed (Film::Property p)
-{
-       switch (p) {
-       case Film::CROP:
-       case Film::FILTERS:
-       {
-               boost::mutex::scoped_lock lm (_filter_graphs_mutex);
-               _filter_graphs.clear ();
+               film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
+       } else {
+               graph = *i;
        }
-       OutputChanged ();
-       break;
 
-       default:
-               break;
+       list<shared_ptr<Image> > images = graph->process (_frame);
+
+       string post_process = Filter::ffmpeg_strings (_ffmpeg_content->filters()).second;
+       
+       for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
+
+               shared_ptr<Image> image = *i;
+               if (!post_process.empty ()) {
+                       image = image->post_process (post_process, true);
+               }
+               
+               int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
+               if (bet != AV_NOPTS_VALUE) {
+
+                       double const pts = bet * av_q2d (_format_context->streams[_video_stream]->time_base) - _pts_offset;
+                       double const next = _video_position / _ffmpeg_content->video_frame_rate();
+                       double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
+                       double delta = pts - next;
+
+                       while (delta > one_frame) {
+                               /* This PTS is more than one frame forward in time of where we think we should be; emit
+                                  a black frame.
+                               */
+                               boost::shared_ptr<Image> black (
+                                       new SimpleImage (
+                                               static_cast<AVPixelFormat> (_frame->format),
+                                               libdcp::Size (video_codec_context()->width, video_codec_context()->height),
+                                               true
+                                               )
+                                       );
+                               
+                               black->make_black ();
+                               video (image, false, _video_position);
+                               delta -= one_frame;
+                       }
+
+                       if (delta > -one_frame) {
+                               /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
+                               video (image, false, _video_position);
+                       }
+               } else {
+                       shared_ptr<const Film> film = _film.lock ();
+                       assert (film);
+                       film->log()->log ("Dropping frame without PTS");
+               }
        }
-}
 
-/** @return Length (in video frames) according to our content's header */
-SourceFrame
-FFmpegDecoder::length () const
-{
-       return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second();
+       return true;
 }
 
+       
 void
-FFmpegDecoder::decode_audio_packet ()
+FFmpegDecoder::setup_subtitle ()
 {
-       shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-       assert (ffa);
-
-       /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
-          several times.
-       */
+       boost::mutex::scoped_lock lm (_mutex);
        
-       AVPacket copy_packet = _packet;
+       if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->id >= int (_format_context->nb_streams)) {
+               return;
+       }
 
-       while (copy_packet.size > 0) {
+       _subtitle_codec_context = _format_context->streams[_ffmpeg_content->subtitle_stream()->id]->codec;
+       _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
 
-               int frame_finished;
-               int const decode_result = avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &copy_packet);
-               if (decode_result < 0) {
-                       /* error */
-                       break;
-               }
-               
-               if (frame_finished) {
-                       
-                       /* Where we are in the source, in seconds */
-                       double const source_pts_seconds = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
-                               * av_frame_get_best_effort_timestamp(_frame);
-                       
-                       int const data_size = av_samples_get_buffer_size (
-                               0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
-                               );
-                       
-                       assert (_audio_codec_context->channels == _film->audio_channels());
-                       Audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds);
-               }
-               
-               copy_packet.data += decode_result;
-               copy_packet.size -= decode_result;
+       if (_subtitle_codec == 0) {
+               throw DecodeError (_("could not find subtitle decoder"));
+       }
+       
+       if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
+               throw DecodeError (N_("could not open subtitle decoder"));
        }
 }
+
+bool
+FFmpegDecoder::done () const
+{
+       bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
+       bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
+       return vd && ad;
+}
+       
index 198f4294e98b8bd8c08ea6cb4c94a251952bb5ae..8f0482aad8331dcd12685e1d4724e08469b20eb8 100644 (file)
@@ -35,119 +35,54 @@ extern "C" {
 #include "decoder.h"
 #include "video_decoder.h"
 #include "audio_decoder.h"
-#include "film.h"
-
-struct AVFilterGraph;
-struct AVCodecContext;
-struct AVFilterContext;
-struct AVFormatContext;
-struct AVFrame;
-struct AVBufferContext;
-struct AVCodec;
-struct AVStream;
-class Job;
-class Options;
-class Image;
-class Log;
-
-class FFmpegAudioStream : public AudioStream
-{
-public:
-       FFmpegAudioStream (std::string n, int i, int s, int64_t c)
-               : AudioStream (s, c)
-               , _name (n)
-               , _id (i)
-       {}
-
-       std::string to_string () const;
-
-       std::string name () const {
-               return _name;
-       }
+#include "ffmpeg.h"
 
-       int id () const {
-               return _id;
-       }
-
-       static boost::shared_ptr<FFmpegAudioStream> create (std::string t, boost::optional<int> v);
-
-private:
-       friend class stream_test;
-       
-       FFmpegAudioStream (std::string t, boost::optional<int> v);
-       
-       std::string _name;
-       int _id;
-};
+class Film;
+class ffmpeg_pts_offset_test;
 
 /** @class FFmpegDecoder
  *  @brief A decoder using FFmpeg to decode content.
  */
-class FFmpegDecoder : public VideoDecoder, public AudioDecoder
+class FFmpegDecoder : public VideoDecoder, public AudioDecoder, public FFmpeg
 {
 public:
-       FFmpegDecoder (boost::shared_ptr<Film>, DecodeOptions);
+       FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio);
        ~FFmpegDecoder ();
 
-       float frames_per_second () const;
-       libdcp::Size native_size () const;
-       SourceFrame length () const;
-       int time_base_numerator () const;
-       int time_base_denominator () const;
-       int sample_aspect_ratio_numerator () const;
-       int sample_aspect_ratio_denominator () const;
-
-       void set_audio_stream (boost::shared_ptr<AudioStream>);
-       void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
-
-       bool seek (double);
-       bool seek_to_last ();
-       void seek_forward ();
+       void pass ();
+       void seek (VideoContent::Frame);
        void seek_back ();
+       bool done () const;
 
 private:
+       friend class ::ffmpeg_pts_offset_test;
 
        /* No copy construction */
        FFmpegDecoder (FFmpegDecoder const &);
+       FFmpegDecoder& operator= (FFmpegDecoder const &);
 
-       bool pass ();
-       bool do_seek (double p, bool, bool);
-       PixelFormat pixel_format () const;
-       AVSampleFormat audio_sample_format () const;
-       int bytes_per_audio_sample () const;
-
-       void filter_and_emit_video ();
+       static double compute_pts_offset (double, double, float);
 
-       void setup_general ();
-       void setup_video ();
-       void setup_audio ();
        void setup_subtitle ();
 
+       AVSampleFormat audio_sample_format () const;
+       int bytes_per_audio_sample () const;
+       void do_seek (VideoContent::Frame, bool, bool);
+
+       bool decode_video_packet ();
        void decode_audio_packet ();
 
        void maybe_add_subtitle ();
        boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
 
-       void film_changed (Film::Property);
-
-       std::string stream_name (AVStream* s) const;
-
-       AVFormatContext* _format_context;
-       int _video_stream;
-       
-       AVFrame* _frame;
-
-       AVCodecContext* _video_codec_context;
-       AVCodec* _video_codec;
-       AVCodecContext* _audio_codec_context;    ///< may be 0 if there is no audio
-       AVCodec* _audio_codec;                   ///< may be 0 if there is no audio
        AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle
        AVCodec* _subtitle_codec;                ///< may be 0 if there is no subtitle
-
-       AVPacket _packet;
-
+       
        std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
        boost::mutex _filter_graphs_mutex;
 
-       static boost::mutex _mutex;
+       bool _decode_video;
+       bool _decode_audio;
+
+       double _pts_offset;
 };
diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc
new file mode 100644 (file)
index 0000000..f45b0fe
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+    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.
+
+*/
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+}
+#include "ffmpeg_examiner.h"
+#include "ffmpeg_content.h"
+
+using std::string;
+using std::cout;
+using std::stringstream;
+using boost::shared_ptr;
+using boost::optional;
+
+FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
+       : FFmpeg (c)
+{
+       /* Find audio and subtitle streams */
+
+       for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
+               AVStream* s = _format_context->streams[i];
+               if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
+
+                       /* This is a hack; sometimes it seems that _audio_codec_context->channel_layout isn't set up,
+                          so bodge it here.  No idea why we should have to do this.
+                       */
+
+                       if (s->codec->channel_layout == 0) {
+                               s->codec->channel_layout = av_get_default_channel_layout (s->codec->channels);
+                       }
+                       
+                       _audio_streams.push_back (
+                               shared_ptr<FFmpegAudioStream> (
+                                       new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channels)
+                                       )
+                               );
+
+               } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
+                       _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (stream_name (s), i)));
+               }
+       }
+
+       /* Run through until we find the first audio (for each stream) and video */
+
+       while (1) {
+               int r = av_read_frame (_format_context, &_packet);
+               if (r < 0) {
+                       break;
+               }
+
+               int frame_finished;
+               avcodec_get_frame_defaults (_frame);
+
+               cout << "got packet " << _packet.stream_index << "\n";
+
+               AVCodecContext* context = _format_context->streams[_packet.stream_index]->codec;
+
+               if (_packet.stream_index == _video_stream && !_first_video) {
+                       if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+                               _first_video = frame_time (_video_stream);
+                       }
+               } else {
+                       for (size_t i = 0; i < _audio_streams.size(); ++i) {
+                               if (_packet.stream_index == _audio_streams[i]->id && !_audio_streams[i]->first_audio) {
+                                       if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+                                               _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->id);
+                                       }
+                               }
+                       }
+               }
+
+               bool have_all_audio = true;
+               size_t i = 0;
+               while (i < _audio_streams.size() && have_all_audio) {
+                       have_all_audio = _audio_streams[i]->first_audio;
+                       ++i;
+               }
+
+               if (_first_video && have_all_audio) {
+                       break;
+               }
+
+               av_free_packet (&_packet);
+       }
+}
+
+optional<double>
+FFmpegExaminer::frame_time (int stream) const
+{
+       optional<double> t;
+       
+       int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
+       if (bet != AV_NOPTS_VALUE) {
+               t = bet * av_q2d (_format_context->streams[stream]->time_base);
+       }
+
+       return t;
+}
+
+float
+FFmpegExaminer::video_frame_rate () const
+{
+       AVStream* s = _format_context->streams[_video_stream];
+
+       if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
+               return av_q2d (s->avg_frame_rate);
+       }
+
+       return av_q2d (s->r_frame_rate);
+}
+
+libdcp::Size
+FFmpegExaminer::video_size () const
+{
+       return libdcp::Size (video_codec_context()->width, video_codec_context()->height);
+}
+
+/** @return Length (in video frames) according to our content's header */
+VideoContent::Frame
+FFmpegExaminer::video_length () const
+{
+       return (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
+}
+
+string
+FFmpegExaminer::stream_name (AVStream* s) const
+{
+       stringstream n;
+
+       if (s->metadata) {
+               AVDictionaryEntry const * lang = av_dict_get (s->metadata, "language", 0, 0);
+               if (lang) {
+                       n << lang->value;
+               }
+               
+               AVDictionaryEntry const * title = av_dict_get (s->metadata, "title", 0, 0);
+               if (title) {
+                       if (!n.str().empty()) {
+                               n << " ";
+                       }
+                       n << title->value;
+               }
+       }
+
+       if (n.str().empty()) {
+               n << "unknown";
+       }
+
+       return n.str ();
+}
diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h
new file mode 100644 (file)
index 0000000..ec84865
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/optional.hpp>
+#include "ffmpeg.h"
+#include "video_examiner.h"
+
+class FFmpegAudioStream;
+class FFmpegSubtitleStream;
+
+class FFmpegExaminer : public FFmpeg, public VideoExaminer
+{
+public:
+       FFmpegExaminer (boost::shared_ptr<const FFmpegContent>);
+       
+       float video_frame_rate () const;
+       libdcp::Size video_size () const;
+       VideoContent::Frame video_length () const;
+
+       std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
+               return _subtitle_streams;
+       }
+       
+       std::vector<boost::shared_ptr<FFmpegAudioStream> > audio_streams () const {
+               return _audio_streams;
+       }
+
+       boost::optional<double> first_video () const {
+               return _first_video;
+       }
+       
+private:
+       std::string stream_name (AVStream* s) const;
+       boost::optional<double> frame_time (int) const;
+       
+        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
+        std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
+       boost::optional<double> _first_video;
+};
index ce555ac8b29b3ac33d17e906ec05a3699620da7e..fa75ab1f1d444905dc61a0d864db5fe7a9a8cf6c 100644 (file)
 #include <boost/algorithm/string.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/date_time.hpp>
+#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
 #include "film.h"
-#include "format.h"
 #include "job.h"
 #include "filter.h"
-#include "transcoder.h"
 #include "util.h"
 #include "job_manager.h"
-#include "ab_transcode_job.h"
 #include "transcode_job.h"
 #include "scp_dcp_job.h"
 #include "log.h"
-#include "options.h"
 #include "exceptions.h"
 #include "examine_content_job.h"
 #include "scaler.h"
-#include "decoder_factory.h"
 #include "config.h"
 #include "version.h"
 #include "ui_signaller.h"
-#include "video_decoder.h"
-#include "audio_decoder.h"
-#include "sndfile_decoder.h"
-#include "analyse_audio_job.h"
+#include "playlist.h"
+#include "player.h"
+#include "ffmpeg_content.h"
+#include "imagemagick_content.h"
+#include "sndfile_content.h"
+#include "dcp_content_type.h"
+#include "ratio.h"
 #include "cross.h"
 
 #include "i18n.h"
@@ -68,9 +68,12 @@ using std::setfill;
 using std::min;
 using std::make_pair;
 using std::endl;
+using std::cout;
 using std::list;
 using boost::shared_ptr;
+using boost::weak_ptr;
 using boost::lexical_cast;
+using boost::dynamic_pointer_cast;
 using boost::to_upper_copy;
 using boost::ends_with;
 using boost::starts_with;
@@ -79,40 +82,32 @@ using libdcp::Size;
 
 int const Film::state_version = 4;
 
-/** Construct a Film object in a given directory, reading any metadata
- *  file that exists in that directory.  An exception will be thrown if
- *  must_exist is true and the specified directory does not exist.
+/** Construct a Film object in a given directory.
  *
  *  @param d Film directory.
- *  @param must_exist true to throw an exception if does not exist.
  */
 
-Film::Film (string d, bool must_exist)
-       : _use_dci_name (true)
-       , _trust_content_header (true)
+Film::Film (string d)
+       : _playlist (new Playlist)
+       , _use_dci_name (true)
        , _dcp_content_type (Config::instance()->default_dcp_content_type ())
-       , _format (Config::instance()->default_format ())
+       , _container (Config::instance()->default_container ())
        , _scaler (Scaler::from_id ("bicubic"))
-       , _trim_start (0)
-       , _trim_end (0)
-       , _trim_type (CPL)
-       , _dcp_ab (false)
-       , _use_content_audio (true)
-       , _audio_gain (0)
-       , _audio_delay (0)
-       , _still_duration (10)
        , _with_subtitles (false)
        , _subtitle_offset (0)
        , _subtitle_scale (1)
        , _colour_lut (0)
        , _j2k_bandwidth (200000000)
        , _dci_metadata (Config::instance()->default_dci_metadata ())
-       , _dcp_frame_rate (0)
+       , _dcp_video_frame_rate (24)
+       , _dcp_audio_channels (MAX_AUDIO_CHANNELS)
        , _minimum_audio_channels (0)
-       , _source_frame_rate (0)
        , _dirty (false)
 {
        set_dci_date_today ();
+
+       _playlist->Changed.connect (bind (&Film::playlist_changed, this));
+       _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
        
        /* Make state.directory a complete path without ..s (where possible)
           (Code swiped from Adam Bowen on stackoverflow)
@@ -133,103 +128,46 @@ Film::Film (string d, bool must_exist)
        }
 
        set_directory (result.string ());
-       
-       if (!boost::filesystem::exists (directory())) {
-               if (must_exist) {
-                       throw OpenFileError (directory());
-               } else {
-                       boost::filesystem::create_directory (directory());
-               }
-       }
-
-       _sndfile_stream = SndfileStream::create ();
-       
-       _log.reset (new FileLog (file ("log")));
-       
-       if (must_exist) {
-               read_metadata ();
-       } else {
-               write_metadata ();
-       }
 }
 
 Film::Film (Film const & o)
        : boost::enable_shared_from_this<Film> (o)
        /* note: the copied film shares the original's log */
        , _log               (o._log)
+       , _playlist          (new Playlist (o._playlist))
        , _directory         (o._directory)
        , _name              (o._name)
        , _use_dci_name      (o._use_dci_name)
-       , _content           (o._content)
-       , _trust_content_header (o._trust_content_header)
        , _dcp_content_type  (o._dcp_content_type)
-       , _format            (o._format)
-       , _crop              (o._crop)
-       , _filters           (o._filters)
+       , _container         (o._container)
        , _scaler            (o._scaler)
-       , _trim_start        (o._trim_start)
-       , _trim_end          (o._trim_end)
-       , _trim_type         (o._trim_type)
-       , _dcp_ab            (o._dcp_ab)
-       , _content_audio_stream (o._content_audio_stream)
-       , _external_audio    (o._external_audio)
-       , _use_content_audio (o._use_content_audio)
-       , _audio_gain        (o._audio_gain)
-       , _audio_delay       (o._audio_delay)
-       , _still_duration    (o._still_duration)
-       , _subtitle_stream   (o._subtitle_stream)
        , _with_subtitles    (o._with_subtitles)
        , _subtitle_offset   (o._subtitle_offset)
        , _subtitle_scale    (o._subtitle_scale)
        , _colour_lut        (o._colour_lut)
        , _j2k_bandwidth     (o._j2k_bandwidth)
        , _dci_metadata      (o._dci_metadata)
+       , _dcp_video_frame_rate (o._dcp_video_frame_rate)
        , _dci_date          (o._dci_date)
-       , _dcp_frame_rate    (o._dcp_frame_rate)
        , _minimum_audio_channels (o._minimum_audio_channels)
-       , _size              (o._size)
-       , _length            (o._length)
-       , _content_digest    (o._content_digest)
-       , _content_audio_streams (o._content_audio_streams)
-       , _sndfile_stream    (o._sndfile_stream)
-       , _subtitle_streams  (o._subtitle_streams)
-       , _source_frame_rate (o._source_frame_rate)
        , _dirty             (o._dirty)
 {
-       
-}
-
-Film::~Film ()
-{
-
+       _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
 }
 
 string
 Film::video_state_identifier () const
 {
-       assert (format ());
+       assert (container ());
        LocaleGuard lg;
 
-       pair<string, string> f = Filter::ffmpeg_strings (filters());
-
        stringstream s;
-       s << format()->id()
-         << "_" << content_digest()
-         << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
-         << "_" << _dcp_frame_rate
-         << "_" << f.first << "_" << f.second
+       s << container()->id()
+         << "_" << _playlist->video_digest()
+         << "_" << _dcp_video_frame_rate
          << "_" << scaler()->id()
          << "_" << j2k_bandwidth()
-         << "_" << boost::lexical_cast<int> (colour_lut());
-
-       if (trim_type() == ENCODE) {
-               s << "_" << trim_start() << "_" << trim_end();
-       }
-
-       if (dcp_ab()) {
-               pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
-               s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
-       }
+         << "_" << lexical_cast<int> (colour_lut());
 
        return s.str ();
 }
@@ -284,13 +222,12 @@ Film::filename_safe_name () const
        return o;
 }
 
-string
-Film::audio_analysis_path () const
+boost::filesystem::path
+Film::audio_analysis_path (shared_ptr<const AudioContent> c) const
 {
-       boost::filesystem::path p;
-       p /= "analysis";
-       p /= content_digest();
-       return file (p.string ());
+       boost::filesystem::path p = dir ("analysis");
+       p /= c->digest();
+       return p;
 }
 
 /** Add suitable Jobs to the JobManager to create a DCP for this Film */
@@ -303,7 +240,7 @@ Film::make_dcp ()
                throw BadSettingError (_("name"), _("cannot contain slashes"));
        }
        
-       log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary()));
+       log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
 
        {
                char buffer[128];
@@ -311,23 +248,18 @@ Film::make_dcp ()
                log()->log (String::compose ("Starting to make DCP on %1", buffer));
        }
        
-       log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
-       if (length()) {
-               log()->log (String::compose ("Content length %1", length().get()));
-       }
-       log()->log (String::compose ("Content digest %1", content_digest()));
-       log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
+//     log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
+//     if (length()) {
+//             log()->log (String::compose ("Content length %1", length().get()));
+//     }
+//     log()->log (String::compose ("Content digest %1", content_digest()));
+//     log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
        log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
        log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
-       if (use_content_audio()) {
-               log()->log ("Using content's audio");
-       } else {
-               log()->log (String::compose ("Using external audio (%1 files)", external_audio().size()));
-       }
-#ifdef DVDOMATIC_DEBUG
-       log()->log ("DVD-o-matic built in debug mode.");
+#ifdef DCPOMATIC_DEBUG
+       log()->log ("DCP-o-matic built in debug mode.");
 #else
-       log()->log ("DVD-o-matic built in optimised mode.");
+       log()->log ("DCP-o-matic built in optimised mode.");
 #endif
 #ifdef LIBDCP_DEBUG
        log()->log ("libdcp built in debug mode.");
@@ -341,12 +273,12 @@ Film::make_dcp ()
                log()->log (String::compose ("Mount: %1 %2", i->first, i->second));
        }
        
-       if (format() == 0) {
-               throw MissingSettingError (_("format"));
+       if (container() == 0) {
+               throw MissingSettingError (_("container"));
        }
 
-       if (content().empty ()) {
-               throw MissingSettingError (_("content"));
+       if (_playlist->content().empty ()) {
+               throw StringError (_("You must add some content to the DCP before creating it"));
        }
 
        if (dcp_content_type() == 0) {
@@ -357,60 +289,7 @@ Film::make_dcp ()
                throw MissingSettingError (_("name"));
        }
 
-       DecodeOptions od;
-       od.decode_subtitles = with_subtitles ();
-
-       shared_ptr<Job> r;
-
-       if (dcp_ab()) {
-               r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od)));
-       } else {
-               r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od)));
-       }
-}
-
-/** Start a job to analyse the audio of our content file */
-void
-Film::analyse_audio ()
-{
-       if (_analyse_audio_job) {
-               return;
-       }
-
-       _analyse_audio_job.reset (new AnalyseAudioJob (shared_from_this()));
-       _analyse_audio_job->Finished.connect (bind (&Film::analyse_audio_finished, this));
-       JobManager::instance()->add (_analyse_audio_job);
-}
-
-/** Start a job to examine our content file */
-void
-Film::examine_content ()
-{
-       if (_examine_content_job) {
-               return;
-       }
-
-       _examine_content_job.reset (new ExamineContentJob (shared_from_this()));
-       _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this));
-       JobManager::instance()->add (_examine_content_job);
-}
-
-void
-Film::analyse_audio_finished ()
-{
-       ensure_ui_thread ();
-
-       if (_analyse_audio_job->finished_ok ()) {
-               AudioAnalysisSucceeded ();
-       }
-       
-       _analyse_audio_job.reset ();
-}
-
-void
-Film::examine_content_finished ()
-{
-       _examine_content_job.reset ();
+       JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
 }
 
 /** Start a job to send our DCP to the configured TMS */
@@ -427,7 +306,7 @@ Film::send_dcp_to_tms ()
 int
 Film::encoded_frames () const
 {
-       if (format() == 0) {
+       if (container() == 0) {
                return 0;
        }
 
@@ -444,87 +323,44 @@ Film::encoded_frames () const
 void
 Film::write_metadata () const
 {
+       if (!boost::filesystem::exists (directory())) {
+               boost::filesystem::create_directory (directory());
+       }
+       
        boost::mutex::scoped_lock lm (_state_mutex);
        LocaleGuard lg;
 
        boost::filesystem::create_directories (directory());
 
-       string const m = file ("metadata");
-       ofstream f (m.c_str ());
-       if (!f.good ()) {
-               throw CreateFileError (m);
-       }
+       xmlpp::Document doc;
+       xmlpp::Element* root = doc.create_root_node ("Metadata");
 
-       f << "version " << state_version << endl;
+       root->add_child("Version")->add_child_text (lexical_cast<string> (state_version));
+       root->add_child("Name")->add_child_text (_name);
+       root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0");
 
-       /* User stuff */
-       f << "name " << _name << endl;
-       f << "use_dci_name " << _use_dci_name << endl;
-       f << "content " << _content << endl;
-       f << "trust_content_header " << (_trust_content_header ? "1" : "0") << endl;
        if (_dcp_content_type) {
-               f << "dcp_content_type " << _dcp_content_type->dci_name () << endl;
-       }
-       if (_format) {
-               f << "format " << _format->as_metadata () << endl;
-       }
-       f << "left_crop " << _crop.left << endl;
-       f << "right_crop " << _crop.right << endl;
-       f << "top_crop " << _crop.top << endl;
-       f << "bottom_crop " << _crop.bottom << endl;
-       for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
-               f << "filter " << (*i)->id () << endl;
-       }
-       f << "scaler " << _scaler->id () << endl;
-       f << "trim_start " << _trim_start << endl;
-       f << "trim_end " << _trim_end << endl;
-       switch (_trim_type) {
-       case CPL:
-               f << "trim_type cpl\n";
-               break;
-       case ENCODE:
-               f << "trim_type encode\n";
-               break;
-       }
-       f << "dcp_ab " << (_dcp_ab ? "1" : "0") << endl;
-       if (_content_audio_stream) {
-               f << "selected_content_audio_stream " << _content_audio_stream->to_string() << endl;
+               root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
        }
-       for (vector<string>::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) {
-               f << "external_audio " << *i << endl;
-       }
-       f << "use_content_audio " << (_use_content_audio ? "1" : "0") << endl;
-       f << "audio_gain " << _audio_gain << endl;
-       f << "audio_delay " << _audio_delay << endl;
-       f << "still_duration " << _still_duration << endl;
-       if (_subtitle_stream) {
-               f << "selected_subtitle_stream " << _subtitle_stream->to_string() << endl;
-       }
-       f << "with_subtitles " << _with_subtitles << endl;
-       f << "subtitle_offset " << _subtitle_offset << endl;
-       f << "subtitle_scale " << _subtitle_scale << endl;
-       f << "colour_lut " << _colour_lut << endl;
-       f << "j2k_bandwidth " << _j2k_bandwidth << endl;
-       _dci_metadata.write (f);
-       f << "dci_date " << boost::gregorian::to_iso_string (_dci_date) << endl;
-       f << "dcp_frame_rate " << _dcp_frame_rate << endl;
-       f << "minimum_audio_channels " << _minimum_audio_channels << endl;
-       f << "width " << _size.width << endl;
-       f << "height " << _size.height << endl;
-       f << "length " << _length.get_value_or(0) << endl;
-       f << "content_digest " << _content_digest << endl;
-
-       for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
-               f << "content_audio_stream " << (*i)->to_string () << endl;
-       }
-
-       f << "external_audio_stream " << _sndfile_stream->to_string() << endl;
 
-       for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
-               f << "subtitle_stream " << (*i)->to_string () << endl;
+       if (_container) {
+               root->add_child("Container")->add_child_text (_container->id ());
        }
 
-       f << "source_frame_rate " << _source_frame_rate << endl;
+       root->add_child("Scaler")->add_child_text (_scaler->id ());
+       root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
+       root->add_child("SubtitleOffset")->add_child_text (lexical_cast<string> (_subtitle_offset));
+       root->add_child("SubtitleScale")->add_child_text (lexical_cast<string> (_subtitle_scale));
+       root->add_child("ColourLUT")->add_child_text (lexical_cast<string> (_colour_lut));
+       root->add_child("J2KBandwidth")->add_child_text (lexical_cast<string> (_j2k_bandwidth));
+       _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+       root->add_child("DCPVideoFrameRate")->add_child_text (lexical_cast<string> (_dcp_video_frame_rate));
+       root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
+       root->add_child("DCPAudioChannels")->add_child_text (lexical_cast<string> (_dcp_audio_channels));
+       root->add_child("MinimumAudioChannels")->add_child_text (lexical_cast<string> (_minimum_audio_channels));
+       _playlist->as_xml (root->add_child ("Playlist"));
+
+       doc.write_to_file_formatted (file ("metadata.xml"));
        
        _dirty = false;
 }
@@ -536,179 +372,46 @@ Film::read_metadata ()
        boost::mutex::scoped_lock lm (_state_mutex);
        LocaleGuard lg;
 
-       _external_audio.clear ();
-       _content_audio_streams.clear ();
-       _subtitle_streams.clear ();
-
-       boost::optional<int> version;
-
-       /* Backward compatibility things */
-       boost::optional<int> audio_sample_rate;
-       boost::optional<int> audio_stream_index;
-       boost::optional<int> subtitle_stream_index;
-
-       ifstream f (file ("metadata").c_str());
-       if (!f.good()) {
-               throw OpenFileError (file ("metadata"));
+       if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
+               throw StringError (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version.  You will need to create a new Film, re-add your content and set it up again.  Sorry!"));
        }
-       
-       multimap<string, string> kv = read_key_value (f);
 
-       /* We need version before anything else */
-       multimap<string, string>::iterator v = kv.find ("version");
-       if (v != kv.end ()) {
-               version = atoi (v->second.c_str());
-       }
+       cxml::File f (file ("metadata.xml"), "Metadata");
        
-       for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
-               string const k = i->first;
-               string const v = i->second;
+       _name = f.string_child ("Name");
+       _use_dci_name = f.bool_child ("UseDCIName");
 
-               if (k == "audio_sample_rate") {
-                       audio_sample_rate = atoi (v.c_str());
-               }
-
-               /* User-specified stuff */
-               if (k == "name") {
-                       _name = v;
-               } else if (k == "use_dci_name") {
-                       _use_dci_name = (v == "1");
-               } else if (k == "content") {
-                       _content = v;
-               } else if (k == "trust_content_header") {
-                       _trust_content_header = (v == "1");
-               } else if (k == "dcp_content_type") {
-                       if (version < 3) {
-                               _dcp_content_type = DCPContentType::from_pretty_name (v);
-                       } else {
-                               _dcp_content_type = DCPContentType::from_dci_name (v);
-                       }
-               } else if (k == "format") {
-                       _format = Format::from_metadata (v);
-               } else if (k == "left_crop") {
-                       _crop.left = atoi (v.c_str ());
-               } else if (k == "right_crop") {
-                       _crop.right = atoi (v.c_str ());
-               } else if (k == "top_crop") {
-                       _crop.top = atoi (v.c_str ());
-               } else if (k == "bottom_crop") {
-                       _crop.bottom = atoi (v.c_str ());
-               } else if (k == "filter") {
-                       _filters.push_back (Filter::from_id (v));
-               } else if (k == "scaler") {
-                       _scaler = Scaler::from_id (v);
-               } else if ( ((!version || version < 2) && k == "dcp_trim_start") || k == "trim_start") {
-                       _trim_start = atoi (v.c_str ());
-               } else if ( ((!version || version < 2) && k == "dcp_trim_end") || k == "trim_end") {
-                       _trim_end = atoi (v.c_str ());
-               } else if (k == "trim_type") {
-                       if (v == "cpl") {
-                               _trim_type = CPL;
-                       } else if (v == "encode") {
-                               _trim_type = ENCODE;
-                       }
-               } else if (k == "dcp_ab") {
-                       _dcp_ab = (v == "1");
-               } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
-                       if (!version) {
-                               audio_stream_index = atoi (v.c_str ());
-                       } else {
-                               _content_audio_stream = audio_stream_factory (v, version);
-                       }
-               } else if (k == "external_audio") {
-                       _external_audio.push_back (v);
-               } else if (k == "use_content_audio") {
-                       _use_content_audio = (v == "1");
-               } else if (k == "audio_gain") {
-                       _audio_gain = atof (v.c_str ());
-               } else if (k == "audio_delay") {
-                       _audio_delay = atoi (v.c_str ());
-               } else if (k == "still_duration") {
-                       _still_duration = atoi (v.c_str ());
-               } else if (k == "selected_subtitle_stream") {
-                       if (!version) {
-                               subtitle_stream_index = atoi (v.c_str ());
-                       } else {
-                               _subtitle_stream = subtitle_stream_factory (v, version);
-                       }
-               } else if (k == "with_subtitles") {
-                       _with_subtitles = (v == "1");
-               } else if (k == "subtitle_offset") {
-                       _subtitle_offset = atoi (v.c_str ());
-               } else if (k == "subtitle_scale") {
-                       _subtitle_scale = atof (v.c_str ());
-               } else if (k == "colour_lut") {
-                       _colour_lut = atoi (v.c_str ());
-               } else if (k == "j2k_bandwidth") {
-                       _j2k_bandwidth = atoi (v.c_str ());
-               } else if (k == "dci_date") {
-                       _dci_date = boost::gregorian::from_undelimited_string (v);
-               } else if (k == "dcp_frame_rate") {
-                       _dcp_frame_rate = atoi (v.c_str ());
-               } else if (k == "minimum_audio_channels") {
-                       _minimum_audio_channels = atoi (v.c_str ());
+       {
+               optional<string> c = f.optional_string_child ("DCPContentType");
+               if (c) {
+                       _dcp_content_type = DCPContentType::from_dci_name (c.get ());
                }
+       }
 
-               _dci_metadata.read (k, v);
-               
-               /* Cached stuff */
-               if (k == "width") {
-                       _size.width = atoi (v.c_str ());
-               } else if (k == "height") {
-                       _size.height = atoi (v.c_str ());
-               } else if (k == "length") {
-                       int const vv = atoi (v.c_str ());
-                       if (vv) {
-                               _length = vv;
-                       }
-               } else if (k == "content_digest") {
-                       _content_digest = v;
-               } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
-                       _content_audio_streams.push_back (audio_stream_factory (v, version));
-               } else if (k == "external_audio_stream") {
-                       _sndfile_stream = audio_stream_factory (v, version);
-               } else if (k == "subtitle_stream") {
-                       _subtitle_streams.push_back (subtitle_stream_factory (v, version));
-               } else if (k == "source_frame_rate") {
-                       _source_frame_rate = atof (v.c_str ());
-               } else if (version < 4 && k == "frames_per_second") {
-                       _source_frame_rate = atof (v.c_str ());
-                       /* Fill in what would have been used for DCP frame rate by the older version */
-                       _dcp_frame_rate = best_dcp_frame_rate (_source_frame_rate);
+       {
+               optional<string> c = f.optional_string_child ("Container");
+               if (c) {
+                       _container = Ratio::from_id (c.get ());
                }
        }
 
-       if (!version) {
-               if (audio_sample_rate) {
-                       /* version < 1 didn't specify sample rate in the audio streams, so fill it in here */
-                       for (vector<shared_ptr<AudioStream> >::iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
-                               (*i)->set_sample_rate (audio_sample_rate.get());
-                       }
-               }
+       _scaler = Scaler::from_id (f.string_child ("Scaler"));
+       _with_subtitles = f.bool_child ("WithSubtitles");
+       _subtitle_offset = f.number_child<float> ("SubtitleOffset");
+       _subtitle_scale = f.number_child<float> ("SubtitleScale");
+       _colour_lut = f.number_child<int> ("ColourLUT");
+       _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
+       _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
+       _dcp_video_frame_rate = f.number_child<int> ("DCPVideoFrameRate");
+       _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
+       _dcp_audio_channels = f.number_child<int> ("DCPAudioChannels");
+       _minimum_audio_channels = f.number_child<int> ("MinimumAudioChannels");
 
-               /* also the selected stream was specified as an index */
-               if (audio_stream_index && audio_stream_index.get() >= 0 && audio_stream_index.get() < (int) _content_audio_streams.size()) {
-                       _content_audio_stream = _content_audio_streams[audio_stream_index.get()];
-               }
+       _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"));
 
-               /* similarly the subtitle */
-               if (subtitle_stream_index && subtitle_stream_index.get() >= 0 && subtitle_stream_index.get() < (int) _subtitle_streams.size()) {
-                       _subtitle_stream = _subtitle_streams[subtitle_stream_index.get()];
-               }
-       }
-               
        _dirty = false;
 }
 
-libdcp::Size
-Film::cropped_size (libdcp::Size s) const
-{
-       boost::mutex::scoped_lock lm (_state_mutex);
-       s.width -= _crop.left + _crop.right;
-       s.height -= _crop.top + _crop.bottom;
-       return s;
-}
-
 /** Given a directory name, return its full path within the Film's directory.
  *  The directory (and its parents) will be created if they do not exist.
  */
@@ -744,67 +447,6 @@ Film::file (string f) const
        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
@@ -829,8 +471,8 @@ 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 ();
@@ -851,22 +493,7 @@ Film::dci_name (bool if_created_now) const
                }
        }
 
-       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;
@@ -929,110 +556,6 @@ Film::set_use_dci_name (bool u)
        signal_changed (USE_DCI_NAME);
 }
 
-void
-Film::set_content (string c)
-{
-       string check = directory ();
-
-       boost::filesystem::path slash ("/");
-       string platform_slash = slash.make_preferred().string ();
-
-       if (!ends_with (check, platform_slash)) {
-               check += platform_slash;
-       }
-       
-       if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) {
-               c = c.substr (_directory.length() + 1);
-       }
-
-       string old_content;
-       
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               if (c == _content) {
-                       return;
-               }
-
-               old_content = _content;
-               _content = c;
-       }
-
-       /* Do this before we start using FFmpeg ourselves */
-       run_ffprobe (c, file ("ffprobe.log"), _log);
-       
-       /* Reset streams here in case the new content doesn't have one or the other */
-       _content_audio_stream = shared_ptr<AudioStream> ();
-       _subtitle_stream = shared_ptr<SubtitleStream> ();
-
-       /* Start off using content audio */
-       set_use_content_audio (true);
-
-       /* Create a temporary decoder so that we can get information
-          about the content.
-       */
-
-       try {
-               Decoders d = decoder_factory (shared_from_this(), DecodeOptions());
-               
-               set_size (d.video->native_size ());
-               set_source_frame_rate (d.video->frames_per_second ());
-               set_dcp_frame_rate (best_dcp_frame_rate (source_frame_rate ()));
-               set_subtitle_streams (d.video->subtitle_streams ());
-               if (d.audio) {
-                       set_content_audio_streams (d.audio->audio_streams ());
-               }
-
-               {
-                       boost::mutex::scoped_lock lm (_state_mutex);
-                       _content = c;
-               }
-               
-               signal_changed (CONTENT);
-               
-               /* Start off with the first audio and subtitle streams */
-               if (d.audio && !d.audio->audio_streams().empty()) {
-                       set_content_audio_stream (d.audio->audio_streams().front());
-               }
-               
-               if (!d.video->subtitle_streams().empty()) {
-                       set_subtitle_stream (d.video->subtitle_streams().front());
-               }
-               
-               examine_content ();
-
-       } catch (...) {
-
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _content = old_content;
-               throw;
-
-       }
-
-       /* Default format */
-       set_format (Config::instance()->default_format ());
-
-       /* Still image DCPs must use external audio */
-       if (content_type() == STILL) {
-               set_use_content_audio (false);
-       }
-}
-
-void
-Film::set_trust_content_header (bool t)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _trust_content_header = t;
-       }
-       
-       signal_changed (TRUST_CONTENT_HEADER);
-
-       if (!_trust_content_header && !content().empty()) {
-               /* We just said that we don't trust the content's header */
-               examine_content ();
-       }
-}
-              
 void
 Film::set_dcp_content_type (DCPContentType const * t)
 {
@@ -1044,90 +567,13 @@ Film::set_dcp_content_type (DCPContentType const * t)
 }
 
 void
-Film::set_format (Format const * f)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _format = f;
-       }
-       signal_changed (FORMAT);
-}
-
-void
-Film::set_crop (Crop c)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _crop = c;
-       }
-       signal_changed (CROP);
-}
-
-void
-Film::set_left_crop (int c)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               
-               if (_crop.left == c) {
-                       return;
-               }
-               
-               _crop.left = c;
-       }
-       signal_changed (CROP);
-}
-
-void
-Film::set_right_crop (int c)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               if (_crop.right == c) {
-                       return;
-               }
-               
-               _crop.right = c;
-       }
-       signal_changed (CROP);
-}
-
-void
-Film::set_top_crop (int c)
+Film::set_container (Ratio const * c)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               if (_crop.top == c) {
-                       return;
-               }
-               
-               _crop.top = c;
-       }
-       signal_changed (CROP);
-}
-
-void
-Film::set_bottom_crop (int c)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               if (_crop.bottom == c) {
-                       return;
-               }
-               
-               _crop.bottom = c;
+               _container = c;
        }
-       signal_changed (CROP);
-}
-
-void
-Film::set_filters (vector<Filter const *> f)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _filters = f;
-       }
-       signal_changed (FILTERS);
+       signal_changed (CONTAINER);
 }
 
 void
@@ -1140,123 +586,6 @@ Film::set_scaler (Scaler const * s)
        signal_changed (SCALER);
 }
 
-void
-Film::set_trim_start (int t)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _trim_start = t;
-       }
-       signal_changed (TRIM_START);
-}
-
-void
-Film::set_trim_end (int t)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _trim_end = t;
-       }
-       signal_changed (TRIM_END);
-}
-
-void
-Film::set_trim_type (TrimType t)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _trim_type = t;
-       }
-       signal_changed (TRIM_TYPE);
-}
-
-void
-Film::set_dcp_ab (bool a)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_ab = a;
-       }
-       signal_changed (DCP_AB);
-}
-
-void
-Film::set_content_audio_stream (shared_ptr<AudioStream> s)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _content_audio_stream = s;
-       }
-       signal_changed (CONTENT_AUDIO_STREAM);
-}
-
-void
-Film::set_external_audio (vector<string> a)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _external_audio = a;
-       }
-
-       shared_ptr<SndfileDecoder> decoder (new SndfileDecoder (shared_from_this(), DecodeOptions()));
-       if (decoder->audio_stream()) {
-               _sndfile_stream = decoder->audio_stream ();
-       }
-       
-       signal_changed (EXTERNAL_AUDIO);
-}
-
-void
-Film::set_use_content_audio (bool e)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _use_content_audio = e;
-       }
-
-       signal_changed (USE_CONTENT_AUDIO);
-}
-
-void
-Film::set_audio_gain (float g)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _audio_gain = g;
-       }
-       signal_changed (AUDIO_GAIN);
-}
-
-void
-Film::set_audio_delay (int d)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _audio_delay = d;
-       }
-       signal_changed (AUDIO_DELAY);
-}
-
-void
-Film::set_still_duration (int d)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _still_duration = d;
-       }
-       signal_changed (STILL_DURATION);
-}
-
-void
-Film::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _subtitle_stream = s;
-       }
-       signal_changed (SUBTITLE_STREAM);
-}
-
 void
 Film::set_with_subtitles (bool w)
 {
@@ -1318,16 +647,6 @@ Film::set_dci_metadata (DCIMetadata m)
 }
 
 
-void
-Film::set_dcp_frame_rate (int f)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_frame_rate = f;
-       }
-       signal_changed (DCP_FRAME_RATE);
-}
-
 void
 Film::set_minimum_audio_channels (int c)
 {
@@ -1339,75 +658,15 @@ Film::set_minimum_audio_channels (int c)
 }
                        
 void
-Film::set_size (libdcp::Size s)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _size = s;
-       }
-       signal_changed (SIZE);
-}
-
-void
-Film::set_length (SourceFrame l)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _length = l;
-       }
-       signal_changed (LENGTH);
-}
-
-void
-Film::unset_length ()
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _length = boost::none;
-       }
-       signal_changed (LENGTH);
-}
-
-void
-Film::set_content_digest (string d)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _content_digest = d;
-       }
-       _dirty = true;
-}
-
-void
-Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _content_audio_streams = s;
-       }
-       signal_changed (CONTENT_AUDIO_STREAMS);
-}
-
-void
-Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s)
+Film::set_dcp_video_frame_rate (int f)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _subtitle_streams = s;
+               _dcp_video_frame_rate = f;
        }
-       signal_changed (SUBTITLE_STREAMS);
+       signal_changed (DCP_VIDEO_FRAME_RATE);
 }
 
-void
-Film::set_source_frame_rate (float f)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _source_frame_rate = f;
-       }
-       signal_changed (SOURCE_FRAME_RATE);
-}
-       
 void
 Film::signal_changed (Property p)
 {
@@ -1416,20 +675,17 @@ 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
@@ -1438,16 +694,6 @@ Film::set_dci_date_today ()
        _dci_date = boost::gregorian::day_clock::local_day ();
 }
 
-boost::shared_ptr<AudioStream>
-Film::audio_stream () const
-{
-       if (use_content_audio()) {
-               return _content_audio_stream;
-       }
-
-       return _sndfile_stream;
-}
-
 string
 Film::info_path (int f) const
 {
@@ -1503,20 +749,146 @@ Film::have_dcp () const
        return true;
 }
 
-bool
-Film::has_audio () const
+shared_ptr<Player>
+Film::player () const
+{
+       return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
+}
+
+shared_ptr<Playlist>
+Film::playlist () const
+{
+       boost::mutex::scoped_lock lm (_state_mutex);
+       return _playlist;
+}
+
+Playlist::ContentList
+Film::content () const
+{
+       return _playlist->content ();
+}
+
+void
+Film::examine_and_add_content (shared_ptr<Content> c)
 {
-       if (use_content_audio()) {
-               return audio_stream();
+       shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
+       j->Finished.connect (bind (&Film::add_content_weak, this, boost::weak_ptr<Content> (c)));
+       JobManager::instance()->add (j);
+}
+
+void
+Film::add_content_weak (weak_ptr<Content> c)
+{
+       shared_ptr<Content> content = c.lock ();
+       if (content) {
+               add_content (content);
        }
+}
 
-       vector<string> const e = external_audio ();
-       for (vector<string>::const_iterator i = e.begin(); i != e.end(); ++i) {
-               if (!i->empty ()) {
-                       return true;
-               }
+void
+Film::add_content (shared_ptr<Content> c)
+{
+       /* Add video content after any existing content */
+       if (dynamic_pointer_cast<VideoContent> (c)) {
+               c->set_start (_playlist->video_end ());
+       }
+
+       _playlist->add (c);
+}
+
+void
+Film::remove_content (shared_ptr<Content> c)
+{
+       _playlist->remove (c);
+}
+
+Time
+Film::length () const
+{
+       return _playlist->length ();
+}
+
+bool
+Film::has_subtitles () const
+{
+       return _playlist->has_subtitles ();
+}
+
+OutputVideoFrame
+Film::best_dcp_video_frame_rate () const
+{
+       return _playlist->best_dcp_frame_rate ();
+}
+
+void
+Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
+{
+       if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
+               set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ());
+       } 
+
+       if (ui_signaller) {
+               ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
        }
+}
+
+void
+Film::playlist_changed ()
+{
+       signal_changed (CONTENT);
+}      
 
-       return false;
+int
+Film::loop () const
+{
+       return _playlist->loop ();
+}
+
+void
+Film::set_loop (int c)
+{
+       _playlist->set_loop (c);
+}
+
+OutputAudioFrame
+Film::time_to_audio_frames (Time t) const
+{
+       return t * dcp_audio_frame_rate () / TIME_HZ;
+}
+
+OutputVideoFrame
+Film::time_to_video_frames (Time t) const
+{
+       return t * dcp_video_frame_rate () / TIME_HZ;
+}
+
+Time
+Film::audio_frames_to_time (OutputAudioFrame f) const
+{
+       return f * TIME_HZ / dcp_audio_frame_rate ();
+}
+
+Time
+Film::video_frames_to_time (OutputVideoFrame f) const
+{
+       return f * TIME_HZ / dcp_video_frame_rate ();
+}
+
+OutputAudioFrame
+Film::dcp_audio_frame_rate () const
+{
+       /* XXX */
+       return 48000;
 }
 
+void
+Film::set_sequence_video (bool s)
+{
+       _playlist->set_sequence_video (s);
+}
+
+libdcp::Size
+Film::full_frame () const
+{
+       return libdcp::Size (2048, 1080);
+}
index ca9bd57f43a0fbd4439d5db12247b3b2e194ea3f..f5a7c1246c38c2f133f8d82044be6531b26662f8 100644 (file)
 */
 
 /** @file  src/film.h
- *  @brief A representation of a piece of video (with sound), including naming,
- *  the source content file, and how it should be presented in a DCP.
+ *  @brief A representation of some audio and video content, and details of
+ *  how they should be presented in a DCP.
  */
 
-#ifndef DVDOMATIC_FILM_H
-#define DVDOMATIC_FILM_H
+#ifndef DCPOMATIC_FILM_H
+#define DCPOMATIC_FILM_H
 
 #include <string>
 #include <vector>
 #include <boost/thread.hpp>
 #include <boost/signals2.hpp>
 #include <boost/enable_shared_from_this.hpp>
-extern "C" {
-#include <libavcodec/avcodec.h>
-}
-#include "dcp_content_type.h"
 #include "util.h"
-#include "stream.h"
 #include "dci_metadata.h"
+#include "types.h"
+#include "ffmpeg_content.h"
+#include "playlist.h"
 
-class Format;
+class DCPContentType;
 class Job;
 class Filter;
 class Log;
 class ExamineContentJob;
 class AnalyseAudioJob;
 class ExternalAudioStream;
+class Content;
+class Player;
 
 /** @class Film
- *  @brief A representation of a video, maybe with sound.
- *
- *  A representation of a piece of video (maybe with sound), including naming,
- *  the source content file, and how it should be presented in a DCP.
+ *  @brief A representation of some audio and video content, and details of
+ *  how they should be presented in a DCP.
  */
 class Film : public boost::enable_shared_from_this<Film>
 {
 public:
-       Film (std::string d, bool must_exist = true);
+       Film (std::string d);
        Film (Film const &);
-       ~Film ();
 
        std::string info_dir () const;
        std::string j2c_path (int f, bool t) const;
        std::string info_path (int f) const;
        std::string internal_video_mxf_dir () const;
        std::string internal_video_mxf_filename () const;
-       std::string audio_analysis_path () const;
+       boost::filesystem::path audio_analysis_path (boost::shared_ptr<const AudioContent>) const;
 
        std::string dcp_video_mxf_filename () const;
        std::string dcp_audio_mxf_filename () const;
 
-       void examine_content ();
-       void analyse_audio ();
        void send_dcp_to_tms ();
-
        void make_dcp ();
 
        /** @return Logger.
@@ -89,15 +83,9 @@ public:
        std::string file (std::string f) const;
        std::string dir (std::string d) const;
 
-       std::string content_path () const;
-       ContentType content_type () const;
-       
-       int target_audio_sample_rate () const;
-       
-       void write_metadata () const;
        void read_metadata ();
+       void write_metadata () const;
 
-       libdcp::Size cropped_size (libdcp::Size) const;
        std::string dci_name (bool if_created_now) const;
        std::string dcp_name (bool if_created_now = false) const;
 
@@ -106,16 +94,32 @@ public:
                return _dirty;
        }
 
-       int audio_channels () const;
-
-       void set_dci_date_today ();
+       libdcp::Size full_frame () const;
 
        bool have_dcp () const;
 
-       enum TrimType {
-               CPL,
-               ENCODE
-       };
+       boost::shared_ptr<Player> player () const;
+       boost::shared_ptr<Playlist> playlist () const;
+
+       OutputAudioFrame dcp_audio_frame_rate () const;
+
+       OutputAudioFrame time_to_audio_frames (Time) const;
+       OutputVideoFrame time_to_video_frames (Time) const;
+       Time video_frames_to_time (OutputVideoFrame) const;
+       Time audio_frames_to_time (OutputAudioFrame) const;
+
+       /* Proxies for some Playlist methods */
+
+       Playlist::ContentList content () const;
+
+       Time length () const;
+       bool has_subtitles () const;
+       OutputVideoFrame best_dcp_video_frame_rate () const;
+
+       void set_loop (int);
+       int loop () const;
+
+       void set_sequence_video (bool);
 
        /** Identifiers for the parts of our state;
            used for signalling changes.
@@ -124,36 +128,19 @@ public:
                NONE,
                NAME,
                USE_DCI_NAME,
+               /** The playlist's content list has changed (i.e. content has been added, moved around or removed) */
                CONTENT,
-               TRUST_CONTENT_HEADER,
+               LOOP,
                DCP_CONTENT_TYPE,
-               FORMAT,
-               CROP,
-               FILTERS,
+               CONTAINER,
                SCALER,
-               TRIM_START,
-               TRIM_END,
-               TRIM_TYPE,
-               DCP_AB,
-               CONTENT_AUDIO_STREAM,
-               EXTERNAL_AUDIO,
-               USE_CONTENT_AUDIO,
-               AUDIO_GAIN,
-               AUDIO_DELAY,
-               STILL_DURATION,
-               SUBTITLE_STREAM,
                WITH_SUBTITLES,
                SUBTITLE_OFFSET,
                SUBTITLE_SCALE,
                COLOUR_LUT,
                J2K_BANDWIDTH,
                DCI_METADATA,
-               SIZE,
-               LENGTH,
-               CONTENT_AUDIO_STREAMS,
-               SUBTITLE_STREAMS,
-               SOURCE_FRAME_RATE,
-               DCP_FRAME_RATE,
+               DCP_VIDEO_FRAME_RATE,
                MINIMUM_AUDIO_CHANNELS
        };
 
@@ -175,34 +162,14 @@ public:
                return _use_dci_name;
        }
 
-       std::string content () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _content;
-       }
-
-       bool trust_content_header () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _trust_content_header;
-       }
-
        DCPContentType const * dcp_content_type () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _dcp_content_type;
        }
 
-       Format const * format () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _format;
-       }
-
-       Crop crop () const {
+       Ratio const * container () const {
                boost::mutex::scoped_lock lm (_state_mutex);
-               return _crop;
-       }
-
-       std::vector<Filter const *> filters () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _filters;
+               return _container;
        }
 
        Scaler const * scaler () const {
@@ -210,63 +177,6 @@ public:
                return _scaler;
        }
 
-       int trim_start () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _trim_start;
-       }
-
-       int trim_end () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _trim_end;
-       }
-
-       TrimType trim_type () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _trim_type;
-       }
-
-       bool dcp_ab () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _dcp_ab;
-       }
-
-       boost::shared_ptr<AudioStream> content_audio_stream () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _content_audio_stream;
-       }
-
-       std::vector<std::string> external_audio () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _external_audio;
-       }
-
-       bool use_content_audio () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _use_content_audio;
-       }
-       
-       float audio_gain () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _audio_gain;
-       }
-
-       int audio_delay () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _audio_delay;
-       }
-
-       int still_duration () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _still_duration;
-       }
-
-       int still_duration_in_frames () const;
-
-       boost::shared_ptr<SubtitleStream> subtitle_stream () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _subtitle_stream;
-       }
-
        bool with_subtitles () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _with_subtitles;
@@ -297,43 +207,15 @@ public:
                return _dci_metadata;
        }
 
-       int dcp_frame_rate () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _dcp_frame_rate;
-       }
-       
-       libdcp::Size size () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _size;
-       }
-
-       boost::optional<SourceFrame> length () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _length;
-       }
-       
-       std::string content_digest () const {
+       /* XXX: -> "video_frame_rate" */
+       int dcp_video_frame_rate () const {
                boost::mutex::scoped_lock lm (_state_mutex);
-               return _content_digest;
-       }
-       
-       std::vector<boost::shared_ptr<AudioStream> > content_audio_streams () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _content_audio_streams;
+               return _dcp_video_frame_rate;
        }
 
-       std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _subtitle_streams;
-       }
-       
-       float source_frame_rate () const {
+       int dcp_audio_channels () const {
                boost::mutex::scoped_lock lm (_state_mutex);
-               if (content_type() == STILL) {
-                       return 24;
-               }
-               
-               return _source_frame_rate;
+               return _dcp_audio_channels;
        }
 
        int minimum_audio_channels () const {
@@ -341,75 +223,48 @@ public:
                return _minimum_audio_channels;
        }
 
-       boost::shared_ptr<AudioStream> audio_stream () const;
-       bool has_audio () const;
-       
        /* SET */
 
        void set_directory (std::string);
        void set_name (std::string);
        void set_use_dci_name (bool);
-       void set_content (std::string);
-       void set_trust_content_header (bool);
+       void examine_and_add_content (boost::shared_ptr<Content>);
+       void add_content (boost::shared_ptr<Content>);
+       void remove_content (boost::shared_ptr<Content>);
        void set_dcp_content_type (DCPContentType const *);
-       void set_format (Format const *);
-       void set_crop (Crop);
-       void set_left_crop (int);
-       void set_right_crop (int);
-       void set_top_crop (int);
-       void set_bottom_crop (int);
-       void set_filters (std::vector<Filter const *>);
+       void set_container (Ratio const *);
        void set_scaler (Scaler const *);
-       void set_trim_start (int);
-       void set_trim_end (int);
-       void set_trim_type (TrimType);
-       void set_dcp_ab (bool);
-       void set_content_audio_stream (boost::shared_ptr<AudioStream>);
-       void set_external_audio (std::vector<std::string>);
-       void set_use_content_audio (bool);
-       void set_audio_gain (float);
-       void set_audio_delay (int);
-       void set_still_duration (int);
-       void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
        void set_with_subtitles (bool);
        void set_subtitle_offset (int);
        void set_subtitle_scale (float);
        void set_colour_lut (int);
        void set_j2k_bandwidth (int);
        void set_dci_metadata (DCIMetadata);
-       void set_dcp_frame_rate (int);
-       void set_size (libdcp::Size);
-       void set_length (SourceFrame);
-       void unset_length ();
-       void set_content_digest (std::string);
-       void set_content_audio_streams (std::vector<boost::shared_ptr<AudioStream> >);
-       void set_subtitle_streams (std::vector<boost::shared_ptr<SubtitleStream> >);
-       void set_source_frame_rate (float);
+       void set_dcp_video_frame_rate (int);
+       void set_dci_date_today ();
        void set_minimum_audio_channels (int);
 
-       /** Emitted when some property has changed */
+       /** Emitted when some property has of the Film has changed */
        mutable boost::signals2::signal<void (Property)> Changed;
 
-       boost::signals2::signal<void ()> AudioAnalysisSucceeded;
+       /** Emitted when some property of our content has changed */
+       mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
 
        /** Current version number of the state file */
        static int const state_version;
 
 private:
        
-       /** Log to write to */
-       boost::shared_ptr<Log> _log;
-
-       /** Any running ExamineContentJob, or 0 */
-       boost::shared_ptr<ExamineContentJob> _examine_content_job;
-       /** Any running AnalyseAudioJob, or 0 */
-       boost::shared_ptr<AnalyseAudioJob> _analyse_audio_job;
-
        void signal_changed (Property);
-       void examine_content_finished ();
-       void analyse_audio_finished ();
        std::string video_state_identifier () const;
+       void playlist_changed ();
+       void playlist_content_changed (boost::weak_ptr<Content>, int);
        std::string filename_safe_name () const;
+       void add_content_weak (boost::weak_ptr<Content>);
+
+       /** Log to write to */
+       boost::shared_ptr<Log> _log;
+       boost::shared_ptr<Playlist> _playlist;
 
        /** Complete path to directory containing the film metadata;
         *  must not be relative.
@@ -418,54 +273,16 @@ private:
        /** Mutex for _directory */
        mutable boost::mutex _directory_mutex;
        
-       /** Name for DVD-o-matic */
+       /** Name for DCP-o-matic */
        std::string _name;
        /** True if a auto-generated DCI-compliant name should be used for our DCP */
        bool _use_dci_name;
-       /** File or directory containing content; may be relative to our directory
-        *  or an absolute path.
-        */
-       std::string _content;
-       /** If this is true, we will believe the length specified by the content
-        *  file's header; if false, we will run through the whole content file
-        *  the first time we see it in order to obtain the length.
-        */
-       bool _trust_content_header;
        /** The type of content that this Film represents (feature, trailer etc.) */
        DCPContentType const * _dcp_content_type;
-       /** The format to present this Film in (flat, scope, etc.) */
-       Format const * _format;
-       /** The crop to apply to the source */
-       Crop _crop;
-       /** Video filters that should be used when generating DCPs */
-       std::vector<Filter const *> _filters;
+       /** The container to put this Film in (flat, scope, etc.) */
+       Ratio const * _container;
        /** Scaler algorithm to use */
        Scaler const * _scaler;
-       /** Frames to trim off the start of the DCP */
-       int _trim_start;
-       /** Frames to trim off the end of the DCP */
-       int _trim_end;
-       TrimType _trim_type;
-       /** true to create an A/B comparison DCP, where the left half of the image
-           is the video without any filters or post-processing, and the right half
-           has the specified filters and post-processing.
-       */
-       bool _dcp_ab;
-       /** The audio stream to use from our content */
-       boost::shared_ptr<AudioStream> _content_audio_stream;
-       /** List of filenames of external audio files, in channel order
-           (L, R, C, Lfe, Ls, Rs)
-       */
-       std::vector<std::string> _external_audio;
-       /** true to use audio from our content file; false to use external audio */
-       bool _use_content_audio;
-       /** Gain to apply to audio in dB */
-       float _audio_gain;
-       /** Delay to apply to audio (positive moves audio later) in milliseconds */
-       int _audio_delay;
-       /** Duration to make still-sourced films (in seconds) */
-       int _still_duration;
-       boost::shared_ptr<SubtitleStream> _subtitle_stream;
        /** True if subtitles should be shown for this film */
        bool _with_subtitles;
        /** y offset for placing subtitles, in source pixels; +ve is further down
@@ -481,32 +298,15 @@ private:
        int _colour_lut;
        /** bandwidth for J2K files in bits per second */
        int _j2k_bandwidth;
-
        /** DCI naming stuff */
        DCIMetadata _dci_metadata;
+       /** Frames per second to run our DCP at */
+       int _dcp_video_frame_rate;
        /** The date that we should use in a DCI name */
        boost::gregorian::date _dci_date;
-       /** Frames per second to run our DCP at */
-       int _dcp_frame_rate;
+       int _dcp_audio_channels;
        int _minimum_audio_channels;
 
-       /* Data which are cached to speed things up */
-
-       /** Size, in pixels, of the source (ignoring cropping) */
-       libdcp::Size _size;
-       /** The length of the source, in video frames (as far as we know) */
-       boost::optional<SourceFrame> _length;
-       /** MD5 digest of our content file */
-       std::string _content_digest;
-       /** The audio streams in our content */
-       std::vector<boost::shared_ptr<AudioStream> > _content_audio_streams;
-       /** A stream to represent possible external audio (will always exist) */
-       boost::shared_ptr<AudioStream> _sndfile_stream;
-       /** the subtitle streams that we can use */
-       std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams;
-       /** Frames per second of the source */
-       float _source_frame_rate;
-
        /** true if our state has changed since we last saved it */
        mutable bool _dirty;
 
index 205d92482b3aa16d3fd134476335e6b6c77336e6..7587312c23dfebf7c2f99167939f3300086270e4 100644 (file)
@@ -21,8 +21,8 @@
  *  @brief A class to describe one of FFmpeg's video or post-processing filters.
  */
 
-#ifndef DVDOMATIC_FILTER_H
-#define DVDOMATIC_FILTER_H
+#ifndef DCPOMATIC_FILTER_H
+#define DCPOMATIC_FILTER_H
 
 #include <string>
 #include <vector>
index 8ff5e75df3d3ee6f5575778147db7210e0350d77..275c469096afb33507f78a885d70172643f64dc0 100644 (file)
@@ -33,24 +33,23 @@ extern "C" {
 #include "filter.h"
 #include "exceptions.h"
 #include "image.h"
-#include "film.h"
-#include "ffmpeg_decoder.h"
 
 #include "i18n.h"
 
 using std::stringstream;
 using std::string;
 using std::list;
+using std::cout;
 using boost::shared_ptr;
+using boost::weak_ptr;
 using libdcp::Size;
 
-/** Construct a FFmpegFilterGraph for the settings in a film.
- *  @param film Film.
- *  @param decoder Decoder that we are using.
+/** Construct a FilterGraph for the settings in a piece of content.
+ *  @param content Content.
  *  @param s Size of the images to process.
  *  @param p Pixel format of the images to process.
  */
-FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p)
+FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p)
        : _buffer_src_context (0)
        , _buffer_sink_context (0)
        , _size (s)
@@ -58,12 +57,13 @@ FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* deco
 {
        _frame = av_frame_alloc ();
        
-       string filters = Filter::ffmpeg_strings (film->filters()).first;
+       string filters = Filter::ffmpeg_strings (content->filters()).first;
        if (!filters.empty ()) {
-               filters += N_(",");
+               filters += ",";
        }
 
-       filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size()));
+       /* XXX; remove */
+       filters += crop_string (Position (), _size);
 
        AVFilterGraph* graph = avfilter_graph_alloc();
        if (graph == 0) {
@@ -83,8 +83,8 @@ FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* deco
        stringstream a;
        a << "video_size=" << _size.width << "x" << _size.height << ":"
          << "pix_fmt=" << _pixel_format << ":"
-         << "time_base=" << decoder->time_base_numerator() << "/" << decoder->time_base_denominator() << ":"
-         << "pixel_aspect=" << decoder->sample_aspect_ratio_numerator() << "/" << decoder->sample_aspect_ratio_denominator();
+         << "time_base=1/1:"
+         << "pixel_aspect=1/1";
 
        int r;
 
@@ -127,7 +127,7 @@ FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* deco
        /* XXX: leaking `inputs' / `outputs' ? */
 }
 
-FFmpegFilterGraph::~FFmpegFilterGraph ()
+FilterGraph::~FilterGraph ()
 {
        av_frame_free (&_frame);
 }
@@ -136,7 +136,7 @@ FFmpegFilterGraph::~FFmpegFilterGraph ()
  *  set of Images.  Caller handles memory management of the input frame.
  */
 list<shared_ptr<Image> >
-FFmpegFilterGraph::process (AVFrame* frame)
+FilterGraph::process (AVFrame* frame)
 {
        list<shared_ptr<Image> > images;
 
@@ -161,25 +161,7 @@ FFmpegFilterGraph::process (AVFrame* frame)
  *  @return true if this chain can process images with `s' and `p', otherwise false.
  */
 bool
-FFmpegFilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
+FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
 {
        return (_size == s && _pixel_format == p);
 }
-
-list<shared_ptr<Image> >
-EmptyFilterGraph::process (AVFrame* frame)
-{
-       list<shared_ptr<Image> > im;
-       im.push_back (shared_ptr<Image> (new SimpleImage (frame)));
-       return im;
-}
-
-shared_ptr<FilterGraph>
-filter_graph_factory (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size size, AVPixelFormat pixel_format)
-{
-       if (film->filters().empty() && film->crop() == Crop()) {
-               return shared_ptr<FilterGraph> (new EmptyFilterGraph);
-       }
-
-       return shared_ptr<FilterGraph> (new FFmpegFilterGraph (film, decoder, size, pixel_format));
-}
index ee378af4cddaf4b1ae7608644d53d7be7f270903..e294812c2baa9836f5aeff99711057bae611148e 100644 (file)
  *  @brief A graph of FFmpeg filters.
  */
 
-#ifndef DVDOMATIC_FILTER_GRAPH_H
-#define DVDOMATIC_FILTER_GRAPH_H
+#ifndef DCPOMATIC_FILTER_GRAPH_H
+#define DCPOMATIC_FILTER_GRAPH_H
 
 #include "util.h"
 
 class Image;
 class VideoFilter;
-class FFmpegDecoder;
 
-class FilterGraph
-{
-public:
-       virtual ~FilterGraph () {}
-       virtual bool can_process (libdcp::Size, AVPixelFormat) const = 0;
-       virtual std::list<boost::shared_ptr<Image> > process (AVFrame *) = 0;
-};
-
-class EmptyFilterGraph : public FilterGraph
-{
-public:
-       bool can_process (libdcp::Size, AVPixelFormat) const {
-               return true;
-       }
-
-       std::list<boost::shared_ptr<Image> > process (AVFrame *);
-};
-
-/** @class FFmpegFilterGraph
+/** @class FilterGraph
  *  @brief A graph of FFmpeg filters.
  */
-class FFmpegFilterGraph : public FilterGraph
+class FilterGraph
 {
 public:
-       FFmpegFilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p);
-       ~FFmpegFilterGraph ();
+       FilterGraph (boost::shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p);
+       ~FilterGraph ();
 
        bool can_process (libdcp::Size s, AVPixelFormat p) const;
        std::list<boost::shared_ptr<Image> > process (AVFrame * frame);
@@ -68,6 +49,4 @@ private:
        AVFrame* _frame;
 };
 
-boost::shared_ptr<FilterGraph> filter_graph_factory (boost::shared_ptr<Film>, FFmpegDecoder *, libdcp::Size, AVPixelFormat);
-
 #endif
diff --git a/src/lib/format.cc b/src/lib/format.cc
deleted file mode 100644 (file)
index 78e2008..0000000
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/format.cc
- *  @brief Class to describe a format (aspect ratio) that a Film should
- *  be shown in.
- */
-
-#include <sstream>
-#include <cstdlib>
-#include <cassert>
-#include <iomanip>
-#include <iostream>
-#include "format.h"
-#include "film.h"
-
-#include "i18n.h"
-
-using std::string;
-using std::setprecision;
-using std::stringstream;
-using std::vector;
-using boost::shared_ptr;
-using libdcp::Size;
-
-vector<Format const *> Format::_formats;
-
-/** @return A name to be presented to the user */
-string
-FixedFormat::name () const
-{
-       stringstream s;
-       if (!_nickname.empty ()) {
-               s << _nickname << N_(" (");
-       }
-
-       s << setprecision(3) << _ratio << N_(":1");
-
-       if (!_nickname.empty ()) {
-               s << N_(")");
-       }
-
-       return s.str ();
-}
-
-/** @return Identifier for this format as metadata for a Film's metadata file */
-string
-Format::as_metadata () const
-{
-       return _id;
-}
-
-/** Fill our _formats vector with all available formats */
-void
-Format::setup_formats ()
-{
-       /// TRANSLATORS: these are film picture aspect ratios; "Academy" means 1.37, "Flat" 1.85 and "Scope" 2.39.
-       _formats.push_back (
-               new FixedFormat (1.19, libdcp::Size (1285, 1080), "119", _("1.19"), "F"
-                       ));
-       
-       _formats.push_back (
-               new FixedFormat (4.0 / 3.0, libdcp::Size (1436, 1080), "133", _("4:3"), "F"
-                       ));
-       
-       _formats.push_back (
-               new FixedFormat (1.38, libdcp::Size (1485, 1080), "138", _("1.375"), "F"
-                       ));
-       
-       _formats.push_back (
-               new FixedFormat (4.0 / 3.0, libdcp::Size (1998, 1080), "133-in-flat", _("4:3 within Flat"), "F"
-                       ));
-       
-       _formats.push_back (
-               new FixedFormat (1.37, libdcp::Size (1480, 1080), "137", _("Academy"), "F"
-                       ));
-       
-       _formats.push_back (
-               new FixedFormat (1.66, libdcp::Size (1793, 1080), "166", _("1.66"), "F"
-                       ));
-       
-       _formats.push_back (
-               new FixedFormat (1.66, libdcp::Size (1998, 1080), "166-in-flat", _("1.66 within Flat"), "F"
-                       ));
-       
-       _formats.push_back (
-               new FixedFormat (1.78, libdcp::Size (1998, 1080), "178-in-flat", _("16:9 within Flat"), "F"
-                       ));
-       
-       _formats.push_back (
-               new FixedFormat (1.78, libdcp::Size (1920, 1080), "178", _("16:9"), "F"
-                       ));
-       
-       _formats.push_back (
-               new FixedFormat (1.85, libdcp::Size (1998, 1080), "185", _("Flat"), "F"
-                       ));
-       
-       _formats.push_back (
-               new FixedFormat (1.78, libdcp::Size (2048, 858), "178-in-scope", _("16:9 within Scope"), "S"
-                       ));
-       
-       _formats.push_back (
-               new FixedFormat (2.39, libdcp::Size (2048, 858), "239", _("Scope"), "S"
-                       ));
-
-       _formats.push_back (
-               new FixedFormat (1.896, libdcp::Size (2048, 1080), "full-frame", _("Full frame"), "C"
-                       ));
-               
-       _formats.push_back (
-               new VariableFormat (libdcp::Size (1998, 1080), "var-185", _("Flat without stretch"), "F"
-                       ));
-       
-       _formats.push_back (
-               new VariableFormat (libdcp::Size (2048, 858), "var-239", _("Scope without stretch"), "S"
-                       ));
-}
-
-/** @param n Nickname.
- *  @return Matching Format, or 0.
- */
-Format const *
-Format::from_nickname (string n)
-{
-       vector<Format const *>::iterator i = _formats.begin ();
-       while (i != _formats.end() && (*i)->nickname() != n) {
-               ++i;
-       }
-
-       if (i == _formats.end ()) {
-               return 0;
-       }
-
-       return *i;
-}
-
-/** @param i Id.
- *  @return Matching Format, or 0.
- */
-Format const *
-Format::from_id (string i)
-{
-       vector<Format const *>::iterator j = _formats.begin ();
-       while (j != _formats.end() && (*j)->id() != i) {
-               ++j;
-       }
-
-       if (j == _formats.end ()) {
-               return 0;
-       }
-
-       return *j;
-}
-
-
-/** @param m Metadata, as returned from as_metadata().
- *  @return Matching Format, or 0.
- */
-Format const *
-Format::from_metadata (string m)
-{
-       return from_id (m);
-}
-
-/** @return All available formats */
-vector<Format const *>
-Format::all ()
-{
-       return _formats;
-}
-
-/** @param r Ratio
- *  @param dcp Size (in pixels) of the images that we should put in a DCP.
- *  @param id ID (e.g. 185)
- *  @param n Nick name (e.g. Flat)
- */
-FixedFormat::FixedFormat (float r, libdcp::Size dcp, string id, string n, string d)
-       : Format (dcp, id, n, d)
-       , _ratio (r)
-{
-
-}
-
-/** @return Number of pixels (int the DCP image) to pad either side of the film
- *  (so there are dcp_padding() pixels on the left and dcp_padding() on the right)
- */
-int
-Format::dcp_padding (shared_ptr<const Film> f) const
-{
-       int p = rint ((_dcp_size.width - (_dcp_size.height * ratio(f))) / 2.0);
-
-       /* This comes out -ve for Scope; bodge it */
-       if (p < 0) {
-               p = 0;
-       }
-       
-       return p;
-}
-
-float
-Format::container_ratio () const
-{
-       return static_cast<float> (_dcp_size.width) / _dcp_size.height;
-}
-
-VariableFormat::VariableFormat (libdcp::Size dcp, string id, string n, string d)
-       : Format (dcp, id, n, d)
-{
-
-}
-
-float
-VariableFormat::ratio (shared_ptr<const Film> f) const
-{
-       libdcp::Size const c = f->cropped_size (f->size ());
-       return float (c.width) / c.height;
-}
-
-/** @return A name to be presented to the user */
-string
-VariableFormat::name () const
-{
-       return _nickname;
-}
diff --git a/src/lib/format.h b/src/lib/format.h
deleted file mode 100644 (file)
index e953062..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/format.h
- *  @brief Classes to describe a format (aspect ratio) that a Film should
- *  be shown in.
- */
-
-#include <string>
-#include <vector>
-#include "util.h"
-
-class Film;
-
-class Format
-{
-public:
-       Format (libdcp::Size dcp, std::string id, std::string n, std::string d)
-               : _dcp_size (dcp)
-               , _id (id)
-               , _nickname (n)
-               , _dci_name (d)
-       {}
-
-       /** @return the ratio of the container (including any padding) */
-       float container_ratio () const;
-
-       int dcp_padding (boost::shared_ptr<const Film> f) const;
-
-       /** @return size in pixels of the images that we should
-        *  put in a DCP for this ratio.  This size will not correspond
-        *  to the ratio when we are doing things like 16:9 in a Flat frame.
-        */
-       libdcp::Size dcp_size () const {
-               return _dcp_size;
-       }
-
-       std::string id () const {
-               return _id;
-       }
-
-       /** @return Full name to present to the user */
-       virtual std::string name () const = 0;
-
-       /** @return Nickname (e.g. Flat, Scope) */
-       std::string nickname () const {
-               return _nickname;
-       }
-
-       std::string dci_name () const {
-               return _dci_name;
-       }
-
-       std::string as_metadata () const;
-
-       static Format const * from_nickname (std::string n);
-       static Format const * from_metadata (std::string m);
-       static Format const * from_id (std::string i);
-       static std::vector<Format const *> all ();
-       static void setup_formats ();
-
-protected:     
-        /** @return the ratio */
-       virtual float ratio (boost::shared_ptr<const Film> f) const = 0;
-
-       /** libdcp::Size in pixels of the images that we should
-        *  put in a DCP for this ratio.  This size will not correspond
-        *  to the ratio when we are doing things like 16:9 in a Flat frame.
-        */
-       libdcp::Size _dcp_size;
-       /** id for use in metadata */
-       std::string _id;
-       /** nickname (e.g. Flat, Scope) */
-       std::string _nickname;
-       std::string _dci_name;
-
-private:       
-       /** all available formats */
-       static std::vector<Format const *> _formats;
-};
-
-/** @class FixedFormat
- *  @brief Class to describe a format whose ratio is fixed regardless
- *  of source size.
- */
-class FixedFormat : public Format
-{
-public:
-       FixedFormat (float, libdcp::Size, std::string, std::string, std::string);
-
-       float ratio (boost::shared_ptr<const Film>) const {
-               return _ratio;
-       }
-
-       std::string name () const;
-       
-private:
-
-       float _ratio;
-};
-
-class VariableFormat : public Format
-{
-public:
-       VariableFormat (libdcp::Size, std::string, std::string, std::string);
-
-       float ratio (boost::shared_ptr<const Film> f) const;
-
-       std::string name () const;
-};
diff --git a/src/lib/gain.cc b/src/lib/gain.cc
deleted file mode 100644 (file)
index ccd779d..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "gain.h"
-
-using boost::shared_ptr;
-
-/** @param gain gain in dB */
-Gain::Gain (shared_ptr<Log> log, float gain)
-       : AudioProcessor (log)
-       , _gain (gain)
-{
-
-}
-
-void
-Gain::process_audio (shared_ptr<const AudioBuffers> b)
-{
-       if (_gain != 0) {
-               float const linear_gain = pow (10, _gain / 20);
-               for (int i = 0; i < b->channels(); ++i) {
-                       for (int j = 0; j < b->frames(); ++j) {
-                               b->data(i)[j] *= linear_gain;
-                       }
-               }
-       }
-
-       Audio (b);
-}
diff --git a/src/lib/gain.h b/src/lib/gain.h
deleted file mode 100644 (file)
index 61fef5e..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "processor.h"
-
-class Gain : public AudioProcessor
-{
-public:
-       Gain (boost::shared_ptr<Log> log, float gain);
-
-       void process_audio (boost::shared_ptr<const AudioBuffers>);
-
-private:
-       float _gain;
-};
index 46bb1d5652dba39b480571c489652075fdef2e2a..890313bc68b1cb3714521e04f6ea89346336fc4d 100644 (file)
@@ -19,5 +19,5 @@
 
 #include <libintl.h>
 
-#define _(x) dgettext ("libdvdomatic", x)
+#define _(x) dgettext ("libdcpomatic", x)
 #define N_(x) x
index f28652d4e2cdac846773df4e202e69902d0c67cf..722ff5d3ceff24509edeb49bb21ca9c575148abb 100644 (file)
@@ -43,8 +43,9 @@ extern "C" {
 
 #include "i18n.h"
 
-using namespace std;
-using namespace boost;
+using std::string;
+using std::min;
+using boost::shared_ptr;
 using libdcp::Size;
 
 void
@@ -53,22 +54,28 @@ Image::swap (Image& other)
        std::swap (_pixel_format, other._pixel_format);
 }
 
-/** @param n Component index.
- *  @return Number of lines in the image for the given component.
- */
 int
-Image::lines (int n) const
+Image::line_factor (int n) const
 {
        if (n == 0) {
-               return size().height;
+               return 1;
        }
-       
+
        AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format);
        if (!d) {
                throw PixelFormatError (N_("lines()"), _pixel_format);
        }
        
-       return size().height / pow(2.0f, d->log2_chroma_h);
+       return pow (2.0f, d->log2_chroma_h);
+}
+
+/** @param n Component index.
+ *  @return Number of lines in the image for the given component.
+ */
+int
+Image::lines (int n) const
+{
+       return rint (ceil (static_cast<double>(size().height) / line_factor (n)));
 }
 
 /** @return Number of components */
@@ -121,7 +128,7 @@ Image::scale (libdcp::Size out_size, Scaler const * scaler, bool result_aligned)
  *  @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
@@ -129,14 +136,11 @@ Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler cons
        */
        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
                );
 
@@ -148,28 +152,6 @@ Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler cons
                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;
@@ -232,12 +214,12 @@ Image::crop (Crop crop, bool aligned) const
        for (int c = 0; c < components(); ++c) {
                int const crop_left_in_bytes = bytes_per_pixel(c) * crop.left;
                int const cropped_width_in_bytes = bytes_per_pixel(c) * cropped_size.width;
-                       
+
                /* 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* in_p = data()[c] + (crop.top / out->line_factor(c)) * 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];
@@ -384,6 +366,21 @@ Image::alpha_blend (shared_ptr<const Image> other, Position position)
        }
 }
 
+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)
 {
@@ -512,12 +509,11 @@ SimpleImage::SimpleImage (AVFrame* frame)
        }
 }
 
-SimpleImage::SimpleImage (shared_ptr<const Image> other)
+SimpleImage::SimpleImage (shared_ptr<const Image> other, bool aligned)
        : Image (*other.get())
+       , _size (other->size())
+       , _aligned (aligned)
 {
-       _size = other->size ();
-       _aligned = true;
-
        allocate ();
 
        for (int i = 0; i < components(); ++i) {
index 70dacfaee4bd9e4bcd24bdf8ba4241916b1941e4..5407ce66e502f8fc7a74d092549a2090eecf81fe 100644 (file)
@@ -21,8 +21,8 @@
  *  @brief A set of classes to describe video images.
  */
 
-#ifndef DVDOMATIC_IMAGE_H
-#define DVDOMATIC_IMAGE_H
+#ifndef DCPOMATIC_IMAGE_H
+#define DCPOMATIC_IMAGE_H
 
 #include <string>
 #include <boost/shared_ptr.hpp>
@@ -69,12 +69,14 @@ public:
        virtual bool aligned () const = 0;
 
        int components () const;
+       int line_factor (int) const;
        int lines (int) const;
 
-       boost::shared_ptr<Image> scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler const * scaler, bool aligned) const;
+       boost::shared_ptr<Image> scale_and_convert_to_rgb (libdcp::Size, Scaler const *, bool) const;
        boost::shared_ptr<Image> scale (libdcp::Size, Scaler const *, bool aligned) const;
        boost::shared_ptr<Image> post_process (std::string, bool aligned) const;
        void alpha_blend (boost::shared_ptr<const Image> image, Position pos);
+       void copy (boost::shared_ptr<const Image> image, Position pos);
        boost::shared_ptr<Image> crop (Crop c, bool aligned) const;
        
        void make_black ();
@@ -108,7 +110,7 @@ public:
        SimpleImage (AVPixelFormat, libdcp::Size, bool);
        SimpleImage (AVFrame *);
        SimpleImage (SimpleImage const &);
-       SimpleImage (boost::shared_ptr<const Image>);
+       SimpleImage (boost::shared_ptr<const Image>, bool);
        SimpleImage& operator= (SimpleImage const &);
        ~SimpleImage ();
 
diff --git a/src/lib/imagemagick.h b/src/lib/imagemagick.h
new file mode 100644 (file)
index 0000000..5a1712a
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+class ImageMagickContent;
+
+class ImageMagick
+{
+public:
+       ImageMagick (boost::shared_ptr<const ImageMagickContent> c)
+               : _imagemagick_content (c)
+       {}
+
+       boost::shared_ptr<const ImageMagickContent> content () const {
+               return _imagemagick_content;
+       }
+
+protected:
+       boost::shared_ptr<const ImageMagickContent> _imagemagick_content;
+};
diff --git a/src/lib/imagemagick_content.cc b/src/lib/imagemagick_content.cc
new file mode 100644 (file)
index 0000000..2fd65ff
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "imagemagick_content.h"
+#include "imagemagick_examiner.h"
+#include "config.h"
+#include "compose.hpp"
+#include "film.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+
+ImageMagickContent::ImageMagickContent (shared_ptr<const Film> f, boost::filesystem::path p)
+       : Content (f, p)
+       , VideoContent (f, p)
+{
+
+}
+
+ImageMagickContent::ImageMagickContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+       : Content (f, node)
+       , VideoContent (f, node)
+{
+       
+}
+
+string
+ImageMagickContent::summary () const
+{
+       return String::compose (_("Image: %1"), file().filename().string());
+}
+
+bool
+ImageMagickContent::valid_file (boost::filesystem::path f)
+{
+       string ext = f.extension().string();
+       transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
+       return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp");
+}
+
+void
+ImageMagickContent::as_xml (xmlpp::Node* node) const
+{
+       node->add_child("Type")->add_child_text ("ImageMagick");
+       Content::as_xml (node);
+       VideoContent::as_xml (node);
+}
+
+void
+ImageMagickContent::examine (shared_ptr<Job> job)
+{
+       Content::examine (job);
+
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       shared_ptr<ImageMagickExaminer> examiner (new ImageMagickExaminer (film, shared_from_this()));
+
+       set_video_length (Config::instance()->default_still_length() * 24);
+       take_from_video_examiner (examiner);
+}
+
+shared_ptr<Content>
+ImageMagickContent::clone () const
+{
+       return shared_ptr<Content> (new ImageMagickContent (*this));
+}
+
+void
+ImageMagickContent::set_video_length (VideoContent::Frame len)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _video_length = len;
+       }
+
+       signal_changed (ContentProperty::LENGTH);
+}
+
+Time
+ImageMagickContent::length () const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       FrameRateConversion frc (24, film->dcp_video_frame_rate ());
+       return video_length() * frc.factor() * TIME_HZ / film->dcp_video_frame_rate ();
+}
+
diff --git a/src/lib/imagemagick_content.h b/src/lib/imagemagick_content.h
new file mode 100644 (file)
index 0000000..04425af
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+    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 (VideoContent::Frame);
+
+       static bool valid_file (boost::filesystem::path);
+};
+
+#endif
index 5ce22c29622a403c33425a5ce6e8e9a5f3e26de5..04d3d9df7c47d166c52e7692b3977d550993b547 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-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
@@ -20,6 +20,7 @@
 #include <iostream>
 #include <boost/filesystem.hpp>
 #include <Magick++.h>
+#include "imagemagick_content.h"
 #include "imagemagick_decoder.h"
 #include "image.h"
 #include "film.h"
@@ -31,66 +32,36 @@ using std::cout;
 using boost::shared_ptr;
 using libdcp::Size;
 
-ImageMagickDecoder::ImageMagickDecoder (
-       boost::shared_ptr<Film> f, DecodeOptions o)
-       : Decoder (f, o)
-       , VideoDecoder (f, o)
+ImageMagickDecoder::ImageMagickDecoder (shared_ptr<const Film> f, shared_ptr<const ImageMagickContent> c)
+       : Decoder (f)
+       , VideoDecoder (f)
+       , ImageMagick (c)
 {
-       if (boost::filesystem::is_directory (_film->content_path())) {
-               for (
-                       boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (_film->content_path());
-                       i != boost::filesystem::directory_iterator();
-                       ++i) {
-
-                       if (still_image_file (i->path().string())) {
-                               _files.push_back (i->path().string());
-                       }
-               }
-       } else {
-               _files.push_back (_film->content_path ());
-       }
-
-       _iter = _files.begin ();
-}
-
-libdcp::Size
-ImageMagickDecoder::native_size () const
-{
-       if (_files.empty ()) {
-               throw DecodeError (_("no still image files found"));
-       }
-
-       /* Look at the first file and assume its size holds for all */
-       using namespace MagickCore;
-       Magick::Image* image = new Magick::Image (_film->content_path ());
-       libdcp::Size const s = libdcp::Size (image->columns(), image->rows());
-       delete image;
 
-       return s;
 }
 
-bool
+void
 ImageMagickDecoder::pass ()
 {
-       if (_iter == _files.end()) {
-               if (video_frame() >= _film->still_duration_in_frames()) {
-                       return true;
-               }
+       if (_video_position >= _imagemagick_content->video_length ()) {
+               return;
+       }
 
-               emit_video (_image, true, double (video_frame()) / frames_per_second());
-               return false;
+       if (_image) {
+               video (_image, true, _video_position);
+               return;
        }
+
+       Magick::Image* magick_image = new Magick::Image (_imagemagick_content->file().string ());
+       _video_size = libdcp::Size (magick_image->columns(), magick_image->rows());
        
-       Magick::Image* magick_image = new Magick::Image (_film->content_path ());
-       
-       libdcp::Size size = native_size ();
-       shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, false));
+       _image.reset (new SimpleImage (PIX_FMT_RGB24, _video_size.get(), false));
 
        using namespace MagickCore;
        
-       uint8_t* p = image->data()[0];
-       for (int y = 0; y < size.height; ++y) {
-               for (int x = 0; x < size.width; ++x) {
+       uint8_t* p = _image->data()[0];
+       for (int y = 0; y < _video_size->height; ++y) {
+               for (int x = 0; x < _video_size->width; ++x) {
                        Magick::Color c = magick_image->pixelColor (x, y);
                        *p++ = c.redQuantum() * 255 / QuantumRange;
                        *p++ = c.greenQuantum() * 255 / QuantumRange;
@@ -100,59 +71,25 @@ ImageMagickDecoder::pass ()
 
        delete magick_image;
 
-       _image = image->crop (_film->crop(), true);
-
-       emit_video (_image, false, double (video_frame()) / frames_per_second());
-
-       ++_iter;
-       return false;
+       video (_image, false, _video_position);
 }
 
-PixelFormat
-ImageMagickDecoder::pixel_format () const
-{
-       /* XXX: always true? */
-       return PIX_FMT_RGB24;
-}
-
-bool
-ImageMagickDecoder::seek_to_last ()
-{
-       if (_iter == _files.end()) {
-               _iter = _files.begin();
-       } else {
-               --_iter;
-       }
-
-       return false;
-}
-
-bool
-ImageMagickDecoder::seek (double t)
+void
+ImageMagickDecoder::seek (VideoContent::Frame frame)
 {
-       int const f = t * frames_per_second();
-       
-       _iter = _files.begin ();
-       for (int i = 0; i < f; ++i) {
-               if (_iter == _files.end()) {
-                       return true;
-               }
-               ++_iter;
-       }
-       
-       return false;
+       _video_position = frame;
 }
 
 void
-ImageMagickDecoder::film_changed (Film::Property p)
+ImageMagickDecoder::seek_back ()
 {
-       if (p == Film::CROP) {
-               OutputChanged ();
+       if (_video_position > 0) {
+               _video_position--;
        }
 }
 
-float
-ImageMagickDecoder::frames_per_second () const
+bool
+ImageMagickDecoder::done () const
 {
-       return _film->source_frame_rate ();
+       return _video_position >= _imagemagick_content->video_length ();
 }
index 80a08f81f8374a3126a425ee413d0c0eedf21e29..286f473378396f9bbd44f39f28f08c7eeefaf0dd 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-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
 */
 
 #include "video_decoder.h"
+#include "imagemagick.h"
 
 namespace Magick {
        class Image;
 }
 
-class ImageMagickDecoder : public VideoDecoder
+class ImageMagickContent;
+
+class ImageMagickDecoder : public VideoDecoder, public ImageMagick
 {
 public:
-       ImageMagickDecoder (boost::shared_ptr<Film>, DecodeOptions);
-
-       float frames_per_second () const;
-
-       libdcp::Size native_size () const;
-
-       SourceFrame length () const {
-               /* We don't know */
-               return 0;
-       }
-
-       int audio_channels () const {
-               return 0;
-       }
-
-       int audio_sample_rate () const {
-               return 0;
-       }
+       ImageMagickDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageMagickContent>);
 
-       int64_t audio_channel_layout () const {
-               return 0;
-       }
+       /* Decoder */
 
-       bool seek (double);
-       bool seek_to_last ();
-
-protected:
-       bool pass ();
-       PixelFormat pixel_format () const;
-
-       int time_base_numerator () const {
-               return 0;
-       }
-
-       int time_base_denominator () const {
-               return 0;
-       }
-
-       int sample_aspect_ratio_numerator () const {
-               /* XXX */
-               return 1;
-       }
-
-       int sample_aspect_ratio_denominator () const {
-               /* XXX */
-               return 1;
-       }
+       void pass ();
+       void seek (VideoContent::Frame);
+       void seek_back ();
+       bool done () const;
 
 private:
-       void film_changed (Film::Property);
-       
-       std::list<std::string> _files;
-       std::list<std::string>::iterator _iter;
-
        boost::shared_ptr<Image> _image;
+       mutable boost::optional<libdcp::Size> _video_size;
 };
diff --git a/src/lib/imagemagick_examiner.cc b/src/lib/imagemagick_examiner.cc
new file mode 100644 (file)
index 0000000..0eee0d4
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+    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 <iostream>
+#include <Magick++.h>
+#include "imagemagick_content.h"
+#include "imagemagick_examiner.h"
+#include "film.h"
+
+#include "i18n.h"
+
+using std::cout;
+using boost::shared_ptr;
+
+ImageMagickExaminer::ImageMagickExaminer (shared_ptr<const Film> f, shared_ptr<const ImageMagickContent> c)
+       : ImageMagick (c)
+       , _film (f)
+{
+       using namespace MagickCore;
+       Magick::Image* image = new Magick::Image (_imagemagick_content->file().string());
+       _video_size = libdcp::Size (image->columns(), image->rows());
+       delete image;
+}
+
+libdcp::Size
+ImageMagickExaminer::video_size () const
+{
+       return _video_size;
+}
+
+int
+ImageMagickExaminer::video_length () const
+{
+       return _imagemagick_content->video_length ();
+}
+
+float
+ImageMagickExaminer::video_frame_rate () const
+{
+       boost::shared_ptr<const Film> f = _film.lock ();
+       if (!f) {
+               return 24;
+       }
+
+       return f->dcp_video_frame_rate ();
+}
+
diff --git a/src/lib/imagemagick_examiner.h b/src/lib/imagemagick_examiner.h
new file mode 100644 (file)
index 0000000..801ede4
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+    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 "imagemagick.h"
+#include "video_examiner.h"
+
+namespace Magick {
+       class Image;
+}
+
+class ImageMagickContent;
+
+class ImageMagickExaminer : public ImageMagick, public VideoExaminer
+{
+public:
+       ImageMagickExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageMagickContent>);
+
+       float video_frame_rate () const;
+       libdcp::Size video_size () const;
+       VideoContent::Frame video_length () const;
+
+private:
+       boost::weak_ptr<const Film> _film;
+       libdcp::Size _video_size;
+};
index 8bb43a91f28bc3c5acbe207c3b61a6433b013518..080d1eaf6018a5588c9d9f0c31a63c894585a4a1 100644 (file)
@@ -36,9 +36,7 @@ using std::list;
 using std::stringstream;
 using boost::shared_ptr;
 
-/** @param s Film that we are operating on.
- */
-Job::Job (shared_ptr<Film> f)
+Job::Job (shared_ptr<const Film> f)
        : _film (f)
        , _thread (0)
        , _state (NEW)
@@ -95,7 +93,7 @@ Job::run_wrapper ()
                set_state (FINISHED_ERROR);
                set_error (
                        e.what (),
-                       _("It is not known what caused this error.  The best idea is to report the problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)")
+                       _("It is not known what caused this error.  The best idea is to report the problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)")
                        );
 
        } catch (...) {
@@ -104,7 +102,7 @@ Job::run_wrapper ()
                set_state (FINISHED_ERROR);
                set_error (
                        _("Unknown error"),
-                       _("It is not known what caused this error.  The best idea is to report the problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)")
+                       _("It is not known what caused this error.  The best idea is to report the problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)")
                        );
 
        }
@@ -204,7 +202,7 @@ Job::set_progress (float p)
        boost::this_thread::interruption_point ();
 
        if (paused ()) {
-               dvdomatic_sleep (1);
+               dcpomatic_sleep (1);
        }
 }
 
index 37fa56d2082e83e4b6ea49c484152d8b75142e11..791a9101b24bed0ca792049cf5bd24123b4c89a8 100644 (file)
@@ -21,8 +21,8 @@
  *  @brief A parent class to represent long-running tasks which are run in their own thread.
  */
 
-#ifndef DVDOMATIC_JOB_H
-#define DVDOMATIC_JOB_H
+#ifndef DCPOMATIC_JOB_H
+#define DCPOMATIC_JOB_H
 
 #include <string>
 #include <boost/thread/mutex.hpp>
@@ -38,7 +38,7 @@ class Film;
 class Job : public boost::enable_shared_from_this<Job>
 {
 public:
-       Job (boost::shared_ptr<Film> s);
+       Job (boost::shared_ptr<const Film>);
        virtual ~Job() {}
 
        /** @return user-readable name of this job */
@@ -71,7 +71,7 @@ public:
        void descend (float);
        float overall_progress () const;
 
-       /** Emitted by the JobManagerView from the UI thread */
+       /** Emitted from the UI thread when the job is finished */
        boost::signals2::signal<void()> Finished;
 
 protected:
@@ -91,8 +91,7 @@ protected:
        void set_state (State);
        void set_error (std::string s, std::string d);
 
-       /** Film for this job */
-       boost::shared_ptr<Film> _film;
+       boost::shared_ptr<const Film> _film;
 
 private:
 
index 9105976280dd3b7d5ab58f81a362bdb72dcf5a9d..f962754677c2c1c672092838a0eca23f73e5873f 100644 (file)
@@ -126,7 +126,7 @@ JobManager::scheduler ()
                        }
                }
 
-               dvdomatic_sleep (1);
+               dcpomatic_sleep (1);
        }
 }
 
index 3a2cfcbfd53e7882a61feaa03268738036a60b1f..3ad6516c11b7b56ee270c269967bd3d59177e464 100644 (file)
@@ -17,8 +17,8 @@
 
 */
 
-#ifndef DVDOMATIC_LOG_H
-#define DVDOMATIC_LOG_H
+#ifndef DCPOMATIC_LOG_H
+#define DCPOMATIC_LOG_H
 
 /** @file src/log.h
  *  @brief A very simple logging class.
diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc
deleted file mode 100644 (file)
index 4acb82a..0000000
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "matcher.h"
-#include "image.h"
-#include "log.h"
-
-#include "i18n.h"
-
-using std::min;
-using std::cout;
-using std::list;
-using boost::shared_ptr;
-
-Matcher::Matcher (shared_ptr<Log> log, int sample_rate, float frames_per_second)
-       : Processor (log)
-       , _sample_rate (sample_rate)
-       , _frames_per_second (frames_per_second)
-       , _video_frames (0)
-       , _audio_frames (0)
-       , _had_first_video (false)
-       , _had_first_audio (false)
-{
-
-}
-
-void
-Matcher::process_video (boost::shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
-{
-       _pixel_format = image->pixel_format ();
-       _size = image->size ();
-
-       _log->log(String::compose("Matcher video @ %1 [audio=%2, video=%3, pending_audio=%4]", t, _audio_frames, _video_frames, _pending_audio.size()));
-
-       if (!_first_input || t < _first_input.get()) {
-               _first_input = t;
-       }
-
-       bool const this_is_first_video = !_had_first_video;
-       _had_first_video = true;
-
-       if (!_had_first_audio) {
-               /* No audio yet; we must postpone these data until we have some */
-               _pending_video.push_back (VideoRecord (image, same, sub, t));
-       } else if (this_is_first_video && _had_first_audio) {
-               /* First video since we got audio */
-               _pending_video.push_back (VideoRecord (image, same, sub, t));
-               fix_start ();
-       } else {
-               /* Normal running */
-
-               /* Difference between where this video is and where it should be */
-               double const delta = t - _first_input.get() - _video_frames / _frames_per_second;
-               double const one_frame = 1 / _frames_per_second;
-               
-               if (delta > one_frame) {
-                       /* Insert frames to make up the difference */
-                       int const extra = rint (delta / one_frame);
-                       for (int i = 0; i < extra; ++i) {
-                               repeat_last_video ();
-                               _log->log (String::compose ("Extra video frame inserted at %1s", _video_frames / _frames_per_second));
-                       }
-               }
-               
-               if (delta > -one_frame) {
-                       Video (image, same, sub);
-                       ++_video_frames;
-               } else {
-                       /* We are omitting a frame to keep things right */
-                       _log->log (String::compose ("Frame removed at %1s; delta %2; first input was at %3", t, delta, _first_input.get()));
-               }
-               
-               _last_image = image;
-               _last_subtitle = sub;
-       }
-}
-
-void
-Matcher::process_audio (boost::shared_ptr<const AudioBuffers> b, double t)
-{
-       _channels = b->channels ();
-
-       _log->log (String::compose (
-                          "Matcher audio (%1 frames) @ %2 [video=%3, audio=%4, pending_video=%5, pending_audio=%6]",
-                          b->frames(), t, _video_frames, _audio_frames, _pending_video.size(), _pending_audio.size()
-                          )
-               );
-
-       if (!_first_input || t < _first_input.get()) {
-               _first_input = t;
-       }
-
-       bool const this_is_first_audio = !_had_first_audio;
-       _had_first_audio = true;
-       
-       if (!_had_first_video) {
-               /* No video yet; we must postpone these data until we have some */
-               _pending_audio.push_back (AudioRecord (b, t));
-       } else if (this_is_first_audio && _had_first_video) {
-               /* First audio since we got video */
-               _pending_audio.push_back (AudioRecord (b, t));
-               fix_start ();
-       } else {
-               /* Normal running.  We assume audio time stamps are consecutive, so there's no equivalent of
-                  the checking / insertion of repeat frames that there is for video.
-               */
-               Audio (b);
-               _audio_frames += b->frames ();
-       }
-}
-
-void
-Matcher::process_end ()
-{
-       if (_audio_frames == 0 || !_pixel_format || !_size || !_channels) {
-               /* We won't do anything */
-               return;
-       }
-
-       _log->log (String::compose ("Matcher has seen %1 video frames (which equals %2 audio frames) and %3 audio frames",
-                                   _video_frames, video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second), _audio_frames));
-       
-       match ((double (_audio_frames) / _sample_rate) - (double (_video_frames) / _frames_per_second));
-}
-
-void
-Matcher::fix_start ()
-{
-       assert (!_pending_video.empty ());
-       assert (!_pending_audio.empty ());
-       
-       _log->log (String::compose ("Fixing start; video at %1, audio at %2", _pending_video.front().time, _pending_audio.front().time));
-
-       match (_pending_video.front().time - _pending_audio.front().time);
-
-       for (list<VideoRecord>::iterator i = _pending_video.begin(); i != _pending_video.end(); ++i) {
-               process_video (i->image, i->same, i->subtitle, i->time);
-       }
-
-       _pending_video.clear ();
-
-       for (list<AudioRecord>::iterator i = _pending_audio.begin(); i != _pending_audio.end(); ++i) {
-               process_audio (i->audio, i->time);
-       }
-       
-       _pending_audio.clear ();
-}
-
-void
-Matcher::match (double extra_video_needed)
-{
-       _log->log (String::compose ("Match %1", extra_video_needed));
-       
-       if (extra_video_needed > 0) {
-
-               /* Emit black video frames */
-               
-               int const black_video_frames = ceil (extra_video_needed * _frames_per_second);
-
-               _log->log (String::compose (N_("Emitting %1 frames of black video"), black_video_frames));
-
-               shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), true));
-               black->make_black ();
-               for (int i = 0; i < black_video_frames; ++i) {
-                       Video (black, i != 0, shared_ptr<Subtitle>());
-                       ++_video_frames;
-               }
-
-               extra_video_needed -= black_video_frames / _frames_per_second;
-       }
-
-       if (extra_video_needed < 0) {
-               
-               /* Emit silence */
-               
-               int64_t to_do = -extra_video_needed * _sample_rate;
-               _log->log (String::compose (N_("Emitting %1 frames of silence"), to_do));
-
-               /* Do things in half second blocks as I think there may be limits
-                  to what FFmpeg (and in particular the resampler) can cope with.
-               */
-               int64_t const block = _sample_rate / 2;
-               shared_ptr<AudioBuffers> b (new AudioBuffers (_channels.get(), block));
-               b->make_silent ();
-               
-               while (to_do > 0) {
-                       int64_t const this_time = min (to_do, block);
-                       b->set_frames (this_time);
-                       Audio (b);
-                       _audio_frames += b->frames ();
-                       to_do -= this_time;
-               }
-       }
-}
-
-void
-Matcher::repeat_last_video ()
-{
-       if (!_last_image) {
-               shared_ptr<Image> im (new SimpleImage (_pixel_format.get(), _size.get(), true));
-               im->make_black ();
-               _last_image = im;
-       }
-
-       Video (_last_image, true, _last_subtitle);
-       ++_video_frames;
-}
-
diff --git a/src/lib/matcher.h b/src/lib/matcher.h
deleted file mode 100644 (file)
index 61fd814..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <boost/optional.hpp>
-#include "processor.h"
-
-class Matcher : public Processor, public TimedAudioSink, public TimedVideoSink, public AudioSource, public VideoSource 
-{
-public:
-       Matcher (boost::shared_ptr<Log> log, int sample_rate, float frames_per_second);
-       void process_video (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s, double);
-       void process_audio (boost::shared_ptr<const AudioBuffers>, double);
-       void process_end ();
-
-private:
-       void fix_start ();
-       void match (double);
-       void repeat_last_video ();
-       
-       int _sample_rate;
-       float _frames_per_second;
-       int _video_frames;
-       int64_t _audio_frames;
-       boost::optional<AVPixelFormat> _pixel_format;
-       boost::optional<libdcp::Size> _size;
-       boost::optional<int> _channels;
-
-       struct VideoRecord {
-               VideoRecord (boost::shared_ptr<const Image> i, bool s, boost::shared_ptr<Subtitle> u, double t)
-                       : image (i)
-                       , same (s)
-                       , subtitle (u)
-                       , time (t)
-               {}
-
-               boost::shared_ptr<const Image> image;
-               bool same;
-               boost::shared_ptr<Subtitle> subtitle;
-               double time;
-       };
-
-       struct AudioRecord {
-               AudioRecord (boost::shared_ptr<const AudioBuffers> a, double t)
-                       : audio (a)
-                       , time (t)
-               {}
-               
-               boost::shared_ptr<const AudioBuffers> audio;
-               double time;
-       };
-
-       std::list<VideoRecord> _pending_video;
-       std::list<AudioRecord> _pending_audio;
-
-       boost::optional<double> _first_input;
-       boost::shared_ptr<const Image> _last_image;
-       boost::shared_ptr<Subtitle> _last_subtitle;
-
-       bool _had_first_video;
-       bool _had_first_audio;
-};
diff --git a/src/lib/options.h b/src/lib/options.h
deleted file mode 100644 (file)
index 0d2c07f..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#ifndef DVDOMATIC_OPTIONS_H
-#define DVDOMATIC_OPTIONS_H
-
-/** @file src/options.h
- *  @brief Options for a decoding operation.
- */
-
-class DecodeOptions
-{
-public:
-       DecodeOptions ()
-               : decode_video (true)
-               , decode_audio (true)
-               , decode_subtitles (false)
-               , video_sync (true)
-       {}
-
-       bool decode_video;
-       bool decode_audio;
-       bool decode_subtitles;
-       bool video_sync;
-};
-
-#endif
diff --git a/src/lib/player.cc b/src/lib/player.cc
new file mode 100644 (file)
index 0000000..9969fbf
--- /dev/null
@@ -0,0 +1,499 @@
+/*
+    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 "ratio.h"
+#include "resampler.h"
+
+using std::list;
+using std::cout;
+using std::min;
+using std::max;
+using std::vector;
+using std::pair;
+using std::map;
+using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
+
+#define DEBUG_PLAYER 1
+
+class Piece
+{
+public:
+       Piece (shared_ptr<Content> c)
+               : content (c)
+               , video_position (c->start ())
+               , audio_position (c->start ())
+       {}
+       
+       Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
+               : content (c)
+               , decoder (d)
+               , video_position (c->start ())
+               , audio_position (c->start ())
+       {}
+       
+       shared_ptr<Content> content;
+       shared_ptr<Decoder> decoder;
+       Time video_position;
+       Time audio_position;
+};
+
+#ifdef DEBUG_PLAYER
+std::ostream& operator<<(std::ostream& s, Piece const & p)
+{
+       if (dynamic_pointer_cast<FFmpegContent> (p.content)) {
+               s << "\tffmpeg     ";
+       } else if (dynamic_pointer_cast<ImageMagickContent> (p.content)) {
+               s << "\timagemagick";
+       } else if (dynamic_pointer_cast<SndfileContent> (p.content)) {
+               s << "\tsndfile    ";
+       }
+       
+       s << " at " << p.content->start() << " until " << p.content->end();
+       
+       return s;
+}
+#endif 
+
+Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
+       : _film (f)
+       , _playlist (p)
+       , _video (true)
+       , _audio (true)
+       , _have_valid_pieces (false)
+       , _video_position (0)
+       , _audio_position (0)
+       , _audio_buffers (f->dcp_audio_channels(), 0)
+{
+       _playlist->Changed.connect (bind (&Player::playlist_changed, this));
+       _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2));
+       set_video_container_size (_film->container()->size (_film->full_frame ()));
+}
+
+void
+Player::disable_video ()
+{
+       _video = false;
+}
+
+void
+Player::disable_audio ()
+{
+       _audio = false;
+}
+
+bool
+Player::pass ()
+{
+       if (!_have_valid_pieces) {
+               setup_pieces ();
+               _have_valid_pieces = true;
+       }
+
+#ifdef DEBUG_PLAYER
+       cout << "= PASS\n";
+#endif 
+
+        Time earliest_t = TIME_MAX;
+        shared_ptr<Piece> earliest;
+       enum {
+               VIDEO,
+               AUDIO
+       } type = VIDEO;
+
+       for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
+               if ((*i)->decoder->done ()) {
+                       continue;
+               }
+
+               if (dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
+                       if ((*i)->video_position < earliest_t) {
+                               earliest_t = (*i)->video_position;
+                               earliest = *i;
+                               type = VIDEO;
+                       }
+               }
+
+               if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
+                       if ((*i)->audio_position < earliest_t) {
+                               earliest_t = (*i)->audio_position;
+                               earliest = *i;
+                               type = AUDIO;
+                       }
+               }
+       }
+
+       if (!earliest) {
+#ifdef DEBUG_PLAYER
+               cout << "no earliest piece.\n";
+#endif         
+               
+               flush ();
+               return true;
+       }
+
+       switch (type) {
+       case VIDEO:
+               if (earliest_t > _video_position) {
+#ifdef DEBUG_PLAYER
+                       cout << "no video here; emitting black frame.\n";
+#endif
+                       emit_black ();
+               } else {
+#ifdef DEBUG_PLAYER
+                       cout << "Pass " << *earliest << "\n";
+#endif                 
+                       earliest->decoder->pass ();
+               }
+               break;
+
+       case AUDIO:
+               if (earliest_t > _audio_position) {
+#ifdef DEBUG_PLAYER
+                       cout << "no audio here; emitting silence.\n";
+#endif
+                       emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
+               } else {
+#ifdef DEBUG_PLAYER
+                       cout << "Pass " << *earliest << "\n";
+#endif                 
+                       earliest->decoder->pass ();
+               }
+               break;
+       }
+
+#ifdef DEBUG_PLAYER
+       cout << "\tpost pass " << _video_position << " " << _audio_position << "\n";
+#endif 
+
+        return false;
+}
+
+void
+Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
+{
+       shared_ptr<Piece> piece = weak_piece.lock ();
+       if (!piece) {
+               return;
+       }
+
+       shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
+       assert (content);
+
+       FrameRateConversion frc (content->video_frame_rate(), _film->dcp_video_frame_rate());
+       if (frc.skip && (frame % 2) == 1) {
+               return;
+       }
+
+       image = image->crop (content->crop(), true);
+
+       libdcp::Size const image_size = content->ratio()->size (_video_container_size);
+       
+       image = image->scale_and_convert_to_rgb (image_size, _film->scaler(), true);
+
+#if 0  
+       if (film->with_subtitles ()) {
+               shared_ptr<Subtitle> sub;
+               if (_timed_subtitle && _timed_subtitle->displayed_at (t)) {
+                       sub = _timed_subtitle->subtitle ();
+               }
+               
+               if (sub) {
+                       dcpomatic::Rect const tx = subtitle_transformed_area (
+                               float (image_size.width) / content->video_size().width,
+                               float (image_size.height) / content->video_size().height,
+                               sub->area(), film->subtitle_offset(), film->subtitle_scale()
+                               );
+                       
+                       shared_ptr<Image> im = sub->image()->scale (tx.size(), film->scaler(), true);
+                       image->alpha_blend (im, tx.position());
+               }
+       }
+#endif 
+
+       if (image_size != _video_container_size) {
+               assert (image_size.width <= _video_container_size.width);
+               assert (image_size.height <= _video_container_size.height);
+               shared_ptr<Image> im (new SimpleImage (PIX_FMT_RGB24, _video_container_size, true));
+               im->make_black ();
+               im->copy (image, Position ((_video_container_size.width - image_size.width) / 2, (_video_container_size.height - image_size.height) / 2));
+               image = im;
+       }
+
+       Time time = content->start() + (frame * frc.factor() * TIME_HZ / _film->dcp_video_frame_rate());
+       
+        Video (image, same, time);
+       time += TIME_HZ / _film->dcp_video_frame_rate();
+
+       if (frc.repeat) {
+               Video (image, true, time);
+               time += TIME_HZ / _film->dcp_video_frame_rate();
+       }
+
+       _video_position = piece->video_position = time;
+}
+
+void
+Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
+{
+       shared_ptr<Piece> piece = weak_piece.lock ();
+       if (!piece) {
+               return;
+       }
+
+       shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
+       assert (content);
+
+       if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
+               audio = resampler(content)->run (audio);
+       }
+
+       /* Remap channels */
+       shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->dcp_audio_channels(), audio->frames()));
+       dcp_mapped->make_silent ();
+       list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp ();
+       for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
+               dcp_mapped->accumulate_channel (audio.get(), i->first, i->second);
+       }
+
+        /* 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 const time = content->start() + (frame * TIME_HZ / _film->dcp_audio_frame_rate());
+
+        if (time > _audio_position) {
+                /* We can emit some audio from our buffers */
+                OutputAudioFrame const N = _film->time_to_audio_frames (time - _audio_position);
+               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, _audio_position);
+                _audio_position = piece->audio_position = time + _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, _audio_position);
+               _audio_position += _film->audio_frames_to_time (_audio_buffers.frames ());
+               _audio_buffers.set_frames (0);
+       }
+
+       while (_video_position < _audio_position) {
+               emit_black ();
+       }
+
+       while (_audio_position < _video_position) {
+               emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+       }
+       
+}
+
+/** @return true on error */
+void
+Player::seek (Time t)
+{
+       if (!_have_valid_pieces) {
+               setup_pieces ();
+               _have_valid_pieces = true;
+       }
+
+       if (_pieces.empty ()) {
+               return;
+       }
+
+       for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
+               shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
+               if (!vc) {
+                       continue;
+               }
+               
+               Time s = t - vc->start ();
+               s = max (static_cast<Time> (0), s);
+               s = min (vc->length(), s);
+
+               FrameRateConversion frc (vc->video_frame_rate(), _film->dcp_video_frame_rate());
+               VideoContent::Frame f = s * _film->dcp_video_frame_rate() / (frc.factor() * TIME_HZ);
+               dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (f);
+       }
+
+       /* XXX: don't seek audio because we don't need to... */
+}
+
+
+void
+Player::seek_back ()
+{
+
+}
+
+void
+Player::setup_pieces ()
+{
+       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<Piece> piece (new Piece (*i));
+
+                /* 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));
+                       
+                       fd->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3));
+                       fd->Audio.connect (bind (&Player::process_audio, this, piece, _1, _2));
+
+                       piece->decoder = fd;
+               }
+               
+               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, piece, _1, _2, _3));
+                       }
+
+                       piece->decoder = id;
+               }
+
+               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, piece, _1, _2));
+
+                       piece->decoder = sd;
+               }
+
+               _pieces.push_back (piece);
+       }
+
+#ifdef DEBUG_PLAYER
+       cout << "=== Player setup:\n";
+       for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
+               cout << *(i->get()) << "\n";
+       }
+#endif 
+}
+
+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;
+}
+
+void
+Player::set_video_container_size (libdcp::Size s)
+{
+       _video_container_size = s;
+       _black_frame.reset (new SimpleImage (PIX_FMT_RGB24, _video_container_size, true));
+       _black_frame->make_black ();
+}
+
+shared_ptr<Resampler>
+Player::resampler (shared_ptr<AudioContent> c)
+{
+       map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
+       if (i != _resamplers.end ()) {
+               return i->second;
+       }
+       
+       shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
+       _resamplers[c] = r;
+       return r;
+}
+
+void
+Player::emit_black ()
+{
+       /* XXX: use same here */
+       Video (_black_frame, false, _video_position);
+       _video_position += _film->video_frames_to_time (1);
+}
+
+void
+Player::emit_silence (OutputAudioFrame most)
+{
+       OutputAudioFrame N = min (most, _film->dcp_audio_frame_rate() / 2);
+       shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->dcp_audio_channels(), N));
+       silence->make_silent ();
+       Audio (silence, _audio_position);
+       _audio_position += _film->audio_frames_to_time (N);
+}
+
+       
+       
diff --git a/src/lib/player.h b/src/lib/player.h
new file mode 100644 (file)
index 0000000..bbdb14e
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_PLAYER_H
+#define DCPOMATIC_PLAYER_H
+
+#include <list>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include "playlist.h"
+#include "audio_buffers.h"
+#include "content.h"
+
+class Job;
+class Film;
+class Playlist;
+class AudioContent;
+class Piece;
+class Image;
+class Resampler;
+
+/** @class Player
+ *  @brief A class which can `play' a Playlist; emitting its audio and video.
+ */
+class Player : public boost::enable_shared_from_this<Player>
+{
+public:
+       Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
+
+       void disable_video ();
+       void disable_audio ();
+
+       bool pass ();
+       void seek (Time);
+       void seek_back ();
+
+       Time video_position () const {
+               return _video_position;
+       }
+
+       void set_video_container_size (libdcp::Size);
+
+       /** Emitted when a video frame is ready.
+        *  First parameter is the video image.
+        *  Second parameter is true if the image is the same as the last one that was emitted.
+        *  Third parameter is the time.
+        */
+       boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, Time)> Video;
+       
+       /** Emitted when some audio data is ready */
+       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
+
+private:
+
+       void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+       void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+       void setup_pieces ();
+       void playlist_changed ();
+       void content_changed (boost::weak_ptr<Content>, int);
+       void do_seek (Time, bool);
+       void flush ();
+       void emit_black ();
+       void emit_silence (OutputAudioFrame);
+       boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>);
+
+       boost::shared_ptr<const Film> _film;
+       boost::shared_ptr<const Playlist> _playlist;
+       
+       bool _video;
+       bool _audio;
+
+       /** Our pieces are ready to go; if this is false the pieces must be (re-)created before they are used */
+       bool _have_valid_pieces;
+       std::list<boost::shared_ptr<Piece> > _pieces;
+
+       /** The time after the last video that we emitted */
+       Time _video_position;
+       /** The time after the last audio that we emitted */
+       Time _audio_position;
+
+       AudioBuffers _audio_buffers;
+
+       libdcp::Size _video_container_size;
+       boost::shared_ptr<Image> _black_frame;
+       std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
+};
+
+#endif
diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc
new file mode 100644 (file)
index 0000000..995067b
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+#include "playlist.h"
+#include "sndfile_content.h"
+#include "sndfile_decoder.h"
+#include "video_content.h"
+#include "ffmpeg_decoder.h"
+#include "ffmpeg_content.h"
+#include "imagemagick_decoder.h"
+#include "imagemagick_content.h"
+#include "job.h"
+#include "config.h"
+#include "util.h"
+
+#include "i18n.h"
+
+using std::list;
+using std::cout;
+using std::vector;
+using std::min;
+using std::max;
+using std::string;
+using std::stringstream;
+using boost::optional;
+using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
+using boost::lexical_cast;
+
+Playlist::Playlist ()
+       : _loop (1)
+       , _sequence_video (true)
+       , _sequencing_video (false)
+{
+
+}
+
+Playlist::Playlist (shared_ptr<const Playlist> other)
+       : _loop (other->_loop)
+{
+       for (ContentList::const_iterator i = other->_content.begin(); i != other->_content.end(); ++i) {
+               _content.push_back ((*i)->clone ());
+       }
+}
+
+Playlist::~Playlist ()
+{
+       _content.clear ();
+       reconnect ();
+}
+
+void
+Playlist::content_changed (weak_ptr<Content> c, int p)
+{
+       if (p == ContentProperty::LENGTH && _sequence_video && !_sequencing_video) {
+               _sequencing_video = true;
+
+               ContentList cl = _content;
+               sort (cl.begin(), cl.end(), ContentSorter ());
+               Time last = 0;
+               for (ContentList::iterator i = cl.begin(); i != cl.end(); ++i) {
+                       if (!dynamic_pointer_cast<VideoContent> (*i)) {
+                               continue;
+                       }
+
+                       (*i)->set_start (last);
+                       last = (*i)->end ();
+               }
+
+               _sequencing_video = false;
+       }
+       
+       ContentChanged (c, p);
+}
+
+string
+Playlist::video_digest () const
+{
+       string t;
+       
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+               if (!dynamic_pointer_cast<const VideoContent> (*i)) {
+                       continue;
+               }
+               
+               t += (*i)->digest ();
+               shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
+               if (fc && fc->subtitle_stream()) {
+                       t += fc->subtitle_stream()->id;
+               }
+       }
+
+       t += lexical_cast<string> (_loop);
+
+       return md5_digest (t.c_str(), t.length());
+}
+
+/** @param node <Playlist> node */
+void
+Playlist::set_from_xml (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node)
+{
+       list<shared_ptr<cxml::Node> > c = node->node_children ("Content");
+       for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
+               string const type = (*i)->string_child ("Type");
+
+               boost::shared_ptr<Content> content;
+
+               if (type == "FFmpeg") {
+                       content.reset (new FFmpegContent (film, *i));
+               } else if (type == "ImageMagick") {
+                       content.reset (new ImageMagickContent (film, *i));
+               } else if (type == "Sndfile") {
+                       content.reset (new SndfileContent (film, *i));
+               }
+
+               _content.push_back (content);
+       }
+
+       reconnect ();
+       _loop = node->number_child<int> ("Loop");
+       _sequence_video = node->bool_child ("SequenceVideo");
+}
+
+/** @param node <Playlist> node */
+void
+Playlist::as_xml (xmlpp::Node* node)
+{
+       for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
+               (*i)->as_xml (node->add_child ("Content"));
+       }
+
+       node->add_child("Loop")->add_child_text(lexical_cast<string> (_loop));
+       node->add_child("SequenceVideo")->add_child_text(_sequence_video ? "1" : "0");
+}
+
+void
+Playlist::add (shared_ptr<Content> c)
+{
+       _content.push_back (c);
+       reconnect ();
+       Changed ();
+}
+
+void
+Playlist::remove (shared_ptr<Content> c)
+{
+       ContentList::iterator i = _content.begin ();
+       while (i != _content.end() && *i != c) {
+               ++i;
+       }
+       
+       if (i != _content.end ()) {
+               _content.erase (i);
+               Changed ();
+       }
+}
+
+void
+Playlist::set_loop (int l)
+{
+       _loop = l;
+       Changed ();
+}
+
+bool
+Playlist::has_subtitles () const
+{
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+               shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i);
+               if (fc && !fc->subtitle_streams().empty()) {
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+class FrameRateCandidate
+{
+public:
+       FrameRateCandidate (float source_, int dcp_)
+               : source (source_)
+               , dcp (dcp_)
+       {}
+
+       float source;
+       int dcp;
+};
+
+int
+Playlist::best_dcp_frame_rate () const
+{
+       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()) {
+
+               float this_error = 0;
+               for (ContentList::const_iterator j = _content.begin(); j != _content.end(); ++j) {
+                       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
+                       if (!vc) {
+                               continue;
+                       }
+
+                       this_error += fabs (i->source - vc->video_frame_rate ());
+               }
+
+               if (this_error < error) {
+                       error = this_error;
+                       best = *i;
+               }
+
+               ++i;
+       }
+
+       if (!best) {
+               return 24;
+       }
+       
+       return best->dcp;
+}
+
+Time
+Playlist::length () const
+{
+       Time len = 0;
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+               len = max (len, (*i)->end ());
+       }
+
+       return len;
+}
+
+void
+Playlist::reconnect ()
+{
+       for (list<boost::signals2::connection>::iterator i = _content_connections.begin(); i != _content_connections.end(); ++i) {
+               i->disconnect ();
+       }
+
+       _content_connections.clear ();
+               
+       for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
+               _content_connections.push_back ((*i)->Changed.connect (bind (&Playlist::content_changed, this, _1, _2)));
+       }
+}
+
+Time
+Playlist::video_end () const
+{
+       Time end = 0;
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+               if (dynamic_pointer_cast<const VideoContent> (*i)) {
+                       end = max (end, (*i)->end ());
+               }
+       }
+
+       return end;
+}
+
+void
+Playlist::set_sequence_video (bool s)
+{
+       _sequence_video = s;
+}
+
+bool
+ContentSorter::operator() (shared_ptr<Content> a, shared_ptr<Content> b)
+{
+       return a->start() < b->start();
+}
diff --git a/src/lib/playlist.h b/src/lib/playlist.h
new file mode 100644 (file)
index 0000000..2d243fe
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+    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_PLAYLIST_H
+#define DCPOMATIC_PLAYLIST_H
+
+#include <list>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include "ffmpeg_content.h"
+#include "audio_mapping.h"
+
+class Content;
+class FFmpegContent;
+class FFmpegDecoder;
+class ImageMagickContent;
+class ImageMagickDecoder;
+class SndfileContent;
+class SndfileDecoder;
+class Job;
+class Film;
+class Region;
+
+/** @class Playlist
+ *  @brief A set of content files (video and audio), with knowledge of how they should be arranged into
+ *  a DCP.
+ *
+ * This class holds Content objects, and it knows how they should be arranged.  At the moment
+ * the ordering is implicit; video content is placed sequentially, and audio content is taken
+ * from the video unless any sound-only files are present.  If sound-only files exist, they
+ * are played simultaneously (i.e. they can be split up into multiple files for different channels)
+ */
+
+struct ContentSorter
+{
+       bool operator() (boost::shared_ptr<Content> a, boost::shared_ptr<Content> b);
+};
+
+class Playlist
+{
+public:
+       Playlist ();
+       Playlist (boost::shared_ptr<const Playlist>);
+       ~Playlist ();
+
+       void as_xml (xmlpp::Node *);
+       void set_from_xml (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+       void add (boost::shared_ptr<Content>);
+       void remove (boost::shared_ptr<Content>);
+
+       bool has_subtitles () const;
+
+       typedef std::vector<boost::shared_ptr<Content> > ContentList;
+       
+       ContentList content () const {
+               return _content;
+       }
+
+       std::string video_digest () const;
+
+       int loop () const {
+               return _loop;
+       }
+       
+       void set_loop (int l);
+
+       Time length () const;
+       int best_dcp_frame_rate () const;
+       Time video_end () const;
+
+       void set_sequence_video (bool);
+
+       mutable boost::signals2::signal<void ()> Changed;
+       mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
+       
+private:
+       void content_changed (boost::weak_ptr<Content>, int);
+       void reconnect ();
+
+       ContentList _content;
+       int _loop;
+       bool _sequence_video;
+       bool _sequencing_video;
+       std::list<boost::signals2::connection> _content_connections;
+};
+
+#endif
index 39b6dfceb25d2ce070ef6c36ddfd40449ddcf799..7a07645b7088eb3371c6f965067c9f7e1edee301 100644 (file)
@@ -5,7 +5,7 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: LIBDVDOMATIC\n"
+"Project-Id-Version: LIBDCPOMATIC\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2013-06-04 23:59+0100\n"
 "PO-Revision-Date: 2013-04-02 19:10-0500\n"
@@ -254,10 +254,10 @@ msgstr "Horizontal deblocking filter A"
 #: src/lib/job.cc:97 src/lib/job.cc:106
 msgid ""
 "It is not known what caused this error.  The best idea is to report the "
-"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
+"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
 msgstr ""
 "Error desconocido. La mejor idea es informar del problema a la lista de "
-"correo de DVD-O-matic (dvdomatic@carlh.net)"
+"correo de DCP-o-matic (dcpomatic@carlh.net)"
 
 #: src/lib/filter.cc:82
 msgid "Kernel deinterlacer"
index 9dcbd42c2f97de7028b2c1989073f24684ca2476..f68631bbd97b21955516aebb0ec44f23bc0ef583 100644 (file)
@@ -5,7 +5,7 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: DVD-o-matic FRENCH\n"
+"Project-Id-Version: DCP-o-matic FRENCH\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2013-06-04 23:59+0100\n"
 "PO-Revision-Date: 2013-05-21 10:30+0100\n"
@@ -249,13 +249,10 @@ msgstr "Filtre dé-bloc horizontal"
 msgid "Horizontal deblocking filter A"
 msgstr "Filtre dé-bloc horizontal"
 
-#: src/lib/job.cc:97 src/lib/job.cc:106
-msgid ""
-"It is not known what caused this error.  The best idea is to report the "
-"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
-msgstr ""
-"Erreur indéterminée. Merci de rapporter le problème Ã  la liste DVD-o-matic "
-"(dvdomatic@carlh.net)"
+#: src/lib/job.cc:97
+#: src/lib/job.cc:106
+msgid "It is not known what caused this error.  The best idea is to report the problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
+msgstr "Erreur indéterminée. Merci de rapporter le problème Ã  la liste DCP-o-matic (dcpomatic@carlh.net)"
 
 #: src/lib/filter.cc:82
 msgid "Kernel deinterlacer"
index 9671c66a712634e59ef47bb676d5f2b1ea1e8b2d..0ddfa7721c2a63a725c38553d6c9f7f32b1e35d9 100644 (file)
@@ -251,10 +251,10 @@ msgstr "Filtro A sblocco orizzontale"
 #: src/lib/job.cc:97 src/lib/job.cc:106
 msgid ""
 "It is not known what caused this error.  The best idea is to report the "
-"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
+"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
 msgstr ""
 "Non sappiamo cosa ha causato questo errore. La cosa migliore Ã¨ inviare un "
-"report del problema alla mailing list di DVD-o-matic (dvdomatic@carlh.net)"
+"report del problema alla mailing list di DCP-o-matic (dcpomatic@carlh.net)"
 
 #: src/lib/filter.cc:82
 msgid "Kernel deinterlacer"
index 9391801dcf10457fc877372a186adce871b41968..c1c62ca41397f4fbe0b6f48b4de2f1272ab90009 100644 (file)
@@ -5,7 +5,7 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: DVD-o-matic\n"
+"Project-Id-Version: DCP-o-matic\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2013-06-04 23:59+0100\n"
 "PO-Revision-Date: 2013-04-10 15:35+0100\n"
@@ -252,10 +252,10 @@ msgstr "Filter för horisontal kantighetsutjämning A"
 #: src/lib/job.cc:97 src/lib/job.cc:106
 msgid ""
 "It is not known what caused this error.  The best idea is to report the "
-"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
+"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
 msgstr ""
 "Det Ã¤r inte känt vad som orsakade detta fel. Bästa sättet att rapportera "
-"problemet Ã¤r till DVD-o-matics mejl-lista (dvdomatic@carlh.net)"
+"problemet Ã¤r till DCP-o-matics mejl-lista (dcpomatic@carlh.net)"
 
 #: src/lib/filter.cc:82
 msgid "Kernel deinterlacer"
diff --git a/src/lib/processor.h b/src/lib/processor.h
deleted file mode 100644 (file)
index 603239f..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file  src/processor.h
- *  @brief Parent class for classes which accept and then emit video or audio data.
- */
-
-#ifndef DVDOMATIC_PROCESSOR_H
-#define DVDOMATIC_PROCESSOR_H
-
-#include "video_source.h"
-#include "video_sink.h"
-#include "audio_source.h"
-#include "audio_sink.h"
-
-class Log;
-
-/** @class Processor
- *  @brief Base class for processors.
- */
-class Processor
-{
-public:
-       /** Construct a Processor.
-        *  @param log Log to use.
-        */
-       Processor (boost::shared_ptr<Log> log)
-               : _log (log)
-       {}
-
-       virtual ~Processor() {}
-
-       /** Will be called at the end of a processing run */
-       virtual void process_end () {}
-
-protected:
-       boost::shared_ptr<Log> _log; ///< log to write to
-};
-
-/** @class AudioVideoProcessor
- *  @brief A processor which handles both video and audio data.
- */
-class AudioVideoProcessor : public Processor, public VideoSource, public VideoSink, public AudioSource, public AudioSink
-{
-public:
-       /** Construct an AudioVideoProcessor.
-        *  @param log Log to write to.
-        */
-       AudioVideoProcessor (boost::shared_ptr<Log> log)
-               : Processor (log)
-       {}
-};
-
-class TimedAudioVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink, public TimedAudioSource, public TimedAudioSink
-{
-public:
-       TimedAudioVideoProcessor (boost::shared_ptr<Log> log)
-               : Processor (log)
-       {}
-};
-                               
-
-/** @class AudioProcessor
- *  @brief A processor which handles just audio data.
- */
-class AudioProcessor : public Processor, public AudioSource, public AudioSink
-{
-public:
-       /** Construct an AudioProcessor.
-        *  @param log Log to write to.
-        */
-       AudioProcessor (boost::shared_ptr<Log> log)
-               : Processor (log)
-       {}
-};
-
-/** @class VideoProcessor
- *  @brief A processor which handles just video data.
- */
-class VideoProcessor : public Processor, public VideoSource, public VideoSink
-{
-public:
-       /** Construct an VideoProcessor.
-        *  @param log Log to write to.
-        */
-       VideoProcessor (boost::shared_ptr<Log> log)
-               : Processor (log)
-       {}
-};
-
-class TimedVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink
-{
-public:
-       TimedVideoProcessor (boost::shared_ptr<Log> log)
-               : Processor (log)
-       {}
-};     
-
-#endif
diff --git a/src/lib/ratio.cc b/src/lib/ratio.cc
new file mode 100644 (file)
index 0000000..5988b34
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libdcp/types.h>
+#include "ratio.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::vector;
+
+vector<Ratio const *> Ratio::_ratios;
+
+libdcp::Size
+Ratio::size (libdcp::Size full_frame) const
+{
+       if (_ratio < static_cast<float>(full_frame.width) / full_frame.height) {
+               return libdcp::Size (full_frame.height * _ratio, full_frame.height);
+       } else {
+               return libdcp::Size (full_frame.width, full_frame.width / _ratio);
+       }
+
+       return libdcp::Size ();
+}
+
+
+void
+Ratio::setup_ratios ()
+{
+       _ratios.push_back (new Ratio (float(1285) / 1080, "119", _("1.19"), "F"));
+       _ratios.push_back (new Ratio (float(1436) / 1080, "133", _("4:3"), "F"));
+       _ratios.push_back (new Ratio (float(1480) / 1080, "137", _("Academy"), "F"));
+       _ratios.push_back (new Ratio (float(1485) / 1080, "138", _("1.375"), "F"));
+       _ratios.push_back (new Ratio (float(1793) / 1080, "166", _("1.66"), "F"));
+       _ratios.push_back (new Ratio (float(1920) / 1080, "178", _("16:9"), "F"));
+       _ratios.push_back (new Ratio (float(1998) / 1080, "185", _("Flat"), "F"));
+       _ratios.push_back (new Ratio (float(2048) /  858, "239", _("Scope"), "S"));
+       _ratios.push_back (new Ratio (float(2048) / 1080, "full-frame", _("Full frame"), "C"));
+}
+
+Ratio const *
+Ratio::from_id (string i)
+{
+       vector<Ratio const *>::iterator j = _ratios.begin ();
+       while (j != _ratios.end() && (*j)->id() != i) {
+               ++j;
+       }
+
+       if (j == _ratios.end ()) {
+               return 0;
+       }
+
+       return *j;
+}
diff --git a/src/lib/ratio.h b/src/lib/ratio.h
new file mode 100644 (file)
index 0000000..5480eee
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_RATIO_H
+#define DCPOMATIC_RATIO_H
+
+#include <vector>
+#include <libdcp/util.h>
+
+class Ratio
+{
+public:
+       Ratio (float ratio, std::string id, std::string n, std::string d)
+               : _ratio (ratio)
+               , _id (id)
+               , _nickname (n)
+               , _dci_name (d)
+       {}
+
+       libdcp::Size size (libdcp::Size) const;
+
+       std::string id () const {
+               return _id;
+       }
+
+       std::string nickname () const {
+               return _nickname;
+       }
+
+       std::string dci_name () const {
+               return _dci_name;
+       }
+
+       float ratio () const {
+               return _ratio;
+       }
+
+       static void setup_ratios ();
+       static Ratio const * from_id (std::string i);
+       static std::vector<Ratio const *> all () {
+               return _ratios;
+       }
+
+private:
+       float _ratio;
+       /** id for use in metadata */
+       std::string _id;
+       /** nickname (e.g. Flat, Scope) */
+       std::string _nickname;
+       std::string _dci_name;
+
+       static std::vector<Ratio const *> _ratios;      
+};
+
+#endif
diff --git a/src/lib/resampler.cc b/src/lib/resampler.cc
new file mode 100644 (file)
index 0000000..1235b90
--- /dev/null
@@ -0,0 +1,61 @@
+extern "C" {
+#include "libavutil/channel_layout.h"
+}      
+#include "resampler.h"
+#include "audio_buffers.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using boost::shared_ptr;
+
+Resampler::Resampler (int in, int out, int channels)
+       : _in_rate (in)
+       , _out_rate (out)
+       , _channels (channels)
+{
+       /* We will be using planar float data when we call the
+          resampler.  As far as I can see, the audio channel
+          layout is not necessary for our purposes; it seems
+          only to be used get the number of channels and
+          decide if rematrixing is needed.  It won't be, since
+          input and output layouts are the same.
+       */
+
+       _swr_context = swr_alloc_set_opts (
+               0,
+               av_get_default_channel_layout (_channels),
+               AV_SAMPLE_FMT_FLTP,
+               _out_rate,
+               av_get_default_channel_layout (_channels),
+               AV_SAMPLE_FMT_FLTP,
+               _in_rate,
+               0, 0
+               );
+       
+       swr_init (_swr_context);
+}
+
+Resampler::~Resampler ()
+{
+       swr_free (&_swr_context);
+}
+
+shared_ptr<const AudioBuffers>
+Resampler::run (shared_ptr<const AudioBuffers> in)
+{
+       /* Compute the resampled frames count and add 32 for luck */
+       int const max_resampled_frames = ceil ((double) in->frames() * _out_rate / _in_rate) + 32;
+       shared_ptr<AudioBuffers> resampled (new AudioBuffers (_channels, max_resampled_frames));
+
+       int const resampled_frames = swr_convert (
+               _swr_context, (uint8_t **) resampled->data(), max_resampled_frames, (uint8_t const **) in->data(), in->frames()
+               );
+       
+       if (resampled_frames < 0) {
+               throw EncodeError (_("could not run sample-rate converter"));
+       }
+       
+       resampled->set_frames (resampled_frames);
+       return resampled;
+}      
diff --git a/src/lib/resampler.h b/src/lib/resampler.h
new file mode 100644 (file)
index 0000000..cda7189
--- /dev/null
@@ -0,0 +1,21 @@
+#include <boost/shared_ptr.hpp>
+extern "C" {
+#include <libswresample/swresample.h>
+}
+
+class AudioBuffers;
+
+class Resampler
+{
+public:
+       Resampler (int, int, int);
+       ~Resampler ();
+
+       boost::shared_ptr<const AudioBuffers> run (boost::shared_ptr<const AudioBuffers>);
+
+private:       
+       SwrContext* _swr_context;
+       int _in_rate;
+       int _out_rate;
+       int _channels;
+};
index c80f4b7dbf76a21679ca8033b8283f838d938ca6..a736e92de0a269b37821fa51b725845c4872bca3 100644 (file)
@@ -21,8 +21,8 @@
  *  @brief A class to describe one of FFmpeg's software scalers.
  */
 
-#ifndef DVDOMATIC_SCALER_H
-#define DVDOMATIC_SCALER_H
+#ifndef DCPOMATIC_SCALER_H
+#define DCPOMATIC_SCALER_H
 
 #include <string>
 #include <vector>
index a9fdfefda2f01671698c83279a624e5102ed894d..8cde44f0258a2d65ad8a4a44fa437b653f216c2d 100644 (file)
@@ -96,7 +96,7 @@ public:
 };
 
 
-SCPDCPJob::SCPDCPJob (shared_ptr<Film> f)
+SCPDCPJob::SCPDCPJob (shared_ptr<const Film> f)
        : Job (f)
        , _status (_("Waiting"))
 {
index 08d8e2c787ec9b26abaec751127e875dd5f627e4..bdc83af187f85a0091d31e107d554ee9dd07ef4c 100644 (file)
@@ -26,7 +26,7 @@
 class SCPDCPJob : public Job
 {
 public:
-       SCPDCPJob (boost::shared_ptr<Film>);
+       SCPDCPJob (boost::shared_ptr<const Film>);
 
        std::string name () const;
        void run ();
@@ -34,7 +34,7 @@ public:
 
 private:
        void set_status (std::string);
-       
+
        mutable boost::mutex _status_mutex;
        std::string _status;
 };
index 9c5a77f681b2903390fec271f5610c62cd2d8b16..5ca04c69249c2292b005432d0605ae021a72e546 100644 (file)
@@ -29,6 +29,7 @@
 #include <boost/algorithm/string.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/scoped_array.hpp>
+#include <libcxml/cxml.h>
 #include "server.h"
 #include "util.h"
 #include "scaler.h"
@@ -51,6 +52,19 @@ using boost::bind;
 using boost::scoped_array;
 using libdcp::Size;
 
+ServerDescription::ServerDescription (shared_ptr<const cxml::Node> node)
+{
+       _host_name = node->string_child ("HostName");
+       _threads = node->number_child<int> ("Threads");
+}
+
+void
+ServerDescription::as_xml (xmlpp::Node* root) const
+{
+       root->add_child("HostName")->add_child_text (_host_name);
+       root->add_child("Threads")->add_child_text (boost::lexical_cast<string> (_threads));
+}
+
 /** Create a server description from a string of metadata returned from as_metadata().
  *  @param v Metadata.
  *  @return ServerDescription, or 0.
@@ -68,15 +82,6 @@ ServerDescription::create_from_metadata (string v)
        return new ServerDescription (b[0], atoi (b[1].c_str ()));
 }
 
-/** @return Description of this server as text */
-string
-ServerDescription::as_metadata () const
-{
-       stringstream s;
-       s << _host_name << N_(" ") << _threads;
-       return s.str ();
-}
-
 Server::Server (shared_ptr<Log> log)
        : _log (log)
 {
@@ -93,45 +98,25 @@ Server::process (shared_ptr<Socket> socket)
        stringstream s (buffer.get());
        multimap<string, string> kv = read_key_value (s);
 
-       if (get_required_string (kv, N_("encode")) != N_("please")) {
+       if (get_required_string (kv, "encode") != "please") {
                return -1;
        }
 
-       libdcp::Size in_size (get_required_int (kv, N_("input_width")), get_required_int (kv, N_("input_height")));
-       int pixel_format_int = get_required_int (kv, N_("input_pixel_format"));
-       libdcp::Size out_size (get_required_int (kv, N_("output_width")), get_required_int (kv, N_("output_height")));
-       int padding = get_required_int (kv, N_("padding"));
-       int subtitle_offset = get_required_int (kv, N_("subtitle_offset"));
-       float subtitle_scale = get_required_float (kv, N_("subtitle_scale"));
-       string scaler_id = get_required_string (kv, N_("scaler"));
-       int frame = get_required_int (kv, N_("frame"));
-       int frames_per_second = get_required_int (kv, N_("frames_per_second"));
-       string post_process = get_optional_string (kv, N_("post_process"));
-       int colour_lut_index = get_required_int (kv, N_("colour_lut"));
-       int j2k_bandwidth = get_required_int (kv, N_("j2k_bandwidth"));
-       Position subtitle_position (get_optional_int (kv, N_("subtitle_x")), get_optional_int (kv, N_("subtitle_y")));
-       libdcp::Size subtitle_size (get_optional_int (kv, N_("subtitle_width")), get_optional_int (kv, N_("subtitle_height")));
+       libdcp::Size size (get_required_int (kv, "width"), get_required_int (kv, "height"));
+       int frame = get_required_int (kv, "frame");
+       int frames_per_second = get_required_int (kv, "frames_per_second");
+       int colour_lut_index = get_required_int (kv, "colour_lut");
+       int j2k_bandwidth = get_required_int (kv, "j2k_bandwidth");
 
        /* This checks that colour_lut_index is within range */
        colour_lut_index_to_name (colour_lut_index);
 
-       PixelFormat pixel_format = (PixelFormat) pixel_format_int;
-       Scaler const * scaler = Scaler::from_id (scaler_id);
-       
-       shared_ptr<Image> image (new SimpleImage (pixel_format, in_size, true));
+       shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, true));
 
        image->read_from_socket (socket);
 
-       shared_ptr<Subtitle> sub;
-       if (subtitle_size.width && subtitle_size.height) {
-               shared_ptr<Image> subtitle_image (new SimpleImage (PIX_FMT_RGBA, subtitle_size, true));
-               subtitle_image->read_from_socket (socket);
-               sub.reset (new Subtitle (subtitle_position, subtitle_image));
-       }
-
        DCPVideoFrame dcp_video_frame (
-               image, sub, out_size, padding, subtitle_offset, subtitle_scale,
-               scaler, frame, frames_per_second, post_process, colour_lut_index, j2k_bandwidth, _log
+               image, frame, frames_per_second, colour_lut_index, j2k_bandwidth, _log
                );
        
        shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally ();
index 89aeca62632c3aeda94bff27b43e7fe494d6efa6..398401a555dd1eff784ad1d2289677e5258660f2 100644 (file)
 #include <boost/thread.hpp>
 #include <boost/asio.hpp>
 #include <boost/thread/condition.hpp>
+#include <libxml++/libxml++.h>
 #include "log.h"
 
 class Socket;
 
+namespace cxml {
+       class Node;
+}
+
 /** @class ServerDescription
  *  @brief Class to describe a server to which we can send encoding work.
  */
@@ -44,6 +49,8 @@ public:
                , _threads (t)
        {}
 
+       ServerDescription (boost::shared_ptr<const cxml::Node>);
+       
        /** @return server's host name or IP address in string form */
        std::string host_name () const {
                return _host_name;
@@ -62,7 +69,7 @@ public:
                _threads = t;
        }
 
-       std::string as_metadata () const;
+       void as_xml (xmlpp::Node *) const;
        
        static ServerDescription * create_from_metadata (std::string v);
 
diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc
new file mode 100644 (file)
index 0000000..beee7cd
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "sndfile_content.h"
+#include "sndfile_decoder.h"
+#include "compose.hpp"
+#include "job.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::path p)
+       : Content (f, p)
+       , AudioContent (f, p)
+       , _audio_channels (0)
+       , _audio_length (0)
+       , _audio_frame_rate (0)
+{
+
+}
+
+SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+       : Content (f, node)
+       , AudioContent (f, node)
+{
+       _audio_channels = node->number_child<int> ("AudioChannels");
+       _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
+       _audio_frame_rate = node->number_child<int> ("AudioFrameRate");
+       _audio_mapping = AudioMapping (node->node_child ("AudioMapping"));
+}
+
+string
+SndfileContent::summary () const
+{
+       return String::compose (_("Sound file: %1"), file().filename().string());
+}
+
+string
+SndfileContent::information () const
+{
+       if (_audio_frame_rate == 0) {
+               return "";
+       }
+       
+       stringstream s;
+
+       s << String::compose (
+               _("%1 channels, %2kHz, %3 samples"),
+               audio_channels(),
+               content_audio_frame_rate() / 1000.0,
+               audio_length()
+               );
+       
+       return s.str ();
+}
+
+bool
+SndfileContent::valid_file (boost::filesystem::path f)
+{
+       /* XXX: more extensions */
+       string ext = f.extension().string();
+       transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
+       return (ext == ".wav" || ext == ".aif" || ext == ".aiff");
+}
+
+shared_ptr<Content>
+SndfileContent::clone () const
+{
+       return shared_ptr<Content> (new SndfileContent (*this));
+}
+
+void
+SndfileContent::examine (shared_ptr<Job> job)
+{
+       job->set_progress_unknown ();
+       Content::examine (job);
+
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+
+       SndfileDecoder dec (film, shared_from_this());
+
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _audio_channels = dec.audio_channels ();
+               _audio_length = dec.audio_length ();
+               _audio_frame_rate = dec.audio_frame_rate ();
+       }
+
+       signal_changed (AudioContentProperty::AUDIO_CHANNELS);
+       signal_changed (AudioContentProperty::AUDIO_LENGTH);
+       signal_changed (AudioContentProperty::AUDIO_FRAME_RATE);
+
+       /* XXX: do this in signal_changed...? */
+       _audio_mapping = AudioMapping (_audio_channels);
+       signal_changed (AudioContentProperty::AUDIO_MAPPING);
+}
+
+void
+SndfileContent::as_xml (xmlpp::Node* node) const
+{
+       node->add_child("Type")->add_child_text ("Sndfile");
+       Content::as_xml (node);
+       AudioContent::as_xml (node);
+       node->add_child("AudioChannels")->add_child_text (lexical_cast<string> (_audio_channels));
+       node->add_child("AudioLength")->add_child_text (lexical_cast<string> (_audio_length));
+       node->add_child("AudioFrameRate")->add_child_text (lexical_cast<string> (_audio_frame_rate));
+       _audio_mapping.as_xml (node->add_child("AudioMapping"));
+}
+
+Time
+SndfileContent::length () const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       return film->audio_frames_to_time (audio_length ());
+}
+
+int
+SndfileContent::output_audio_frame_rate () const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       return film->dcp_audio_frame_rate ();
+}
+
+void
+SndfileContent::set_audio_mapping (AudioMapping m)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _audio_mapping = m;
+       }
+
+       signal_changed (AudioContentProperty::AUDIO_MAPPING);
+}
diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h
new file mode 100644 (file)
index 0000000..876d660
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+    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.
+
+*/
+
+extern "C" {
+#include <libavutil/audioconvert.h>
+}
+#include "audio_content.h"
+
+namespace cxml {
+       class Node;
+}
+
+class SndfileContent : public AudioContent
+{
+public:
+       SndfileContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+       SndfileContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+       boost::shared_ptr<SndfileContent> shared_from_this () {
+               return boost::dynamic_pointer_cast<SndfileContent> (Content::shared_from_this ());
+       }
+       
+       void examine (boost::shared_ptr<Job>);
+       std::string summary () const;
+       std::string information () const;
+       void as_xml (xmlpp::Node *) const;
+       boost::shared_ptr<Content> clone () const;
+       Time length () const;
+
+        /* AudioContent */
+        int audio_channels () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _audio_channels;
+       }
+       
+        AudioContent::Frame audio_length () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _audio_length;
+       }
+       
+        int content_audio_frame_rate () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _audio_frame_rate;
+       }
+
+       int output_audio_frame_rate () const;
+
+       AudioMapping audio_mapping () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _audio_mapping;
+       }
+
+       void set_audio_mapping (AudioMapping);
+       
+       static bool valid_file (boost::filesystem::path);
+
+private:
+       int _audio_channels;
+       AudioContent::Frame _audio_length;
+       int _audio_frame_rate;
+       AudioMapping _audio_mapping;
+};
index 7e9e67d0fa2e24fd0ede23561ac47bbe682187e8..80a6afd2baf79eae384164a310119a127e21c5bf 100644 (file)
 
 #include <iostream>
 #include <sndfile.h>
+#include "sndfile_content.h"
 #include "sndfile_decoder.h"
 #include "film.h"
 #include "exceptions.h"
+#include "audio_buffers.h"
 
 #include "i18n.h"
 
 using std::vector;
 using std::string;
-using std::stringstream;
 using std::min;
 using std::cout;
 using boost::shared_ptr;
-using boost::optional;
 
-SndfileDecoder::SndfileDecoder (shared_ptr<Film> f, DecodeOptions o)
-       : Decoder (f, o)
-       , AudioDecoder (f, o)
-       , _done (0)
-       , _frames (0)
+SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c)
+       : Decoder (f)
+       , AudioDecoder (f)
+       , _sndfile_content (c)
+       , _deinterleave_buffer (0)
 {
-       _done = 0;
-       _frames = 0;
-       
-       vector<string> const files = _film->external_audio ();
-
-       int N = 0;
-       for (size_t i = 0; i < files.size(); ++i) {
-               if (!files[i].empty()) {
-                       N = i + 1;
-               }
-       }
-
-       if (N == 0) {
-               return;
+       _sndfile = sf_open (_sndfile_content->file().string().c_str(), SFM_READ, &_info);
+       if (!_sndfile) {
+               throw DecodeError (_("could not open audio file for reading"));
        }
 
-       bool first = true;
-       
-       for (size_t i = 0; i < (size_t) N; ++i) {
-               if (files[i].empty ()) {
-                       _sndfiles.push_back (0);
-               } else {
-                       SF_INFO info;
-                       SNDFILE* s = sf_open (files[i].c_str(), SFM_READ, &info);
-                       if (!s) {
-                               throw DecodeError (_("could not open external audio file for reading"));
-                       }
+       _done = 0;
+       _remaining = _info.frames;
+}
 
-                       if (info.channels != 1) {
-                               throw DecodeError (_("external audio files must be mono"));
-                       }
-                       
-                       _sndfiles.push_back (s);
-
-                       if (first) {
-                               shared_ptr<SndfileStream> st (
-                                       new SndfileStream (
-                                               info.samplerate, av_get_default_channel_layout (N)
-                                               )
-                                       );
-                               
-                               _audio_streams.push_back (st);
-                               _audio_stream = st;
-                               _frames = info.frames;
-                               first = false;
-                       } else {
-                               if (info.frames != _frames) {
-                                       throw DecodeError (_("external audio files have differing lengths"));
-                               }
-                       }
-               }
-       }
+SndfileDecoder::~SndfileDecoder ()
+{
+       sf_close (_sndfile);
+       delete[] _deinterleave_buffer;
 }
 
-bool
+void
 SndfileDecoder::pass ()
 {
-       if (_audio_streams.empty ()) {
-               return true;
-       }
-       
        /* Do things in half second blocks as I think there may be limits
           to what FFmpeg (and in particular the resampler) can cope with.
        */
-       sf_count_t const block = _audio_stream->sample_rate() / 2;
-       shared_ptr<AudioBuffers> audio (new AudioBuffers (_audio_stream->channels(), block));
-       sf_count_t const this_time = min (block, _frames - _done);
-       for (size_t i = 0; i < _sndfiles.size(); ++i) {
-               if (!_sndfiles[i]) {
-                       audio->make_silent (i);
-               } else {
-                       sf_read_float (_sndfiles[i], audio->data(i), this_time);
-               }
-       }
+       sf_count_t const block = _sndfile_content->content_audio_frame_rate() / 2;
+       sf_count_t const this_time = min (block, _remaining);
 
-       audio->set_frames (this_time);
-       Audio (audio, double(_done) / _audio_stream->sample_rate());
-       _done += this_time;
-
-       return (_done == _frames);
-}
-
-SndfileDecoder::~SndfileDecoder ()
-{
-       for (size_t i = 0; i < _sndfiles.size(); ++i) {
-               if (_sndfiles[i]) {
-                       sf_close (_sndfiles[i]);
+       int const channels = _sndfile_content->audio_channels ();
+       
+       shared_ptr<AudioBuffers> data (new AudioBuffers (channels, this_time));
+
+       if (_sndfile_content->audio_channels() == 1) {
+               /* No de-interleaving required */
+               sf_read_float (_sndfile, data->data(0), this_time);
+       } else {
+               /* Deinterleave */
+               if (!_deinterleave_buffer) {
+                       _deinterleave_buffer = new float[block * channels];
+               }
+               sf_readf_float (_sndfile, _deinterleave_buffer, this_time);
+               vector<float*> out_ptr (channels);
+               for (int i = 0; i < channels; ++i) {
+                       out_ptr[i] = data->data(i);
+               }
+               float* in_ptr = _deinterleave_buffer;
+               for (int i = 0; i < this_time; ++i) {
+                       for (int j = 0; j < channels; ++j) {
+                               *out_ptr[j]++ = *in_ptr++;
+                       }
                }
        }
+               
+       data->set_frames (this_time);
+       audio (data, double(_done) / audio_frame_rate());
+       _done += this_time;
+       _remaining -= this_time;
 }
 
-shared_ptr<SndfileStream>
-SndfileStream::create ()
-{
-       return shared_ptr<SndfileStream> (new SndfileStream);
-}
-
-shared_ptr<SndfileStream>
-SndfileStream::create (string t, optional<int> v)
+int
+SndfileDecoder::audio_channels () const
 {
-       if (!v) {
-               /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
-               return shared_ptr<SndfileStream> ();
-       }
-
-       stringstream s (t);
-       string type;
-       s >> type;
-       if (type != N_("external")) {
-               return shared_ptr<SndfileStream> ();
-       }
-
-       return shared_ptr<SndfileStream> (new SndfileStream (t, v));
+       return _info.channels;
 }
 
-SndfileStream::SndfileStream (string t, optional<int> v)
+AudioContent::Frame
+SndfileDecoder::audio_length () const
 {
-       assert (v);
-
-       stringstream s (t);
-       string type;
-       s >> type >> _sample_rate >> _channel_layout;
+       return _info.frames;
 }
 
-SndfileStream::SndfileStream ()
+int
+SndfileDecoder::audio_frame_rate () const
 {
-
+       return _info.samplerate;
 }
 
-string
-SndfileStream::to_string () const
+bool
+SndfileDecoder::done () const
 {
-       return String::compose (N_("external %1 %2"), _sample_rate, _channel_layout);
+       return _audio_position >= _sndfile_content->audio_length ();
 }
index 9489cb5ec5b56e4a6b076ff0da1360e643a19734..77fa6d17734da4096757dae504a759c9925e0e8b 100644 (file)
 #include <sndfile.h>
 #include "decoder.h"
 #include "audio_decoder.h"
-#include "stream.h"
 
-class SndfileStream : public AudioStream
-{
-public:
-       SndfileStream (int sample_rate, int64_t layout)
-               : AudioStream (sample_rate, layout)
-       {}
-                              
-       std::string to_string () const;
-
-       static boost::shared_ptr<SndfileStream> create ();
-       static boost::shared_ptr<SndfileStream> create (std::string t, boost::optional<int> v);
-
-private:
-       friend class stream_test;
-       
-       SndfileStream ();
-       SndfileStream (std::string t, boost::optional<int> v);
-};
+class SndfileContent;
 
 class SndfileDecoder : public AudioDecoder
 {
 public:
-       SndfileDecoder (boost::shared_ptr<Film>, DecodeOptions);
+       SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
        ~SndfileDecoder ();
 
-       bool pass ();
+       void pass ();
+       bool done () const;
+
+       int audio_channels () const;
+       AudioContent::Frame audio_length () const;
+       int audio_frame_rate () const;
 
 private:
-       std::vector<SNDFILE*> _sndfiles;
-       sf_count_t _done;
-       sf_count_t _frames;
+       boost::shared_ptr<const SndfileContent> _sndfile_content;
+       SNDFILE* _sndfile;
+       SF_INFO _info;
+       AudioContent::Frame _done;
+       AudioContent::Frame _remaining;
+       float* _deinterleave_buffer;
 };
index 2edf388409755294153daf07d74e1002cebbc62c..bdbe72ba2798b689a7f2aec5b3205f0c788cba2b 100644 (file)
@@ -21,8 +21,8 @@
  *  @brief A class to describe a sound processor.
  */
 
-#ifndef DVDOMATIC_SOUND_PROCESSOR_H
-#define DVDOMATIC_SOUND_PROCESSOR_H
+#ifndef DCPOMATIC_SOUND_PROCESSOR_H
+#define DCPOMATIC_SOUND_PROCESSOR_H
 
 #include <string>
 #include <vector>
index 20a5c5be74011d0898282907b12d53c01fa58bb6..a8183d3444c7c426f47fcbcc3d60b70548039f47 100644 (file)
@@ -1,5 +1,3 @@
-/** -*- c-basic-offset: 4; default-tab-width: 4; indent-tabs-mode: nil; -*- */
-
 // Copyright 2007 Edd Dawson.
 // Distributed under the Boost Software License, Version 1.0.
 // (See accompanying file LICENSE_1_0.txt or copy at
index 2b622d0208b080630e1077f00ba82b73023a5eb1..73a13bf85064b3842e68c637a22f6b2d05d40af1 100644 (file)
@@ -1,5 +1,3 @@
-/** -*- c-basic-offset: 4; default-tab-width: 4; indent-tabs-mode: nil; -*- */
-
 // Copyright 2007 Edd Dawson.
 // Distributed under the Boost Software License, Version 1.0.
 // (See accompanying file LICENSE_1_0.txt or copy at
diff --git a/src/lib/stream.cc b/src/lib/stream.cc
deleted file mode 100644 (file)
index bfe7b5e..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <sstream>
-#include "compose.hpp"
-#include "stream.h"
-#include "ffmpeg_decoder.h"
-#include "sndfile_decoder.h"
-
-#include "i18n.h"
-
-using std::string;
-using std::stringstream;
-using boost::shared_ptr;
-using boost::optional;
-
-/** Construct a SubtitleStream from a value returned from to_string().
- *  @param t String returned from to_string().
- *  @param v State file version.
- */
-SubtitleStream::SubtitleStream (string t, boost::optional<int>)
-{
-       stringstream n (t);
-       n >> _id;
-
-       size_t const s = t.find (' ');
-       if (s != string::npos) {
-               _name = t.substr (s + 1);
-       }
-}
-
-/** @return A canonical string representation of this stream */
-string
-SubtitleStream::to_string () const
-{
-       return String::compose (N_("%1 %2"), _id, _name);
-}
-
-/** Create a SubtitleStream from a value returned from to_string().
- *  @param t String returned from to_string().
- *  @param v State file version.
- */
-shared_ptr<SubtitleStream>
-SubtitleStream::create (string t, optional<int> v)
-{
-       return shared_ptr<SubtitleStream> (new SubtitleStream (t, v));
-}
-
-/** Create an AudioStream from a string returned from to_string().
- *  @param t String returned from to_string().
- *  @param v State file version.
- *  @return AudioStream, or 0.
- */
-shared_ptr<AudioStream>
-audio_stream_factory (string t, optional<int> v)
-{
-       shared_ptr<AudioStream> s;
-
-       s = FFmpegAudioStream::create (t, v);
-       if (!s) {
-               s = SndfileStream::create (t, v);
-       }
-
-       return s;
-}
-
-/** Create a SubtitleStream from a string returned from to_string().
- *  @param t String returned from to_string().
- *  @param v State file version.
- *  @return SubtitleStream, or 0.
- */
-shared_ptr<SubtitleStream>
-subtitle_stream_factory (string t, optional<int> v)
-{
-       return SubtitleStream::create (t, v);
-}
diff --git a/src/lib/stream.h b/src/lib/stream.h
deleted file mode 100644 (file)
index 16b06e4..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/lib/stream.h
- *  @brief Representations of audio and subtitle streams.
- *
- *  Some content may have multiple `streams' of audio and/or subtitles; perhaps
- *  for multiple languages, or for stereo / surround mixes.  These classes represent
- *  those streams, and know about their details.
- */
-
-#ifndef DVDOMATIC_STREAM_H
-#define DVDOMATIC_STREAM_H
-
-#include <stdint.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/optional.hpp>
-extern "C" {
-#include <libavutil/audioconvert.h>
-}
-
-/** @class Stream
- *  @brief Parent class for streams.
- */
-class Stream
-{
-public:
-       virtual ~Stream () {}
-       virtual std::string to_string () const = 0;
-};
-
-/** @class AudioStream
- *  @brief A stream of audio data.
- */
-struct AudioStream : public Stream
-{
-public:
-       AudioStream (int r, int64_t l)
-               : _sample_rate (r)
-               , _channel_layout (l)
-       {}
-
-       /* Only used for backwards compatibility for state file version < 1 */
-       void set_sample_rate (int s) {
-               _sample_rate = s;
-       }
-
-       int channels () const {
-               return av_get_channel_layout_nb_channels (_channel_layout);
-       }
-
-       int sample_rate () const {
-               return _sample_rate;
-       }
-
-       int64_t channel_layout () const {
-               return _channel_layout;
-       }
-
-protected:
-       AudioStream ()
-               : _sample_rate (0)
-               , _channel_layout (0)
-       {}
-
-       int _sample_rate;
-       int64_t _channel_layout;
-};
-
-/** @class SubtitleStream
- *  @brief A stream of subtitle data.
- */
-class SubtitleStream : public Stream
-{
-public:
-       SubtitleStream (std::string n, int i)
-               : _name (n)
-               , _id (i)
-       {}
-
-       std::string to_string () const;
-
-       std::string name () const {
-               return _name;
-       }
-
-       int id () const {
-               return _id;
-       }
-
-       static boost::shared_ptr<SubtitleStream> create (std::string t, boost::optional<int> v);
-
-private:
-       friend class stream_test;
-       
-       SubtitleStream (std::string t, boost::optional<int> v);
-       
-       std::string _name;
-       int _id;
-};
-
-boost::shared_ptr<AudioStream> audio_stream_factory (std::string t, boost::optional<int> version);
-boost::shared_ptr<SubtitleStream> subtitle_stream_factory (std::string t, boost::optional<int> version);
-
-#endif
index 5c2a0d0b5f31d65efacf76f4194a812c104bff3f..7013f1d7d9004a635ac0732a8f77c638f92f6550 100644 (file)
@@ -27,8 +27,7 @@
 
 #include "i18n.h"
 
-using namespace std;
-using namespace boost;
+using boost::shared_ptr;
 using libdcp::Size;
 
 /** Construct a TimedSubtitle.  This is a subtitle image, position,
@@ -45,8 +44,8 @@ TimedSubtitle::TimedSubtitle (AVSubtitle const & sub)
        double const packet_time = static_cast<double> (sub.pts) / AV_TIME_BASE;
        
        /* hence start time for this sub */
-       _from = packet_time + (double (sub.start_display_time) / 1e3);
-       _to = packet_time + (double (sub.end_display_time) / 1e3);
+       _from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
+       _to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
 
        if (sub.num_rects > 1) {
                throw DecodeError (_("multi-part subtitles not yet supported"));
@@ -80,9 +79,9 @@ TimedSubtitle::TimedSubtitle (AVSubtitle const & sub)
        _subtitle.reset (new Subtitle (Position (rect->x, rect->y), image));
 }      
 
-/** @param t Time in seconds from the start of the source */
+/** @param t Time from the start of the source */
 bool
-TimedSubtitle::displayed_at (double t) const
+TimedSubtitle::displayed_at (Time t) const
 {
        return t >= _from && t <= _to;
 }
@@ -108,13 +107,13 @@ Subtitle::Subtitle (Position p, shared_ptr<Image> i)
  *  in the coordinate space of the source.
  *  @param subtitle_scale scaling factor to apply to the subtitle image.
  */
-dvdomatic::Rect
+dcpomatic::Rect
 subtitle_transformed_area (
        float target_x_scale, float target_y_scale,
-       dvdomatic::Rect sub_area, int subtitle_offset, float subtitle_scale
+       dcpomatic::Rect sub_area, int subtitle_offset, float subtitle_scale
        )
 {
-       dvdomatic::Rect tx;
+       dcpomatic::Rect tx;
 
        sub_area.y += subtitle_offset;
 
@@ -143,8 +142,8 @@ subtitle_transformed_area (
 }
 
 /** @return area that this subtitle takes up, in the original uncropped source's coordinate space */
-dvdomatic::Rect
+dcpomatic::Rect
 Subtitle::area () const
 {
-       return dvdomatic::Rect (_position.x, _position.y, _image->size().width, _image->size().height);
+       return dcpomatic::Rect (_position.x, _position.y, _image->size().width, _image->size().height);
 }
index e3a853695f0fb313f2f20f2f5e6775e359eada92..1020397cced8e44a4967615bc8f49fd932a50898 100644 (file)
@@ -23,7 +23,7 @@
 
 #include <list>
 #include <boost/shared_ptr.hpp>
-#include "util.h"
+#include "types.h"
 
 struct AVSubtitle;
 class Image;
@@ -46,17 +46,17 @@ public:
                return _image;
        }
 
-       dvdomatic::Rect area () const;
+       dcpomatic::Rect area () const;
        
 private:
        Position _position;
        boost::shared_ptr<Image> _image;
 };
 
-dvdomatic::Rect
+dcpomatic::Rect
 subtitle_transformed_area (
        float target_x_scale, float target_y_scale,
-       dvdomatic::Rect sub_area, int subtitle_offset, float subtitle_scale
+       dcpomatic::Rect sub_area, int subtitle_offset, float subtitle_scale
        );
 
 /** A Subtitle class with details of the time over which it should be shown */
@@ -65,7 +65,7 @@ class TimedSubtitle
 public:
        TimedSubtitle (AVSubtitle const &);
 
-       bool displayed_at (double t) const;
+       bool displayed_at (Time) const;
        
        boost::shared_ptr<Subtitle> subtitle () const {
                return _subtitle;
@@ -74,8 +74,8 @@ public:
 private:
        /** the subtitle */
        boost::shared_ptr<Subtitle> _subtitle;
-       /** display from time in seconds from the start of the film */
-       double _from;
-       /** display to time in seconds from the start of the film */
-       double _to;
+       /** display from time from the start of the content */
+       Time _from;
+       /** display to time from the start of the content */
+       Time _to;
 };
index f509a7492888178d6192d0c4af8ec2e38d30d7d4..173d0d961f44daed81d3145d4c5a24d3edfe8c75 100644 (file)
@@ -22,8 +22,8 @@
  *  @brief Some timing classes for debugging and profiling.
  */
 
-#ifndef DVDOMATIC_TIMER_H
-#define DVDOMATIC_TIMER_H
+#ifndef DCPOMATIC_TIMER_H
+#define DCPOMATIC_TIMER_H
 
 #include <string>
 #include <map>
index 234ebe051f0b44ae678b43cd8e20cd1def6af5dc..6d5edd7c060c662dc404445c1e3ed1a1175d4ace 100644 (file)
 #include <iomanip>
 #include "transcode_job.h"
 #include "film.h"
-#include "format.h"
 #include "transcoder.h"
 #include "log.h"
-#include "encoder.h"
 
 #include "i18n.h"
 
@@ -39,11 +37,9 @@ using std::setprecision;
 using boost::shared_ptr;
 
 /** @param s Film to use.
- *  @param o Decode options.
  */
-TranscodeJob::TranscodeJob (shared_ptr<Film> f, DecodeOptions o)
+TranscodeJob::TranscodeJob (shared_ptr<const Film> f)
        : Job (f)
-       , _decode_opt (o)
 {
        
 }
@@ -60,11 +56,9 @@ TranscodeJob::run ()
        try {
 
                _film->log()->log (N_("Transcode job starting"));
-               _film->log()->log (String::compose (N_("Audio delay is %1ms"), _film->audio_delay()));
 
-               _encoder.reset (new Encoder (_film));
-               Transcoder w (_film, _decode_opt, this, _encoder);
-               w.go ();
+               _transcoder.reset (new Transcoder (_film, shared_from_this ()));
+               _transcoder->go ();
                set_progress (1);
                set_state (FINISHED_OK);
 
@@ -83,11 +77,11 @@ TranscodeJob::run ()
 string
 TranscodeJob::status () const
 {
-       if (!_encoder) {
+       if (!_transcoder) {
                return _("0%");
        }
 
-       float const fps = _encoder->current_frames_per_second ();
+       float const fps = _transcoder->current_encoding_rate ();
        if (fps == 0) {
                return Job::status ();
        }
@@ -106,24 +100,17 @@ TranscodeJob::status () const
 int
 TranscodeJob::remaining_time () const
 {
-       float fps = _encoder->current_frames_per_second ();
-       if (fps == 0) {
+       if (!_transcoder) {
                return 0;
        }
+       
+       float fps = _transcoder->current_encoding_rate ();
 
-       if (!_film->length()) {
+       if (fps == 0) {
                return 0;
        }
 
        /* Compute approximate proposed length here, as it's only here that we need it */
-       int length = _film->length().get();
-       FrameRateConversion const frc (_film->source_frame_rate(), _film->dcp_frame_rate());
-       if (frc.skip) {
-               length /= 2;
-       }
-       /* If we are repeating it shouldn't affect transcode time, so don't take it into account */
-
-       /* We assume that dcp_length() is valid, if it is set */
-       int const left = length - _encoder->video_frames_out();
+       OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - _transcoder->video_frames_out();
        return left / fps;
 }
index 9b69e4e6563ac39f7260f6821afd2cad881c181b..9128206d29dc9c9e42460cafecfa4748f0a1c0db 100644 (file)
@@ -23,9 +23,8 @@
 
 #include <boost/shared_ptr.hpp>
 #include "job.h"
-#include "options.h"
 
-class Encoder;
+class Transcoder;
 
 /** @class TranscodeJob
  *  @brief A job which transcodes from one format to another.
@@ -33,16 +32,14 @@ class Encoder;
 class TranscodeJob : public Job
 {
 public:
-       TranscodeJob (boost::shared_ptr<Film> f, DecodeOptions od);
+       TranscodeJob (boost::shared_ptr<const Film> f);
        
        std::string name () const;
        void run ();
        std::string status () const;
 
-protected:
+private:
        int remaining_time () const;
 
-private:
-       DecodeOptions _decode_opt;
-       boost::shared_ptr<Encoder> _encoder;
+       boost::shared_ptr<Transcoder> _transcoder;
 };
index a202d440c0c736cd63d0c24521a2e1ab28984f73..f4a52639a3cc5a887f3ae7c79991171e41527ac4 100644 (file)
 #include <boost/signals2.hpp>
 #include "transcoder.h"
 #include "encoder.h"
-#include "decoder_factory.h"
 #include "film.h"
-#include "matcher.h"
-#include "delay_line.h"
-#include "options.h"
-#include "gain.h"
 #include "video_decoder.h"
 #include "audio_decoder.h"
-#include "trimmer.h"
+#include "player.h"
+#include "job.h"
 
 using std::string;
 using boost::shared_ptr;
+using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
 
+static void
+video_proxy (weak_ptr<Encoder> encoder, shared_ptr<const Image> image, bool same)
+{
+       shared_ptr<Encoder> e = encoder.lock ();
+       if (e) {
+               e->process_video (image, same);
+       }
+}
+
+static void
+audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio)
+{
+       shared_ptr<Encoder> e = encoder.lock ();
+       if (e) {
+               e->process_audio (audio);
+       }
+}
+
 /** Construct a transcoder using a Decoder that we create and a supplied Encoder.
  *  @param f Film that we are transcoding.
- *  @param o Decode options.
  *  @param j Job that we are running under, or 0.
  *  @param e Encoder to use.
  */
-Transcoder::Transcoder (shared_ptr<Film> f, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
+Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j)
        : _job (j)
-       , _encoder (e)
-       , _decoders (decoder_factory (f, o))
+       , _player (f->player ())
+       , _encoder (new Encoder (f, j))
 {
-       assert (_encoder);
-
-       shared_ptr<AudioStream> st = f->audio_stream();
-       if (st && st->sample_rate ()) {
-               _matcher.reset (new Matcher (f->log(), st->sample_rate(), f->source_frame_rate()));
-       }
-       _delay_line.reset (new DelayLine (f->log(), f->audio_delay() / 1000.0f));
-       _gain.reset (new Gain (f->log(), f->audio_gain()));
-
-       int const sr = st ? st->sample_rate() : 0;
-       int const trim_start = f->trim_type() == Film::ENCODE ? f->trim_start() : 0;
-       int const trim_end = f->trim_type() == Film::ENCODE ? f->trim_end() : 0;
-       _trimmer.reset (new Trimmer (
-                               f->log(), trim_start, trim_end, f->length().get_value_or(0),
-                               sr, f->source_frame_rate(), f->dcp_frame_rate()
-                               ));
-
-       /* Set up the decoder to use the film's set streams */
-       _decoders.video->set_subtitle_stream (f->subtitle_stream ());
-       if (f->audio_stream ()) {
-               _decoders.audio->set_audio_stream (f->audio_stream ());
-       }
-
-       _decoders.video->connect_video (_delay_line);
-       if (_matcher) {
-               _delay_line->connect_video (_matcher);
-               _matcher->connect_video (_trimmer);
-       } else {
-               _delay_line->connect_video (_trimmer);
-       }
-       _trimmer->connect_video (_encoder);
-       
-       _decoders.audio->connect_audio (_delay_line);
-       if (_matcher) {
-               _delay_line->connect_audio (_matcher);
-               _matcher->connect_audio (_gain);
-       } else {
-               _delay_line->connect_audio (_gain);
-       }
-       _gain->connect_audio (_trimmer);
-       _trimmer->connect_audio (_encoder);
+       _player->Video.connect (bind (video_proxy, _encoder, _1, _2));
+       _player->Audio.connect (bind (audio_proxy, _encoder, _1));
 }
 
-/** Run the decoder, passing its output to the encoder, until the decoder
- *  has no more data to present.
- */
 void
 Transcoder::go ()
 {
        _encoder->process_begin ();
-
-       bool done[2] = { false, false };
-       
-       while (1) {
-               if (!done[0]) {
-                       done[0] = _decoders.video->pass ();
-                       if (_job) {
-                               _decoders.video->set_progress (_job);
-                       }
-               }
-               
-               if (!done[1] && _decoders.audio && dynamic_pointer_cast<Decoder> (_decoders.audio) != dynamic_pointer_cast<Decoder> (_decoders.video)) {
-                       done[1] = _decoders.audio->pass ();
-               } else {
-                       done[1] = true;
-               }
-               
-               if (done[0] && done[1]) {
-                       break;
-               }
-       }
-       
-       _delay_line->process_end ();
-       if (_matcher) {
-               _matcher->process_end ();
-       }
-       _gain->process_end ();
+       while (!_player->pass ()) {}
        _encoder->process_end ();
 }
+
+float
+Transcoder::current_encoding_rate () const
+{
+       return _encoder->current_encoding_rate ();
+}
+
+int
+Transcoder::video_frames_out () const
+{
+       return _encoder->video_frames_out ();
+}
index f5b8ae6e329d3892735bf5d5a4d36f1615c44fc5..b3c8f888b1106dfd4ffb6753ae247672ded2fff4 100644 (file)
 
 */
 
+#include "types.h"
+
 /** @file  src/transcoder.h
- *  @brief A class which takes a Film and some Options, then uses those to transcode the film.
  *
  *  A decoder is selected according to the content type, and the encoder can be specified
  *  as a parameter to the constructor.
  */
 
-#include "decoder_factory.h"
-
 class Film;
 class Job;
 class Encoder;
-class Matcher;
 class VideoFilter;
-class Gain;
-class VideoDecoder;
-class AudioDecoder;
-class DelayLine;
-class Trimmer;
+class Player;
 
 /** @class Transcoder
- *  @brief A class which takes a Film and some Options, then uses those to transcode the film.
  *
  *  A decoder is selected according to the content type, and the encoder can be specified
  *  as a parameter to the constructor.
@@ -47,27 +40,18 @@ class Transcoder
 {
 public:
        Transcoder (
-               boost::shared_ptr<Film> f,
-               DecodeOptions o,
-               Job* j,
-               boost::shared_ptr<Encoder> e
+               boost::shared_ptr<const Film> f,
+               boost::shared_ptr<Job> j
                );
 
        void go ();
 
-       boost::shared_ptr<VideoDecoder> video_decoder () const {
-               return _decoders.video;
-       }
+       float current_encoding_rate () const;
+       int video_frames_out () const;
 
-protected:
+private:
        /** A Job that is running this Transcoder, or 0 */
-       Job* _job;
-       /** The encoder that we will use */
+       boost::shared_ptr<Job> _job;
+       boost::shared_ptr<Player> _player;
        boost::shared_ptr<Encoder> _encoder;
-       /** The decoders that we will use */
-       Decoders _decoders;
-       boost::shared_ptr<Matcher> _matcher;
-       boost::shared_ptr<DelayLine> _delay_line;
-       boost::shared_ptr<Gain> _gain;
-       boost::shared_ptr<Trimmer> _trimmer;
 };
diff --git a/src/lib/trimmer.h b/src/lib/trimmer.h
deleted file mode 100644 (file)
index 45b3f14..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
-    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 "processor.h"
-
-class Trimmer : public AudioVideoProcessor
-{
-public:
-       Trimmer (boost::shared_ptr<Log>, int, int, int, int, float, int);
-
-       void process_video (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s);
-       void process_audio (boost::shared_ptr<const AudioBuffers>);
-
-private:
-       friend class trimmer_test;
-
-       int _video_start;
-       int _video_end;
-       int _video_in;
-       int64_t _audio_start;
-       int64_t _audio_end;
-       int64_t _audio_in;
-};
diff --git a/src/lib/types.cc b/src/lib/types.cc
new file mode 100644 (file)
index 0000000..78cb4cd
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+    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 "types.h"
+
+using std::max;
+using std::min;
+
+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 other A Rect.
+ *  @return The intersection of this with `other'.
+ */
+dcpomatic::Rect
+dcpomatic::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
+               );
+}
+
+bool
+dcpomatic::Rect::contains (Position p) const
+{
+       return (p.x >= x && p.x <= (x + width) && p.y >= y && p.y <= (y + height));
+}
diff --git a/src/lib/types.h b/src/lib/types.h
new file mode 100644 (file)
index 0000000..33f8239
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+    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_TYPES_H
+#define DCPOMATIC_TYPES_H
+
+#include <vector>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+#include <libdcp/util.h>
+
+class Content;
+
+typedef int64_t Time;
+#define TIME_MAX INT64_MAX
+#define TIME_HZ  ((Time) 96000)
+typedef int64_t OutputAudioFrame;
+typedef int     OutputVideoFrame;
+
+/** @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 dcpomatic {
+
+/** @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;
+
+       bool contains (Position) const;
+};
+
+}
+
+#endif
index 0d19660bfd8847b2d6c999b1249ca5e69d0800dc..73db8bff85d1b67b78c5e5c4704322ad1acc5528 100644 (file)
@@ -17,8 +17,8 @@
 
 */
 
-#ifndef DVDOMATIC_UI_SIGNALLER_H
-#define DVDOMATIC_UI_SIGNALLER_H
+#ifndef DCPOMATIC_UI_SIGNALLER_H
+#define DCPOMATIC_UI_SIGNALLER_H
 
 #include <boost/bind.hpp>
 #include <boost/asio.hpp>
index 83980f82829ac3b7c5e00001dbfd4cd863f877af..d425fc8fefe009c3f0ee44ed94eab18c1d969285 100644 (file)
@@ -27,7 +27,7 @@
 #include <iostream>
 #include <fstream>
 #include <climits>
-#ifdef DVDOMATIC_POSIX
+#ifdef DCPOMATIC_POSIX
 #include <execinfo.h>
 #include <cxxabi.h>
 #endif
@@ -56,32 +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"
-#include "film.h"
-#ifdef DVDOMATIC_WINDOWS
+#include "ratio.h"
+#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;
@@ -114,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")
  */
@@ -150,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.
  */
@@ -249,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;
@@ -263,9 +274,9 @@ LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *)
  *  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);
@@ -273,7 +284,7 @@ dvdomatic_setup ()
        
        avfilter_register_all ();
        
-       Format::setup_formats ();
+       Ratio::setup_ratios ();
        DCPContentType::setup_dcp_content_types ();
        Scaler::setup_scalers ();
        Filter::setup_filters ();
@@ -282,7 +293,7 @@ dvdomatic_setup ()
        ui_thread = boost::this_thread::get_id ();
 }
 
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
 boost::filesystem::path
 mo_path ()
 {
@@ -297,9 +308,9 @@ 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
 
@@ -315,15 +326,15 @@ dvdomatic_setup_gettext_i18n (string lang)
        }
 
        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
 }
 
@@ -384,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);
@@ -445,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;
@@ -513,16 +469,6 @@ dcp_audio_sample_rate (int fs)
        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.
  */
@@ -635,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.
@@ -767,151 +697,6 @@ get_optional_int (multimap<string, string> const & kv, string k)
        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 ()
@@ -919,30 +704,17 @@ 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 (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
 {
        return ((int64_t) v * audio_sample_rate / frames_per_second);
 }
 
-/** @param f Filename.
- *  @return true if this file is a still image, false if it is something else.
- */
-bool
-still_image_file (string f)
-{
-       string ext = boost::filesystem::path(f).extension().string();
-
-       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)
 {
@@ -963,71 +735,6 @@ audio_channel_name (int c)
        return channels[c];
 }
 
-AudioMapping::AudioMapping (shared_ptr<const Film> f)
-       : _source_channels (f->audio_stream() ? f->audio_stream()->channels() : 0)
-       , _minimum_channels (f->minimum_audio_channels ())
-{
-
-}
-
-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);
-}
-
-/** @return minimum number of DCP channels that we can allow in this
-    DCP, given the nature of the source.
-*/
-int
-AudioMapping::minimum_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;
-}
-
-/** @return number of channels that there should be in the DCP, including
- *  any silent padded ones.
- */
-int
-AudioMapping::dcp_channels () const
-{
-       return max (_source_channels, _minimum_channels);
-}
-
 FrameRateConversion::FrameRateConversion (float source, int dcp)
        : skip (false)
        , repeat (false)
index c9e5bef16c0388c34ecbf50182b6ce4d90ac44c9..7af8ffedf27b07e357011bfaa467c6b8a0eb9ff5 100644 (file)
@@ -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,8 +37,10 @@ extern "C" {
 #include <libavfilter/avfilter.h>
 }
 #include "compose.hpp"
+#include "types.h"
+#include "video_content.h"
 
-#ifdef DVDOMATIC_DEBUG
+#ifdef DCPOMATIC_DEBUG
 #define TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TIMING);
 #else
 #define TIMING(...)
@@ -53,23 +55,22 @@ class Scaler;
 class Film;
 
 extern std::string seconds_to_hms (int);
+extern std::string time_to_hms (Time);
 extern std::string seconds_to_approximate_hms (int);
 extern void stacktrace (std::ostream &, int);
 extern std::string dependency_version_summary ();
 extern double seconds (struct timeval);
-extern void dvdomatic_setup ();
-extern void dvdomatic_setup_gettext_i18n (std::string);
+extern void dcpomatic_setup ();
+extern void dcpomatic_setup_gettext_i18n (std::string);
 extern std::vector<std::string> split_at_spaces_considering_quotes (std::string);
-extern std::string md5_digest (std::string);
+extern std::string md5_digest (boost::filesystem::path);
 extern std::string md5_digest (void const *, int);
 extern void ensure_ui_thread ();
 extern std::string audio_channel_name (int);
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
 extern boost::filesystem::path mo_path ();
 #endif
 
-typedef int SourceFrame;
-
 struct FrameRateConversion
 {
        FrameRateConversion (float, int);
@@ -105,96 +106,8 @@ struct FrameRateConversion
        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);
@@ -207,7 +120,7 @@ extern std::string get_optional_string (std::multimap<std::string, std::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.
@@ -241,68 +154,7 @@ private:
        int _timeout;
 };
 
-/** @class AudioBuffers
- *  @brief A class to hold multi-channel audio data in float format.
- */
-class AudioBuffers
-{
-public:
-       AudioBuffers (int channels, int frames);
-       AudioBuffers (AudioBuffers const &);
-       AudioBuffers (boost::shared_ptr<const AudioBuffers>);
-       ~AudioBuffers ();
-
-       float** data () const {
-               return _data;
-       }
-       
-       float* data (int) const;
-
-       int channels () const {
-               return _channels;
-       }
-
-       int frames () const {
-               return _frames;
-       }
-
-       void set_frames (int f);
-
-       void make_silent ();
-       void make_silent (int c);
-
-       void copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset);
-       void move (int from, int to, int frames);
-
-private:
-       /** Number of channels */
-       int _channels;
-       /** Number of frames (where a frame is one sample across all channels) */
-       int _frames;
-       /** Number of frames that _data can hold */
-       int _allocated_frames;
-       /** Audio data (so that, e.g. _data[2][6] is channel 2, sample 6) */
-       float** _data;
-};
-
-class AudioMapping
-{
-public:
-       AudioMapping (boost::shared_ptr<const Film>);
-
-       boost::optional<libdcp::Channel> source_to_dcp (int c) const;
-       boost::optional<int> dcp_to_source (libdcp::Channel c) const;
-
-       int minimum_dcp_channels () const;
-       int dcp_channels () const;
-
-private:
-       int _source_channels;
-       int _minimum_channels;
-};
-
-extern int64_t video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second);
-extern bool still_image_file (std::string);
+extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
 
 class LocaleGuard
 {
index e1ec9067cf69189a34a01ef5c2e1f4e7f70a0b10..b70be8343ec11b4fe86bc9e16c74b6604f5d3fa1 100644 (file)
@@ -1,4 +1,4 @@
 
-extern char const * dvdomatic_version;
-extern char const * dvdomatic_git_commit;
-extern char const * dvdomatic_cxx_flags;
+extern char const * dcpomatic_version;
+extern char const * dcpomatic_git_commit;
+extern char const * dcpomatic_cxx_flags;
diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc
new file mode 100644 (file)
index 0000000..3818fa7
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+    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 <iomanip>
+#include <libcxml/cxml.h>
+#include "video_content.h"
+#include "video_examiner.h"
+#include "ratio.h"
+#include "compose.hpp"
+
+#include "i18n.h"
+
+int const VideoContentProperty::VIDEO_SIZE      = 0;
+int const VideoContentProperty::VIDEO_FRAME_RATE = 1;
+int const VideoContentProperty::VIDEO_CROP      = 2;
+int const VideoContentProperty::VIDEO_RATIO     = 3;
+
+using std::string;
+using std::stringstream;
+using std::setprecision;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::optional;
+
+VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
+       : Content (f, s)
+       , _video_length (len)
+       , _video_frame_rate (0)
+       , _ratio (Ratio::from_id ("185"))
+{
+
+}
+
+VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p)
+       : Content (f, p)
+       , _video_length (0)
+       , _video_frame_rate (0)
+       , _ratio (Ratio::from_id ("185"))
+{
+
+}
+
+VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+       : Content (f, node)
+{
+       _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
+       _video_size.width = node->number_child<int> ("VideoWidth");
+       _video_size.height = node->number_child<int> ("VideoHeight");
+       _video_frame_rate = node->number_child<float> ("VideoFrameRate");
+       _crop.left = node->number_child<int> ("LeftCrop");
+       _crop.right = node->number_child<int> ("RightCrop");
+       _crop.top = node->number_child<int> ("TopCrop");
+       _crop.bottom = node->number_child<int> ("BottomCrop");
+       optional<string> r = node->optional_string_child ("Ratio");
+       if (r) {
+               _ratio = Ratio::from_id (r.get ());
+       }
+}
+
+VideoContent::VideoContent (VideoContent const & o)
+       : Content (o)
+       , _video_length (o._video_length)
+       , _video_size (o._video_size)
+       , _video_frame_rate (o._video_frame_rate)
+       , _ratio (o._ratio)
+{
+
+}
+
+void
+VideoContent::as_xml (xmlpp::Node* node) const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length));
+       node->add_child("VideoWidth")->add_child_text (lexical_cast<string> (_video_size.width));
+       node->add_child("VideoHeight")->add_child_text (lexical_cast<string> (_video_size.height));
+       node->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate));
+       node->add_child("LeftCrop")->add_child_text (boost::lexical_cast<string> (_crop.left));
+       node->add_child("RightCrop")->add_child_text (boost::lexical_cast<string> (_crop.right));
+       node->add_child("TopCrop")->add_child_text (boost::lexical_cast<string> (_crop.top));
+       node->add_child("BottomCrop")->add_child_text (boost::lexical_cast<string> (_crop.bottom));
+       if (_ratio) {
+               node->add_child("Ratio")->add_child_text (_ratio->id ());
+       }
+}
+
+void
+VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
+{
+       /* These examiner calls could call other content methods which take a lock on the mutex */
+       libdcp::Size const vs = d->video_size ();
+       float const vfr = d->video_frame_rate ();
+       
+        {
+                boost::mutex::scoped_lock lm (_mutex);
+                _video_size = vs;
+               _video_frame_rate = vfr;
+        }
+        
+        signal_changed (VideoContentProperty::VIDEO_SIZE);
+        signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
+}
+
+
+string
+VideoContent::information () const
+{
+       if (video_size().width == 0 || video_size().height == 0) {
+               return "";
+       }
+       
+       stringstream s;
+
+       s << String::compose (
+               _("%1x%2 pixels (%3:1)"),
+               video_size().width,
+               video_size().height,
+               setprecision (3), float (video_size().width) / video_size().height
+               );
+       
+       return s.str ();
+}
+
+void
+VideoContent::set_crop (Crop c)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _crop = c;
+       }
+       signal_changed (VideoContentProperty::VIDEO_CROP);
+}
+
+void
+VideoContent::set_left_crop (int c)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               
+               if (_crop.left == c) {
+                       return;
+               }
+               
+               _crop.left = c;
+       }
+       
+       signal_changed (VideoContentProperty::VIDEO_CROP);
+}
+
+void
+VideoContent::set_right_crop (int c)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               if (_crop.right == c) {
+                       return;
+               }
+               
+               _crop.right = c;
+       }
+       
+       signal_changed (VideoContentProperty::VIDEO_CROP);
+}
+
+void
+VideoContent::set_top_crop (int c)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               if (_crop.top == c) {
+                       return;
+               }
+               
+               _crop.top = c;
+       }
+       
+       signal_changed (VideoContentProperty::VIDEO_CROP);
+}
+
+void
+VideoContent::set_bottom_crop (int c)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               if (_crop.bottom == c) {
+                       return;
+               }
+               
+               _crop.bottom = c;
+       }
+
+       signal_changed (VideoContentProperty::VIDEO_CROP);
+}
+
+void
+VideoContent::set_ratio (Ratio const * r)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               if (_ratio == r) {
+                       return;
+               }
+
+               _ratio = r;
+       }
+
+       signal_changed (VideoContentProperty::VIDEO_RATIO);
+}
diff --git a/src/lib/video_content.h b/src/lib/video_content.h
new file mode 100644 (file)
index 0000000..372cab3
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+    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_VIDEO_CONTENT_H
+#define DCPOMATIC_VIDEO_CONTENT_H
+
+#include "content.h"
+
+class VideoExaminer;
+class Ratio;
+
+class VideoContentProperty
+{
+public:
+       static int const VIDEO_SIZE;
+       static int const VIDEO_FRAME_RATE;
+       static int const VIDEO_CROP;
+       static int const VIDEO_RATIO;
+};
+
+class VideoContent : public virtual Content
+{
+public:
+       typedef int Frame;
+
+       VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
+       VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+       VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+       VideoContent (VideoContent const &);
+
+       void as_xml (xmlpp::Node *) const;
+       virtual std::string information () const;
+
+       VideoContent::Frame video_length () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _video_length;
+       }
+
+       libdcp::Size video_size () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _video_size;
+       }
+       
+       float video_frame_rate () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _video_frame_rate;
+       }
+
+       void set_crop (Crop);
+       void set_left_crop (int);
+       void set_right_crop (int);
+       void set_top_crop (int);
+       void set_bottom_crop (int);
+
+       Crop crop () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _crop;
+       }
+
+       void set_ratio (Ratio const *);
+
+       Ratio const * ratio () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _ratio;
+       }
+
+protected:
+       void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
+
+       VideoContent::Frame _video_length;
+
+private:
+       libdcp::Size _video_size;
+       float _video_frame_rate;
+       Crop _crop;
+       Ratio const * _ratio;
+};
+
+#endif
index 16a076698eff8c652061a693dc95960f38e0cf9e..f61e63d4d1a4c312c99054c436bfee462f2afdab 100644 (file)
 #include "subtitle.h"
 #include "film.h"
 #include "image.h"
-#include "log.h"
-#include "options.h"
-#include "job.h"
+#include "ratio.h"
 
 #include "i18n.h"
 
 using std::cout;
 using boost::shared_ptr;
-using boost::optional;
 
-VideoDecoder::VideoDecoder (shared_ptr<Film> f, DecodeOptions o)
-       : Decoder (f, o)
-       , _video_frame (0)
-       , _last_source_time (0)
+VideoDecoder::VideoDecoder (shared_ptr<const Film> f)
+       : Decoder (f)
+       , _video_position (0)
 {
 
 }
 
-/** Called by subclasses to tell the world that some video data is ready.
- *  We find a subtitle then emit it for listeners.
- *  @param image frame to emit.
- *  @param t Time of the frame within the source, in seconds.
- */
 void
-VideoDecoder::emit_video (shared_ptr<Image> image, bool same, double t)
+VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
 {
-       shared_ptr<Subtitle> sub;
-       if (_timed_subtitle && _timed_subtitle->displayed_at (t)) {
-               sub = _timed_subtitle->subtitle ();
-       }
-
-       Video (image, same, sub, t);
-       ++_video_frame;
-       
-       _last_source_time = t;
+        Video (image, same, frame);
+       _video_position = frame + 1;
 }
 
-/** Set up the current subtitle.  This will be put onto frames that
- *  fit within its time specification.  s may be 0 to say that there
- *  is no current subtitle.
+#if 0
+
+/** Called by subclasses when a subtitle is ready.
+ *  s may be 0 to say that there is no current subtitle.
  *  @param s New current subtitle, or 0.
  */
 void
-VideoDecoder::emit_subtitle (shared_ptr<TimedSubtitle> s)
+VideoDecoder::subtitle (shared_ptr<TimedSubtitle> s)
 {
        _timed_subtitle = s;
        
        if (_timed_subtitle) {
                Position const p = _timed_subtitle->subtitle()->position ();
-               _timed_subtitle->subtitle()->set_position (Position (p.x - _film->crop().left, p.y - _film->crop().top));
+               _timed_subtitle->subtitle()->set_position (Position (p.x - _video_content->crop().left, p.y - _video_content->crop().top));
        }
 }
+#endif
 
-/** Set which stream of subtitles we should use from our source.
- *  @param s Stream to use.
- */
-void
-VideoDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
-       _subtitle_stream = s;
-}
-
-void
-VideoDecoder::set_progress (Job* j) const
-{
-       assert (j);
-       
-       if (_film->length()) {
-               j->set_progress (float (_video_frame) / _film->length().get());
-       }
-}
index 6e4fd48c0019710a2632e921d35645efa119c239..d24219d956cba2df1b92b8a58e2d3f5bc8d24d78 100644 (file)
 
 */
 
-#ifndef DVDOMATIC_VIDEO_DECODER_H
-#define DVDOMATIC_VIDEO_DECODER_H
+#ifndef DCPOMATIC_VIDEO_DECODER_H
+#define DCPOMATIC_VIDEO_DECODER_H
 
-#include "video_source.h"
-#include "stream.h"
 #include "decoder.h"
+#include "util.h"
 
-class VideoDecoder : public TimedVideoSource, public virtual Decoder
+class VideoContent;
+
+class VideoDecoder : public virtual Decoder
 {
 public:
-       VideoDecoder (boost::shared_ptr<Film>, DecodeOptions);
-
-       /** @return video frames per second, or 0 if unknown */
-       virtual float frames_per_second () const = 0;
-       /** @return native size in pixels */
-       virtual libdcp::Size native_size () const = 0;
-       /** @return length (in source video frames), according to our content's header */
-       virtual SourceFrame length () const = 0;
-
-       virtual int time_base_numerator () const = 0;
-       virtual int time_base_denominator () const = 0;
-       virtual int sample_aspect_ratio_numerator () const = 0;
-       virtual int sample_aspect_ratio_denominator () const = 0;
+       VideoDecoder (boost::shared_ptr<const Film>);
 
-       virtual void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
+       virtual void seek (VideoContent::Frame) = 0;
+       virtual void seek_back () = 0;
 
-       void set_progress (Job *) const;
+       /** Emitted when a video frame is ready.
+        *  First parameter is the video image.
+        *  Second parameter is true if the image is the same as the last one that was emitted.
+        *  Third parameter is the frame within our source.
+        */
+       boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, VideoContent::Frame)> Video;
        
-       int video_frame () const {
-               return _video_frame;
-       }
-
-       boost::shared_ptr<SubtitleStream> subtitle_stream () const {
-               return _subtitle_stream;
-       }
-
-       std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
-               return _subtitle_streams;
-       }
-
-       double last_source_time () const {
-               return _last_source_time;
-       }
-
 protected:
-       
-       virtual PixelFormat pixel_format () const = 0;
-
-       void emit_video (boost::shared_ptr<Image>, bool, double);
-       void emit_subtitle (boost::shared_ptr<TimedSubtitle>);
 
-       /** Subtitle stream to use when decoding */
-       boost::shared_ptr<SubtitleStream> _subtitle_stream;
-       /** Subtitle streams that this decoder's content has */
-       std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams;
-
-private:
-       int _video_frame;
-       double _last_source_time;
-       
-       boost::shared_ptr<TimedSubtitle> _timed_subtitle;
+       void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+       VideoContent::Frame _video_position;
 };
 
 #endif
diff --git a/src/lib/video_examiner.h b/src/lib/video_examiner.h
new file mode 100644 (file)
index 0000000..72f6ccc
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+    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 <libdcp/types.h>
+#include "types.h"
+#include "video_content.h"
+
+class VideoExaminer
+{
+public:
+       virtual float video_frame_rate () const = 0;
+       virtual libdcp::Size video_size () const = 0;
+       virtual VideoContent::Frame video_length () const = 0;
+};
diff --git a/src/lib/video_sink.h b/src/lib/video_sink.h
deleted file mode 100644 (file)
index 0170c73..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#ifndef DVDOMATIC_VIDEO_SINK_H
-#define DVDOMATIC_VIDEO_SINK_H
-
-#include <boost/shared_ptr.hpp>
-#include "util.h"
-
-class Subtitle;
-class Image;
-
-class VideoSink
-{
-public:
-       /** Call with a frame of video.
-        *  @param i Video frame image.
-        *  @param same true if i is the same as last time we were called.
-        *  @param s A subtitle that should be on this frame, or 0.
-        */
-       virtual void process_video (boost::shared_ptr<const Image> i, bool same, boost::shared_ptr<Subtitle> s) = 0;
-};
-
-class TimedVideoSink
-{
-public:
-       /** Call with a frame of video.
-        *  @param i Video frame image.
-        *  @param same true if i is the same as last time we were called.
-        *  @param s A subtitle that should be on this frame, or 0.
-        *  @param t Source timestamp.
-        */
-       virtual void process_video (boost::shared_ptr<const Image> i, bool same, boost::shared_ptr<Subtitle> s, double t) = 0;
-};
-
-#endif
diff --git a/src/lib/video_source.cc b/src/lib/video_source.cc
deleted file mode 100644 (file)
index 5392434..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "video_source.h"
-#include "video_sink.h"
-
-using boost::shared_ptr;
-using boost::bind;
-
-void
-VideoSource::connect_video (shared_ptr<VideoSink> s)
-{
-       Video.connect (bind (&VideoSink::process_video, s, _1, _2, _3));
-}
-
-void
-TimedVideoSource::connect_video (shared_ptr<TimedVideoSink> s)
-{
-       Video.connect (bind (&TimedVideoSink::process_video, s, _1, _2, _3, _4));
-}
-
-void
-TimedVideoSource::connect_video (shared_ptr<VideoSink> s)
-{
-       Video.connect (bind (&VideoSink::process_video, s, _1, _2, _3));
-}
-
-       
diff --git a/src/lib/video_source.h b/src/lib/video_source.h
deleted file mode 100644 (file)
index 748cb6f..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file  src/video_source.h
- *  @brief Parent class for classes which emit video data.
- */
-
-#ifndef DVDOMATIC_VIDEO_SOURCE_H
-#define DVDOMATIC_VIDEO_SOURCE_H
-
-#include <boost/shared_ptr.hpp>
-#include <boost/signals2.hpp>
-#include "util.h"
-
-class VideoSink;
-class TimedVideoSink;
-class Subtitle;
-class Image;
-
-/** @class VideoSource
- *  @param A class that emits video data without timestamps.
- */
-class VideoSource
-{
-public:
-
-       /** Emitted when a video frame is ready.
-        *  First parameter is the video image.
-        *  Second parameter is true if the image is the same as the last one that was emitted.
-        *  Third parameter is either 0 or a subtitle that should be on this frame.
-        */
-       boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, boost::shared_ptr<Subtitle>)> Video;
-
-       void connect_video (boost::shared_ptr<VideoSink>);
-};
-
-/** @class TimedVideoSource
- *  @param A class that emits video data with timestamps.
- */
-class TimedVideoSource
-{
-public:
-
-       /** Emitted when a video frame is ready.
-        *  First parameter is the video image.
-        *  Second parameter is true if the image is the same as the last one that was emitted.
-        *  Third parameter is either 0 or a subtitle that should be on this frame.
-        *  Fourth parameter is the source timestamp of this frame.
-        */
-       boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, boost::shared_ptr<Subtitle>, double)> Video;
-
-       void connect_video (boost::shared_ptr<VideoSink>);
-       void connect_video (boost::shared_ptr<TimedVideoSink>);
-};
-
-#endif
index cff0b5be23925ea09592b03dbcc27410f6e64e9c..cbb84a94019bc0a97fc67abf14e05545ccaf4675 100644 (file)
 #include <libdcp/sound_asset.h>
 #include <libdcp/picture_frame.h>
 #include <libdcp/reel.h>
+#include <libdcp/dcp.h>
 #include <libdcp/cpl.h>
 #include "writer.h"
 #include "compose.hpp"
 #include "film.h"
-#include "format.h"
+#include "ratio.h"
 #include "log.h"
 #include "dcp_video_frame.h"
+#include "dcp_content_type.h"
+#include "player.h"
+#include "audio_mapping.h"
 #include "config.h"
+#include "job.h"
 
 #include "i18n.h"
 
@@ -44,8 +49,9 @@ using boost::shared_ptr;
 
 int const Writer::_maximum_frames_in_memory = 8;
 
-Writer::Writer (shared_ptr<Film> f)
+Writer::Writer (shared_ptr<const Film> f, shared_ptr<Job> j)
        : _film (f)
+       , _job (j)
        , _first_nonexistant_frame (0)
        , _thread (0)
        , _finish (false)
@@ -70,28 +76,24 @@ Writer::Writer (shared_ptr<Film> f)
                new libdcp::MonoPictureAsset (
                        _film->internal_video_mxf_dir (),
                        _film->internal_video_mxf_filename (),
-                       _film->dcp_frame_rate (),
-                       _film->format()->dcp_size ()
+                       _film->dcp_video_frame_rate (),
+                       _film->container()->size (_film->full_frame ())
                        )
                );
 
        _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
 
-       AudioMapping m (_film);
+       _sound_asset.reset (
+               new libdcp::SoundAsset (
+                       _film->dir (_film->dcp_name()),
+                       _film->dcp_audio_mxf_filename (),
+                       _film->dcp_video_frame_rate (),
+                       _film->dcp_audio_channels (),
+                       _film->dcp_audio_frame_rate()
+                       )
+               );
        
-       if (m.dcp_channels() > 0) {
-               _sound_asset.reset (
-                       new libdcp::SoundAsset (
-                               _film->dir (_film->dcp_name()),
-                               _film->dcp_audio_mxf_filename (),
-                               _film->dcp_frame_rate (),
-                               m.dcp_channels (),
-                               dcp_audio_sample_rate (_film->audio_stream()->sample_rate())
-                               )
-                       );
-
-               _sound_asset_writer = _sound_asset->start_write ();
-       }
+       _sound_asset_writer = _sound_asset->start_write ();
 
        _thread = new boost::thread (boost::bind (&Writer::thread, this));
 }
@@ -201,6 +203,10 @@ try
                        }
                        }
                        lock.lock ();
+                       
+                       if (_film->length ()) {
+                               _job->set_progress (float(_full_written + _fake_written + _repeat_written) / _film->time_to_video_frames (_film->length()));
+                       }
 
                        ++_last_written_frame;
                }
@@ -256,21 +262,11 @@ Writer::finish ()
        _thread = 0;
 
        _picture_asset_writer->finalize ();
-
-       if (_sound_asset_writer) {
-               _sound_asset_writer->finalize ();
-       }
-
+       _sound_asset_writer->finalize ();
+       
        int const frames = _last_written_frame + 1;
-       int duration = 0;
-       if (_film->trim_type() == Film::CPL) {
-               duration = frames - _film->trim_start() - _film->trim_end();
-               _picture_asset->set_entry_point (_film->trim_start ());
-       } else {
-               duration = frames;
-       }
        
-       _picture_asset->set_duration (duration);
+       _picture_asset->set_duration (frames);
 
        /* Hard-link the video MXF into the DCP */
 
@@ -294,13 +290,7 @@ Writer::finish ()
 
        _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
        _picture_asset->set_file_name (_film->dcp_video_mxf_filename ());
-
-       if (_sound_asset) {
-               if (_film->trim_type() == Film::CPL) {
-                       _sound_asset->set_entry_point (_film->trim_start ());
-               }
-               _sound_asset->set_duration (duration);
-       }
+       _sound_asset->set_duration (frames);
        
        libdcp::DCP dcp (_film->dir (_film->dcp_name()));
 
@@ -310,7 +300,7 @@ Writer::finish ()
                        _film->dcp_name(),
                        _film->dcp_content_type()->libdcp_kind (),
                        frames,
-                       _film->dcp_frame_rate ()
+                       _film->dcp_video_frame_rate ()
                        )
                );
        
index beb16c7b9107da1972bcbd5f92a2dc07d3b1b701..e56e12e75a2255ac95bab0b1563733f3a5521e94 100644 (file)
@@ -26,6 +26,7 @@
 class Film;
 class EncodedData;
 class AudioBuffers;
+class Job;
 
 namespace libdcp {
        class MonoPictureAsset;
@@ -63,7 +64,7 @@ bool operator== (QueueItem const & a, QueueItem const & b);
 class Writer : public ExceptionStore
 {
 public:
-       Writer (boost::shared_ptr<Film>);
+       Writer (boost::shared_ptr<const Film>, boost::shared_ptr<Job>);
 
        bool can_fake_write (int) const;
        
@@ -79,7 +80,8 @@ private:
        void check_existing_picture_mxf ();
 
        /** our Film */
-       boost::shared_ptr<Film> _film;
+       boost::shared_ptr<const Film> _film;
+       boost::shared_ptr<Job> _job;
        /** the first frame index that does not already exist in our MXF */
        int _first_nonexistant_frame;
 
index 7c7a64d580bd63c002b03ba0f06469710af655c2..2f86539841da3efade020391e8b9a97aeb8972a6 100644 (file)
@@ -2,53 +2,57 @@ import os
 import i18n
 
 sources = """
-          ab_transcode_job.cc
-         ab_transcoder.cc
           analyse_audio_job.cc
           audio_analysis.cc
+          audio_buffers.cc
+          audio_content.cc
           audio_decoder.cc
-          audio_source.cc
+          audio_mapping.cc
           config.cc
-          combiner.cc
+          content.cc
           cross.cc
           dci_metadata.cc
           dcp_content_type.cc
           dcp_video_frame.cc
           decoder.cc
-          decoder_factory.cc
-          delay_line.cc
           dolby_cp750.cc
           encoder.cc
           examine_content_job.cc
           exceptions.cc
           filter_graph.cc
+          ffmpeg.cc
+          ffmpeg_content.cc
           ffmpeg_decoder.cc
+          ffmpeg_examiner.cc
           film.cc
           filter.cc
-          format.cc
-          gain.cc
           image.cc
+          imagemagick_content.cc
           imagemagick_decoder.cc
+          imagemagick_examiner.cc
           job.cc
           job_manager.cc
           log.cc
           lut.cc
-          matcher.cc
+          player.cc
+          playlist.cc
+          ratio.cc
+          resampler.cc
           scp_dcp_job.cc
           scaler.cc
           server.cc
+          sndfile_content.cc
           sndfile_decoder.cc
           sound_processor.cc
-          stream.cc
           subtitle.cc
           timer.cc
           transcode_job.cc
           transcoder.cc
-          trimmer.cc
+          types.cc
           ui_signaller.cc
           util.cc
+          video_content.cc
           video_decoder.cc
-          video_source.cc
           writer.cc
           """
 
@@ -58,12 +62,12 @@ def build(bld):
     else:
         obj = bld(features = 'cxx cxxshlib')
 
-    obj.name = 'libdvdomatic'
+    obj.name = 'libdcpomatic'
     obj.export_includes = ['.']
     obj.uselib = """
                  AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE 
                  BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 
-                 SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP GLIB LZMA
+                 SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA
                  """
 
     obj.source = sources + ' version.cc'
@@ -71,13 +75,15 @@ def build(bld):
     if bld.env.TARGET_WINDOWS:
         obj.uselib += ' WINSOCK2 BFD DBGHELP IBERTY SHLWAPI'
         obj.source += ' stack.cpp'
+    if bld.env.STATIC:
+        obj.uselib += ' XML++'
+    obj.source = sources + " version.cc"
+    obj.target = 'dcpomatic'
 
-    obj.target = 'dvdomatic'
-
-    i18n.po_to_mo(os.path.join('src', 'lib'), 'libdvdomatic', bld)
+    i18n.po_to_mo(os.path.join('src', 'lib'), 'libdcpomatic', bld)
 
 def pot(bld):
-    i18n.pot(os.path.join('src', 'lib'), sources, 'libdvdomatic')
+    i18n.pot(os.path.join('src', 'lib'), sources, 'libdcpomatic')
 
 def pot_merge(bld):
-    i18n.pot_merge(os.path.join('src', 'lib'), 'libdvdomatic')
+    i18n.pot_merge(os.path.join('src', 'lib'), 'libdcpomatic')
diff --git a/src/tools/dcpomatic.cc b/src/tools/dcpomatic.cc
new file mode 100644 (file)
index 0000000..ac39d4f
--- /dev/null
@@ -0,0 +1,518 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <fstream>
+#include <boost/filesystem.hpp>
+#ifdef __WXMSW__
+#include <shellapi.h>
+#endif
+#ifdef __WXOSX__
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+#include <wx/generic/aboutdlgg.h>
+#include <wx/stdpaths.h>
+#include <wx/cmdline.h>
+#include "wx/film_viewer.h"
+#include "wx/film_editor.h"
+#include "wx/job_manager_view.h"
+#include "wx/config_dialog.h"
+#include "wx/job_wrapper.h"
+#include "wx/wx_util.h"
+#include "wx/new_film_dialog.h"
+#include "wx/properties_dialog.h"
+#include "wx/wx_ui_signaller.h"
+#include "wx/about_dialog.h"
+#include "lib/film.h"
+#include "lib/config.h"
+#include "lib/util.h"
+#include "lib/version.h"
+#include "lib/ui_signaller.h"
+#include "lib/log.h"
+
+using std::cout;
+using std::string;
+using std::wstring;
+using std::stringstream;
+using std::map;
+using std::make_pair;
+using std::exception;
+using std::ofstream;
+using boost::shared_ptr;
+
+static FilmEditor* film_editor = 0;
+static FilmViewer* film_viewer = 0;
+static shared_ptr<Film> film;
+static std::string log_level;
+static std::string film_to_load;
+static std::string film_to_create;
+static wxMenu* jobs_menu = 0;
+
+static void set_menu_sensitivity ();
+
+class FilmChangedDialog
+{
+public:
+       FilmChangedDialog ()
+       {
+               _dialog = new wxMessageDialog (
+                       0,
+                       wxString::Format (_("Save changes to film \"%s\" before closing?"), std_to_wx (film->name ()).data()),
+                       _("Film changed"),
+                       wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION
+                       );
+       }
+
+       ~FilmChangedDialog ()
+       {
+               _dialog->Destroy ();
+       }
+
+       int run ()
+       {
+               return _dialog->ShowModal ();
+       }
+
+private:       
+       wxMessageDialog* _dialog;
+};
+
+
+void
+maybe_save_then_delete_film ()
+{
+       if (!film) {
+               return;
+       }
+                       
+       if (film->dirty ()) {
+               FilmChangedDialog d;
+               switch (d.run ()) {
+               case wxID_NO:
+                       break;
+               case wxID_YES:
+                       film->write_metadata ();
+                       break;
+               }
+       }
+       
+       film.reset ();
+}
+
+enum Sensitivity {
+       ALWAYS,
+       NEEDS_FILM
+};
+
+map<wxMenuItem*, Sensitivity> menu_items;
+       
+void
+add_item (wxMenu* menu, wxString text, int id, Sensitivity sens)
+{
+       wxMenuItem* item = menu->Append (id, text);
+       menu_items.insert (make_pair (item, sens));
+}
+
+void
+set_menu_sensitivity ()
+{
+       for (map<wxMenuItem*, Sensitivity>::iterator i = menu_items.begin(); i != menu_items.end(); ++i) {
+               if (i->second == NEEDS_FILM) {
+                       i->first->Enable (film != 0);
+               } else {
+                       i->first->Enable (true);
+               }
+       }
+}
+
+enum {
+       ID_file_new = 1,
+       ID_file_open,
+       ID_file_save,
+       ID_file_properties,
+       ID_jobs_make_dcp,
+       ID_jobs_send_dcp_to_tms,
+       ID_jobs_show_dcp,
+};
+
+void
+setup_menu (wxMenuBar* m)
+{
+       wxMenu* file = new wxMenu;
+       add_item (file, _("New..."), ID_file_new, ALWAYS);
+       add_item (file, _("&Open..."), ID_file_open, ALWAYS);
+       file->AppendSeparator ();
+       add_item (file, _("&Save"), ID_file_save, NEEDS_FILM);
+       file->AppendSeparator ();
+       add_item (file, _("&Properties..."), ID_file_properties, NEEDS_FILM);
+#ifndef __WXOSX__      
+       file->AppendSeparator ();
+#endif
+       add_item (file, _("&Exit"), wxID_EXIT, ALWAYS);
+
+#ifdef __WXOSX__       
+       add_item (file, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
+#else
+       wxMenu* edit = new wxMenu;
+       add_item (edit, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
+#endif 
+
+       jobs_menu = new wxMenu;
+       add_item (jobs_menu, _("&Make DCP"), ID_jobs_make_dcp, NEEDS_FILM);
+       add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM);
+       add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM);
+
+       wxMenu* help = new wxMenu;
+#ifdef __WXOSX__       
+       add_item (help, _("About DCP-o-matic"), wxID_ABOUT, ALWAYS);
+#else  
+       add_item (help, _("About"), wxID_ABOUT, ALWAYS);
+#endif 
+
+       m->Append (file, _("&File"));
+#ifndef __WXOSX__      
+       m->Append (edit, _("&Edit"));
+#endif 
+       m->Append (jobs_menu, _("&Jobs"));
+       m->Append (help, _("&Help"));
+}
+
+bool
+window_closed (wxCommandEvent &)
+{
+       maybe_save_then_delete_film ();
+       return false;
+}
+
+class Frame : public wxFrame
+{
+public:
+       Frame (wxString const & title)
+               : wxFrame (NULL, -1, title)
+       {
+               wxMenuBar* bar = new wxMenuBar;
+               setup_menu (bar);
+               SetMenuBar (bar);
+
+               Connect (ID_file_new, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_new));
+               Connect (ID_file_open, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_open));
+               Connect (ID_file_save, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_save));
+               Connect (ID_file_properties, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_properties));
+               Connect (wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_exit));
+               Connect (wxID_PREFERENCES, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::edit_preferences));
+               Connect (ID_jobs_make_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_make_dcp));
+               Connect (ID_jobs_send_dcp_to_tms, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_send_dcp_to_tms));
+               Connect (ID_jobs_show_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_show_dcp));
+               Connect (wxID_ABOUT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::help_about));
+
+               Connect (wxID_ANY, wxEVT_MENU_OPEN, wxMenuEventHandler (Frame::menu_opened));
+
+               film_editor = new FilmEditor (film, this);
+               film_viewer = new FilmViewer (film, this);
+               JobManagerView* job_manager_view = new JobManagerView (this, static_cast<JobManagerView::Buttons> (0));
+
+               wxBoxSizer* right_sizer = new wxBoxSizer (wxVERTICAL);
+               right_sizer->Add (film_viewer, 2, wxEXPAND | wxALL, 6);
+               right_sizer->Add (job_manager_view, 1, wxEXPAND | wxALL, 6);
+
+               wxBoxSizer* main_sizer = new wxBoxSizer (wxHORIZONTAL);
+               main_sizer->Add (film_editor, 1, wxEXPAND | wxALL, 6);
+               main_sizer->Add (right_sizer, 2, wxEXPAND | wxALL, 6);
+
+               set_menu_sensitivity ();
+
+               film_editor->FileChanged.connect (bind (&Frame::file_changed, this, _1));
+               if (film) {
+                       file_changed (film->directory ());
+               } else {
+                       file_changed ("");
+               }
+
+               set_film ();
+               SetSizer (main_sizer);
+       }
+
+private:
+
+       void menu_opened (wxMenuEvent& ev)
+       {
+               if (ev.GetMenu() != jobs_menu) {
+                       return;
+               }
+
+               bool const have_dcp = film && film->have_dcp();
+               jobs_menu->Enable (ID_jobs_send_dcp_to_tms, have_dcp);
+               jobs_menu->Enable (ID_jobs_show_dcp, have_dcp);
+       }
+
+       void set_film ()
+       {
+               film_viewer->set_film (film);
+               film_editor->set_film (film);
+               set_menu_sensitivity ();
+       }
+
+       void file_changed (string f)
+       {
+               stringstream s;
+               s << wx_to_std (_("DCP-o-matic"));
+               if (!f.empty ()) {
+                       s << " - " << f;
+               }
+               
+               SetTitle (std_to_wx (s.str()));
+       }
+       
+       void file_new (wxCommandEvent &)
+       {
+               NewFilmDialog* d = new NewFilmDialog (this);
+               int const r = d->ShowModal ();
+               
+               if (r == wxID_OK) {
+
+                       if (boost::filesystem::exists (d->get_path()) && !boost::filesystem::is_empty(d->get_path())) {
+                               if (!confirm_dialog (
+                                           this,
+                                           std_to_wx (
+                                                   String::compose (wx_to_std (_("The directory %1 already exists and is not empty.  "
+                                                                                 "Are you sure you want to use it?")),
+                                                                    d->get_path().c_str())
+                                                   )
+                                           )) {
+                                       return;
+                               }
+                       }
+                       
+                       maybe_save_then_delete_film ();
+                       film.reset (new Film (d->get_path ()));
+                       film->write_metadata ();
+                       film->log()->set_level (log_level);
+                       film->set_name (boost::filesystem::path (d->get_path()).filename().generic_string());
+                       set_film ();
+               }
+               
+               d->Destroy ();
+       }
+
+       void file_open (wxCommandEvent &)
+       {
+               wxDirDialog* c = new wxDirDialog (this, _("Select film to open"), wxStandardPaths::Get().GetDocumentsDir(), wxDEFAULT_DIALOG_STYLE | wxDD_DIR_MUST_EXIST);
+               int r;
+               while (1) {
+                       r = c->ShowModal ();
+                       if (r == wxID_OK && c->GetPath() == wxStandardPaths::Get().GetDocumentsDir()) {
+                               error_dialog (this, _("You did not select a folder.  Make sure that you select a folder before clicking Open."));
+                       } else {
+                               break;
+                       }
+               }
+                       
+               if (r == wxID_OK) {
+                       maybe_save_then_delete_film ();
+                       try {
+                               film.reset (new Film (wx_to_std (c->GetPath ())));
+                               film->log()->set_level (log_level);
+                               set_film ();
+                       } catch (std::exception& e) {
+                               wxString p = c->GetPath ();
+                               wxCharBuffer b = p.ToUTF8 ();
+                               error_dialog (this, wxString::Format (_("Could not open film at %s (%s)"), p.data(), std_to_wx (e.what()).data()));
+                       }
+               }
+
+               c->Destroy ();
+       }
+
+       void file_save (wxCommandEvent &)
+       {
+               film->write_metadata ();
+       }
+
+       void file_properties (wxCommandEvent &)
+       {
+               PropertiesDialog* d = new PropertiesDialog (this, film);
+               d->ShowModal ();
+               d->Destroy ();
+       }
+       
+       void file_exit (wxCommandEvent &)
+       {
+               maybe_save_then_delete_film ();
+               Close (true);
+       }
+
+       void edit_preferences (wxCommandEvent &)
+       {
+               ConfigDialog* d = new ConfigDialog (this);
+               d->ShowModal ();
+               d->Destroy ();
+               Config::instance()->write ();
+       }
+
+       void jobs_make_dcp (wxCommandEvent &)
+       {
+               JobWrapper::make_dcp (this, film);
+       }
+       
+       void jobs_send_dcp_to_tms (wxCommandEvent &)
+       {
+               film->send_dcp_to_tms ();
+       }
+
+       void jobs_show_dcp (wxCommandEvent &)
+       {
+#ifdef __WXMSW__
+               string d = film->directory();
+               wstring w;
+               w.assign (d.begin(), d.end());
+               ShellExecute (0, L"open", w.c_str(), 0, 0, SW_SHOWDEFAULT);
+#else
+               int r = system ("which nautilus");
+               if (WEXITSTATUS (r) == 0) {
+                       system (string ("nautilus " + film->directory()).c_str ());
+               } else {
+                       int r = system ("which konqueror");
+                       if (WEXITSTATUS (r) == 0) {
+                               system (string ("konqueror " + film->directory()).c_str ());
+                       }
+               }
+#endif         
+       }
+
+       void help_about (wxCommandEvent &)
+       {
+               AboutDialog* d = new AboutDialog (this);
+               d->ShowModal ();
+               d->Destroy ();
+       }
+};
+
+#if wxMINOR_VERSION == 9
+static const wxCmdLineEntryDesc command_line_description[] = {
+       { wxCMD_LINE_OPTION, "l", "log", "set log level (silent, verbose or timing)", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
+       { wxCMD_LINE_SWITCH, "n", "new", "create new film", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
+        { wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
+       { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
+};
+#else
+static const wxCmdLineEntryDesc command_line_description[] = {
+       { wxCMD_LINE_OPTION, wxT("l"), wxT("log"), wxT("set log level (silent, verbose or timing)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
+       { wxCMD_LINE_SWITCH, wxT("n"), wxT("new"), wxT("create new film"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
+        { wxCMD_LINE_PARAM, 0, 0, wxT("film to load or create"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
+       { wxCMD_LINE_NONE, wxT(""), wxT(""), wxT(""), wxCmdLineParamType (0), 0 }
+};
+#endif
+
+class App : public wxApp
+{
+       bool OnInit ()
+       {
+               if (!wxApp::OnInit()) {
+                       return false;
+               }
+               
+#ifdef DCPOMATIC_LINUX 
+               unsetenv ("UBUNTU_MENUPROXY");
+#endif
+
+#ifdef __WXOSX__               
+               ProcessSerialNumber serial;
+               GetCurrentProcess (&serial);
+               TransformProcessType (&serial, kProcessTransformToForegroundApplication);
+#endif         
+
+               wxInitAllImageHandlers ();
+
+               /* Enable i18n; this will create a Config object
+                  to look for a force-configured language.  This Config
+                  object will be wrong, however, because dcpomatic_setup
+                  hasn't yet been called and there aren't any scalers, filters etc.
+                  set up yet.
+               */
+               dcpomatic_setup_i18n ();
+
+               /* Set things up, including scalers / filters etc.
+                  which will now be internationalised correctly.
+               */
+               dcpomatic_setup ();
+
+               /* Force the configuration to be re-loaded correctly next
+                  time it is needed.
+               */
+               Config::drop ();
+
+               if (!film_to_load.empty() && boost::filesystem::is_directory (film_to_load)) {
+                       try {
+                               film.reset (new Film (film_to_load));
+                               film->read_metadata ();
+                               film->log()->set_level (log_level);
+                       } catch (exception& e) {
+                               error_dialog (0, std_to_wx (String::compose (wx_to_std (_("Could not load film %1 (%2)")), film_to_load, e.what())));
+                       }
+               }
+
+               if (!film_to_create.empty ()) {
+                       film.reset (new Film (film_to_create));
+                       film->write_metadata ();
+                       film->log()->set_level (log_level);
+                       film->set_name (boost::filesystem::path (film_to_create).filename().generic_string ());
+               }
+
+               Frame* f = new Frame (_("DCP-o-matic"));
+               SetTopWindow (f);
+               f->Maximize ();
+               f->Show ();
+
+               ui_signaller = new wxUISignaller (this);
+               this->Connect (-1, wxEVT_IDLE, wxIdleEventHandler (App::idle));
+
+               return true;
+       }
+
+       void OnInitCmdLine (wxCmdLineParser& parser)
+       {
+               parser.SetDesc (command_line_description);
+               parser.SetSwitchChars (wxT ("-"));
+       }
+
+       bool OnCmdLineParsed (wxCmdLineParser& parser)
+       {
+               if (parser.GetParamCount() > 0) {
+                       if (parser.Found (wxT ("new"))) {
+                               film_to_create = wx_to_std (parser.GetParam (0));
+                       } else {
+                               film_to_load = wx_to_std (parser.GetParam(0));
+                       }
+               }
+
+               wxString log;
+               if (parser.Found (wxT ("log"), &log)) {
+                       log_level = wx_to_std (log);
+               }
+
+               return true;
+       }
+
+       void idle (wxIdleEvent &)
+       {
+               ui_signaller->ui_idle ();
+       }
+};
+
+IMPLEMENT_APP (App)
diff --git a/src/tools/dcpomatic_batch.cc b/src/tools/dcpomatic_batch.cc
new file mode 100644 (file)
index 0000000..b4ab054
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+    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 --git a/src/tools/dcpomatic_cli.cc b/src/tools/dcpomatic_cli.cc
new file mode 100644 (file)
index 0000000..ee9e2cd
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <iomanip>
+#include <getopt.h>
+#include <libdcp/version.h>
+#include "film.h"
+#include "filter.h"
+#include "transcode_job.h"
+#include "job_manager.h"
+#include "util.h"
+#include "scaler.h"
+#include "version.h"
+#include "cross.h"
+#include "config.h"
+#include "log.h"
+
+using std::string;
+using std::cerr;
+using std::cout;
+using std::vector;
+using std::pair;
+using std::list;
+using boost::shared_ptr;
+
+static void
+help (string n)
+{
+       cerr << "Syntax: " << n << " [OPTION] <FILM>\n"
+            << "  -v, --version      show DCP-o-matic version\n"
+            << "  -h, --help         show this help\n"
+            << "  -d, --deps         list DCP-o-matic dependency details and quit\n"
+            << "  -f, --flags        show flags passed to C++ compiler on build\n"
+            << "  -n, --no-progress  do not print progress to stdout\n"
+            << "  -r, --no-remote    do not use any remote servers\n"
+            << "\n"
+            << "<FILM> is the film directory.\n";
+}
+
+int
+main (int argc, char* argv[])
+{
+       string film_dir;
+       bool progress = true;
+       bool no_remote = false;
+       int log_level = 0;
+
+       int option_index = 0;
+       while (1) {
+               static struct option long_options[] = {
+                       { "version", no_argument, 0, 'v'},
+                       { "help", no_argument, 0, 'h'},
+                       { "deps", no_argument, 0, 'd'},
+                       { "flags", no_argument, 0, 'f'},
+                       { "no-progress", no_argument, 0, 'n'},
+                       { "no-remote", no_argument, 0, 'r'},
+                       { "log-level", required_argument, 0, 'l' },
+                       { 0, 0, 0, 0 }
+               };
+
+               int c = getopt_long (argc, argv, "vhdfnrl:", long_options, &option_index);
+
+               if (c == -1) {
+                       break;
+               }
+
+               switch (c) {
+               case 'v':
+                       cout << "dcpomatic version " << dcpomatic_version << " " << dcpomatic_git_commit << "\n";
+                       exit (EXIT_SUCCESS);
+               case 'h':
+                       help (argv[0]);
+                       exit (EXIT_SUCCESS);
+               case 'd':
+                       cout << dependency_version_summary () << "\n";
+                       exit (EXIT_SUCCESS);
+               case 'f':
+                       cout << dcpomatic_cxx_flags << "\n";
+                       exit (EXIT_SUCCESS);
+               case 'n':
+                       progress = false;
+                       break;
+               case 'r':
+                       no_remote = true;
+                       break;
+               case 'l':
+                       log_level = atoi (optarg);
+                       break;
+               }
+       }
+
+       if (optind >= argc) {
+               help (argv[0]);
+               exit (EXIT_FAILURE);
+       }
+
+       film_dir = argv[optind];
+                       
+       dcpomatic_setup ();
+
+       if (no_remote) {
+               Config::instance()->set_servers (vector<ServerDescription*> ());
+       }
+
+       cout << "DCP-o-matic " << dcpomatic_version << " git " << dcpomatic_git_commit;
+       char buf[256];
+       if (gethostname (buf, 256) == 0) {
+               cout << " on " << buf;
+       }
+       cout << "\n";
+
+       shared_ptr<Film> film;
+       try {
+               film.reset (new Film (film_dir));
+               film->read_metadata ();
+       } catch (std::exception& e) {
+               cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n";
+               exit (EXIT_FAILURE);
+       }
+
+       film->log()->set_level ((Log::Level) log_level);
+
+       cout << "\nMaking DCP for " << film->name() << "\n";
+//     cout << "Content: " << film->content() << "\n";
+//     pair<string, string> const f = Filter::ffmpeg_strings (film->filters ());
+//     cout << "Filters: " << f.first << " " << f.second << "\n";
+
+       film->make_dcp ();
+
+       bool should_stop = false;
+       bool first = true;
+       bool error = false;
+       while (!should_stop) {
+
+               dcpomatic_sleep (5);
+
+               list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
+
+               if (!first && progress) {
+                       cout << "\033[" << jobs.size() << "A";
+                       cout.flush ();
+               }
+
+               first = false;
+
+               int unfinished = 0;
+               int finished_in_error = 0;
+
+               for (list<shared_ptr<Job> >::iterator i = jobs.begin(); i != jobs.end(); ++i) {
+                       if (progress) {
+                               cout << (*i)->name() << ": ";
+                               
+                               float const p = (*i)->overall_progress ();
+                               
+                               if (p >= 0) {
+                                       cout << (*i)->status() << "                         \n";
+                               } else {
+                                       cout << ": Running           \n";
+                               }
+                       }
+
+                       if (!(*i)->finished ()) {
+                               ++unfinished;
+                       }
+
+                       if ((*i)->finished_in_error ()) {
+                               ++finished_in_error;
+                               error = true;
+                       }
+
+                       if (!progress && (*i)->finished_in_error ()) {
+                               /* We won't see this error if we haven't been showing progress,
+                                  so show it now.
+                               */
+                               cout << (*i)->status() << "\n";
+                       }
+               }
+
+               if (unfinished == 0 || finished_in_error != 0) {
+                       should_stop = true;
+               }
+       }
+
+       return error ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+         
diff --git a/src/tools/dcpomatic_server.cc b/src/tools/dcpomatic_server.cc
new file mode 100644 (file)
index 0000000..d3a3531
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/thread.hpp>
+#include <wx/taskbar.h>
+#include <wx/icon.h>
+#include "wx_util.h"
+#include "lib/util.h"
+#include "lib/server.h"
+#include "lib/config.h"
+
+using std::cout;
+using std::string;
+using boost::shared_ptr;
+using boost::thread;
+using boost::bind;
+
+enum {
+       ID_status = 1,
+       ID_quit,
+       ID_timer
+};
+
+class MemoryLog : public Log
+{
+public:
+
+       string get () const {
+               boost::mutex::scoped_lock (_mutex);
+               return _log;
+       }
+
+private:
+       void do_log (string m)
+       {
+               _log = m;
+       }
+
+       string _log;    
+};
+
+static shared_ptr<MemoryLog> memory_log (new MemoryLog);
+
+class StatusDialog : public wxDialog
+{
+public:
+       StatusDialog ()
+               : wxDialog (0, wxID_ANY, _("DCP-o-matic encode server"), wxDefaultPosition, wxSize (600, 80), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+               , _timer (this, ID_timer)
+       {
+               _sizer = new wxFlexGridSizer (1, 6, 6);
+               _sizer->AddGrowableCol (0, 1);
+
+               _text = new wxTextCtrl (this, wxID_ANY, _(""), wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
+               _sizer->Add (_text, 1, wxEXPAND);
+
+               SetSizer (_sizer);
+               _sizer->Layout ();
+
+               Connect (ID_timer, wxEVT_TIMER, wxTimerEventHandler (StatusDialog::update));
+               _timer.Start (1000);
+       }
+
+private:
+       void update (wxTimerEvent &)
+       {
+               _text->ChangeValue (std_to_wx (memory_log->get ()));
+               _sizer->Layout ();
+       }
+
+       wxFlexGridSizer* _sizer;
+       wxTextCtrl* _text;
+       wxTimer _timer;
+};
+
+class TaskBarIcon : public wxTaskBarIcon
+{
+public:
+       TaskBarIcon ()
+       {
+#ifdef __WXMSW__               
+               wxIcon icon (std_to_wx ("taskbar_icon"));
+#endif
+#ifdef __WXGTK__
+               wxInitAllImageHandlers();
+               wxBitmap bitmap (wxString::Format (wxT ("%s/taskbar_icon.png"), POSIX_ICON_PREFIX), wxBITMAP_TYPE_PNG);
+               wxIcon icon;
+               icon.CopyFromBitmap (bitmap);
+#endif
+#ifndef __WXOSX__
+               /* XXX: fix this for OS X */
+               SetIcon (icon, std_to_wx ("DCP-o-matic encode server"));
+#endif         
+
+               Connect (ID_status, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (TaskBarIcon::status));
+               Connect (ID_quit, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (TaskBarIcon::quit));
+       }
+       
+       wxMenu* CreatePopupMenu ()
+       {
+               wxMenu* menu = new wxMenu;
+               menu->Append (ID_status, std_to_wx ("Status..."));
+               menu->Append (ID_quit, std_to_wx ("Quit"));
+               return menu;
+       }
+
+private:
+       void status (wxCommandEvent &)
+       {
+               StatusDialog* d = new StatusDialog;
+               d->Show ();
+       }
+
+       void quit (wxCommandEvent &)
+       {
+               wxTheApp->ExitMainLoop ();
+       }
+};
+
+class App : public wxApp
+{
+public:
+       App ()
+               : wxApp ()
+               , _thread (0)
+               , _icon (0)
+       {}
+
+private:       
+       
+       bool OnInit ()
+       {
+               if (!wxApp::OnInit ()) {
+                       return false;
+               }
+               
+               dcpomatic_setup ();
+
+               _icon = new TaskBarIcon;
+               _thread = new thread (bind (&App::main_thread, this));
+               
+               return true;
+       }
+
+       int OnExit ()
+       {
+               delete _icon;
+               return wxApp::OnExit ();
+       }
+
+       void main_thread ()
+       {
+               Server server (memory_log);
+               server.run (Config::instance()->num_local_encoding_threads ());
+       }
+
+       boost::thread* _thread;
+       TaskBarIcon* _icon;
+};
+
+IMPLEMENT_APP (App)
diff --git a/src/tools/dcpomatic_server_cli.cc b/src/tools/dcpomatic_server_cli.cc
new file mode 100644 (file)
index 0000000..76d0850
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+    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 "lib/server.h"
+#include <iostream>
+#include <stdexcept>
+#include <sstream>
+#include <cstring>
+#include <vector>
+#include <unistd.h>
+#include <errno.h>
+#include <getopt.h>
+#include <boost/array.hpp>
+#include <boost/asio.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/condition.hpp>
+#include "config.h"
+#include "dcp_video_frame.h"
+#include "exceptions.h"
+#include "util.h"
+#include "config.h"
+#include "scaler.h"
+#include "image.h"
+#include "log.h"
+#include "version.h"
+
+using std::cerr;
+using std::string;
+using std::cout;
+using boost::shared_ptr;
+
+static void
+help (string n)
+{
+       cerr << "Syntax: " << n << " [OPTION]\n"
+            << "  -v, --version      show DCP-o-matic version\n"
+            << "  -h, --help         show this help\n"
+            << "  -t, --threads      number of parallel encoding threads to use\n";
+}
+
+int
+main (int argc, char* argv[])
+{
+       int num_threads = Config::instance()->num_local_encoding_threads ();
+
+       int option_index = 0;
+       while (1) {
+               static struct option long_options[] = {
+                       { "version", no_argument, 0, 'v'},
+                       { "help", no_argument, 0, 'h'},
+                       { "threads", required_argument, 0, 't'},
+                       { 0, 0, 0, 0 }
+               };
+
+               int c = getopt_long (argc, argv, "vht:", long_options, &option_index);
+
+               if (c == -1) {
+                       break;
+               }
+
+               switch (c) {
+               case 'v':
+                       cout << "dcpomatic version " << dcpomatic_version << " " << dcpomatic_git_commit << "\n";
+                       exit (EXIT_SUCCESS);
+               case 'h':
+                       help (argv[0]);
+                       exit (EXIT_SUCCESS);
+               case 't':
+                       num_threads = atoi (optarg);
+                       break;
+               }
+       }
+
+       Scaler::setup_scalers ();
+       shared_ptr<FileLog> log (new FileLog ("servomatic.log"));
+       Server server (log);
+       server.run (num_threads);
+       return 0;
+}
diff --git a/src/tools/dvdomatic.cc b/src/tools/dvdomatic.cc
deleted file mode 100644 (file)
index e0629bb..0000000
+++ /dev/null
@@ -1,536 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <iostream>
-#include <fstream>
-#include <boost/filesystem.hpp>
-#ifdef __WXMSW__
-#include <shellapi.h>
-#endif
-#ifdef __WXOSX__
-#include <ApplicationServices/ApplicationServices.h>
-#endif
-#include <wx/generic/aboutdlgg.h>
-#include <wx/stdpaths.h>
-#include <wx/cmdline.h>
-#include "wx/film_viewer.h"
-#include "wx/film_editor.h"
-#include "wx/job_manager_view.h"
-#include "wx/config_dialog.h"
-#include "wx/job_wrapper.h"
-#include "wx/wx_util.h"
-#include "wx/new_film_dialog.h"
-#include "wx/properties_dialog.h"
-#include "wx/wx_ui_signaller.h"
-#include "wx/about_dialog.h"
-#include "lib/film.h"
-#include "lib/format.h"
-#include "lib/config.h"
-#include "lib/filter.h"
-#include "lib/util.h"
-#include "lib/scaler.h"
-#include "lib/exceptions.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 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__       
-       add_item (help, _("About DVD-o-matic"), wxID_ABOUT, ALWAYS);
-#else  
-       add_item (help, _("About"), wxID_ABOUT, ALWAYS);
-#endif 
-
-       m->Append (file, _("&File"));
-#ifndef __WXOSX__      
-       m->Append (edit, _("&Edit"));
-#endif 
-       m->Append (jobs_menu, _("&Jobs"));
-       m->Append (help, _("&Help"));
-}
-
-bool
-window_closed (wxCommandEvent &)
-{
-       maybe_save_then_delete_film ();
-       return false;
-}
-
-class Frame : public wxFrame
-{
-public:
-       Frame (wxString const & title)
-               : wxFrame (NULL, -1, title)
-       {
-               wxMenuBar* bar = new wxMenuBar;
-               setup_menu (bar);
-               SetMenuBar (bar);
-
-               Connect (ID_file_new, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_new));
-               Connect (ID_file_open, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_open));
-               Connect (ID_file_save, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_save));
-               Connect (ID_file_properties, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_properties));
-               Connect (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));
-
-               wxPanel* panel = new wxPanel (this);
-               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               s->Add (panel, 1, wxEXPAND);
-               SetSizer (s);
-
-               film_editor = new FilmEditor (film, panel);
-               film_viewer = new FilmViewer (film, panel);
-               JobManagerView* job_manager_view = new JobManagerView (panel, static_cast<JobManagerView::Buttons> (0));
-
-               _top_sizer = new wxBoxSizer (wxHORIZONTAL);
-               _top_sizer->Add (film_editor, 0, wxALL, 6);
-               _top_sizer->Add (film_viewer, 1, wxEXPAND | wxALL, 6);
-
-               wxBoxSizer* main_sizer = new wxBoxSizer (wxVERTICAL);
-               main_sizer->Add (_top_sizer, 2, wxEXPAND | wxALL, 6);
-               main_sizer->Add (job_manager_view, 1, wxEXPAND | wxALL, 6);
-               panel->SetSizer (main_sizer);
-
-               set_menu_sensitivity ();
-
-               /* XXX: calling these here is a bit of a hack */
-               film_editor->setup_visibility ();
-               
-               film_editor->FileChanged.connect (bind (&Frame::file_changed, this, _1));
-               if (film) {
-                       file_changed (film->directory ());
-               } else {
-                       file_changed ("");
-               }
-
-               set_film ();
-
-               film_editor->Connect (wxID_ANY, wxEVT_SIZE, wxSizeEventHandler (Frame::film_editor_sized), 0, this);
-       }
-
-private:
-
-       void film_editor_sized (wxSizeEvent &)
-       {
-               static bool in_layout = false;
-               if (!in_layout) {
-                       in_layout = true;
-                       _top_sizer->Layout ();
-                       in_layout = false;
-               }
-       }
-
-       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 (_("DVD-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 (), false));
-                       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 &)
-       {
-               AboutDialog* d = new AboutDialog (this);
-               d->ShowModal ();
-               d->Destroy ();
-       }
-
-       wxSizer* _top_sizer;
-};
-
-#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_PARAM, 0, 0, "film to load", 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_PARAM, 0, 0, wxT("film to load"), 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 DVDOMATIC_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 dvdomatic_setup
-                  hasn't yet been called and there aren't any scalers, filters etc.
-                  set up yet.
-               */
-               dvdomatic_setup_i18n ();
-
-               /* Set things up, including scalers / filters etc.
-                  which will now be internationalised correctly.
-               */
-               dvdomatic_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->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())));
-                       }
-               }
-
-               Frame* f = new Frame (_("DVD-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) {
-                       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)
diff --git a/src/tools/dvdomatic_batch.cc b/src/tools/dvdomatic_batch.cc
deleted file mode 100644 (file)
index d9ddb9d..0000000
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
-    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 (_("DVD-o-matic Batch Converter"));
-               if (strcmp (dvdomatic_git_commit, "release") == 0) {
-                       info.SetVersion (std_to_wx (String::compose ("version %1", dvdomatic_version)));
-               } else {
-                       info.SetVersion (std_to_wx (String::compose ("version %1 git %2", dvdomatic_version, dvdomatic_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/dvdomatic"));
-               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->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 DVDOMATIC_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 dvdomatic_setup
-                  hasn't yet been called and there aren't any scalers, filters etc.
-                  set up yet.
-               */
-               dvdomatic_setup_i18n ();
-
-               /* Set things up, including scalers / filters etc.
-                  which will now be internationalised correctly.
-               */
-               dvdomatic_setup ();
-
-               /* Force the configuration to be re-loaded correctly next
-                  time it is needed.
-               */
-               Config::drop ();
-
-               Frame* f = new Frame (_("DVD-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 --git a/src/tools/makedcp.cc b/src/tools/makedcp.cc
deleted file mode 100644 (file)
index 1cd5145..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <iostream>
-#include <iomanip>
-#include <getopt.h>
-#include <libdcp/version.h>
-#include "format.h"
-#include "film.h"
-#include "filter.h"
-#include "transcode_job.h"
-#include "job_manager.h"
-#include "ab_transcode_job.h"
-#include "util.h"
-#include "scaler.h"
-#include "version.h"
-#include "cross.h"
-#include "config.h"
-#include "log.h"
-
-using std::string;
-using std::cerr;
-using std::cout;
-using std::vector;
-using std::pair;
-using std::list;
-using boost::shared_ptr;
-
-static void
-help (string n)
-{
-       cerr << "Syntax: " << n << " [OPTION] <FILM>\n"
-            << "  -v, --version      show DVD-o-matic version\n"
-            << "  -h, --help         show this help\n"
-            << "  -d, --deps         list DVD-o-matic dependency details\n"
-            << "  -f, --flags        show flags passed to C++ compiler on build\n"
-            << "  -n, --no-progress  do not print progress to stdout\n"
-            << "  -r, --no-remote    do not use any remote servers\n"
-            << "\n"
-            << "<FILM> is the film directory.\n";
-}
-
-int
-main (int argc, char* argv[])
-{
-       string film_dir;
-       bool progress = true;
-       bool no_remote = false;
-       int log_level = 0;
-
-       int option_index = 0;
-       while (1) {
-               static struct option long_options[] = {
-                       { "version", no_argument, 0, 'v'},
-                       { "help", no_argument, 0, 'h'},
-                       { "deps", no_argument, 0, 'd'},
-                       { "flags", no_argument, 0, 'f'},
-                       { "no-progress", no_argument, 0, 'n'},
-                       { "no-remote", no_argument, 0, 'r'},
-                       { "log-level", required_argument, 0, 'l' },
-                       { 0, 0, 0, 0 }
-               };
-
-               int c = getopt_long (argc, argv, "vhdfnrl:", long_options, &option_index);
-
-               if (c == -1) {
-                       break;
-               }
-
-               switch (c) {
-               case 'v':
-                       cout << "dvdomatic version " << dvdomatic_version << " " << dvdomatic_git_commit << "\n";
-                       exit (EXIT_SUCCESS);
-               case 'h':
-                       help (argv[0]);
-                       exit (EXIT_SUCCESS);
-               case 'd':
-                       cout << dependency_version_summary () << "\n";
-                       exit (EXIT_SUCCESS);
-               case 'f':
-                       cout << dvdomatic_cxx_flags << "\n";
-                       exit (EXIT_SUCCESS);
-               case 'n':
-                       progress = false;
-                       break;
-               case 'r':
-                       no_remote = true;
-                       break;
-               case 'l':
-                       log_level = atoi (optarg);
-                       break;
-               }
-       }
-
-       if (optind >= argc) {
-               help (argv[0]);
-               exit (EXIT_FAILURE);
-       }
-
-       film_dir = argv[optind];
-                       
-       dvdomatic_setup ();
-
-       if (no_remote) {
-               Config::instance()->set_servers (vector<ServerDescription*> ());
-       }
-
-       cout << "DVD-o-matic " << dvdomatic_version << " git " << dvdomatic_git_commit;
-       char buf[256];
-       if (gethostname (buf, 256) == 0) {
-               cout << " on " << buf;
-       }
-       cout << "\n";
-
-       shared_ptr<Film> film;
-       try {
-               film.reset (new Film (film_dir, true));
-       } catch (std::exception& e) {
-               cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n";
-               exit (EXIT_FAILURE);
-       }
-
-       film->log()->set_level ((Log::Level) log_level);
-
-       cout << "\nMaking ";
-       if (film->dcp_ab()) {
-               cout << "A/B ";
-       }
-       cout << "DCP for " << film->name() << "\n";
-       cout << "Content: " << film->content() << "\n";
-       pair<string, string> const f = Filter::ffmpeg_strings (film->filters ());
-       cout << "Filters: " << f.first << " " << f.second << "\n";
-
-       film->make_dcp ();
-
-       bool should_stop = false;
-       bool first = true;
-       bool error = false;
-       while (!should_stop) {
-
-               dvdomatic_sleep (5);
-
-               list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
-
-               if (!first && progress) {
-                       cout << "\033[" << jobs.size() << "A";
-                       cout.flush ();
-               }
-
-               first = false;
-
-               int unfinished = 0;
-               int finished_in_error = 0;
-
-               for (list<shared_ptr<Job> >::iterator i = jobs.begin(); i != jobs.end(); ++i) {
-                       if (progress) {
-                               cout << (*i)->name() << ": ";
-                               
-                               float const p = (*i)->overall_progress ();
-                               
-                               if (p >= 0) {
-                                       cout << (*i)->status() << "                         \n";
-                               } else {
-                                       cout << ": Running           \n";
-                               }
-                       }
-
-                       if (!(*i)->finished ()) {
-                               ++unfinished;
-                       }
-
-                       if ((*i)->finished_in_error ()) {
-                               ++finished_in_error;
-                               error = true;
-                       }
-
-                       if (!progress && (*i)->finished_in_error ()) {
-                               /* We won't see this error if we haven't been showing progress,
-                                  so show it now.
-                               */
-                               cout << (*i)->status() << "\n";
-                       }
-               }
-
-               if (unfinished == 0 || finished_in_error != 0) {
-                       should_stop = true;
-               }
-       }
-
-       return error ? EXIT_FAILURE : EXIT_SUCCESS;
-}
-
-         
index bbae550d77718971513035634ed63378b835b1e6..43c9b12f116d9aff5b968a58fefb32c09a4dc7fc 100644 (file)
@@ -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,16 +90,16 @@ msgstr "No se pudo cargar la película %s (%s)"
 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/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 "
index 8a28a0e015041220d9c7d1fcb619114ca185e858..b9e39b3929e0035f0b1f9aa123ffcae5f5b97c17 100644 (file)
@@ -5,7 +5,7 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: DVD-o-matic FRENCH\n"
+"Project-Id-Version: DCP-o-matic FRENCH\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2013-06-04 23:59+0100\n"
 "PO-Revision-Date: 2013-05-10 14:09+0100\n"
index 662b74bb39dab3cd4daf82654bd8865bcaae86b6..157b1fb19a2f38a5d1f259346dd28b51ad30a627 100644 (file)
@@ -5,7 +5,7 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: DVD-o-matic\n"
+"Project-Id-Version: DCP-o-matic\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2013-06-04 23:59+0100\n"
 "PO-Revision-Date: 2013-04-09 10:12+0100\n"
@@ -89,10 +89,10 @@ msgstr "Kunde inte Ã¶ppna filmen %1 (%2)"
 msgid "Could not open film at %s (%s)"
 msgstr "Kunde inte Ã¶ppna filmen vid %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/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:79
 msgid "Film changed"
@@ -114,7 +114,7 @@ msgstr "&Visa DCP"
 #: src/tools/dvdomatic.cc:78
 #, fuzzy, c-format
 msgid "Save changes to film \"%s\" before closing?"
-msgstr "Spara Ã¤ndringarna till filmen \"%1\" före avslut?"
+msgstr "Spara Ã¤ndringarna till filmen \"%s\" före avslut?"
 
 #: src/tools/dvdomatic.cc:340
 msgid "Select film to open"
diff --git a/src/tools/servomatic_cli.cc b/src/tools/servomatic_cli.cc
deleted file mode 100644 (file)
index 6626d45..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "lib/server.h"
-#include <iostream>
-#include <stdexcept>
-#include <sstream>
-#include <cstring>
-#include <vector>
-#include <unistd.h>
-#include <errno.h>
-#include <getopt.h>
-#include <boost/array.hpp>
-#include <boost/asio.hpp>
-#include <boost/algorithm/string.hpp>
-#include <boost/thread.hpp>
-#include <boost/thread/mutex.hpp>
-#include <boost/thread/condition.hpp>
-#include "config.h"
-#include "dcp_video_frame.h"
-#include "exceptions.h"
-#include "util.h"
-#include "config.h"
-#include "scaler.h"
-#include "image.h"
-#include "log.h"
-#include "version.h"
-
-using std::cerr;
-using std::string;
-using std::cout;
-using boost::shared_ptr;
-
-static void
-help (string n)
-{
-       cerr << "Syntax: " << n << " [OPTION]\n"
-            << "  -v, --version      show DVD-o-matic version\n"
-            << "  -h, --help         show this help\n"
-            << "  -t, --threads      number of parallel encoding threads to use\n";
-}
-
-int
-main (int argc, char* argv[])
-{
-       int num_threads = Config::instance()->num_local_encoding_threads ();
-
-       int option_index = 0;
-       while (1) {
-               static struct option long_options[] = {
-                       { "version", no_argument, 0, 'v'},
-                       { "help", no_argument, 0, 'h'},
-                       { "threads", required_argument, 0, 't'},
-                       { 0, 0, 0, 0 }
-               };
-
-               int c = getopt_long (argc, argv, "vht:", long_options, &option_index);
-
-               if (c == -1) {
-                       break;
-               }
-
-               switch (c) {
-               case 'v':
-                       cout << "dvdomatic version " << dvdomatic_version << " " << dvdomatic_git_commit << "\n";
-                       exit (EXIT_SUCCESS);
-               case 'h':
-                       help (argv[0]);
-                       exit (EXIT_SUCCESS);
-               case 't':
-                       num_threads = atoi (optarg);
-                       break;
-               }
-       }
-
-       Scaler::setup_scalers ();
-       shared_ptr<FileLog> log (new FileLog ("servomatic.log"));
-       Server server (log);
-       server.run (num_threads);
-       return 0;
-}
diff --git a/src/tools/servomatic_gui.cc b/src/tools/servomatic_gui.cc
deleted file mode 100644 (file)
index 000c201..0000000
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <boost/thread.hpp>
-#include <wx/taskbar.h>
-#include <wx/icon.h>
-#include "wx_util.h"
-#include "lib/util.h"
-#include "lib/server.h"
-#include "lib/config.h"
-
-using std::cout;
-using std::string;
-using boost::shared_ptr;
-using boost::thread;
-using boost::bind;
-
-enum {
-       ID_status = 1,
-       ID_quit,
-       ID_timer
-};
-
-class MemoryLog : public Log
-{
-public:
-
-       string get () const {
-               boost::mutex::scoped_lock (_mutex);
-               return _log;
-       }
-
-private:
-       void do_log (string m)
-       {
-               _log = m;
-       }
-
-       string _log;    
-};
-
-static shared_ptr<MemoryLog> memory_log (new MemoryLog);
-
-class StatusDialog : public wxDialog
-{
-public:
-       StatusDialog ()
-               : wxDialog (0, wxID_ANY, _("DVD-o-matic encode server"), wxDefaultPosition, wxSize (600, 80), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
-               , _timer (this, ID_timer)
-       {
-               _sizer = new wxFlexGridSizer (1, 6, 6);
-               _sizer->AddGrowableCol (0, 1);
-
-               _text = new wxTextCtrl (this, wxID_ANY, _(""), wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
-               _sizer->Add (_text, 1, wxEXPAND);
-
-               SetSizer (_sizer);
-               _sizer->Layout ();
-
-               Connect (ID_timer, wxEVT_TIMER, wxTimerEventHandler (StatusDialog::update));
-               _timer.Start (1000);
-       }
-
-private:
-       void update (wxTimerEvent &)
-       {
-               _text->ChangeValue (std_to_wx (memory_log->get ()));
-               _sizer->Layout ();
-       }
-
-       wxFlexGridSizer* _sizer;
-       wxTextCtrl* _text;
-       wxTimer _timer;
-};
-
-class TaskBarIcon : public wxTaskBarIcon
-{
-public:
-       TaskBarIcon ()
-       {
-#ifdef __WXMSW__               
-               wxIcon icon (std_to_wx ("taskbar_icon"));
-#endif
-#ifdef __WXGTK__
-               wxInitAllImageHandlers();
-               wxBitmap bitmap (wxString::Format (wxT ("%s/taskbar_icon.png"), POSIX_ICON_PREFIX), wxBITMAP_TYPE_PNG);
-               wxIcon icon;
-               icon.CopyFromBitmap (bitmap);
-#endif
-#ifndef __WXOSX__
-               /* XXX: fix this for OS X */
-               SetIcon (icon, std_to_wx ("DVD-o-matic encode server"));
-#endif         
-
-               Connect (ID_status, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (TaskBarIcon::status));
-               Connect (ID_quit, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (TaskBarIcon::quit));
-       }
-       
-       wxMenu* CreatePopupMenu ()
-       {
-               wxMenu* menu = new wxMenu;
-               menu->Append (ID_status, std_to_wx ("Status..."));
-               menu->Append (ID_quit, std_to_wx ("Quit"));
-               return menu;
-       }
-
-private:
-       void status (wxCommandEvent &)
-       {
-               StatusDialog* d = new StatusDialog;
-               d->Show ();
-       }
-
-       void quit (wxCommandEvent &)
-       {
-               wxTheApp->ExitMainLoop ();
-       }
-};
-
-class App : public wxApp
-{
-public:
-       App ()
-               : wxApp ()
-               , _thread (0)
-               , _icon (0)
-       {}
-
-private:       
-       
-       bool OnInit ()
-       {
-               if (!wxApp::OnInit ()) {
-                       return false;
-               }
-               
-               dvdomatic_setup ();
-
-               _icon = new TaskBarIcon;
-               _thread = new thread (bind (&App::main_thread, this));
-               
-               return true;
-       }
-
-       int OnExit ()
-       {
-               delete _icon;
-               return wxApp::OnExit ();
-       }
-
-       void main_thread ()
-       {
-               Server server (memory_log);
-               server.run (Config::instance()->num_local_encoding_threads ());
-       }
-
-       boost::thread* _thread;
-       TaskBarIcon* _icon;
-};
-
-IMPLEMENT_APP (App)
index 5e1cf49b4b0b1e04faff88718f58801948492173..88974eed731e6be7334a6dd0ab57d473dbb15583 100644 (file)
 #include "scaler.h"
 #include "server.h"
 #include "dcp_video_frame.h"
-#include "options.h"
 #include "decoder.h"
 #include "exceptions.h"
 #include "scaler.h"
 #include "log.h"
-#include "decoder_factory.h"
 #include "video_decoder.h"
+#include "player.h"
 
 using std::cout;
 using std::cerr;
@@ -146,22 +145,20 @@ main (int argc, char* argv[])
                exit (EXIT_FAILURE);
        }
 
-       dvdomatic_setup ();
+       dcpomatic_setup ();
 
        server = new ServerDescription (server_host, 1);
-       shared_ptr<Film> film (new Film (film_dir, true));
+       shared_ptr<Film> film (new Film (film_dir));
+       film->read_metadata ();
 
-       DecodeOptions opt;
-       opt.decode_audio = false;
-       opt.decode_subtitles = true;
-       opt.video_sync = true;
+       shared_ptr<Player> player = film->player ();
+       player->disable_audio ();
 
-       Decoders decoders = decoder_factory (film, opt);
        try {
-               decoders.video->Video.connect (boost::bind (process_video, _1, _2, _3));
+               player->Video.connect (boost::bind (process_video, _1, _2, _3));
                bool done = false;
                while (!done) {
-                       done = decoders.video->pass ();
+                       done = player->pass ();
                }
        } catch (std::exception& e) {
                cerr << "Error: " << e.what() << "\n";
index 20a92cad2f4adb38df59f88394426b4f0306853b..c7ab4460401ef99656ddacdbd11edcd2576038db 100644 (file)
@@ -4,31 +4,31 @@ from waflib import Logs
 import i18n
 
 def build(bld):
-    for t in ['makedcp', 'servomatic_cli', 'servomatictest']:
+    for t in ['dcpomatic_cli', 'dcpomatic_server_cli']:
         obj = bld(features = 'cxx cxxprogram')
-        obj.uselib = 'BOOST_THREAD OPENJPEG DCP AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML WXWIDGETS'
+        obj.uselib = 'BOOST_THREAD OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC WXWIDGETS'
         obj.includes = ['..']
-        obj.use    = ['libdvdomatic']
+        obj.use    = ['libdcpomatic']
         obj.source = '%s.cc' % t
         obj.target = t
 
     if not bld.env.DISABLE_GUI:
-        for t in ['dvdomatic', 'dvdomatic_batch', 'servomatic_gui']:
+        for t in ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server']:
             obj = bld(features = 'cxx cxxprogram')
             obj.uselib = 'DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML WXWIDGETS'
             if bld.env.STATIC:
                 obj.uselib += ' GTK'
             obj.includes = ['..']
-            obj.use    = ['libdvdomatic', 'libdvdomatic-wx']
+            obj.use    = ['libdcpomatic', 'libdcpomatic-wx']
             obj.source = '%s.cc' % t
             if bld.env.TARGET_WINDOWS:
-                obj.source += ' ../../platform/windows/dvdomatic.rc'
+                obj.source += ' ../../platform/windows/dcpomatic.rc'
             obj.target = t
 
-        i18n.po_to_mo(os.path.join('src', 'tools'), 'dvdomatic', bld)
+        i18n.po_to_mo(os.path.join('src', 'tools'), 'dcpomatic', bld)
 
 def pot(bld):
-    i18n.pot(os.path.join('src', 'tools'), 'dvdomatic.cc', 'dvdomatic')
+    i18n.pot(os.path.join('src', 'tools'), 'dcpomatic.cc', 'dcpomatic')
 
 def pot_merge(bld):
-    i18n.pot_merge(os.path.join('src', 'tools'), 'dvdomatic')
+    i18n.pot_merge(os.path.join('src', 'tools'), 'dcpomatic')
index cbac3ed4339541c21a84cdd348da77adccc48af8..ca19326a1de2abacd87ef7311332d4258cc1c4fe 100644 (file)
@@ -27,7 +27,7 @@
 using std::vector;
 
 AboutDialog::AboutDialog (wxWindow* parent)
-       : wxDialog (parent, wxID_ANY, _("About DVD-o-matic"))
+       : wxDialog (parent, wxID_ANY, _("About DCP-o-matic"))
 {
        wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
        
@@ -38,15 +38,15 @@ AboutDialog::AboutDialog (wxWindow* parent)
        wxFont version_font (*wxNORMAL_FONT);
        version_font.SetWeight (wxFONTWEIGHT_BOLD);
        
-       wxStaticText* t = new wxStaticText (this, wxID_ANY, _("DVD-o-matic"));
+       wxStaticText* t = new wxStaticText (this, wxID_ANY, _("DCP-o-matic"));
        t->SetFont (title_font);
        sizer->Add (t, wxSizerFlags().Centre().Border());
 
        wxString s;
-       if (strcmp (dvdomatic_git_commit, "release") == 0) {
-               t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1", dvdomatic_version)));
+       if (strcmp (dcpomatic_git_commit, "release") == 0) {
+               t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1", dcpomatic_version)));
        } else {
-               t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1 git %2", dvdomatic_version, dvdomatic_git_commit)));
+               t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1 git %2", dcpomatic_version, dcpomatic_git_commit)));
        }
        t->SetFont (version_font);
        sizer->Add (t, wxSizerFlags().Centre().Border());
@@ -62,8 +62,8 @@ AboutDialog::AboutDialog (wxWindow* parent)
 
        wxHyperlinkCtrl* h = new wxHyperlinkCtrl (
                this, wxID_ANY,
-               wxT ("www.carlh.net/software/dvdomatic"),
-               wxT ("http://www.carlh.net/software/dvdomatic")
+               wxT ("dcpomatic.com"),
+               wxT ("http://dcpomatic.com")
                );
 
        sizer->Add (h, wxSizerFlags().Centre().Border());
@@ -117,10 +117,6 @@ AboutDialog::AboutDialog (wxWindow* parent)
 
        sizer->Add (_notebook, wxSizerFlags().Centre().Border().Expand());
        
-#if 0  
-       info.SetWebSite (wxT ("http://carlh.net/software/dvdomatic"));
-#endif
-
        SetSizerAndFit (sizer);
 }
 
index 21e4e5940e4b100c089ec1e8f1175371c434aa17..22e09cc7a8c1988973c21011a2027f064803ad38 100644 (file)
 */
 
 #include <boost/filesystem.hpp>
+#include "lib/audio_analysis.h"
+#include "lib/film.h"
 #include "audio_dialog.h"
 #include "audio_plot.h"
-#include "audio_analysis.h"
-#include "film.h"
 #include "wx_util.h"
 
 using boost::shared_ptr;
@@ -43,7 +43,6 @@ AudioDialog::AudioDialog (wxWindow* parent)
                wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Channels"));
                side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
        }
-       
 
        for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
                _channel_checkbox[i] = new wxCheckBox (this, wxID_ANY, std_to_wx (audio_channel_name (i)));
@@ -84,62 +83,39 @@ AudioDialog::AudioDialog (wxWindow* parent)
 }
 
 void
-AudioDialog::set_film (boost::shared_ptr<Film> f)
+AudioDialog::set_content (shared_ptr<AudioContent> c)
 {
-       _film_changed_connection.disconnect ();
-       _film_audio_analysis_succeeded_connection.disconnect ();
+       _content_changed_connection.disconnect ();
        
-       _film = f;
+       _content = c;
 
        try_to_load_analysis ();
-       setup_channels ();
-       _plot->set_gain (_film->audio_gain ());
+       _plot->set_gain (_content->audio_gain ());
 
-       _film_changed_connection = _film->Changed.connect (bind (&AudioDialog::film_changed, this, _1));
-       _film_audio_analysis_succeeded_connection = _film->AudioAnalysisSucceeded.connect (bind (&AudioDialog::try_to_load_analysis, this));
+       _content_changed_connection = _content->Changed.connect (bind (&AudioDialog::content_changed, this, _2));
 
-       SetTitle (wxString::Format (_("DVD-o-matic audio - %s"), std_to_wx(_film->name()).data()));
+       SetTitle (wxString::Format (_("DCP-o-matic audio - %s"), std_to_wx(_content->file().filename().string()).data()));
 }
 
-void
-AudioDialog::setup_channels ()
-{
-       if (!_film->audio_stream()) {
-               return;
-       }
-
-       AudioMapping m (_film);
-       
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               if (m.dcp_to_source(static_cast<libdcp::Channel>(i))) {
-                       _channel_checkbox[i]->Show ();
-               } else {
-                       _channel_checkbox[i]->Hide ();
-               }
-       }
-}      
-
 void
 AudioDialog::try_to_load_analysis ()
 {
        shared_ptr<AudioAnalysis> a;
 
-       if (boost::filesystem::exists (_film->audio_analysis_path())) {
-               a.reset (new AudioAnalysis (_film->audio_analysis_path ()));
+       if (boost::filesystem::exists (_content->audio_analysis_path())) {
+               a.reset (new AudioAnalysis (_content->audio_analysis_path ()));
        } else {
                if (IsShown ()) {
-                       _film->analyse_audio ();
+                       _content->analyse_audio (bind (&AudioDialog::try_to_load_analysis, this));
                }
        }
                
        _plot->set_analysis (a);
 
-       AudioMapping m (_film);
-       optional<libdcp::Channel> c = m.source_to_dcp (0);
-       if (c) {
-               _channel_checkbox[c.get()]->SetValue (true);
-               _plot->set_channel_visible (0, true);
+       if (_channel_checkbox[0]) {
+               _channel_checkbox[0]->SetValue (true);
        }
+       _plot->set_channel_visible (0, true);
 
        for (int i = 0; i < AudioPoint::COUNT; ++i) {
                _type_checkbox[i]->SetValue (true);
@@ -157,27 +133,14 @@ AudioDialog::channel_clicked (wxCommandEvent& ev)
 
        assert (c < MAX_AUDIO_CHANNELS);
 
-       AudioMapping m (_film);
-       optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (c));
-       if (s) {
-               _plot->set_channel_visible (s.get(), _channel_checkbox[c]->GetValue ());
-       }
+       _plot->set_channel_visible (c, _channel_checkbox[c]->GetValue ());
 }
 
 void
-AudioDialog::film_changed (Film::Property p)
+AudioDialog::content_changed (int p)
 {
-       switch (p) {
-       case Film::AUDIO_GAIN:
-               _plot->set_gain (_film->audio_gain ());
-               break;
-       case Film::CONTENT_AUDIO_STREAM:
-       case Film::EXTERNAL_AUDIO:
-       case Film::USE_CONTENT_AUDIO:
-               setup_channels ();
-               break;
-       default:
-               break;
+       if (p == AudioContentProperty::AUDIO_GAIN) {
+               _plot->set_gain (_content->audio_gain ());
        }
 }
 
index 514faeea0e952ff83c716392d3aacf5a5514d2ee..c69baf282fc1f893a2a992d0b2cff717d5a6f090 100644 (file)
@@ -31,21 +31,19 @@ class AudioDialog : public wxDialog
 public:
        AudioDialog (wxWindow *);
 
-       void set_film (boost::shared_ptr<Film>);
+       void set_content (boost::shared_ptr<AudioContent>);
 
 private:
-       void film_changed (Film::Property);
+       void content_changed (int);
        void channel_clicked (wxCommandEvent &);
        void type_clicked (wxCommandEvent &);
        void smoothing_changed (wxScrollEvent &);
        void try_to_load_analysis ();
-       void setup_channels ();
 
-       boost::shared_ptr<Film> _film;
+       boost::shared_ptr<AudioContent> _content;
        AudioPlot* _plot;
        wxCheckBox* _channel_checkbox[MAX_AUDIO_CHANNELS];
        wxCheckBox* _type_checkbox[AudioPoint::COUNT];
        wxSlider* _smoothing;
-       boost::signals2::scoped_connection _film_changed_connection;
-       boost::signals2::scoped_connection _film_audio_analysis_succeeded_connection;
+       boost::signals2::scoped_connection _content_changed_connection;
 };
diff --git a/src/wx/audio_mapping_view.cc b/src/wx/audio_mapping_view.cc
new file mode 100644 (file)
index 0000000..3ea0cdd
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+    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/wx.h>
+#include <wx/renderer.h>
+#include <wx/grid.h>
+#include <libdcp/types.h>
+#include "lib/audio_mapping.h"
+#include "audio_mapping_view.h"
+#include "wx_util.h"
+
+using std::cout;
+using std::list;
+using boost::shared_ptr;
+
+/* This could go away with wxWidgets 2.9, which has an API call
+   to find these values.
+*/
+
+#ifdef __WXMSW__
+#define CHECKBOX_WIDTH 16
+#define CHECKBOX_HEIGHT 16
+#endif
+
+#ifdef __WXGTK__
+#define CHECKBOX_WIDTH 20
+#define CHECKBOX_HEIGHT 20
+#endif
+
+
+class NoSelectionStringRenderer : public wxGridCellStringRenderer
+{
+public:
+       void Draw (wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool)
+       {
+               wxGridCellStringRenderer::Draw (grid, attr, dc, rect, row, col, false);
+       }
+};
+
+class CheckBoxRenderer : public wxGridCellRenderer
+{
+public:
+
+       void Draw (wxGrid& grid, wxGridCellAttr &, wxDC& dc, const wxRect& rect, int row, int col, bool)
+       {
+#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
+               dc.SetPen (*wxThePenList->FindOrCreatePen (wxColour (255, 255, 255), 0, wxPENSTYLE_SOLID));
+#else          
+               dc.SetPen (*wxThePenList->FindOrCreatePen (wxColour (255, 255, 255), 0, wxSOLID));
+#endif         
+               dc.DrawRectangle (rect);
+               
+               wxRendererNative::Get().DrawCheckBox (
+                       &grid,
+                       dc, rect,
+                       grid.GetCellValue (row, col) == wxT("1") ? static_cast<int>(wxCONTROL_CHECKED) : 0
+                       );
+       }
+
+       wxSize GetBestSize (wxGrid &, wxGridCellAttr &, wxDC &, int, int)
+       {
+               return wxSize (CHECKBOX_WIDTH + 4, CHECKBOX_HEIGHT + 4);
+       }
+       
+       wxGridCellRenderer* Clone () const
+       {
+               return new CheckBoxRenderer;
+       }
+};
+
+
+AudioMappingView::AudioMappingView (wxWindow* parent)
+       : wxPanel (parent, wxID_ANY)
+{
+       _grid = new wxGrid (this, wxID_ANY);
+
+       _grid->CreateGrid (0, 7);
+#if wxMINOR_VERSION == 9
+       _grid->HideRowLabels ();
+#else
+       _grid->SetRowLabelSize (0);
+#endif 
+       _grid->DisableDragRowSize ();
+       _grid->DisableDragColSize ();
+       _grid->EnableEditing (false);
+       _grid->SetCellHighlightPenWidth (0);
+       _grid->SetDefaultRenderer (new NoSelectionStringRenderer);
+
+       _grid->SetColLabelValue (0, _("Content channel"));
+       _grid->SetColLabelValue (1, _("L"));
+       _grid->SetColLabelValue (2, _("R"));
+       _grid->SetColLabelValue (3, _("C"));
+       _grid->SetColLabelValue (4, _("Lfe"));
+       _grid->SetColLabelValue (5, _("Ls"));
+       _grid->SetColLabelValue (6, _("Rs"));
+
+       _grid->AutoSize ();
+
+       _sizer = new wxBoxSizer (wxVERTICAL);
+       _sizer->Add (_grid, 1, wxEXPAND | wxALL);
+       SetSizerAndFit (_sizer);
+
+       Connect (wxID_ANY, wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler (AudioMappingView::left_click), 0, this);
+}
+
+void
+AudioMappingView::left_click (wxGridEvent& ev)
+{
+       if (ev.GetCol() == 0) {
+               return;
+       }
+       
+       if (_grid->GetCellValue (ev.GetRow(), ev.GetCol()) == wxT("1")) {
+               _grid->SetCellValue (ev.GetRow(), ev.GetCol(), wxT("0"));
+       } else {
+               _grid->SetCellValue (ev.GetRow(), ev.GetCol(), wxT("1"));
+       }
+
+       AudioMapping mapping;
+       for (int i = 0; i < _grid->GetNumberRows(); ++i) {
+               for (int j = 1; j < _grid->GetNumberCols(); ++j) {
+                       if (_grid->GetCellValue (i, j) == wxT ("1")) {
+                               mapping.add (i, static_cast<libdcp::Channel> (j - 1));
+                       }
+               }
+       }
+
+       Changed (mapping);
+}
+
+void
+AudioMappingView::set (AudioMapping map)
+{
+       if (_grid->GetNumberRows ()) {
+               _grid->DeleteRows (0, _grid->GetNumberRows ());
+       }
+
+       list<int> content_channels = map.content_channels ();
+       _grid->InsertRows (0, content_channels.size ());
+
+       for (size_t r = 0; r < content_channels.size(); ++r) {
+               for (int c = 1; c < 7; ++c) {
+                       _grid->SetCellRenderer (r, c, new CheckBoxRenderer);
+               }
+       }
+       
+       int n = 0;
+       for (list<int>::iterator i = content_channels.begin(); i != content_channels.end(); ++i) {
+               _grid->SetCellValue (n, 0, wxString::Format (wxT("%d"), *i + 1));
+
+               list<libdcp::Channel> const d = map.content_to_dcp (*i);
+               for (list<libdcp::Channel>::const_iterator j = d.begin(); j != d.end(); ++j) {
+                       _grid->SetCellValue (n, static_cast<int> (*j) + 1, wxT("1"));
+               }
+               ++n;
+       }
+}
+
diff --git a/src/wx/audio_mapping_view.h b/src/wx/audio_mapping_view.h
new file mode 100644 (file)
index 0000000..e0709c8
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/signals2.hpp>
+#include <wx/wx.h>
+#include <wx/grid.h>
+#include "lib/audio_mapping.h"
+
+class AudioMappingView : public wxPanel
+{
+public:
+       AudioMappingView (wxWindow *);
+
+       void set (AudioMapping);
+
+       boost::signals2::signal<void (AudioMapping)> Changed;
+
+private:
+       void left_click (wxGridEvent &);
+
+       wxGrid* _grid;
+       wxSizer* _sizer;
+};
index 3fec1d3fe3dec0f2e2af8026e94c1e9aed497f20..e2e2cdf764026eddc9a8f73b3728698aaf051bb2 100644 (file)
@@ -21,7 +21,6 @@
 #include <boost/bind.hpp>
 #include <wx/graphics.h>
 #include "audio_plot.h"
-#include "lib/decoder_factory.h"
 #include "lib/audio_decoder.h"
 #include "lib/audio_analysis.h"
 #include "wx/wx_util.h"
@@ -38,7 +37,7 @@ int const AudioPlot::_minimum = -70;
 int const AudioPlot::max_smoothing = 128;
 
 AudioPlot::AudioPlot (wxWindow* parent)
-       : wxPanel (parent)
+       : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
        , _gain (0)
        , _smoothing (max_smoothing / 2)
 {
@@ -192,6 +191,10 @@ AudioPlot::y_for_linear (float p) const
 void
 AudioPlot::plot_peak (wxGraphicsPath& path, int channel) const
 {
+       if (_analysis->points (channel) == 0) {
+               return;
+       }
+       
        path.MoveToPoint (_db_label_width, y_for_linear (_analysis->get_point(channel, 0)[AudioPoint::PEAK]));
 
        float peak = 0;
@@ -212,6 +215,10 @@ AudioPlot::plot_peak (wxGraphicsPath& path, int channel) const
 void
 AudioPlot::plot_rms (wxGraphicsPath& path, int channel) const
 {
+       if (_analysis->points (channel) == 0) {
+               return;
+       }
+       
        path.MoveToPoint (_db_label_width, y_for_linear (_analysis->get_point(channel, 0)[AudioPoint::RMS]));
 
        list<float> smoothing;
index e622c331b647b91033995e04c3a167919919c3e4..e66be174d003045b1bc8807a8f21e14935e81a99 100644 (file)
@@ -28,7 +28,7 @@
 #include <wx/notebook.h>
 #include "lib/config.h"
 #include "lib/server.h"
-#include "lib/format.h"
+#include "lib/ratio.h"
 #include "lib/scaler.h"
 #include "lib/filter.h"
 #include "lib/dcp_content_type.h"
@@ -57,8 +57,6 @@ ConfigDialog::ConfigDialog (wxWindow* parent)
        _notebook->AddPage (_metadata_panel, _("Metadata"), false);
        make_tms_panel ();
        _notebook->AddPage (_tms_panel, _("TMS"), false);
-       make_ab_panel ();
-       _notebook->AddPage (_ab_panel, _("A/B mode"), false);
 
        wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
        overall_sizer->Add (s, 1, wxEXPAND | wxALL, 6);
@@ -80,7 +78,7 @@ ConfigDialog::make_misc_panel ()
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
        _misc_panel->SetSizer (s);
 
-       wxFlexGridSizer* table = new wxFlexGridSizer (3, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+       wxFlexGridSizer* table = new wxFlexGridSizer (3, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
        s->Add (table, 1, wxALL | wxEXPAND, 8);
 
@@ -108,8 +106,13 @@ ConfigDialog::make_misc_panel ()
        table->Add (_num_local_encoding_threads, 1);
        table->AddSpacer (0);
 
+       add_label_to_sizer (table, _misc_panel, _("Default duration of still images"), true);
+       _default_still_length = new wxSpinCtrl (_misc_panel);
+       table->Add (_default_still_length, 1, wxEXPAND);
+       add_label_to_sizer (table, _misc_panel, _("s"), false);
+
        add_label_to_sizer (table, _misc_panel, _("Default directory for new films"), true);
-#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
        _default_directory = new DirPickerCtrl (_misc_panel);
 #else  
        _default_directory = new wxDirPickerCtrl (_misc_panel, wxDD_DIR_MUST_EXIST);
@@ -122,9 +125,9 @@ ConfigDialog::make_misc_panel ()
        table->Add (_default_dci_metadata_button);
        table->AddSpacer (1);
 
-       add_label_to_sizer (table, _misc_panel, _("Default format"), true);
-       _default_format = new wxChoice (_misc_panel, wxID_ANY);
-       table->Add (_default_format);
+       add_label_to_sizer (table, _misc_panel, _("Default container"), true);
+       _default_container = new wxChoice (_misc_panel, wxID_ANY);
+       table->Add (_default_container);
        table->AddSpacer (1);
 
        add_label_to_sizer (table, _misc_panel, _("Default content type"), true);
@@ -157,22 +160,26 @@ ConfigDialog::make_misc_panel ()
        _num_local_encoding_threads->SetValue (config->num_local_encoding_threads ());
        _num_local_encoding_threads->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ConfigDialog::num_local_encoding_threads_changed), 0, this);
 
+       _default_still_length->SetRange (1, 3600);
+       _default_still_length->SetValue (config->default_still_length ());
+       _default_still_length->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ConfigDialog::default_still_length_changed), 0, this);
+
        _default_directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir()))));
        _default_directory->Connect (wxID_ANY, wxEVT_COMMAND_DIRPICKER_CHANGED, wxCommandEventHandler (ConfigDialog::default_directory_changed), 0, this);
 
        _default_dci_metadata_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_default_dci_metadata_clicked), 0, this);
 
-       vector<Format const *> fmt = Format::all ();
+       vector<Ratio const *> ratio = Ratio::all ();
        int n = 0;
-       for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
-               _default_format->Append (std_to_wx ((*i)->name ()));
-               if (*i == config->default_format ()) {
-                       _default_format->SetSelection (n);
+       for (vector<Ratio const *>::iterator i = ratio.begin(); i != ratio.end(); ++i) {
+               _default_container->Append (std_to_wx ((*i)->nickname ()));
+               if (*i == config->default_container ()) {
+                       _default_container->SetSelection (n);
                }
                ++n;
        }
 
-       _default_format->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::default_format_changed), 0, this);
+       _default_container->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::default_container_changed), 0, this);
        
        vector<DCPContentType const *> const ct = DCPContentType::all ();
        n = 0;
@@ -194,7 +201,7 @@ ConfigDialog::make_tms_panel ()
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
        _tms_panel->SetSizer (s);
 
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
        s->Add (table, 1, wxALL | wxEXPAND, 8);
 
@@ -233,7 +240,7 @@ ConfigDialog::make_metadata_panel ()
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
        _metadata_panel->SetSizer (s);
 
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
        s->Add (table, 1, wxALL | wxEXPAND, 8);
 
@@ -253,48 +260,6 @@ ConfigDialog::make_metadata_panel ()
        _creator->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::creator_changed), 0, this);
 }
 
-void
-ConfigDialog::make_ab_panel ()
-{
-       _ab_panel = new wxPanel (_notebook);
-       wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-       _ab_panel->SetSizer (s);
-
-       wxFlexGridSizer* table = new wxFlexGridSizer (3, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
-       table->AddGrowableCol (1, 1);
-       s->Add (table, 1, wxALL, 8);
-       
-       add_label_to_sizer (table, _ab_panel, _("Reference scaler"), true);
-       _reference_scaler = new wxChoice (_ab_panel, wxID_ANY);
-       vector<Scaler const *> const sc = Scaler::all ();
-       for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
-               _reference_scaler->Append (std_to_wx ((*i)->name ()));
-       }
-
-       table->Add (_reference_scaler, 1, wxEXPAND);
-       table->AddSpacer (0);
-
-       {
-               add_label_to_sizer (table, _ab_panel, _("Reference filters"), true);
-               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _reference_filters = new wxStaticText (_ab_panel, wxID_ANY, wxT (""));
-               s->Add (_reference_filters, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 6);
-               _reference_filters_button = new wxButton (_ab_panel, wxID_ANY, _("Edit..."));
-               s->Add (_reference_filters_button, 0);
-               table->Add (s, 1, wxEXPAND);
-               table->AddSpacer (0);
-       }
-
-       Config* config = Config::instance ();
-       
-       _reference_scaler->SetSelection (Scaler::as_index (config->reference_scaler ()));
-       _reference_scaler->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::reference_scaler_changed), 0, this);
-
-       pair<string, string> p = Filter::ffmpeg_strings (config->reference_filters ());
-       _reference_filters->SetLabel (std_to_wx (p.first) + N_(" ") + std_to_wx (p.second));
-       _reference_filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_reference_filters_clicked), 0, this);
-}
-
 void
 ConfigDialog::make_servers_panel ()
 {
@@ -302,7 +267,7 @@ ConfigDialog::make_servers_panel ()
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
        _servers_panel->SetSizer (s);
 
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (0, 1);
        s->Add (table, 1, wxALL | wxEXPAND, 8);
 
@@ -474,32 +439,6 @@ ConfigDialog::server_selection_changed (wxListEvent &)
        _remove_server->Enable (i >= 0);
 }
 
-void
-ConfigDialog::reference_scaler_changed (wxCommandEvent &)
-{
-       int const n = _reference_scaler->GetSelection ();
-       if (n >= 0) {
-               Config::instance()->set_reference_scaler (Scaler::from_index (n));
-       }
-}
-
-void
-ConfigDialog::edit_reference_filters_clicked (wxCommandEvent &)
-{
-       FilterDialog* d = new FilterDialog (this, Config::instance()->reference_filters ());
-       d->ActiveChanged.connect (boost::bind (&ConfigDialog::reference_filters_changed, this, _1));
-       d->ShowModal ();
-       d->Destroy ();
-}
-
-void
-ConfigDialog::reference_filters_changed (vector<Filter const *> f)
-{
-       Config::instance()->set_reference_filters (f);
-       pair<string, string> p = Filter::ffmpeg_strings (Config::instance()->reference_filters ());
-       _reference_filters->SetLabel (std_to_wx (p.first) + N_(" ") + std_to_wx (p.second));
-}
-
 void
 ConfigDialog::edit_default_dci_metadata_clicked (wxCommandEvent &)
 {
@@ -527,10 +466,16 @@ ConfigDialog::setup_language_sensitivity ()
 }
 
 void
-ConfigDialog::default_format_changed (wxCommandEvent &)
+ConfigDialog::default_still_length_changed (wxCommandEvent &)
+{
+       Config::instance()->set_default_still_length (_default_still_length->GetValue ());
+}
+
+void
+ConfigDialog::default_container_changed (wxCommandEvent &)
 {
-       vector<Format const *> fmt = Format::all ();
-       Config::instance()->set_default_format (fmt[_default_format->GetSelection()]);
+       vector<Ratio const *> ratio = Ratio::all ();
+       Config::instance()->set_default_container (ratio[_default_container->GetSelection()]);
 }
 
 void
index a97788942654fdf6e560750094775f86c1efbda0..459d64dd701327c2e8bd07f6f5f9e027aae58ebd 100644 (file)
@@ -48,16 +48,14 @@ private:
        void tms_user_changed (wxCommandEvent &);
        void tms_password_changed (wxCommandEvent &);
        void num_local_encoding_threads_changed (wxCommandEvent &);
+       void default_still_length_changed (wxCommandEvent &);
        void default_directory_changed (wxCommandEvent &);
        void edit_default_dci_metadata_clicked (wxCommandEvent &);
-       void reference_scaler_changed (wxCommandEvent &);
-       void edit_reference_filters_clicked (wxCommandEvent &);
-       void reference_filters_changed (std::vector<Filter const *>);
        void add_server_clicked (wxCommandEvent &);
        void edit_server_clicked (wxCommandEvent &);
        void remove_server_clicked (wxCommandEvent &);
        void server_selection_changed (wxListEvent &);
-       void default_format_changed (wxCommandEvent &);
+       void default_container_changed (wxCommandEvent &);
        void default_dcp_content_type_changed (wxCommandEvent &);
        void issuer_changed (wxCommandEvent &);
        void creator_changed (wxCommandEvent &);
@@ -68,33 +66,29 @@ private:
        void make_misc_panel ();
        void make_tms_panel ();
        void make_metadata_panel ();
-       void make_ab_panel ();
        void make_servers_panel ();
 
        wxNotebook* _notebook;
        wxPanel* _misc_panel;
        wxPanel* _tms_panel;
-       wxPanel* _ab_panel;
        wxPanel* _servers_panel;
        wxPanel* _metadata_panel;
        wxCheckBox* _set_language;
        wxChoice* _language;
-       wxChoice* _default_format;
+       wxChoice* _default_container;
        wxChoice* _default_dcp_content_type;
        wxTextCtrl* _tms_ip;
        wxTextCtrl* _tms_path;
        wxTextCtrl* _tms_user;
        wxTextCtrl* _tms_password;
        wxSpinCtrl* _num_local_encoding_threads;
-#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
+       wxSpinCtrl* _default_still_length;
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
        DirPickerCtrl* _default_directory;
 #else
        wxDirPickerCtrl* _default_directory;
 #endif
        wxButton* _default_dci_metadata_button;
-       wxChoice* _reference_scaler;
-       wxStaticText* _reference_filters;
-       wxButton* _reference_filters_button;
        wxListCtrl* _servers;
        wxButton* _add_server;
        wxButton* _edit_server;
index 9c124ed74704cafaf70ff4be809fb093af731509..df3a24f626aa786f74aaab693de2199ab04036d9 100644 (file)
@@ -27,7 +27,7 @@ using boost::shared_ptr;
 DCIMetadataDialog::DCIMetadataDialog (wxWindow* parent, DCIMetadata dm)
        : wxDialog (parent, wxID_ANY, _("DCI name"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
 {
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
 
        add_label_to_sizer (table, this, _("Audio Language (e.g. EN)"), true);
index a54b412b30ff51b24171d3516d924f193315bc6b..2d8c752a1229883cfd313031e9eb627805d98bb8 100644 (file)
 #include <iomanip>
 #include <wx/wx.h>
 #include <wx/notebook.h>
+#include <wx/listctrl.h>
 #include <boost/thread.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/lexical_cast.hpp>
-#include "lib/format.h"
 #include "lib/film.h"
 #include "lib/transcode_job.h"
 #include "lib/exceptions.h"
-#include "lib/ab_transcode_job.h"
 #include "lib/job_manager.h"
 #include "lib/filter.h"
+#include "lib/ratio.h"
 #include "lib/config.h"
-#include "lib/ffmpeg_decoder.h"
-#include "lib/log.h"
+#include "lib/imagemagick_content.h"
+#include "lib/sndfile_content.h"
+#include "lib/dcp_content_type.h"
 #include "filter_dialog.h"
 #include "wx_util.h"
 #include "film_editor.h"
 #include "dci_metadata_dialog.h"
 #include "scaler.h"
 #include "audio_dialog.h"
+#include "imagemagick_content_dialog.h"
+#include "timeline_dialog.h"
+#include "audio_mapping_view.h"
+#include "timecode.h"
 
 using std::string;
 using std::cout;
@@ -55,60 +60,60 @@ using std::fixed;
 using std::setprecision;
 using std::list;
 using std::vector;
+using std::max;
 using boost::shared_ptr;
+using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
+using boost::lexical_cast;
 
 /** @param f Film to edit */
 FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
        : wxPanel (parent)
-       , _film (f)
        , _generally_sensitive (true)
        , _audio_dialog (0)
+       , _timeline_dialog (0)
 {
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-       SetSizer (s);
-       _notebook = new wxNotebook (this, wxID_ANY);
-       s->Add (_notebook, 1);
 
-       make_film_panel ();
-       _notebook->AddPage (_film_panel, _("Film"), true);
-       make_video_panel ();
-       _notebook->AddPage (_video_panel, _("Video"), false);
-       make_audio_panel ();
-       _notebook->AddPage (_audio_panel, _("Audio"), false);
-       make_subtitle_panel ();
-       _notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
+       _main_notebook = new wxNotebook (this, wxID_ANY);
+       s->Add (_main_notebook, 1);
+
+       make_content_panel ();
+       _main_notebook->AddPage (_content_panel, _("Content"), true);
+       make_dcp_panel ();
+       _main_notebook->AddPage (_dcp_panel, _("DCP"), false);
+       
+       setup_ratios ();
 
-       set_film (_film);
+       set_film (f);
        connect_to_widgets ();
 
        JobManager::instance()->ActiveJobsChanged.connect (
                bind (&FilmEditor::active_jobs_changed, this, _1)
                );
        
-       setup_visibility ();
-       setup_formats ();
+       SetSizerAndFit (s);
 }
 
 void
-FilmEditor::make_film_panel ()
+FilmEditor::make_dcp_panel ()
 {
-       _film_panel = new wxPanel (_notebook);
-       _film_sizer = new wxBoxSizer (wxVERTICAL);
-       _film_panel->SetSizer (_film_sizer);
+       _dcp_panel = new wxPanel (_main_notebook);
+       _dcp_sizer = new wxBoxSizer (wxVERTICAL);
+       _dcp_panel->SetSizer (_dcp_sizer);
 
-       wxGridBagSizer* grid = new wxGridBagSizer (DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
-       _film_sizer->Add (grid, 0, wxALL, 8);
+       wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       _dcp_sizer->Add (grid, 0, wxEXPAND | wxALL, 8);
 
        int r = 0;
        
-       add_label_to_grid_bag_sizer (grid, _film_panel, _("Name"), true, wxGBPosition (r, 0));
-       _name = new wxTextCtrl (_film_panel, wxID_ANY);
+       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Name"), true, wxGBPosition (r, 0));
+       _name = new wxTextCtrl (_dcp_panel, wxID_ANY);
        grid->Add (_name, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND | wxLEFT | wxRIGHT);
        ++r;
        
-       add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Name"), true, wxGBPosition (r, 0));
-       _dcp_name = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
+       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Name"), true, wxGBPosition (r, 0));
+       _dcp_name = new wxStaticText (_dcp_panel, wxID_ANY, wxT (""));
        grid->Add (_dcp_name, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
        ++r;
 
@@ -116,94 +121,58 @@ FilmEditor::make_film_panel ()
 #ifdef __WXOSX__
        flags |= wxALIGN_RIGHT;
 #endif 
-       
-       _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, _("Use DCI name"));
+
+       _use_dci_name = new wxCheckBox (_dcp_panel, wxID_ANY, _("Use DCI name"));
        grid->Add (_use_dci_name, wxGBPosition (r, 0), wxDefaultSpan, flags);
-       _edit_dci_button = new wxButton (_film_panel, wxID_ANY, _("Details..."));
+       _edit_dci_button = new wxButton (_dcp_panel, wxID_ANY, _("Details..."));
        grid->Add (_edit_dci_button, wxGBPosition (r, 1), wxDefaultSpan);
        ++r;
 
-       add_label_to_grid_bag_sizer (grid, _film_panel, _("Content"), true, wxGBPosition (r, 0));
-       _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), _("Select Content File"), wxT("*.*"));
-       grid->Add (_content, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
-       ++r;
-
-       _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, _("Trust content's header"));
-       video_control (_trust_content_header);
-       grid->Add (_trust_content_header, wxGBPosition (r, 0), wxGBSpan(1, 2));
+       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Container"), true, wxGBPosition (r, 0));
+       _container = new wxChoice (_dcp_panel, wxID_ANY);
+       grid->Add (_container, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
        ++r;
 
-       add_label_to_grid_bag_sizer (grid, _film_panel, _("Content Type"), true, wxGBPosition (r, 0));
-       _dcp_content_type = new wxChoice (_film_panel, wxID_ANY);
+       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Content Type"), true, wxGBPosition (r, 0));
+       _dcp_content_type = new wxChoice (_dcp_panel, wxID_ANY);
        grid->Add (_dcp_content_type, wxGBPosition (r, 1));
        ++r;
 
-       video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Original Frame Rate"), true, wxGBPosition (r, 0)));
-       _source_frame_rate = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
-       grid->Add (video_control (_source_frame_rate), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
-       ++r;
-
        {
-               add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Frame Rate"), true, wxGBPosition (r, 0));
+               add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Frame Rate"), true, wxGBPosition (r, 0));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _dcp_frame_rate = new wxChoice (_film_panel, wxID_ANY);
+               _dcp_frame_rate = new wxChoice (_dcp_panel, wxID_ANY);
                s->Add (_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL);
-               _best_dcp_frame_rate = new wxButton (_film_panel, wxID_ANY, _("Use best"));
-               s->Add (_best_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 6);
+               _best_dcp_frame_rate = new wxButton (_dcp_panel, wxID_ANY, _("Use best"));
+               s->Add (_best_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND);
                grid->Add (s, wxGBPosition (r, 1));
        }
        ++r;
 
-       _frame_rate_description = new wxStaticText (_film_panel, wxID_ANY, wxT ("\n \n "), wxDefaultPosition, wxDefaultSize);
-       grid->Add (video_control (_frame_rate_description), wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
-       wxFont font = _frame_rate_description->GetFont();
-       font.SetStyle(wxFONTSTYLE_ITALIC);
-       font.SetPointSize(font.GetPointSize() - 1);
-       _frame_rate_description->SetFont(font);
-       ++r;
-       
-       video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Length"), true, wxGBPosition (r, 0)));
-       _length = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
-       grid->Add (video_control (_length), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
-       ++r;
-
-
        {
-               video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim frames"), true, wxGBPosition (r, 0)));
-               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               video_control (add_label_to_sizer (s, _film_panel, _("Start"), true));
-               _trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
-               s->Add (video_control (_trim_start));
-               video_control (add_label_to_sizer (s, _film_panel, _("End"), true));
-               _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
-               s->Add (video_control (_trim_end));
-
+               add_label_to_grid_bag_sizer (grid, _dcp_panel, _("JPEG2000 bandwidth"), true, wxGBPosition (r, 0));
+               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _j2k_bandwidth = new wxSpinCtrl (_dcp_panel, wxID_ANY);
+               s->Add (_j2k_bandwidth, 1);
+               add_label_to_sizer (s, _dcp_panel, _("MBps"), false);
                grid->Add (s, wxGBPosition (r, 1));
        }
        ++r;
 
-       video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim method"), true, wxGBPosition (r, 0)));
-       _trim_type = new wxChoice (_film_panel, wxID_ANY);
-       grid->Add (video_control (_trim_type), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
+       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Scaler"), true, wxGBPosition (r, 0));
+       _scaler = new wxChoice (_dcp_panel, wxID_ANY);
+       grid->Add (_scaler, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
        ++r;
 
-       _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B"));
-       video_control (_dcp_ab);
-       grid->Add (_dcp_ab, wxGBPosition (r, 0));
-       ++r;
+       vector<Scaler const *> const sc = Scaler::all ();
+       for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
+               _scaler->Append (std_to_wx ((*i)->name()));
+       }
 
-       /* STILL-only stuff */
-       {
-               still_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Duration"), true, wxGBPosition (r, 0)));
-               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _still_duration = new wxSpinCtrl (_film_panel);
-               still_control (_still_duration);
-               s->Add (_still_duration, 1, wxEXPAND);
-               /// TRANSLATORS: `s' here is an abbreviation for seconds, the unit of time
-               still_control (add_label_to_sizer (s, _film_panel, _("s"), false));
-               grid->Add (s, wxGBPosition (r, 1));
+       vector<Ratio const *> const ratio = Ratio::all ();
+       for (vector<Ratio const *>::const_iterator i = ratio.begin(); i != ratio.end(); ++i) {
+               _container->Append (std_to_wx ((*i)->nickname ()));
        }
-       ++r;
 
        vector<DCPContentType const *> const ct = DCPContentType::all ();
        for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
@@ -215,73 +184,64 @@ FilmEditor::make_film_panel ()
                _dcp_frame_rate->Append (std_to_wx (boost::lexical_cast<string> (*i)));
        }
 
-       _trim_type->Append (_("encode all frames and play the subset"));
-       _trim_type->Append (_("encode only the subset"));
+       _j2k_bandwidth->SetRange (50, 250);
 }
 
 void
 FilmEditor::connect_to_widgets ()
 {
-       _name->Connect                 (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED,         wxCommandEventHandler (FilmEditor::name_changed), 0, this);
-       _use_dci_name->Connect         (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this);
-       _edit_dci_button->Connect      (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
-       _format->Connect               (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::format_changed), 0, this);
-       _content->Connect              (wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED,   wxCommandEventHandler (FilmEditor::content_changed), 0, this);
-       _trust_content_header->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::trust_content_header_changed), 0, this);
-       _left_crop->Connect            (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
-       _right_crop->Connect           (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
-       _top_crop->Connect             (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
-       _bottom_crop->Connect          (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this);
-       _filters_button->Connect       (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this);
-       _scaler->Connect               (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::scaler_changed), 0, this);
-       _dcp_content_type->Connect     (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
-       _dcp_frame_rate->Connect       (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::dcp_frame_rate_changed), 0, this);
-       _best_dcp_frame_rate->Connect  (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::best_dcp_frame_rate_clicked), 0, this);
-       _pad_with_silence->Connect     (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::pad_with_silence_toggled), 0, this);
-       _minimum_audio_channels->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,   wxCommandEventHandler (FilmEditor::minimum_audio_channels_changed), 0, this);
-       _dcp_ab->Connect               (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this);
-       _still_duration->Connect       (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this);
-       _trim_start->Connect           (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::trim_start_changed), 0, this);
-       _trim_end->Connect             (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::trim_end_changed), 0, this);
-       _trim_type->Connect            (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::trim_type_changed), 0, this);
-       _with_subtitles->Connect       (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
-       _subtitle_offset->Connect      (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this);
-       _subtitle_scale->Connect       (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
-       _colour_lut->Connect           (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::colour_lut_changed), 0, this);
-       _j2k_bandwidth->Connect        (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::j2k_bandwidth_changed), 0, this);
-       _subtitle_stream->Connect      (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
-       _audio_stream->Connect         (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this);
-       _audio_gain->Connect           (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
+       _name->Connect                   (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED,         wxCommandEventHandler (FilmEditor::name_changed), 0, this);
+       _use_dci_name->Connect           (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this);
+       _edit_dci_button->Connect        (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
+       _container->Connect              (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::container_changed), 0, this);
+       _ratio->Connect                  (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::ratio_changed), 0, this);
+       _content->Connect                (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_SELECTED,   wxListEventHandler    (FilmEditor::content_selection_changed), 0, this);
+       _content->Connect                (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler    (FilmEditor::content_selection_changed), 0, this);
+       _content_add->Connect            (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_add_clicked), 0, this);
+       _content_remove->Connect         (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_remove_clicked), 0, this);
+       _content_timeline->Connect       (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_timeline_clicked), 0, this);
+       _loop_content->Connect           (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::loop_content_toggled), 0, this);
+       _loop_count->Connect             (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::loop_count_changed), 0, this);
+       _left_crop->Connect              (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
+       _right_crop->Connect             (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
+       _top_crop->Connect               (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
+       _bottom_crop->Connect            (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this);
+       _filters_button->Connect         (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this);
+       _scaler->Connect                 (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::scaler_changed), 0, this);
+       _dcp_content_type->Connect       (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
+       _dcp_frame_rate->Connect         (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::dcp_frame_rate_changed), 0, this);
+       _best_dcp_frame_rate->Connect    (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::best_dcp_frame_rate_clicked), 0, this);
+//     _pad_with_silence->Connect       (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::pad_with_silence_toggled), 0, this);
+//     _minimum_audio_channels->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,   wxCommandEventHandler (FilmEditor::minimum_audio_channels_changed), 0, this);
+       _with_subtitles->Connect         (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
+       _subtitle_offset->Connect        (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this);
+       _subtitle_scale->Connect         (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
+       _colour_lut->Connect             (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::colour_lut_changed), 0, this);
+       _j2k_bandwidth->Connect          (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::j2k_bandwidth_changed), 0, this);
+       _audio_gain->Connect             (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
        _audio_gain_calculate_button->Connect (
                wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this
                );
-       _show_audio->Connect           (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this);
-       _audio_delay->Connect          (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
-       _use_content_audio->Connect    (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
-       _use_external_audio->Connect   (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               _external_audio[i]->Connect (
-                       wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::external_audio_changed), 0, this
-                       );
-       }
+       _show_audio->Connect             (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this);
+       _audio_delay->Connect            (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
+       _audio_stream->Connect           (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this);
+       _subtitle_stream->Connect        (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
+       _audio_mapping->Changed.connect  (boost::bind (&FilmEditor::audio_mapping_changed, this, _1));
+       _start->Changed.connect          (boost::bind (&FilmEditor::start_changed, this));
+       _length->Changed.connect         (boost::bind (&FilmEditor::length_changed, this));
 }
 
 void
 FilmEditor::make_video_panel ()
 {
-       _video_panel = new wxPanel (_notebook);
-       _video_sizer = new wxBoxSizer (wxVERTICAL);
-       _video_panel->SetSizer (_video_sizer);
+       _video_panel = new wxPanel (_content_notebook);
+       wxBoxSizer* video_sizer = new wxBoxSizer (wxVERTICAL);
+       _video_panel->SetSizer (video_sizer);
        
-       wxGridBagSizer* grid = new wxGridBagSizer (DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
-       _video_sizer->Add (grid, 0, wxALL, 8);
+       wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       video_sizer->Add (grid, 0, wxALL, 8);
 
        int r = 0;
-       add_label_to_grid_bag_sizer (grid, _video_panel, _("Format"), true, wxGBPosition (r, 0));
-       _format = new wxChoice (_video_panel, wxID_ANY);
-       grid->Add (_format, wxGBPosition (r, 1));
-       ++r;
-
        add_label_to_grid_bag_sizer (grid, _video_panel, _("Left crop"), true, wxGBPosition (r, 0));
        _left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
        grid->Add (_left_crop, wxGBPosition (r, 1));
@@ -302,6 +262,11 @@ FilmEditor::make_video_panel ()
        grid->Add (_bottom_crop, wxGBPosition (r, 1));
        ++r;
 
+       add_label_to_grid_bag_sizer (grid, _video_panel, _("Scale to"), true, wxGBPosition (r, 0));
+       _ratio = new wxChoice (_video_panel, wxID_ANY);
+       grid->Add (_ratio, wxGBPosition (r, 1));
+       ++r;
+
        _scaling_description = new wxStaticText (_video_panel, wxID_ANY, wxT ("\n \n \n \n"), wxDefaultPosition, wxDefaultSize);
        grid->Add (_scaling_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
        wxFont font = _scaling_description->GetFont();
@@ -312,28 +277,16 @@ FilmEditor::make_video_panel ()
 
        /* VIDEO-only stuff */
        {
-               video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), true, wxGBPosition (r, 0)));
+               add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), true, wxGBPosition (r, 0));
                wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _filters = new wxStaticText (_video_panel, wxID_ANY, _("None"));
-               video_control (_filters);
                s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
                _filters_button = new wxButton (_video_panel, wxID_ANY, _("Edit..."));
-               video_control (_filters_button);
                s->Add (_filters_button, 0, wxALIGN_CENTER_VERTICAL);
                grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
        }
        ++r;
 
-       video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Scaler"), true, wxGBPosition (r, 0)));
-       _scaler = new wxChoice (_video_panel, wxID_ANY);
-       grid->Add (video_control (_scaler), wxGBPosition (r, 1));
-       ++r;
-
-       vector<Scaler const *> const sc = Scaler::all ();
-       for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
-               _scaler->Append (std_to_wx ((*i)->name()));
-       }
-
        add_label_to_grid_bag_sizer (grid, _video_panel, _("Colour look-up table"), true, wxGBPosition (r, 0));
        _colour_lut = new wxChoice (_video_panel, wxID_ANY);
        for (int i = 0; i < 2; ++i) {
@@ -343,62 +296,117 @@ FilmEditor::make_video_panel ()
        grid->Add (_colour_lut, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
        ++r;
 
-       {
-               add_label_to_grid_bag_sizer (grid, _video_panel, _("JPEG2000 bandwidth"), true, wxGBPosition (r, 0));
-               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _j2k_bandwidth = new wxSpinCtrl (_video_panel, wxID_ANY);
-               s->Add (_j2k_bandwidth, 1);
-               add_label_to_sizer (s, _video_panel, _("MBps"), false);
-               grid->Add (s, wxGBPosition (r, 1));
-       }
-       ++r;
-
        _left_crop->SetRange (0, 1024);
        _top_crop->SetRange (0, 1024);
        _right_crop->SetRange (0, 1024);
        _bottom_crop->SetRange (0, 1024);
-       _still_duration->SetRange (1, 60 * 60);
-       _trim_start->SetRange (0, 24 * 60 * 60);
-       _trim_end->SetRange (0, 24 * 60 * 60);
-       _j2k_bandwidth->SetRange (50, 250);
+}
+
+void
+FilmEditor::make_content_panel ()
+{
+       _content_panel = new wxPanel (_main_notebook);
+       _content_sizer = new wxBoxSizer (wxVERTICAL);
+       _content_panel->SetSizer (_content_sizer);
+
+        {
+                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+                
+                _content = new wxListCtrl (_content_panel, wxID_ANY, wxDefaultPosition, wxSize (320, 160), wxLC_REPORT | wxLC_NO_HEADER | wxLC_SINGLE_SEL);
+                s->Add (_content, 1, wxEXPAND | wxTOP | wxBOTTOM, 6);
+
+                _content->InsertColumn (0, wxT(""));
+               _content->SetColumnWidth (0, 512);
+
+                wxBoxSizer* b = new wxBoxSizer (wxVERTICAL);
+                _content_add = new wxButton (_content_panel, wxID_ANY, _("Add..."));
+                b->Add (_content_add, 1, wxEXPAND | wxLEFT | wxRIGHT);
+                _content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove"));
+                b->Add (_content_remove, 1, wxEXPAND | wxLEFT | wxRIGHT);
+               _content_timeline = new wxButton (_content_panel, wxID_ANY, _("Timeline..."));
+               b->Add (_content_timeline, 1, wxEXPAND | wxLEFT | wxRIGHT);
+
+                s->Add (b, 0, wxALL, 4);
+
+                _content_sizer->Add (s, 0.75, wxEXPAND | wxALL, 6);
+        }
+
+       wxBoxSizer* h = new wxBoxSizer (wxHORIZONTAL);
+       _loop_content = new wxCheckBox (_content_panel, wxID_ANY, _("Loop everything"));
+       h->Add (_loop_content, 0, wxALL, 6);
+       _loop_count = new wxSpinCtrl (_content_panel, wxID_ANY);
+       h->Add (_loop_count, 0, wxALL, 6);
+       add_label_to_sizer (h, _content_panel, _("times"), false);
+       _content_sizer->Add (h, 0, wxALL, 6);
+
+       _content_notebook = new wxNotebook (_content_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_LEFT);
+       _content_sizer->Add (_content_notebook, 1, wxEXPAND | wxTOP, 6);
+
+       make_video_panel ();
+       _content_notebook->AddPage (_video_panel, _("Video"), false);
+       make_audio_panel ();
+       _content_notebook->AddPage (_audio_panel, _("Audio"), false);
+       make_subtitle_panel ();
+       _content_notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
+       make_timing_panel ();
+       _content_notebook->AddPage (_timing_panel, _("Timing"), false);
+
+       _loop_count->SetRange (2, 1024);
 }
 
 void
 FilmEditor::make_audio_panel ()
 {
-       _audio_panel = new wxPanel (_notebook);
-       _audio_sizer = new wxBoxSizer (wxVERTICAL);
-       _audio_panel->SetSizer (_audio_sizer);
+       _audio_panel = new wxPanel (_content_notebook);
+       wxBoxSizer* audio_sizer = new wxBoxSizer (wxVERTICAL);
+       _audio_panel->SetSizer (audio_sizer);
        
-       wxFlexGridSizer* grid = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
-       _audio_sizer->Add (grid, 0, wxALL, 8);
+       wxFlexGridSizer* grid = new wxFlexGridSizer (3, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       audio_sizer->Add (grid, 0, wxALL, 8);
 
        _show_audio = new wxButton (_audio_panel, wxID_ANY, _("Show Audio..."));
        grid->Add (_show_audio, 1);
        grid->AddSpacer (0);
+       grid->AddSpacer (0);
 
+       add_label_to_sizer (grid, _audio_panel, _("Audio Gain"), true);
        {
-               video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Gain"), true));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _audio_gain = new wxSpinCtrl (_audio_panel);
-               s->Add (video_control (_audio_gain), 1);
-               video_control (add_label_to_sizer (s, _audio_panel, _("dB"), false));
-               _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate..."));
-               video_control (_audio_gain_calculate_button);
-               s->Add (_audio_gain_calculate_button, 1, wxEXPAND);
-               grid->Add (s);
+               s->Add (_audio_gain, 1);
+               add_label_to_sizer (s, _audio_panel, _("dB"), false);
+               grid->Add (s, 1);
        }
+       
+       _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate..."));
+       grid->Add (_audio_gain_calculate_button);
 
+       add_label_to_sizer (grid, _audio_panel, _("Audio Delay"), false);
        {
-               video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Delay"), true));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _audio_delay = new wxSpinCtrl (_audio_panel);
-               s->Add (video_control (_audio_delay), 1);
+               s->Add (_audio_delay, 1);
                /// TRANSLATORS: this is an abbreviation for milliseconds, the unit of time
-               video_control (add_label_to_sizer (s, _audio_panel, _("ms"), false));
+               add_label_to_sizer (s, _audio_panel, _("ms"), false);
                grid->Add (s);
        }
 
+       grid->AddSpacer (0);
+
+       add_label_to_sizer (grid, _audio_panel, _("Audio Stream"), true);
+       _audio_stream = new wxChoice (_audio_panel, wxID_ANY);
+       grid->Add (_audio_stream, 1);
+       _audio_description = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
+       grid->AddSpacer (0);
+       
+       grid->Add (_audio_description, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
+       grid->AddSpacer (0);
+       grid->AddSpacer (0);
+       
+       _audio_mapping = new AudioMappingView (_audio_panel);
+       audio_sizer->Add (_audio_mapping, 1, wxEXPAND | wxALL, 6);
+
+#if 0  
        {
                _pad_with_silence = new wxCheckBox (_audio_panel, wxID_ANY, _("Pad with silence to"));
                grid->Add (_pad_with_silence);
@@ -419,139 +427,117 @@ FilmEditor::make_audio_panel ()
                s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
                grid->Add (s);
        }
-
-       _use_external_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use external audio"));
-       grid->Add (_use_external_audio);
-       grid->AddSpacer (0);
-
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               add_label_to_sizer (grid, _audio_panel, std_to_wx (audio_channel_name (i)), true);
-               _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), _("Select Audio File"), wxT ("*.wav"));
-               grid->Add (_external_audio[i], 1, wxEXPAND);
-       }
+#endif 
 
        _audio_gain->SetRange (-60, 60);
        _audio_delay->SetRange (-1000, 1000);
-       _minimum_audio_channels->SetRange (0, MAX_AUDIO_CHANNELS);
+//     _minimum_audio_channels->SetRange (0, MAX_AUDIO_CHANNELS);
 }
 
 void
 FilmEditor::make_subtitle_panel ()
 {
-       _subtitle_panel = new wxPanel (_notebook);
-       _subtitle_sizer = new wxBoxSizer (wxVERTICAL);
-       _subtitle_panel->SetSizer (_subtitle_sizer);
-       wxFlexGridSizer* grid = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
-       _subtitle_sizer->Add (grid, 0, wxALL, 8);
+       _subtitle_panel = new wxPanel (_content_notebook);
+       wxBoxSizer* subtitle_sizer = new wxBoxSizer (wxVERTICAL);
+       _subtitle_panel->SetSizer (subtitle_sizer);
+       wxFlexGridSizer* grid = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       subtitle_sizer->Add (grid, 0, wxALL, 8);
 
        _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, _("With Subtitles"));
-       video_control (_with_subtitles);
        grid->Add (_with_subtitles, 1);
+       grid->AddSpacer (0);
        
-       _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
-       grid->Add (video_control (_subtitle_stream));
-
        {
-               video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"), true));
+               add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"), true);
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _subtitle_offset = new wxSpinCtrl (_subtitle_panel);
                s->Add (_subtitle_offset);
-               video_control (add_label_to_sizer (s, _subtitle_panel, _("pixels"), false));
+               add_label_to_sizer (s, _subtitle_panel, _("pixels"), false);
                grid->Add (s);
        }
 
        {
-               video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"), true));
+               add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"), true);
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _subtitle_scale = new wxSpinCtrl (_subtitle_panel);
-               s->Add (video_control (_subtitle_scale));
-               video_control (add_label_to_sizer (s, _subtitle_panel, _("%"), false));
+               s->Add (_subtitle_scale);
+               add_label_to_sizer (s, _subtitle_panel, _("%"), false);
                grid->Add (s);
        }
 
+       add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Stream"), true);
+       _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
+       grid->Add (_subtitle_stream, 1, wxEXPAND | wxALL, 6);
+       grid->AddSpacer (0);
+       
        _subtitle_offset->SetRange (-1024, 1024);
        _subtitle_scale->SetRange (1, 1000);
 }
 
+void
+FilmEditor::make_timing_panel ()
+{
+       _timing_panel = new wxPanel (_content_notebook);
+       wxBoxSizer* timing_sizer = new wxBoxSizer (wxVERTICAL);
+       _timing_panel->SetSizer (timing_sizer);
+       wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
+       timing_sizer->Add (grid, 0, wxALL, 8);
+
+       add_label_to_sizer (grid, _timing_panel, _("Start time"), true);
+       _start = new Timecode (_timing_panel);
+       grid->Add (_start);
+       add_label_to_sizer (grid, _timing_panel, _("Length"), true);
+       _length = new Timecode (_timing_panel);
+       grid->Add (_length);
+}
+
+
 /** Called when the left crop widget has been changed */
 void
 FilmEditor::left_crop_changed (wxCommandEvent &)
 {
-       if (!_film) {
+       shared_ptr<VideoContent> c = selected_video_content ();
+       if (!c) {
                return;
        }
 
-       _film->set_left_crop (_left_crop->GetValue ());
+       c->set_left_crop (_left_crop->GetValue ());
 }
 
 /** Called when the right crop widget has been changed */
 void
 FilmEditor::right_crop_changed (wxCommandEvent &)
 {
-       if (!_film) {
+       shared_ptr<VideoContent> c = selected_video_content ();
+       if (!c) {
                return;
        }
 
-       _film->set_right_crop (_right_crop->GetValue ());
+       c->set_right_crop (_right_crop->GetValue ());
 }
 
 /** Called when the top crop widget has been changed */
 void
 FilmEditor::top_crop_changed (wxCommandEvent &)
 {
-       if (!_film) {
+       shared_ptr<VideoContent> c = selected_video_content ();
+       if (!c) {
                return;
        }
 
-       _film->set_top_crop (_top_crop->GetValue ());
+       c->set_top_crop (_top_crop->GetValue ());
 }
 
 /** Called when the bottom crop value has been changed */
 void
 FilmEditor::bottom_crop_changed (wxCommandEvent &)
 {
-       if (!_film) {
-               return;
-       }
-
-       _film->set_bottom_crop (_bottom_crop->GetValue ());
-}
-
-/** Called when the content filename has been changed */
-void
-FilmEditor::content_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
-
-       try {
-               _film->set_content (wx_to_std (_content->GetPath ()));
-       } catch (std::exception& e) {
-               _content->SetPath (std_to_wx (_film->directory ()));
-               error_dialog (this, wxString::Format (_("Could not set content: %s"), std_to_wx (e.what()).data()));
-       }
-}
-
-void
-FilmEditor::trust_content_header_changed (wxCommandEvent &)
-{
-       if (!_film) {
+       shared_ptr<VideoContent> c = selected_video_content ();
+       if (!c) {
                return;
        }
 
-       _film->set_trust_content_header (_trust_content_header->GetValue ());
-}
-
-/** Called when the DCP A/B switch has been toggled */
-void
-FilmEditor::dcp_ab_toggled (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
-       
-       _film->set_dcp_ab (_dcp_ab->GetValue ());
+       c->set_bottom_crop (_bottom_crop->GetValue ());
 }
 
 /** Called when the name widget has been changed */
@@ -612,7 +598,7 @@ FilmEditor::dcp_frame_rate_changed (wxCommandEvent &)
                return;
        }
 
-       _film->set_dcp_frame_rate (
+       _film->set_dcp_video_frame_rate (
                boost::lexical_cast<int> (
                        wx_to_std (_dcp_frame_rate->GetString (_dcp_frame_rate->GetSelection ()))
                        )
@@ -639,118 +625,30 @@ FilmEditor::film_changed (Film::Property p)
        case Film::NONE:
                break;
        case Film::CONTENT:
-               checked_set (_content, _film->content ());
-               setup_visibility ();
-               setup_formats ();
-               setup_subtitle_control_sensitivity ();
-               setup_streams ();
-               setup_show_audio_sensitivity ();
-               setup_frame_rate_description ();
-               setup_minimum_audio_channels ();
-               break;
-       case Film::TRUST_CONTENT_HEADER:
-               checked_set (_trust_content_header, _film->trust_content_header ());
-               break;
-       case Film::SUBTITLE_STREAMS:
+               setup_content ();
                setup_subtitle_control_sensitivity ();
-               setup_streams ();
-               break;
-       case Film::CONTENT_AUDIO_STREAMS:
-               setup_streams ();
                setup_show_audio_sensitivity ();
-               setup_frame_rate_description ();
                setup_minimum_audio_channels ();
                break;
-       case Film::FORMAT:
-       {
-               int n = 0;
-               vector<Format const *>::iterator i = _formats.begin ();
-               while (i != _formats.end() && *i != _film->format ()) {
-                       ++i;
-                       ++n;
-               }
-               if (i == _formats.end()) {
-                       checked_set (_format, -1);
-               } else {
-                       checked_set (_format, n);
-               }
-               setup_dcp_name ();
-               setup_scaling_description ();
-               break;
-       }
-       case Film::CROP:
-               checked_set (_left_crop, _film->crop().left);
-               checked_set (_right_crop, _film->crop().right);
-               checked_set (_top_crop, _film->crop().top);
-               checked_set (_bottom_crop, _film->crop().bottom);
-               setup_scaling_description ();
+       case Film::LOOP:
+               checked_set (_loop_content, _film->loop() > 1);
+               checked_set (_loop_count, _film->loop());
+               setup_loop_sensitivity ();
                break;
-       case Film::FILTERS:
-       {
-               pair<string, string> p = Filter::ffmpeg_strings (_film->filters ());
-               if (p.first.empty () && p.second.empty ()) {
-                       _filters->SetLabel (_("None"));
-               } else {
-                       string const b = p.first + " " + p.second;
-                       _filters->SetLabel (std_to_wx (b));
-               }
-               _film_sizer->Layout ();
+       case Film::CONTAINER:
+               setup_container ();
                break;
-       }
        case Film::NAME:
                checked_set (_name, _film->name());
                setup_dcp_name ();
                break;
-       case Film::SOURCE_FRAME_RATE:
-               s << fixed << setprecision(2) << _film->source_frame_rate();
-               _source_frame_rate->SetLabel (std_to_wx (s.str ()));
-               setup_frame_rate_description ();
-               break;
-       case Film::SIZE:
-               setup_scaling_description ();
-               break;
-       case Film::LENGTH:
-               if (_film->source_frame_rate() > 0 && _film->length()) {
-                       s << _film->length().get() << " "
-                         << wx_to_std (_("frames")) << "; " << seconds_to_hms (_film->length().get() / _film->source_frame_rate());
-               } else if (_film->length()) {
-                       s << _film->length().get() << " "
-                         << wx_to_std (_("frames"));
-               } 
-               _length->SetLabel (std_to_wx (s.str ()));
-               if (_film->length()) {
-                       _trim_start->SetRange (0, _film->length().get());
-                       _trim_end->SetRange (0, _film->length().get());
-               }
-               break;
        case Film::DCP_CONTENT_TYPE:
                checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
                setup_dcp_name ();
                break;
-       case Film::DCP_AB:
-               checked_set (_dcp_ab, _film->dcp_ab ());
-               break;
        case Film::SCALER:
                checked_set (_scaler, Scaler::as_index (_film->scaler ()));
                break;
-       case Film::TRIM_START:
-               checked_set (_trim_start, _film->trim_start());
-               break;
-       case Film::TRIM_END:
-               checked_set (_trim_end, _film->trim_end());
-               break;
-       case Film::TRIM_TYPE:
-               checked_set (_trim_type, _film->trim_type() == Film::CPL ? 0 : 1);
-               break;
-       case Film::AUDIO_GAIN:
-               checked_set (_audio_gain, _film->audio_gain ());
-               break;
-       case Film::AUDIO_DELAY:
-               checked_set (_audio_delay, _film->audio_delay ());
-               break;
-       case Film::STILL_DURATION:
-               checked_set (_still_duration, _film->still_duration ());
-               break;
        case Film::WITH_SUBTITLES:
                checked_set (_with_subtitles, _film->with_subtitles ());
                setup_subtitle_control_sensitivity ();
@@ -775,109 +673,177 @@ FilmEditor::film_changed (Film::Property p)
        case Film::DCI_METADATA:
                setup_dcp_name ();
                break;
-       case Film::CONTENT_AUDIO_STREAM:
-               if (_film->content_audio_stream()) {
-                       checked_set (_audio_stream, _film->content_audio_stream()->to_string());
-               }
-               setup_dcp_name ();
-               setup_audio_details ();
-               setup_audio_control_sensitivity ();
-               setup_show_audio_sensitivity ();
-               setup_frame_rate_description ();
-               setup_minimum_audio_channels ();
-               break;
-       case Film::USE_CONTENT_AUDIO:
-               _film->log()->log (String::compose ("Film::USE_CONTENT_AUDIO changed; setting GUI using %1", _film->use_content_audio ()));
-               checked_set (_use_content_audio, _film->use_content_audio());
-               checked_set (_use_external_audio, !_film->use_content_audio());
-               setup_dcp_name ();
-               setup_audio_details ();
-               setup_audio_control_sensitivity ();
-               setup_show_audio_sensitivity ();
-               setup_frame_rate_description ();
-               setup_minimum_audio_channels ();
-               break;
-       case Film::SUBTITLE_STREAM:
-               if (_film->subtitle_stream()) {
-                       checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
-               }
-               break;
-       case Film::EXTERNAL_AUDIO:
+       case Film::DCP_VIDEO_FRAME_RATE:
        {
-               vector<string> a = _film->external_audio ();
-               for (size_t i = 0; i < a.size() && i < MAX_AUDIO_CHANNELS; ++i) {
-                       checked_set (_external_audio[i], a[i]);
-               }
-               setup_audio_details ();
-               setup_show_audio_sensitivity ();
-               setup_frame_rate_description ();
-               setup_minimum_audio_channels ();
-               break;
-       }
-       case Film::DCP_FRAME_RATE:
+               bool done = false;
                for (unsigned int i = 0; i < _dcp_frame_rate->GetCount(); ++i) {
-                       if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_frame_rate())) {
-                               if (_dcp_frame_rate->GetSelection() != int(i)) {
-                                       _dcp_frame_rate->SetSelection (i);
-                                       break;
-                               }
+                       if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_video_frame_rate())) {
+                               checked_set (_dcp_frame_rate, i);
+                               done = true;
+                               break;
                        }
                }
 
-               if (_film->source_frame_rate()) {
-                       _best_dcp_frame_rate->Enable (best_dcp_frame_rate (_film->source_frame_rate ()) != _film->dcp_frame_rate ());
-               } else {
-                       _best_dcp_frame_rate->Disable ();
+               if (!done) {
+                       checked_set (_dcp_frame_rate, -1);
                }
 
-               setup_frame_rate_description ();
+               _best_dcp_frame_rate->Enable (_film->best_dcp_video_frame_rate () != _film->dcp_video_frame_rate ());
+               break;
+       }
        case Film::MINIMUM_AUDIO_CHANNELS:
-               checked_set (_minimum_audio_channels, _film->minimum_audio_channels ());
+//             checked_set (_minimum_audio_channels, _film->minimum_audio_channels ());
                setup_minimum_audio_channels ();
                break;
        }
 }
 
 void
-FilmEditor::setup_frame_rate_description ()
+FilmEditor::film_content_changed (weak_ptr<Content> weak_content, int property)
 {
-       wxString d;
-       int lines = 0;
-       
-       if (_film->source_frame_rate()) {
-               d << std_to_wx (FrameRateConversion (_film->source_frame_rate(), _film->dcp_frame_rate()).description);
-               ++lines;
-#ifdef HAVE_SWRESAMPLE
-               if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate ()) {
-                       d << wxString::Format (
-                               _("Audio will be resampled from %dHz to %dHz\n"),
-                               _film->audio_stream()->sample_rate(),
-                               _film->target_audio_sample_rate()
-                               );
-                       ++lines;
-               }
-#endif         
+       if (!_film) {
+               /* We call this method ourselves (as well as using it as a signal handler)
+                  so _film can be 0.
+               */
+               return;
        }
 
-       for (int i = lines; i < 2; ++i) {
-               d << wxT ("\n ");
+       shared_ptr<Content> content = weak_content.lock ();
+       shared_ptr<VideoContent> video_content;
+       shared_ptr<AudioContent> audio_content;
+       shared_ptr<FFmpegContent> ffmpeg_content;
+       if (content) {
+               video_content = dynamic_pointer_cast<VideoContent> (content);
+               audio_content = dynamic_pointer_cast<AudioContent> (content);
+               ffmpeg_content = dynamic_pointer_cast<FFmpegContent> (content);
        }
 
-       _frame_rate_description->SetLabel (d);
+       /* We can't use case {} here */
+       
+       if (property == ContentProperty::START) {
+               if (content) {
+                       _start->set (content->start (), _film->dcp_video_frame_rate ());
+               } else {
+                       _start->set (0, 24);
+               }
+       } else if (property == ContentProperty::LENGTH) {
+               if (content) {
+                       _length->set (content->length (), _film->dcp_video_frame_rate ());
+               } else {
+                       _length->set (0, 24);
+               }
+       } else if (property == VideoContentProperty::VIDEO_CROP) {
+               checked_set (_left_crop,   video_content ? video_content->crop().left :   0);
+               checked_set (_right_crop,  video_content ? video_content->crop().right :  0);
+               checked_set (_top_crop,    video_content ? video_content->crop().top :    0);
+               checked_set (_bottom_crop, video_content ? video_content->crop().bottom : 0);
+               setup_scaling_description ();
+       } else if (property == VideoContentProperty::VIDEO_RATIO) {
+               if (video_content) {
+                       int n = 0;
+                       vector<Ratio const *> ratios = Ratio::all ();
+                       vector<Ratio const *>::iterator i = ratios.begin ();
+                       while (i != ratios.end() && *i != video_content->ratio()) {
+                               ++i;
+                               ++n;
+                       }
+
+                       if (i == ratios.end()) {
+                               checked_set (_ratio, -1);
+                       } else {
+                               checked_set (_ratio, n);
+                       }
+               } else {
+                       checked_set (_ratio, -1);
+               }
+       } else if (property == AudioContentProperty::AUDIO_GAIN) {
+               checked_set (_audio_gain, audio_content ? audio_content->audio_gain() : 0);
+       } else if (property == AudioContentProperty::AUDIO_DELAY) {
+               checked_set (_audio_delay, audio_content ? audio_content->audio_delay() : 0);
+       } else if (property == AudioContentProperty::AUDIO_MAPPING) {
+               _audio_mapping->set (audio_content ? audio_content->audio_mapping () : AudioMapping ());
+       } else if (property == FFmpegContentProperty::SUBTITLE_STREAMS) {
+               _subtitle_stream->Clear ();
+               if (ffmpeg_content) {
+                       vector<shared_ptr<FFmpegSubtitleStream> > s = ffmpeg_content->subtitle_streams ();
+                       if (s.empty ()) {
+                               _subtitle_stream->Enable (false);
+                       }
+                       for (vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
+                               _subtitle_stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
+                       }
+                       
+                       if (ffmpeg_content->subtitle_stream()) {
+                               checked_set (_subtitle_stream, lexical_cast<string> (ffmpeg_content->subtitle_stream()->id));
+                       } else {
+                               _subtitle_stream->SetSelection (wxNOT_FOUND);
+                       }
+               }
+               setup_subtitle_control_sensitivity ();
+       } else if (property == FFmpegContentProperty::AUDIO_STREAMS) {
+               _audio_stream->Clear ();
+               if (ffmpeg_content) {
+                       vector<shared_ptr<FFmpegAudioStream> > a = ffmpeg_content->audio_streams ();
+                       for (vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin(); i != a.end(); ++i) {
+                               _audio_stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
+                       }
+                       
+                       if (ffmpeg_content->audio_stream()) {
+                               checked_set (_audio_stream, lexical_cast<string> (ffmpeg_content->audio_stream()->id));
+                       }
+               }
+               setup_show_audio_sensitivity ();
+       } else if (property == FFmpegContentProperty::AUDIO_STREAM) {
+               setup_dcp_name ();
+               setup_show_audio_sensitivity ();
+       } else if (property == FFmpegContentProperty::FILTERS) {
+               if (ffmpeg_content) {
+                       pair<string, string> p = Filter::ffmpeg_strings (ffmpeg_content->filters ());
+                       if (p.first.empty () && p.second.empty ()) {
+                               _filters->SetLabel (_("None"));
+                       } else {
+                               string const b = p.first + " " + p.second;
+                               _filters->SetLabel (std_to_wx (b));
+                       }
+                       _dcp_sizer->Layout ();
+               }
+       }
 }
 
-/** Called when the format widget has been changed */
 void
-FilmEditor::format_changed (wxCommandEvent &)
+FilmEditor::setup_container ()
+{
+       int n = 0;
+       vector<Ratio const *> ratios = Ratio::all ();
+       vector<Ratio const *>::iterator i = ratios.begin ();
+       while (i != ratios.end() && *i != _film->container ()) {
+               ++i;
+               ++n;
+       }
+       
+       if (i == ratios.end()) {
+               checked_set (_container, -1);
+       } else {
+               checked_set (_container, n);
+       }
+       
+       setup_dcp_name ();
+       setup_scaling_description ();
+}      
+
+/** Called when the container widget has been changed */
+void
+FilmEditor::container_changed (wxCommandEvent &)
 {
        if (!_film) {
                return;
        }
 
-       int const n = _format->GetSelection ();
+       int const n = _container->GetSelection ();
        if (n >= 0) {
-               assert (n < int (_formats.size()));
-               _film->set_format (_formats[n]);
+               vector<Ratio const *> ratios = Ratio::all ();
+               assert (n < int (ratios.size()));
+               _film->set_container (ratios[n]);
        }
 }
 
@@ -899,12 +865,17 @@ FilmEditor::dcp_content_type_changed (wxCommandEvent &)
 void
 FilmEditor::set_film (shared_ptr<Film> f)
 {
-       _film = f;
+       set_things_sensitive (f != 0);
 
-       set_things_sensitive (_film != 0);
+       if (_film == f) {
+               return;
+       }
+       
+       _film = f;
 
        if (_film) {
                _film->Changed.connect (bind (&FilmEditor::film_changed, this, _1));
+               _film->ContentChanged.connect (bind (&FilmEditor::film_content_changed, this, _1, _2));
        }
 
        if (_film) {
@@ -913,41 +884,23 @@ FilmEditor::set_film (shared_ptr<Film> f)
                FileChanged ("");
        }
 
-       if (_audio_dialog) {
-               _audio_dialog->set_film (_film);
-       }
-       
        film_changed (Film::NAME);
        film_changed (Film::USE_DCI_NAME);
        film_changed (Film::CONTENT);
-       film_changed (Film::TRUST_CONTENT_HEADER);
+       film_changed (Film::LOOP);
        film_changed (Film::DCP_CONTENT_TYPE);
-       film_changed (Film::FORMAT);
-       film_changed (Film::CROP);
-       film_changed (Film::FILTERS);
+       film_changed (Film::CONTAINER);
        film_changed (Film::SCALER);
-       film_changed (Film::TRIM_START);
-       film_changed (Film::TRIM_END);
-       film_changed (Film::TRIM_TYPE);
-       film_changed (Film::DCP_AB);
-       film_changed (Film::CONTENT_AUDIO_STREAM);
-       film_changed (Film::EXTERNAL_AUDIO);
-       film_changed (Film::USE_CONTENT_AUDIO);
-       film_changed (Film::AUDIO_GAIN);
-       film_changed (Film::AUDIO_DELAY);
-       film_changed (Film::STILL_DURATION);
        film_changed (Film::WITH_SUBTITLES);
        film_changed (Film::SUBTITLE_OFFSET);
        film_changed (Film::SUBTITLE_SCALE);
        film_changed (Film::COLOUR_LUT);
        film_changed (Film::J2K_BANDWIDTH);
        film_changed (Film::DCI_METADATA);
-       film_changed (Film::SIZE);
-       film_changed (Film::LENGTH);
-       film_changed (Film::CONTENT_AUDIO_STREAMS);
-       film_changed (Film::SUBTITLE_STREAMS);
-       film_changed (Film::SOURCE_FRAME_RATE);
-       film_changed (Film::DCP_FRAME_RATE);
+       film_changed (Film::DCP_VIDEO_FRAME_RATE);
+
+       wxListEvent ev;
+       content_selection_changed (ev);
 }
 
 /** Updates the sensitivity of lots of widgets to a given value.
@@ -961,41 +914,48 @@ FilmEditor::set_things_sensitive (bool s)
        _name->Enable (s);
        _use_dci_name->Enable (s);
        _edit_dci_button->Enable (s);
-       _format->Enable (s);
+       _ratio->Enable (s);
        _content->Enable (s);
-       _trust_content_header->Enable (s);
        _left_crop->Enable (s);
        _right_crop->Enable (s);
        _top_crop->Enable (s);
        _bottom_crop->Enable (s);
        _filters_button->Enable (s);
        _scaler->Enable (s);
-       _audio_stream->Enable (s);
        _dcp_content_type->Enable (s);
+       _best_dcp_frame_rate->Enable (s);
        _dcp_frame_rate->Enable (s);
-       _trim_start->Enable (s);
-       _trim_end->Enable (s);
-       _trim_type->Enable (s);
-       _dcp_ab->Enable (s);
        _colour_lut->Enable (s);
        _j2k_bandwidth->Enable (s);
        _audio_gain->Enable (s);
        _audio_gain_calculate_button->Enable (s);
        _show_audio->Enable (s);
        _audio_delay->Enable (s);
-       _still_duration->Enable (s);
+       _container->Enable (s);
+       _loop_content->Enable (s);
+       _loop_count->Enable (s);
 
        setup_subtitle_control_sensitivity ();
-       setup_audio_control_sensitivity ();
        setup_show_audio_sensitivity ();
+       setup_content_sensitivity ();
 }
 
 /** Called when the `Edit filters' button has been clicked */
 void
 FilmEditor::edit_filters_clicked (wxCommandEvent &)
 {
-       FilterDialog* d = new FilterDialog (this, _film->filters());
-       d->ActiveChanged.connect (bind (&Film::set_filters, _film, _1));
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return;
+       }
+
+       shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
+       if (!fc) {
+               return;
+       }
+       
+       FilterDialog* d = new FilterDialog (this, fc->filters());
+       d->ActiveChanged.connect (bind (&FFmpegContent::set_filters, fc, _1));
        d->ShowModal ();
        d->Destroy ();
 }
@@ -1017,105 +977,39 @@ FilmEditor::scaler_changed (wxCommandEvent &)
 void
 FilmEditor::audio_gain_changed (wxCommandEvent &)
 {
-       if (!_film) {
+       shared_ptr<AudioContent> ac = selected_audio_content ();
+       if (!ac) {
                return;
        }
 
-       _film->set_audio_gain (_audio_gain->GetValue ());
+       ac->set_audio_gain (_audio_gain->GetValue ());
 }
 
 void
 FilmEditor::audio_delay_changed (wxCommandEvent &)
 {
-       if (!_film) {
+       shared_ptr<AudioContent> ac = selected_audio_content ();
+       if (!ac) {
                return;
        }
 
-       _film->set_audio_delay (_audio_delay->GetValue ());
-}
-
-wxControl *
-FilmEditor::video_control (wxControl* c)
-{
-       _video_controls.push_back (c);
-       return c;
-}
-
-wxControl *
-FilmEditor::still_control (wxControl* c)
-{
-       _still_controls.push_back (c);
-       return c;
+       ac->set_audio_delay (_audio_delay->GetValue ());
 }
 
 void
-FilmEditor::setup_visibility ()
+FilmEditor::setup_main_notebook_size ()
 {
-       ContentType c = VIDEO;
-
-       if (_film) {
-               c = _film->content_type ();
-       }
-
-       for (list<wxControl*>::iterator i = _video_controls.begin(); i != _video_controls.end(); ++i) {
-               (*i)->Show (c == VIDEO);
-       }
-
-       for (list<wxControl*>::iterator i = _still_controls.begin(); i != _still_controls.end(); ++i) {
-               (*i)->Show (c == STILL);
-       }
+       _main_notebook->InvalidateBestSize ();
 
-       setup_notebook_size ();
-}
+       _content_sizer->Layout ();
+       _content_sizer->SetSizeHints (_content_panel);
+       _dcp_sizer->Layout ();
+       _dcp_sizer->SetSizeHints (_dcp_panel);
 
-void
-FilmEditor::setup_notebook_size ()
-{
-       _notebook->InvalidateBestSize ();
-       
-       _film_sizer->Layout ();
-       _film_sizer->SetSizeHints (_film_panel);
-       _video_sizer->Layout ();
-       _video_sizer->SetSizeHints (_video_panel);
-       _audio_sizer->Layout ();
-       _audio_sizer->SetSizeHints (_audio_panel);
-       _subtitle_sizer->Layout ();
-       _subtitle_sizer->SetSizeHints (_subtitle_panel);
-
-       _notebook->Fit ();
+       _main_notebook->Fit ();
        Fit ();
 }
 
-void
-FilmEditor::still_duration_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
-
-       _film->set_still_duration (_still_duration->GetValue ());
-}
-
-void
-FilmEditor::trim_start_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
-
-       _film->set_trim_start (_trim_start->GetValue ());
-}
-
-void
-FilmEditor::trim_end_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
-
-       _film->set_trim_end (_trim_end->GetValue ());
-}
-
 void
 FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &)
 {
@@ -1144,29 +1038,16 @@ FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &)
 }
 
 void
-FilmEditor::setup_formats ()
+FilmEditor::setup_ratios ()
 {
-       ContentType c = VIDEO;
+       _ratios = Ratio::all ();
 
-       if (_film) {
-               c = _film->content_type ();
+       _ratio->Clear ();
+       for (vector<Ratio const *>::iterator i = _ratios.begin(); i != _ratios.end(); ++i) {
+               _ratio->Append (std_to_wx ((*i)->nickname ()));
        }
-       
-       _formats.clear ();
 
-       vector<Format const *> fmt = Format::all ();
-       for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
-               if (c == VIDEO || (c == STILL && dynamic_cast<VariableFormat const *> (*i))) {
-                       _formats.push_back (*i);
-               }
-       }
-
-       _format->Clear ();
-       for (vector<Format const *>::iterator i = _formats.begin(); i != _formats.end(); ++i) {
-               _format->Append (std_to_wx ((*i)->name ()));
-       }
-
-       _film_sizer->Layout ();
+       _dcp_sizer->Layout ();
 }
 
 void
@@ -1184,7 +1065,7 @@ FilmEditor::setup_subtitle_control_sensitivity ()
 {
        bool h = false;
        if (_generally_sensitive && _film) {
-               h = !_film->subtitle_streams().empty();
+               h = _film->has_subtitles ();
        }
        
        _with_subtitles->Enable (h);
@@ -1194,29 +1075,10 @@ FilmEditor::setup_subtitle_control_sensitivity ()
                j = _film->with_subtitles ();
        }
        
-       _subtitle_stream->Enable (j);
        _subtitle_offset->Enable (j);
        _subtitle_scale->Enable (j);
 }
 
-void
-FilmEditor::setup_audio_control_sensitivity ()
-{
-       _use_content_audio->Enable (_generally_sensitive && _film && !_film->content_audio_streams().empty());
-       _use_external_audio->Enable (_generally_sensitive);
-       
-       bool const source = _generally_sensitive && _use_content_audio->GetValue();
-       bool const external = _generally_sensitive && _use_external_audio->GetValue();
-
-       _audio_stream->Enable (source);
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               _external_audio[i]->Enable (external);
-       }
-
-       _pad_with_silence->Enable (_generally_sensitive && _film && _film->audio_stream() && _film->audio_stream()->channels() < MAX_AUDIO_CHANNELS);
-       _minimum_audio_channels->Enable (_generally_sensitive && _pad_with_silence->GetValue ());
-}
-
 void
 FilmEditor::use_dci_name_toggled (wxCommandEvent &)
 {
@@ -1241,143 +1103,202 @@ FilmEditor::edit_dci_button_clicked (wxCommandEvent &)
 }
 
 void
-FilmEditor::setup_streams ()
+FilmEditor::active_jobs_changed (bool a)
 {
-       _audio_stream->Clear ();
-       vector<shared_ptr<AudioStream> > a = _film->content_audio_streams ();
-       for (vector<shared_ptr<AudioStream> >::iterator i = a.begin(); i != a.end(); ++i) {
-               shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (*i);
-               assert (ffa);
-               _audio_stream->Append (std_to_wx (ffa->name()), new wxStringClientData (std_to_wx (ffa->to_string ())));
-       }
-       
-       if (_film->use_content_audio() && _film->audio_stream()) {
-               checked_set (_audio_stream, _film->audio_stream()->to_string());
-       }
+       set_things_sensitive (!a);
+}
 
-       _subtitle_stream->Clear ();
-       vector<shared_ptr<SubtitleStream> > s = _film->subtitle_streams ();
-       for (vector<shared_ptr<SubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
-               _subtitle_stream->Append (std_to_wx ((*i)->name()), new wxStringClientData (std_to_wx ((*i)->to_string ())));
-       }
-       if (_film->subtitle_stream()) {
-               checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
+void
+FilmEditor::setup_dcp_name ()
+{
+       string s = _film->dcp_name (true);
+       if (s.length() > 28) {
+               _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("..."));
+               _dcp_name->SetToolTip (std_to_wx (s));
        } else {
-               _subtitle_stream->SetSelection (wxNOT_FOUND);
+               _dcp_name->SetLabel (std_to_wx (s));
        }
 }
 
 void
-FilmEditor::audio_stream_changed (wxCommandEvent &)
+FilmEditor::show_audio_clicked (wxCommandEvent &)
 {
-       if (!_film) {
+       if (_audio_dialog) {
+               _audio_dialog->Destroy ();
+               _audio_dialog = 0;
+       }
+
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
                return;
        }
 
-       _film->set_content_audio_stream (
-               audio_stream_factory (
-                       string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ())),
-                       Film::state_version
-                       )
-               );
+       shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (c);
+       if (!ac) {
+               return;
+       }
+       
+       _audio_dialog = new AudioDialog (this);
+       _audio_dialog->Show ();
+       _audio_dialog->set_content (ac);
 }
 
 void
-FilmEditor::subtitle_stream_changed (wxCommandEvent &)
+FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &)
 {
        if (!_film) {
                return;
        }
+       
+       _film->set_dcp_video_frame_rate (_film->best_dcp_video_frame_rate ());
+}
 
-       _film->set_subtitle_stream (
-               subtitle_stream_factory (
-                       string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ())),
-                       Film::state_version
-                       )
-               );
+void
+FilmEditor::setup_show_audio_sensitivity ()
+{
+       _show_audio->Enable (_film);
 }
 
 void
-FilmEditor::setup_audio_details ()
+FilmEditor::setup_content ()
 {
-       if (!_film->content_audio_stream()) {
-               _audio->SetLabel (wxT (""));
-       } else {
-               wxString s;
-               if (_film->audio_stream()->channels() == 1) {
-                       s << _("1 channel");
-               } else {
-                       s << _film->audio_stream()->channels () << wxT (" ") << _("channels");
+       string selected_summary;
+       int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+       if (s != -1) {
+               selected_summary = wx_to_std (_content->GetItemText (s));
+       }
+       
+       _content->DeleteAllItems ();
+
+       Playlist::ContentList content = _film->content ();
+       for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) {
+               int const t = _content->GetItemCount ();
+               _content->InsertItem (t, std_to_wx ((*i)->summary ()));
+               if ((*i)->summary() == selected_summary) {
+                       _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
                }
-               s << wxT (", ") << _film->audio_stream()->sample_rate() << _("Hz");
-               _audio->SetLabel (s);
        }
 
-       setup_notebook_size ();
+       if (selected_summary.empty () && !content.empty ()) {
+               /* Select the item of content if none was selected before */
+               _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
+       }
 }
 
 void
-FilmEditor::active_jobs_changed (bool a)
+FilmEditor::content_add_clicked (wxCommandEvent &)
 {
-       set_things_sensitive (!a);
+       wxFileDialog* d = new wxFileDialog (this, _("Choose a file or files"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE);
+       int const r = d->ShowModal ();
+       d->Destroy ();
+
+       if (r != wxID_OK) {
+               return;
+       }
+
+       wxArrayString paths;
+       d->GetPaths (paths);
+
+       for (unsigned int i = 0; i < paths.GetCount(); ++i) {
+               boost::filesystem::path p (wx_to_std (paths[i]));
+
+               shared_ptr<Content> c;
+
+               if (ImageMagickContent::valid_file (p)) {
+                       c.reset (new ImageMagickContent (_film, p));
+               } else if (SndfileContent::valid_file (p)) {
+                       c.reset (new SndfileContent (_film, p));
+               } else {
+                       c.reset (new FFmpegContent (_film, p));
+               }
+
+               _film->examine_and_add_content (c);
+       }
 }
 
 void
-FilmEditor::use_audio_changed (wxCommandEvent &)
+FilmEditor::content_remove_clicked (wxCommandEvent &)
 {
-       _film->set_use_content_audio (_use_content_audio->GetValue());
+       shared_ptr<Content> c = selected_content ();
+       if (c) {
+               _film->remove_content (c);
+       }
 }
 
 void
-FilmEditor::external_audio_changed (wxCommandEvent &)
+FilmEditor::content_selection_changed (wxListEvent &)
 {
-       vector<string> a;
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               a.push_back (wx_to_std (_external_audio[i]->GetPath()));
+        setup_content_sensitivity ();
+       shared_ptr<Content> s = selected_content ();
+       
+       if (_audio_dialog && s && dynamic_pointer_cast<AudioContent> (s)) {
+               _audio_dialog->set_content (dynamic_pointer_cast<AudioContent> (s));
        }
-
-       _film->set_external_audio (a);
+       
+       film_content_changed (s, ContentProperty::START);
+       film_content_changed (s, ContentProperty::LENGTH);
+       film_content_changed (s, VideoContentProperty::VIDEO_CROP);
+       film_content_changed (s, VideoContentProperty::VIDEO_RATIO);
+       film_content_changed (s, AudioContentProperty::AUDIO_GAIN);
+       film_content_changed (s, AudioContentProperty::AUDIO_DELAY);
+       film_content_changed (s, AudioContentProperty::AUDIO_MAPPING);
+       film_content_changed (s, FFmpegContentProperty::AUDIO_STREAM);
+       film_content_changed (s, FFmpegContentProperty::AUDIO_STREAMS);
+       film_content_changed (s, FFmpegContentProperty::SUBTITLE_STREAM);
 }
 
 void
-FilmEditor::setup_dcp_name ()
+FilmEditor::setup_content_sensitivity ()
 {
-       string s = _film->dcp_name (true);
-       if (s.length() > 28) {
-               _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("..."));
-               _dcp_name->SetToolTip (std_to_wx (s));
-       } else {
-               _dcp_name->SetLabel (std_to_wx (s));
-       }
+        _content_add->Enable (_generally_sensitive);
+
+       shared_ptr<Content> selection = selected_content ();
+
+        _content_remove->Enable (selection && _generally_sensitive);
+       _content_timeline->Enable (_generally_sensitive);
+
+       _video_panel->Enable    (selection && dynamic_pointer_cast<VideoContent>  (selection) && _generally_sensitive);
+       _audio_panel->Enable    (selection && dynamic_pointer_cast<AudioContent>  (selection) && _generally_sensitive);
+       _subtitle_panel->Enable (selection && dynamic_pointer_cast<FFmpegContent> (selection) && _generally_sensitive);
+       _timing_panel->Enable   (selection && _generally_sensitive);
 }
 
-void
-FilmEditor::show_audio_clicked (wxCommandEvent &)
+shared_ptr<Content>
+FilmEditor::selected_content ()
 {
-       if (_audio_dialog) {
-               _audio_dialog->Destroy ();
-               _audio_dialog = 0;
+       int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+       if (s == -1) {
+               return shared_ptr<Content> ();
+       }
+
+       Playlist::ContentList c = _film->content ();
+       if (s < 0 || size_t (s) >= c.size ()) {
+               return shared_ptr<Content> ();
        }
        
-       _audio_dialog = new AudioDialog (this);
-       _audio_dialog->Show ();
-       _audio_dialog->set_film (_film);
+       return c[s];
 }
 
-void
-FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &)
+shared_ptr<VideoContent>
+FilmEditor::selected_video_content ()
 {
-       if (!_film) {
-               return;
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return shared_ptr<VideoContent> ();
        }
-       
-       _film->set_dcp_frame_rate (best_dcp_frame_rate (_film->source_frame_rate ()));
+
+       return dynamic_pointer_cast<VideoContent> (c);
 }
 
-void
-FilmEditor::setup_show_audio_sensitivity ()
+shared_ptr<AudioContent>
+FilmEditor::selected_audio_content ()
 {
-       _show_audio->Enable (_film && _film->has_audio ());
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return shared_ptr<AudioContent> ();
+       }
+
+       return dynamic_pointer_cast<AudioContent> (c);
 }
 
 void
@@ -1385,13 +1306,15 @@ FilmEditor::setup_scaling_description ()
 {
        wxString d;
 
+#if 0  
+XXX
        int lines = 0;
 
-       if (_film->size().width && _film->size().height) {
+       if (_film->video_size().width && _film->video_size().height) {
                d << wxString::Format (
                        _("Original video is %dx%d (%.2f:1)\n"),
-                       _film->size().width, _film->size().height,
-                       float (_film->size().width) / _film->size().height
+                       _film->video_size().width, _film->video_size().height,
+                       float (_film->video_size().width) / _film->video_size().height
                        );
                ++lines;
        }
@@ -1433,18 +1356,194 @@ FilmEditor::setup_scaling_description ()
                d << wxT ("\n ");
        }
 
+#endif 
        _scaling_description->SetLabel (d);
 }
 
 void
-FilmEditor::trim_type_changed (wxCommandEvent &)
+FilmEditor::loop_content_toggled (wxCommandEvent &)
+{
+       if (_loop_content->GetValue ()) {
+               _film->set_loop (_loop_count->GetValue ());
+       } else {
+               _film->set_loop (1);
+       }
+               
+       setup_loop_sensitivity ();
+}
+
+void
+FilmEditor::loop_count_changed (wxCommandEvent &)
 {
-       _film->set_trim_type (_trim_type->GetSelection () == 0 ? Film::CPL : Film::ENCODE);
+       _film->set_loop (_loop_count->GetValue ());
+}
+
+void
+FilmEditor::setup_loop_sensitivity ()
+{
+       _loop_count->Enable (_loop_content->GetValue ());
+}
+
+void
+FilmEditor::content_timeline_clicked (wxCommandEvent &)
+{
+       if (_timeline_dialog) {
+               _timeline_dialog->Destroy ();
+               _timeline_dialog = 0;
+       }
+       
+       _timeline_dialog = new TimelineDialog (this, _film);
+       _timeline_dialog->Show ();
+}
+
+void
+FilmEditor::audio_stream_changed (wxCommandEvent &)
+{
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return;
+       }
+       
+       shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
+       if (!fc) {
+               return;
+       }
+       
+       vector<shared_ptr<FFmpegAudioStream> > a = fc->audio_streams ();
+       vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin ();
+       string const s = string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ()));
+       while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
+               ++i;
+       }
+
+       if (i != a.end ()) {
+               fc->set_audio_stream (*i);
+       }
+
+       if (!fc->audio_stream ()) {
+               _audio_description->SetLabel (wxT (""));
+       } else {
+               wxString s;
+               if (fc->audio_channels() == 1) {
+                       s << _("1 channel");
+               } else {
+                       s << fc->audio_channels() << wxT (" ") << _("channels");
+               }
+               s << wxT (", ") << fc->content_audio_frame_rate() << _("Hz");
+               _audio_description->SetLabel (s);
+       }
+}
+
+
+
+void
+FilmEditor::subtitle_stream_changed (wxCommandEvent &)
+{
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return;
+       }
+       
+       shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
+       if (!fc) {
+               return;
+       }
+       
+       vector<shared_ptr<FFmpegSubtitleStream> > a = fc->subtitle_streams ();
+       vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = a.begin ();
+       string const s = string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ()));
+       while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
+               ++i;
+       }
+
+       if (i != a.end ()) {
+               fc->set_subtitle_stream (*i);
+       }
+}
+
+void
+FilmEditor::audio_mapping_changed (AudioMapping m)
+{
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return;
+       }
+       
+       shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (c);
+       if (!ac) {
+               return;
+       }
+
+       ac->set_audio_mapping (m);
+}
+
+void
+FilmEditor::start_changed ()
+{
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return;
+       }
+
+       c->set_start (_start->get (_film->dcp_video_frame_rate ()));
+}
+
+void
+FilmEditor::length_changed ()
+{
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return;
+       }
+
+       shared_ptr<ImageMagickContent> ic = dynamic_pointer_cast<ImageMagickContent> (c);
+       if (ic) {
+               ic->set_video_length (_length->get(_film->dcp_video_frame_rate()) * ic->video_frame_rate() / TIME_HZ);
+       }
+}
+
+void
+FilmEditor::set_selection (weak_ptr<Content> wc)
+{
+       Playlist::ContentList content = _film->content ();
+       for (size_t i = 0; i < content.size(); ++i) {
+               if (content[i] == wc.lock ()) {
+                       _content->SetItemState (i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
+               } else {
+                       _content->SetItemState (i, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
+               }
+       }
+}
+
+void
+FilmEditor::ratio_changed (wxCommandEvent &)
+{
+       if (!_film) {
+               return;
+       }
+
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return;
+       }
+
+       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c);
+       if (!vc) {
+               return;
+       }
+       
+       int const n = _ratio->GetSelection ();
+       if (n >= 0) {
+               vector<Ratio const *> ratios = Ratio::all ();
+               assert (n < int (ratios.size()));
+               vc->set_ratio (ratios[n]);
+       }
 }
 
 void
 FilmEditor::setup_minimum_audio_channels ()
 {
+#if 0  
        if (!_film || !_film->audio_stream ()) {
                _pad_with_silence->SetValue (false);
                return;
@@ -1454,12 +1553,13 @@ FilmEditor::setup_minimum_audio_channels ()
 
        AudioMapping m (_film);
        _minimum_audio_channels->SetRange (m.minimum_dcp_channels() + 1, MAX_AUDIO_CHANNELS);
+#endif 
 }
 
 void
 FilmEditor::pad_with_silence_toggled (wxCommandEvent &)
 {
-       setup_audio_control_sensitivity ();
+
 }
 
 void
@@ -1469,5 +1569,5 @@ FilmEditor::minimum_audio_channels_changed (wxCommandEvent &)
                return;
        }
 
-       _film->set_minimum_audio_channels (_minimum_audio_channels->GetValue ());
+//     _film->set_minimum_audio_channels (_minimum_audio_channels->GetValue ());
 }
index c2d064ca202bc1d3305c52182b35092ecaa088e1..705eb16af9b98f6305a3d28d4d7d251f339e56fc 100644 (file)
@@ -16,7 +16,7 @@
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
 */
-
 /** @file src/film_editor.h
  *  @brief A wx widget to edit a film's metadata, and perform various functions.
  */
 #include "lib/film.h"
 
 class wxNotebook;
+class wxListCtrl;
+class wxListEvent;
 class Film;
 class AudioDialog;
+class TimelineDialog;
+class AudioMappingView;
+class Ratio;
+class Timecode;
 
 /** @class FilmEditor
  *  @brief A wx widget to edit a film's metadata, and perform various functions.
@@ -41,15 +47,17 @@ public:
        FilmEditor (boost::shared_ptr<Film>, wxWindow *);
 
        void set_film (boost::shared_ptr<Film>);
-       void setup_visibility ();
+       void set_selection (boost::weak_ptr<Content>);
 
        boost::signals2::signal<void (std::string)> FileChanged;
 
 private:
-       void make_film_panel ();
+       void make_dcp_panel ();
+       void make_content_panel ();
        void make_video_panel ();
        void make_audio_panel ();
        void make_subtitle_panel ();
+       void make_timing_panel ();
        void connect_to_widgets ();
        
        /* Handle changes to the view */
@@ -60,14 +68,13 @@ private:
        void right_crop_changed (wxCommandEvent &);
        void top_crop_changed (wxCommandEvent &);
        void bottom_crop_changed (wxCommandEvent &);
-       void content_changed (wxCommandEvent &);
-       void trust_content_header_changed (wxCommandEvent &);
-       void format_changed (wxCommandEvent &);
-       void trim_start_changed (wxCommandEvent &);
-       void trim_end_changed (wxCommandEvent &);
-       void trim_type_changed (wxCommandEvent &);
+       void trust_content_headers_changed (wxCommandEvent &);
+       void content_selection_changed (wxListEvent &);
+       void content_add_clicked (wxCommandEvent &);
+       void content_remove_clicked (wxCommandEvent &);
+       void imagemagick_video_length_changed (wxCommandEvent &);
+       void container_changed (wxCommandEvent &);
        void dcp_content_type_changed (wxCommandEvent &);
-       void dcp_ab_toggled (wxCommandEvent &);
        void scaler_changed (wxCommandEvent &);
        void audio_gain_changed (wxCommandEvent &);
        void audio_gain_calculate_button_clicked (wxCommandEvent &);
@@ -78,121 +85,103 @@ private:
        void subtitle_scale_changed (wxCommandEvent &);
        void colour_lut_changed (wxCommandEvent &);
        void j2k_bandwidth_changed (wxCommandEvent &);
-       void still_duration_changed (wxCommandEvent &);
-       void audio_stream_changed (wxCommandEvent &);
-       void subtitle_stream_changed (wxCommandEvent &);
-       void use_audio_changed (wxCommandEvent &);
-       void external_audio_changed (wxCommandEvent &);
        void dcp_frame_rate_changed (wxCommandEvent &);
        void best_dcp_frame_rate_clicked (wxCommandEvent &);
+       void edit_filters_clicked (wxCommandEvent &);
+       void loop_content_toggled (wxCommandEvent &);
+       void loop_count_changed (wxCommandEvent &);
+       void content_timeline_clicked (wxCommandEvent &);
+       void audio_stream_changed (wxCommandEvent &);
+       void subtitle_stream_changed (wxCommandEvent &);
+       void audio_mapping_changed (AudioMapping);
+       void start_changed ();
+       void length_changed ();
+       void ratio_changed (wxCommandEvent &);
        void pad_with_silence_toggled (wxCommandEvent &);
        void minimum_audio_channels_changed (wxCommandEvent &);
 
        /* Handle changes to the model */
        void film_changed (Film::Property);
-
-       /* Button clicks */
-       void edit_filters_clicked (wxCommandEvent &);
+       void film_content_changed (boost::weak_ptr<Content>, int);
 
        void set_things_sensitive (bool);
-       void setup_formats ();
+       void setup_ratios ();
        void setup_subtitle_control_sensitivity ();
-       void setup_audio_control_sensitivity ();
-       void setup_streams ();
-       void setup_audio_details ();
        void setup_dcp_name ();
        void setup_show_audio_sensitivity ();
        void setup_scaling_description ();
-       void setup_notebook_size ();
-       void setup_frame_rate_description ();
+       void setup_main_notebook_size ();
+       void setup_content ();
+       void setup_container ();
+       void setup_content_sensitivity ();
+       void setup_loop_sensitivity ();
        void setup_minimum_audio_channels ();
        
-       wxControl* video_control (wxControl *);
-       wxControl* still_control (wxControl *);
-
        void active_jobs_changed (bool);
-
-       wxNotebook* _notebook;
-       wxPanel* _film_panel;
-       wxSizer* _film_sizer;
+       boost::shared_ptr<Content> selected_content ();
+       boost::shared_ptr<VideoContent> selected_video_content ();
+       boost::shared_ptr<AudioContent> selected_audio_content ();
+
+       wxNotebook* _main_notebook;
+       wxNotebook* _content_notebook;
+       wxPanel* _dcp_panel;
+       wxSizer* _dcp_sizer;
+       wxPanel* _content_panel;
+       wxSizer* _content_sizer;
        wxPanel* _video_panel;
-       wxSizer* _video_sizer;
        wxPanel* _audio_panel;
-       wxSizer* _audio_sizer;
        wxPanel* _subtitle_panel;
-       wxSizer* _subtitle_sizer;
+       wxPanel* _timing_panel;
 
        /** The film we are editing */
        boost::shared_ptr<Film> _film;
-       /** The Film's name */
        wxTextCtrl* _name;
        wxStaticText* _dcp_name;
        wxCheckBox* _use_dci_name;
+       wxChoice* _container;
+       wxListCtrl* _content;
+       wxButton* _content_add;
+       wxButton* _content_remove;
+       wxButton* _content_earlier;
+       wxButton* _content_later;
+       wxButton* _content_timeline;
+       wxCheckBox* _loop_content;
+       wxSpinCtrl* _loop_count;
        wxButton* _edit_dci_button;
-       /** The Film's format */
-       wxChoice* _format;
+       wxChoice* _ratio;
+       wxStaticText* _ratio_description;
        wxStaticText* _scaling_description;
-       /** The Film's content file */
-       wxFilePickerCtrl* _content;
-       wxCheckBox* _trust_content_header;
-       /** The Film's left crop */
        wxSpinCtrl* _left_crop;
-       /** The Film's right crop */
        wxSpinCtrl* _right_crop;
-       /** The Film's top crop */
        wxSpinCtrl* _top_crop;
-       /** The Film's bottom crop */
        wxSpinCtrl* _bottom_crop;
-       /** Currently-applied filters */
        wxStaticText* _filters;
-       /** Button to open the filters dialogue */
        wxButton* _filters_button;
-       /** The Film's scaler */
        wxChoice* _scaler;
-       wxRadioButton* _use_content_audio;
-       wxChoice* _audio_stream;
-       wxRadioButton* _use_external_audio;
-       wxFilePickerCtrl* _external_audio[MAX_AUDIO_CHANNELS];
-       /** The Film's audio gain */
        wxSpinCtrl* _audio_gain;
-       /** A button to open the gain calculation dialogue */
        wxButton* _audio_gain_calculate_button;
        wxButton* _show_audio;
-       /** The Film's audio delay */
        wxSpinCtrl* _audio_delay;
        wxCheckBox* _with_subtitles;
-       wxChoice* _subtitle_stream;
        wxSpinCtrl* _subtitle_offset;
        wxSpinCtrl* _subtitle_scale;
        wxChoice* _colour_lut;
        wxSpinCtrl* _j2k_bandwidth;
-       /** The Film's DCP content type */
        wxChoice* _dcp_content_type;
-       /** The Film's source frame rate */
-       wxStaticText* _source_frame_rate;
        wxChoice* _dcp_frame_rate;
        wxButton* _best_dcp_frame_rate;
        wxCheckBox* _pad_with_silence;
        wxSpinCtrl* _minimum_audio_channels;
-       wxStaticText* _frame_rate_description;
-       /** The Film's length */
-       wxStaticText* _length;
-       /** The Film's audio details */
-       wxStaticText* _audio;
-       /** The Film's duration for still sources */
-       wxSpinCtrl* _still_duration;
-
-       wxSpinCtrl* _trim_start;
-       wxSpinCtrl* _trim_end;
-       wxChoice* _trim_type;
-       /** Selector to generate an A/B comparison DCP */
-       wxCheckBox* _dcp_ab;
-
-       std::list<wxControl*> _video_controls;
-       std::list<wxControl*> _still_controls;
+       wxChoice* _audio_stream;
+       wxStaticText* _audio_description;
+       wxChoice* _subtitle_stream;
+       AudioMappingView* _audio_mapping;
+       Timecode* _start;
+       Timecode* _length;
 
-       std::vector<Format const *> _formats;
+       std::vector<Ratio const *> _ratios;
 
        bool _generally_sensitive;
        AudioDialog* _audio_dialog;
+       TimelineDialog* _timeline_dialog;
 };
index 6845031cf96cf2688775bef192da3548093de5bb..8ef64d509c9534907e6f22f47f8e9e4dd2537d80 100644 (file)
 #include <iomanip>
 #include <wx/tglbtn.h>
 #include "lib/film.h"
-#include "lib/format.h"
+#include "lib/ratio.h"
 #include "lib/util.h"
 #include "lib/job_manager.h"
-#include "lib/options.h"
-#include "lib/subtitle.h"
 #include "lib/image.h"
 #include "lib/scaler.h"
 #include "lib/exceptions.h"
 #include "lib/examine_content_job.h"
 #include "lib/filter.h"
+#include "lib/player.h"
+#include "lib/video_content.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/imagemagick_content.h"
 #include "film_viewer.h"
 #include "wx_util.h"
 #include "video_decoder.h"
 
 using std::string;
 using std::pair;
+using std::min;
 using std::max;
 using std::cout;
 using std::list;
 using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::weak_ptr;
 using libdcp::Size;
 
 FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
@@ -56,7 +61,6 @@ FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        , _frame (new wxStaticText (this, wxID_ANY, wxT("")))
        , _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
        , _play_button (new wxToggleButton (this, wxID_ANY, _("Play")))
-       , _display_frame_x (0)
        , _got_frame (false)
 {
 #ifndef __WXOSX__
@@ -111,49 +115,27 @@ void
 FilmViewer::film_changed (Film::Property p)
 {
        switch (p) {
-       case Film::FORMAT:
+       case Film::CONTAINER:
                calculate_sizes ();
                update_from_raw ();
                break;
        case Film::CONTENT:
        {
-               DecodeOptions o;
-               o.decode_audio = false;
-               o.decode_subtitles = true;
-               o.video_sync = false;
-
-               try {
-                       _decoders = decoder_factory (_film, o);
-               } catch (StringError& e) {
-                       error_dialog (this, wxString::Format (_("Could not open content file (%s)"), std_to_wx(e.what()).data()));
-                       return;
-               }
-               
-               if (_decoders.video == 0) {
-                       break;
-               }
-               _decoders.video->set_subtitle_stream (_film->subtitle_stream());
-               _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3, _4));
-               _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this));
                calculate_sizes ();
-               get_frame ();
-               _panel->Refresh ();
-               _slider->Show (_film->content_type() == VIDEO);
-               _play_button->Show (_film->content_type() == VIDEO);
-               _v_sizer->Layout ();
+               wxScrollEvent ev;
+               slider_moved (ev);
                break;
        }
        case Film::WITH_SUBTITLES:
        case Film::SUBTITLE_OFFSET:
        case Film::SUBTITLE_SCALE:
-       case Film::SCALER:
-       case Film::FILTERS:
-               update_from_raw ();
+               update_from_decoder ();
+               raw_to_display ();
+               _panel->Refresh ();
+               _panel->Update ();
                break;
-       case Film::SUBTITLE_STREAM:
-               if (_decoders.video) {
-                       _decoders.video->set_subtitle_stream (_film->subtitle_stream ());
-               }
+       case Film::SCALER:
+               update_from_decoder ();
                break;
        default:
                break;
@@ -166,7 +148,7 @@ FilmViewer::set_film (shared_ptr<Film> f)
        if (_film == f) {
                return;
        }
-       
+
        _film = f;
 
        _raw_frame.reset ();
@@ -178,23 +160,28 @@ FilmViewer::set_film (shared_ptr<Film> f)
                return;
        }
 
+       _player = f->player ();
+       _player->disable_audio ();
+       _player->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3));
+       
        _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
+       _film->ContentChanged.connect (boost::bind (&FilmViewer::film_content_changed, this, _1, _2));
 
        film_changed (Film::CONTENT);
-       film_changed (Film::FORMAT);
+       film_changed (Film::CONTAINER);
        film_changed (Film::WITH_SUBTITLES);
        film_changed (Film::SUBTITLE_OFFSET);
        film_changed (Film::SUBTITLE_SCALE);
-       film_changed (Film::SUBTITLE_STREAM);
 }
 
 void
-FilmViewer::decoder_changed ()
+FilmViewer::update_from_decoder ()
 {
-       if (_decoders.video == 0 || _decoders.video->seek_to_last ()) {
+       if (!_player) {
                return;
        }
 
+       _player->seek (_player->video_position() - _film->video_frames_to_time (1));
        get_frame ();
        _panel->Refresh ();
        _panel->Update ();
@@ -203,14 +190,14 @@ FilmViewer::decoder_changed ()
 void
 FilmViewer::timer (wxTimerEvent &)
 {
-       if (!_film || !_decoders.video) {
+       if (!_player) {
                return;
        }
        
        get_frame ();
 
        if (_film->length()) {
-               int const new_slider_position = 4096 * _decoders.video->last_source_time() / (_film->length().get() / _film->source_frame_rate());
+               int const new_slider_position = 4096 * _player->video_position() / _film->length();
                if (new_slider_position != _slider->GetValue()) {
                        _slider->SetValue (new_slider_position);
                }
@@ -231,22 +218,9 @@ FilmViewer::paint_panel (wxPaintEvent &)
                return;
        }
 
-       if (_display_frame_x) {
-               dc.SetPen(*wxBLACK_PEN);
-               dc.SetBrush(*wxBLACK_BRUSH);
-               dc.DrawRectangle (0, 0, _display_frame_x, _film_size.height);
-               dc.DrawRectangle (_display_frame_x + _film_size.width, 0, _display_frame_x, _film_size.height);
-       }
-
-       wxImage frame (_film_size.width, _film_size.height, _display_frame->data()[0], true);
+       wxImage frame (_out_size.width, _out_size.height, _display_frame->data()[0], true);
        wxBitmap frame_bitmap (frame);
-       dc.DrawBitmap (frame_bitmap, _display_frame_x, 0);
-
-       if (_film->with_subtitles() && _display_sub) {
-               wxImage sub (_display_sub->size().width, _display_sub->size().height, _display_sub->data()[0], _display_sub->alpha(), true);
-               wxBitmap sub_bitmap (sub);
-               dc.DrawBitmap (sub_bitmap, _display_sub_position.x, _display_sub_position.y);
-       }
+       dc.DrawBitmap (frame_bitmap, 0, 0);
 
        if (_out_size.width < _panel_size.width) {
                wxPen p (GetBackgroundColour ());
@@ -269,12 +243,10 @@ FilmViewer::paint_panel (wxPaintEvent &)
 void
 FilmViewer::slider_moved (wxScrollEvent &)
 {
-       if (!_film || !_film->length() || !_decoders.video) {
-               return;
-       }
+       cout << "slider " << _slider->GetValue() << " " << _film->length() << "\n";
        
-       if (_decoders.video->seek (_slider->GetValue() * _film->length().get() / (4096 * _film->source_frame_rate()))) {
-               return;
+       if (_film && _player) {
+               _player->seek (_slider->GetValue() * _film->length() / 4096);
        }
        
        get_frame ();
@@ -311,49 +283,21 @@ FilmViewer::raw_to_display ()
                return;
        }
 
-       boost::shared_ptr<const Image> input = _raw_frame;
-
-       pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
-       if (!s.second.empty ()) {
-               input = input->post_process (s.second, true);
-       }
-       
        /* Get a compacted image as we have to feed it to wxWidgets */
-       _display_frame = input->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false);
-
-       if (_raw_sub) {
-
-               /* Our output is already cropped by the decoder, so we need to account for that
-                  when working out the scale that we are applying.
-               */
-
-               Size const cropped_size = _film->cropped_size (_film->size ());
-
-               dvdomatic::Rect tx = subtitle_transformed_area (
-                       float (_film_size.width) / cropped_size.width,
-                       float (_film_size.height) / cropped_size.height,
-                       _raw_sub->area(), _film->subtitle_offset(), _film->subtitle_scale()
-                       );
-               
-               _display_sub.reset (new RGBPlusAlphaImage (_raw_sub->image()->scale (tx.size(), _film->scaler(), false)));
-               _display_sub_position = tx.position();
-               _display_sub_position.x += _display_frame_x;
-       } else {
-               _display_sub.reset ();
-       }
+       _display_frame.reset (new SimpleImage (_raw_frame, false));
 }      
 
 void
 FilmViewer::calculate_sizes ()
 {
-       if (!_film) {
+       if (!_film || !_player) {
                return;
        }
 
-       Format const * format = _film->format ();
+       Ratio const * container = _film->container ();
        
        float const panel_ratio = static_cast<float> (_panel_size.width) / _panel_size.height;
-       float const film_ratio = format ? format->container_ratio () : 1.78;
+       float const film_ratio = container ? container->ratio () : 1.78;
                        
        if (panel_ratio < film_ratio) {
                /* panel is less widscreen than the film; clamp width */
@@ -365,21 +309,12 @@ FilmViewer::calculate_sizes ()
                _out_size.width = _out_size.height * film_ratio;
        }
 
-       /* Work out how much padding there is in terms of our display; this will be the x position
-          of our _display_frame.
-       */
-       _display_frame_x = 0;
-       if (format) {
-               _display_frame_x = static_cast<float> (format->dcp_padding (_film)) * _out_size.width / format->dcp_size().width;
-       }
-
-       _film_size = _out_size;
-       _film_size.width -= _display_frame_x * 2;
-
        /* Catch silly values */
-       if (_out_size.width < 64) {
-               _out_size.width = 64;
-       }
+       _out_size.width = max (64, _out_size.width);
+       _out_size.height = max (64, _out_size.height);
+
+       _player->set_video_container_size (_out_size);
+       update_from_decoder ();
 }
 
 void
@@ -391,32 +326,31 @@ FilmViewer::play_clicked (wxCommandEvent &)
 void
 FilmViewer::check_play_state ()
 {
-       if (!_film) {
+       if (!_film || _film->dcp_video_frame_rate() == 0) {
                return;
        }
        
        if (_play_button->GetValue()) {
-               _timer.Start (1000 / _film->source_frame_rate());
+               _timer.Start (1000 / _film->dcp_video_frame_rate());
        } else {
                _timer.Stop ();
        }
 }
 
 void
-FilmViewer::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subtitle> sub, double t)
+FilmViewer::process_video (shared_ptr<const Image> image, bool, Time t)
 {
        _raw_frame = image;
-       _raw_sub = sub;
 
        raw_to_display ();
 
        _got_frame = true;
 
-       double const fps = _decoders.video->frames_per_second ();
+       double const fps = _film->dcp_video_frame_rate ();
        /* Count frame number from 1 ... not sure if this is the best idea */
-       _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps)) + 1));
+       _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ)) + 1));
 
-       double w = t;
+       double w = static_cast<double>(t) / TIME_HZ;
        int const h = (w / 3600);
        w -= h * 3600;
        int const m = (w / 60);
@@ -427,13 +361,16 @@ FilmViewer::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subti
        _timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d:%02d"), h, m, s, f));
 }
 
+/** Get a new _raw_frame from the decoder and then do
+ *  raw_to_display ().
+ */
 void
 FilmViewer::get_frame ()
 {
        /* Clear our raw frame in case we don't get a new one */
        _raw_frame.reset ();
 
-       if (_decoders.video == 0) {
+       if (!_player) {
                _display_frame.reset ();
                return;
        }
@@ -441,7 +378,7 @@ FilmViewer::get_frame ()
        try {
                _got_frame = false;
                while (!_got_frame) {
-                       if (_decoders.video->pass ()) {
+                       if (_player->pass ()) {
                                /* We didn't get a frame before the decoder gave up,
                                   so clear our display frame.
                                */
@@ -476,14 +413,26 @@ FilmViewer::active_jobs_changed (bool a)
        _play_button->Enable (!a);
 }
 
+void
+FilmViewer::film_content_changed (weak_ptr<Content>, int p)
+{
+       if (p == ContentProperty::LENGTH) {
+               /* Force an update to our frame */
+               wxScrollEvent ev;
+               slider_moved (ev);
+       } else if (p == VideoContentProperty::VIDEO_CROP || p == VideoContentProperty::VIDEO_RATIO) {
+               update_from_decoder ();
+       }               
+}
+
 void
 FilmViewer::back_clicked (wxCommandEvent &)
 {
-       if (!_decoders.video) {
+       if (!_player) {
                return;
        }
        
-       _decoders.video->seek_back ();
+       _player->seek_back ();
        get_frame ();
        _panel->Refresh ();
        _panel->Update ();
@@ -492,11 +441,10 @@ FilmViewer::back_clicked (wxCommandEvent &)
 void
 FilmViewer::forward_clicked (wxCommandEvent &)
 {
-       if (!_decoders.video) {
+       if (!_player) {
                return;
        }
 
-       _decoders.video->seek_forward ();
        get_frame ();
        _panel->Refresh ();
        _panel->Update ();
index ed5874fbcc6f945d6285db27bbe74801998fb893..6c18c7c5bc97b12298fcf9571bf759e6f96afc17 100644 (file)
 
 #include <wx/wx.h>
 #include "lib/film.h"
-#include "lib/decoder_factory.h"
 
 class wxToggleButton;
 class FFmpegPlayer;
 class Image;
 class RGBPlusAlphaImage;
-class Subtitle;
 
 /** @class FilmViewer
  *  @brief A wx widget to view a preview of a Film.
+ *
+ *  The film takes the following path through the viewer:
+ *
+ *  1.  get_frame() asks our _player to decode some data.  If it does, process_video()
+ *      will be called.
+ *
+ *  2.  process_video() takes the image from the decoder (_raw_frame) and calls raw_to_display().
+ * 
+ *  3.  raw_to_display() copies _raw_frame to _display_frame, processing it and scaling it.
+ *
+ *  4.  calling _panel->Refresh() and _panel->Update() results in paint_panel() being called;
+ *      this creates frame_bitmap from _display_frame and blits it to the display.
+ *
+ * update_from_decoder() asks the player to re-emit its current frame on the next pass(), and then
+ * starts from step #1.
+ *
+ * update_from_raw() starts at step #3, then calls _panel->Refresh and _panel->Update.
  */
 class FilmViewer : public wxPanel
 {
@@ -43,16 +58,17 @@ public:
 
 private:
        void film_changed (Film::Property);
+       void film_content_changed (boost::weak_ptr<Content>, int);
        void paint_panel (wxPaintEvent &);
        void panel_sized (wxSizeEvent &);
        void slider_moved (wxScrollEvent &);
        void play_clicked (wxCommandEvent &);
        void timer (wxTimerEvent &);
-       void process_video (boost::shared_ptr<const Image>, bool, boost::shared_ptr<Subtitle>, double);
+       void process_video (boost::shared_ptr<const Image>, bool, Time);
        void calculate_sizes ();
        void check_play_state ();
        void update_from_raw ();
-       void decoder_changed ();
+       void update_from_decoder ();
        void raw_to_display ();
        void get_frame ();
        void active_jobs_changed (bool);
@@ -60,6 +76,7 @@ private:
        void forward_clicked (wxCommandEvent &);
 
        boost::shared_ptr<Film> _film;
+       boost::shared_ptr<Player> _player;
 
        wxSizer* _v_sizer;
        wxPanel* _panel;
@@ -71,22 +88,12 @@ private:
        wxToggleButton* _play_button;
        wxTimer _timer;
 
-       Decoders _decoders;
        boost::shared_ptr<const Image> _raw_frame;
-       boost::shared_ptr<Subtitle> _raw_sub;
        boost::shared_ptr<const Image> _display_frame;
-       /* The x offset at which we display the actual film content; this corresponds
-          to the film's padding converted to our coordinates.
-       */
-       int _display_frame_x;
-       boost::shared_ptr<RGBPlusAlphaImage> _display_sub;
-       Position _display_sub_position;
        bool _got_frame;
 
        /** Size of our output (including padding if we have any) */
        libdcp::Size _out_size;
-       /** Size that we will make our film (equal to _out_size unless we have padding) */
-       libdcp::Size _film_size;
        /** Size of the panel that we have available */
        libdcp::Size _panel_size;
 };
index 7499cbf8ba472659e25b807e02eae1c97c3d2d5e..17ebbb9831c37ad7ab297903ad0d62bc90571ce3 100644 (file)
@@ -26,7 +26,7 @@ using namespace boost;
 GainCalculatorDialog::GainCalculatorDialog (wxWindow* parent)
        : wxDialog (parent, wxID_ANY, _("Gain Calculator"))
 {
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
 
        add_label_to_sizer (table, this, _("I want to play this back at fader"), true);
diff --git a/src/wx/imagemagick_content_dialog.cc b/src/wx/imagemagick_content_dialog.cc
new file mode 100644 (file)
index 0000000..6aa7562
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <wx/spinctrl.h>
+#include "lib/imagemagick_content.h"
+#include "imagemagick_content_dialog.h"
+#include "wx_util.h"
+
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+
+ImageMagickContentDialog::ImageMagickContentDialog (wxWindow* parent, shared_ptr<ImageMagickContent> content)
+       : wxDialog (parent, wxID_ANY, _("Image"))
+       , _content (content)
+{
+       wxFlexGridSizer* grid = new wxFlexGridSizer (3, 6, 6);
+       grid->AddGrowableCol (1, 1);
+
+       {
+               add_label_to_sizer (grid, this, _("Duration"), true);
+               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _video_length = new wxSpinCtrl (this);
+               s->Add (_video_length);
+               /// TRANSLATORS: this is an abbreviation for seconds, the unit of time
+               add_label_to_sizer (s, this, _("s"), false);
+               grid->Add (s);
+       }
+
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       overall_sizer->Add (grid, 1, wxEXPAND | wxALL, 6);
+
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
+       if (buttons) {
+               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+
+       SetSizer (overall_sizer);
+       overall_sizer->Layout ();
+       overall_sizer->SetSizeHints (this);
+
+       checked_set (_video_length, content->video_length () / 24);
+       _video_length->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ImageMagickContentDialog::video_length_changed), 0, this);
+}
+
+void
+ImageMagickContentDialog::video_length_changed (wxCommandEvent &)
+{
+       shared_ptr<ImageMagickContent> c = _content.lock ();
+       if (!c) {
+               return;
+       }
+       
+       c->set_video_length (_video_length->GetValue() * 24);
+}
diff --git a/src/wx/imagemagick_content_dialog.h b/src/wx/imagemagick_content_dialog.h
new file mode 100644 (file)
index 0000000..23722f1
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+    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/wx.h>
+
+class wxSpinCtrl;
+class ImageMagickContent;
+class Region;
+
+class ImageMagickContentDialog : public wxDialog
+{
+public:
+       ImageMagickContentDialog (wxWindow *, boost::shared_ptr<ImageMagickContent>);
+
+private:
+       void video_length_changed (wxCommandEvent &);
+
+       boost::weak_ptr<ImageMagickContent> _content;
+       wxSpinCtrl* _video_length;
+};
index cfe09aec83454abac8d7b5a353d9d2f19cf8d6e9..b5c66bd3ed5740f74136b29a49195b56e5543f8e 100644 (file)
@@ -86,6 +86,7 @@ JobManagerView::update ()
                        JobRecord r;
                        int n = 1;
                        r.finalised = false;
+                       r.scroll_nudged = false;
                        r.gauge = new wxGauge (_panel, wxID_ANY, 100);
                        _table->Insert (index + n, r.gauge, 1, wxEXPAND | wxLEFT | wxRIGHT);
                        ++n;
@@ -113,6 +114,7 @@ JobManagerView::update ()
                        ++n;
                        
                        _job_records[*i] = r;
+
                }
 
                string const st = (*i)->status ();
@@ -126,8 +128,25 @@ JobManagerView::update ()
                                checked_set (_job_records[*i].message, wx_to_std (_("Running")));
                                _job_records[*i].gauge->Pulse ();
                        }
+
                }
 
+               if (!_job_records[*i].scroll_nudged && ((*i)->running () || (*i)->finished())) {
+                       int x, y;
+                       _job_records[*i].gauge->GetPosition (&x, &y);
+                       int px, py;
+                       GetScrollPixelsPerUnit (&px, &py);
+                       int vx, vy;
+                       GetViewStart (&vx, &vy);
+                       int sx, sy;
+                       GetClientSize (&sx, &sy);
+
+                       if (y > (vy * py + sy / 2)) {
+                               Scroll (-1, y / py);
+                               _job_records[*i].scroll_nudged = true;
+                       }
+               }
+                       
                if ((*i)->finished() && !_job_records[*i].finalised) {
                        checked_set (_job_records[*i].message, st);
                        if (!(*i)->finished_cancelled()) {
@@ -155,7 +174,7 @@ JobManagerView::details_clicked (wxCommandEvent& ev)
 {
        wxObject* o = ev.GetEventObject ();
 
-       for (map<boost::shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
+       for (map<shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
                if (i->second.details == o) {
                        string s = i->first->error_summary();
                        s[0] = toupper (s[0]);
@@ -169,7 +188,7 @@ JobManagerView::cancel_clicked (wxCommandEvent& ev)
 {
        wxObject* o = ev.GetEventObject ();
 
-       for (map<boost::shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
+       for (map<shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
                if (i->second.cancel == o) {
                        i->first->cancel ();
                }
index fc29eadb4760c3bb74d9f2a55b4142c9f7967e19..3d1ad30c07f60e60a53d4473b374fe7d82c13f39 100644 (file)
@@ -57,6 +57,7 @@ private:
                wxButton* pause;
                wxButton* details;
                bool finalised;
+               bool scroll_nudged;
        };
                
        std::map<boost::shared_ptr<Job>, JobRecord> _job_records;
index 289926b8e6a0f870abc62b492c95dcceafe05c26..d4b78d5bff1ad845afd1732c3ef79c83720aecf7 100644 (file)
@@ -22,7 +22,7 @@
 #include "lib/config.h"
 #include "new_film_dialog.h"
 #include "wx_util.h"
-#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
 #include "dir_picker_ctrl.h"
 #endif
 
@@ -37,7 +37,7 @@ NewFilmDialog::NewFilmDialog (wxWindow* parent)
        wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
        SetSizer (overall_sizer);
        
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
        overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6);
 
@@ -47,7 +47,7 @@ NewFilmDialog::NewFilmDialog (wxWindow* parent)
 
        add_label_to_sizer (table, this, _("Create in folder"), true);
 
-#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
        _folder = new DirPickerCtrl (this); 
 #else  
        _folder = new wxDirPickerCtrl (this, wxDD_DIR_MUST_EXIST);
index 220bba732ef1858c499583e944979cac4619f92c..f8f3aa08dd1ca5f360afaa495299afd1d8470346 100644 (file)
@@ -33,7 +33,7 @@ public:
 
 private:
        wxTextCtrl* _name;
-#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
        DirPickerCtrl* _folder;
 #else  
        wxDirPickerCtrl* _folder;
index 8bc80675561edb758520f3875bab0909d2fe37b2..34646c2b7ea99f70af6d49e36009d8bcd3d9db5d 100644 (file)
@@ -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"
@@ -21,10 +21,6 @@ msgstr ""
 msgid "%"
 msgstr "%"
 
-#: 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"
@@ -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
@@ -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"
index 5c0a7b63c780cc0dc8e821cc5848f8120d1d0959..3f150bb0edc88d4b3e52fb5448c62c19117ddb4e 100644 (file)
@@ -5,7 +5,7 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: DVD-o-matic FRENCH\n"
+"Project-Id-Version: DCP-o-matic FRENCH\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2013-06-04 23:59+0100\n"
 "PO-Revision-Date: 2013-05-10 14:19+0100\n"
@@ -148,9 +148,13 @@ msgstr "Cadence image du DCP"
 msgid "DCP Name"
 msgstr "Nom du 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 "Préférences DCP-o-matic"
 
 #: src/wx/config_dialog.cc:46
 #, fuzzy
@@ -159,8 +163,8 @@ msgstr "Préférences de DCP-o-matic"
 
 #: src/wx/audio_dialog.cc:101
 #, c-format
-msgid "DVD-o-matic audio - %s"
-msgstr "Son DVD-o-matic  - %s"
+msgid "DCP-o-matic audio - %s"
+msgstr "Son DCP-o-matic  - %s"
 
 #: src/wx/config_dialog.cc:120
 msgid "Default DCI name details"
index 518bd7ddef1a9583edcdcc00d7d075533626260a..92161172a4fb3c11bd1f42f7eadedfe92720799e 100644 (file)
@@ -21,10 +21,9 @@ msgstr ""
 msgid "%"
 msgstr "%"
 
-#: src/wx/config_dialog.cc:98
-#, fuzzy
-msgid "(restart DVD-o-matic to see language changes)"
-msgstr "(riavviare DVD-o-matic per vedere i cambiamenti di lingua)"
+#: src/wx/config_dialog.cc:61
+msgid "(restart DCP-o-matic to see language changes)"
+msgstr "(riavviare DCP-o-matic per vedere i cambiamenti di lingua)"
 
 #: src/wx/film_editor.cc:1276
 msgid "1 channel"
@@ -150,9 +149,13 @@ msgstr "Frequenza fotogrammi del DCP"
 msgid "DCP Name"
 msgstr "Nome del 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 "Preferenze DCP-o-matic"
 
 #: src/wx/config_dialog.cc:46
 #, fuzzy
@@ -161,8 +164,8 @@ msgstr "Preferenze DVD-o-matic"
 
 #: src/wx/audio_dialog.cc:101
 #, c-format
-msgid "DVD-o-matic audio - %s"
-msgstr "Audio DVD-o-matic - %s"
+msgid "DCP-o-matic audio - %s"
+msgstr "Audio DCP-o-matic - %s"
 
 #: src/wx/config_dialog.cc:120
 msgid "Default DCI name details"
index 6eda1cf4bf52a1780b11183f9b2a5c60902819f1..02df467ca4b503b7f187e68ea8da7c584055cf9e 100644 (file)
@@ -5,7 +5,7 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: DVD-o-matic\n"
+"Project-Id-Version: DCP-o-matic\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2013-06-04 23:59+0100\n"
 "PO-Revision-Date: 2013-04-09 10:13+0100\n"
@@ -21,10 +21,9 @@ msgstr ""
 msgid "%"
 msgstr "%"
 
-#: src/wx/config_dialog.cc:98
-#, fuzzy
-msgid "(restart DVD-o-matic to see language changes)"
-msgstr "(starta om DVD-o-matic för att se sprÃ¥kändringar)"
+#: src/wx/config_dialog.cc:61
+msgid "(restart DCP-o-matic to see language changes)"
+msgstr "(starta om DCP-o-matic för att se sprÃ¥kändringar)"
 
 #: src/wx/film_editor.cc:1276
 msgid "1 channel"
@@ -150,9 +149,13 @@ msgstr "DCP bildhastighet"
 msgid "DCP Name"
 msgstr "DCP Namn"
 
-#: 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 "DCP-o-matic Inställningar"
 
 #: src/wx/config_dialog.cc:46
 #, fuzzy
@@ -161,8 +164,8 @@ msgstr "DVD-o-matic Inställningar"
 
 #: src/wx/audio_dialog.cc:101
 #, c-format
-msgid "DVD-o-matic audio - %s"
-msgstr "DVD-o-matic audio - %s"
+msgid "DCP-o-matic audio - %s"
+msgstr "DCP-o-matic audio - %s"
 
 #: src/wx/config_dialog.cc:120
 msgid "Default DCI name details"
index aa97623cd943ca3dbc29deba2a1e9b80452e4660..d525fe38b115d059bd2cc2f279952540dccb456b 100644 (file)
@@ -36,7 +36,7 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film)
        : wxDialog (parent, wxID_ANY, _("Film Properties"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
        , _film (film)
 {
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
 
        add_label_to_sizer (table, this, _("Frames"), true);
        _frames = new wxStaticText (this, wxID_ANY, wxT (""));
@@ -50,18 +50,11 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film)
        _encoded = new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this));
        table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL);
 
-       if (_film->length()) {
-               _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().get())));
-               FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate());
-               int const dcp_length = _film->length().get() * frc.factor();
-               double const disk = ((double) _film->j2k_bandwidth() / 8) * dcp_length / (_film->dcp_frame_rate() * 1073741824.0f);
-               stringstream s;
-               s << fixed << setprecision (1) << disk << wx_to_std (_("Gb"));
-               _disk->SetLabel (std_to_wx (s.str ()));
-       } else {
-               _frames->SetLabel (_("unknown"));
-               _disk->SetLabel (_("unknown"));
-       }
+       _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->time_to_video_frames (_film->length()))));
+       double const disk = ((double) _film->j2k_bandwidth() / 8) * _film->length() / (TIME_HZ * 1073741824.0f);
+       stringstream s;
+       s << fixed << setprecision (1) << disk << wx_to_std (_("Gb"));
+       _disk->SetLabel (std_to_wx (s.str ()));
 
        wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
        overall_sizer->Add (table, 0, wxALL, 6);
@@ -87,7 +80,7 @@ PropertiesDialog::frames_already_encoded () const
        
        if (_film->length()) {
                /* XXX: encoded_frames() should check which frames have been encoded */
-               u << " (" << (_film->encoded_frames() * 100 / _film->length().get()) << "%)";
+               u << " (" << (_film->encoded_frames() * 100 / _film->time_to_video_frames (_film->length())) << "%)";
        }
        return u.str ();
 }
index 30e3c0f83d07637a46403b0810cfee2e07809648..33cb392bfe52886f2878ebfd33ad001022483f58 100644 (file)
@@ -30,7 +30,7 @@ ServerDialog::ServerDialog (wxWindow* parent, ServerDescription* server)
                _server = new ServerDescription (wx_to_std (N_("localhost")), 1);
        }
                
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
 
        add_label_to_sizer (table, this, _("Host name or IP address"), true);
diff --git a/src/wx/timecode.cc b/src/wx/timecode.cc
new file mode 100644 (file)
index 0000000..6ce1c1c
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/lexical_cast.hpp>
+#include "timecode.h"
+#include "wx_util.h"
+
+using std::string;
+using std::cout;
+using boost::lexical_cast;
+
+Timecode::Timecode (wxWindow* parent)
+       : wxPanel (parent)
+       , _in_set (false)
+{
+       wxClientDC dc (parent);
+       wxSize size = dc.GetTextExtent (wxT ("9999"));
+       size.SetHeight (-1);
+
+       wxTextValidator validator (wxFILTER_INCLUDE_CHAR_LIST);
+       wxArrayString list;
+
+       wxString n (wxT ("0123456789"));
+       for (size_t i = 0; i < n.Length(); ++i) {
+               list.Add (n[i]);
+       }
+
+       validator.SetIncludes (list);
+       
+       wxBoxSizer* sizer = new wxBoxSizer (wxHORIZONTAL);
+       _hours = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size, 0, validator);
+       _hours->SetMaxLength (2);
+       sizer->Add (_hours);
+       add_label_to_sizer (sizer, this, wxT (":"), false);
+       _minutes = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size);
+       _minutes->SetMaxLength (2);
+       sizer->Add (_minutes);
+       add_label_to_sizer (sizer, this, wxT (":"), false);
+       _seconds = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size);
+       _seconds->SetMaxLength (2);
+       sizer->Add (_seconds);
+       add_label_to_sizer (sizer, this, wxT ("."), false);
+       _frames = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size);
+       _frames->SetMaxLength (2);
+       sizer->Add (_frames);
+
+       _hours->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this);
+       _minutes->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this);
+       _seconds->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this);
+       _frames->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this);
+
+       SetSizerAndFit (sizer);
+}
+
+void
+Timecode::set (Time t, int fps)
+{
+       _in_set = true;
+       
+       int const h = t / (3600 * TIME_HZ);
+       t -= h * 3600 * TIME_HZ;
+       int const m = t / (60 * TIME_HZ);
+       t -= m * 60 * TIME_HZ;
+       int const s = t / TIME_HZ;
+       t -= s * TIME_HZ;
+       int const f = t * fps / TIME_HZ;
+
+       _hours->SetValue (wxString::Format (wxT ("%d"), h));
+       _minutes->SetValue (wxString::Format (wxT ("%d"), m));
+       _seconds->SetValue (wxString::Format (wxT ("%d"), s));
+       _frames->SetValue (wxString::Format (wxT ("%d"), f));
+
+       _in_set = false;
+}
+
+Time
+Timecode::get (int fps) const
+{
+       Time t = 0;
+       string const h = wx_to_std (_hours->GetValue ());
+       t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ;
+       string const m = wx_to_std (_minutes->GetValue());
+       t += lexical_cast<int> (m.empty() ? "0" : m) * 60 * TIME_HZ;
+       string const s = wx_to_std (_seconds->GetValue());
+       t += lexical_cast<int> (s.empty() ? "0" : s) * TIME_HZ;
+       string const f = wx_to_std (_frames->GetValue());
+       t += lexical_cast<int> (f.empty() ? "0" : f) * TIME_HZ / fps;
+       return t;
+}
+
+void
+Timecode::changed (wxCommandEvent &)
+{
+       if (_in_set) {
+               return;
+       }
+       
+       Changed ();
+}
diff --git a/src/wx/timecode.h b/src/wx/timecode.h
new file mode 100644 (file)
index 0000000..9b6fe66
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/signals2.hpp>
+#include <wx/wx.h>
+#include "lib/types.h"
+
+class Timecode : public wxPanel
+{
+public:
+       Timecode (wxWindow *);
+
+       void set (Time, int);
+       Time get (int) const;
+
+       boost::signals2::signal<void ()> Changed;
+
+private:
+       void changed (wxCommandEvent &);
+       
+       wxTextCtrl* _hours;
+       wxTextCtrl* _minutes;
+       wxTextCtrl* _seconds;
+       wxTextCtrl* _frames;
+
+       bool _in_set;
+};
diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc
new file mode 100644 (file)
index 0000000..113e883
--- /dev/null
@@ -0,0 +1,561 @@
+/*
+    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 <list>
+#include <wx/graphics.h>
+#include <boost/weak_ptr.hpp>
+#include "film.h"
+#include "film_editor.h"
+#include "timeline.h"
+#include "wx_util.h"
+#include "lib/playlist.h"
+
+using std::list;
+using std::cout;
+using std::max;
+using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
+using boost::bind;
+
+class View
+{
+public:
+       View (Timeline& t)
+               : _timeline (t)
+       {
+
+       }
+               
+       void paint (wxGraphicsContext* g)
+       {
+               _last_paint_bbox = bbox ();
+               do_paint (g);
+       }
+       
+       void force_redraw ()
+       {
+               _timeline.force_redraw (_last_paint_bbox);
+               _timeline.force_redraw (bbox ());
+       }
+
+       virtual dcpomatic::Rect bbox () const = 0;
+
+protected:
+       virtual void do_paint (wxGraphicsContext *) = 0;
+       
+       int time_x (Time t) const
+       {
+               return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit();
+       }
+       
+       Timeline& _timeline;
+
+private:
+       dcpomatic::Rect _last_paint_bbox;
+};
+
+class ContentView : public View
+{
+public:
+       ContentView (Timeline& tl, shared_ptr<Content> c, int t)
+               : View (tl)
+               , _content (c)
+               , _track (t)
+               , _selected (false)
+       {
+               _content_connection = c->Changed.connect (bind (&ContentView::content_changed, this, _2));
+       }
+
+       dcpomatic::Rect bbox () const
+       {
+               shared_ptr<const Film> film = _timeline.film ();
+               shared_ptr<const Content> content = _content.lock ();
+               if (!film || !content) {
+                       return dcpomatic::Rect ();
+               }
+               
+               return dcpomatic::Rect (
+                       time_x (content->start ()) - 8,
+                       y_pos (_track) - 8,
+                       content->length () * _timeline.pixels_per_time_unit() + 16,
+                       _timeline.track_height() + 16
+                       );
+       }
+
+       void set_selected (bool s) {
+               _selected = s;
+               force_redraw ();
+       }
+       
+       bool selected () const {
+               return _selected;
+       }
+
+       weak_ptr<Content> content () const {
+               return _content;
+       }
+
+       void set_track (int t) {
+               _track = t;
+       }
+
+       int track () const {
+               return _track;
+       }
+
+       virtual wxString type () const = 0;
+       virtual wxColour colour () const = 0;
+       
+private:
+
+       void do_paint (wxGraphicsContext* gc)
+       {
+               shared_ptr<const Film> film = _timeline.film ();
+               shared_ptr<const Content> content = _content.lock ();
+               if (!film || !content) {
+                       return;
+               }
+
+               Time const start = content->start ();
+               Time const len = content->length ();
+
+               wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2);
+
+               gc->SetPen (*wxBLACK_PEN);
+               
+#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
+               gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 4, wxPENSTYLE_SOLID));
+               if (_selected) {
+                       gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (selected, wxBRUSHSTYLE_SOLID));
+               } else {
+                       gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (colour(), wxBRUSHSTYLE_SOLID));
+               }
+#else                  
+               gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 4, wxSOLID));
+               if (_selected) {
+                       gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (selected, wxSOLID));
+               } else {
+                       gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (colour(), wxSOLID));
+               }
+#endif
+               
+               wxGraphicsPath path = gc->CreatePath ();
+               path.MoveToPoint    (time_x (start),       y_pos (_track) + 4);
+               path.AddLineToPoint (time_x (start + len), y_pos (_track) + 4);
+               path.AddLineToPoint (time_x (start + len), y_pos (_track + 1) - 4);
+               path.AddLineToPoint (time_x (start),       y_pos (_track + 1) - 4);
+               path.AddLineToPoint (time_x (start),       y_pos (_track) + 4);
+               gc->StrokePath (path);
+               gc->FillPath (path);
+
+               wxString name = wxString::Format (wxT ("%s [%s]"), std_to_wx (content->file().filename().string()).data(), type().data());
+               wxDouble name_width;
+               wxDouble name_height;
+               wxDouble name_descent;
+               wxDouble name_leading;
+               gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading);
+               
+               gc->Clip (wxRegion (time_x (start), y_pos (_track), len * _timeline.pixels_per_time_unit(), _timeline.track_height()));
+               gc->DrawText (name, time_x (start) + 12, y_pos (_track + 1) - name_height - 4);
+               gc->ResetClip ();
+       }
+
+       int y_pos (int t) const
+       {
+               return _timeline.tracks_position().y + t * _timeline.track_height();
+       }
+
+       void content_changed (int p)
+       {
+               if (p == ContentProperty::START || p == ContentProperty::LENGTH) {
+                       force_redraw ();
+               }
+       }
+
+       boost::weak_ptr<Content> _content;
+       int _track;
+       bool _selected;
+
+       boost::signals2::scoped_connection _content_connection;
+};
+
+class AudioContentView : public ContentView
+{
+public:
+       AudioContentView (Timeline& tl, shared_ptr<Content> c, int t)
+               : ContentView (tl, c, t)
+       {}
+       
+private:
+       wxString type () const
+       {
+               return _("audio");
+       }
+
+       wxColour colour () const
+       {
+               return wxColour (149, 121, 232, 255);
+       }
+};
+
+class VideoContentView : public ContentView
+{
+public:
+       VideoContentView (Timeline& tl, shared_ptr<Content> c, int t)
+               : ContentView (tl, c, t)
+       {}
+
+private:       
+
+       wxString type () const
+       {
+               return _("video");
+       }
+
+       wxColour colour () const
+       {
+               return wxColour (242, 92, 120, 255);
+       }
+};
+
+class TimeAxisView : public View
+{
+public:
+       TimeAxisView (Timeline& tl, int y)
+               : View (tl)
+               , _y (y)
+       {}
+       
+       dcpomatic::Rect bbox () const
+       {
+               return dcpomatic::Rect (0, _y - 4, _timeline.width(), 24);
+       }
+
+       void set_y (int y)
+       {
+               _y = y;
+               force_redraw ();
+       }
+
+private:       
+
+       void do_paint (wxGraphicsContext* gc)
+       {
+#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
+               gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID));
+#else              
+               gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxSOLID));
+#endif             
+               
+               int mark_interval = rint (128 / (TIME_HZ * _timeline.pixels_per_time_unit ()));
+               if (mark_interval > 5) {
+                       mark_interval -= mark_interval % 5;
+               }
+               if (mark_interval > 10) {
+                       mark_interval -= mark_interval % 10;
+               }
+               if (mark_interval > 60) {
+                       mark_interval -= mark_interval % 60;
+               }
+               if (mark_interval > 3600) {
+                       mark_interval -= mark_interval % 3600;
+               }
+               
+               if (mark_interval < 1) {
+                       mark_interval = 1;
+               }
+
+               wxGraphicsPath path = gc->CreatePath ();
+               path.MoveToPoint (_timeline.x_offset(), _y);
+               path.AddLineToPoint (_timeline.width(), _y);
+               gc->StrokePath (path);
+
+               Time t = 0;
+               while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) {
+                       wxGraphicsPath path = gc->CreatePath ();
+                       path.MoveToPoint (time_x (t), _y - 4);
+                       path.AddLineToPoint (time_x (t), _y + 4);
+                       gc->StrokePath (path);
+
+                       int tc = t / TIME_HZ;
+                       int const h = tc / 3600;
+                       tc -= h * 3600;
+                       int const m = tc / 60;
+                       tc -= m * 60;
+                       int const s = tc;
+                       
+                       wxString str = wxString::Format (wxT ("%02d:%02d:%02d"), h, m, s);
+                       wxDouble str_width;
+                       wxDouble str_height;
+                       wxDouble str_descent;
+                       wxDouble str_leading;
+                       gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading);
+                       
+                       int const tx = _timeline.x_offset() + t * _timeline.pixels_per_time_unit();
+                       if ((tx + str_width) < _timeline.width()) {
+                               gc->DrawText (str, time_x (t), _y + 16);
+                       }
+                       
+                       t += mark_interval * TIME_HZ;
+               }
+       }
+
+private:
+       int _y;
+};
+
+Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
+       : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
+       , _film_editor (ed)
+       , _film (film)
+       , _time_axis_view (new TimeAxisView (*this, 32))
+       , _tracks (0)
+       , _pixels_per_time_unit (0)
+       , _left_down (false)
+       , _down_view_start (0)
+       , _first_move (false)
+{
+       SetDoubleBuffered (true);
+
+       setup_pixels_per_time_unit ();
+       
+       Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (Timeline::paint), 0, this);
+       Connect (wxID_ANY, wxEVT_LEFT_DOWN, wxMouseEventHandler (Timeline::left_down), 0, this);
+       Connect (wxID_ANY, wxEVT_LEFT_UP, wxMouseEventHandler (Timeline::left_up), 0, this);
+       Connect (wxID_ANY, wxEVT_MOTION, wxMouseEventHandler (Timeline::mouse_moved), 0, this);
+       Connect (wxID_ANY, wxEVT_SIZE, wxSizeEventHandler (Timeline::resized), 0, this);
+
+       playlist_changed ();
+
+       SetMinSize (wxSize (640, tracks() * track_height() + 96));
+
+       _playlist_connection = film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this));
+
+       _views.push_back (_time_axis_view);
+}
+
+void
+Timeline::paint (wxPaintEvent &)
+{
+       wxPaintDC dc (this);
+
+       wxGraphicsContext* gc = wxGraphicsContext::Create (dc);
+       if (!gc) {
+               return;
+       }
+
+       gc->SetFont (gc->CreateFont (*wxNORMAL_FONT));
+
+       for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) {
+               (*i)->paint (gc);
+       }
+
+       delete gc;
+}
+
+void
+Timeline::playlist_changed ()
+{
+       shared_ptr<const Film> fl = _film.lock ();
+       if (!fl) {
+               return;
+       }
+
+       _views.clear ();
+
+       Playlist::ContentList content = fl->playlist()->content ();
+
+       for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) {
+               if (dynamic_pointer_cast<VideoContent> (*i)) {
+                       _views.push_back (shared_ptr<View> (new VideoContentView (*this, *i, 0)));
+               }
+               if (dynamic_pointer_cast<AudioContent> (*i)) {
+                       _views.push_back (shared_ptr<View> (new AudioContentView (*this, *i, 0)));
+               }
+       }
+
+       assign_tracks ();
+       Refresh ();
+}
+
+void
+Timeline::assign_tracks ()
+{
+       for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) {
+               shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
+               if (cv) {
+                       cv->set_track (0);
+                       _tracks = 1;
+               }
+       }
+
+       for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) {
+               shared_ptr<AudioContentView> acv = dynamic_pointer_cast<AudioContentView> (*i);
+               if (!acv) {
+                       continue;
+               }
+       
+               shared_ptr<Content> acv_content = acv->content().lock ();
+               assert (acv_content);
+               
+               int t = 1;
+               while (1) {
+                       list<shared_ptr<View> >::iterator j = _views.begin();
+                       while (j != _views.end()) {
+                               shared_ptr<AudioContentView> test = dynamic_pointer_cast<AudioContentView> (*j);
+                               if (!test) {
+                                       ++j;
+                                       continue;
+                               }
+                               
+                               shared_ptr<Content> test_content = test->content().lock ();
+                               assert (test_content);
+                                       
+                               if (test && test->track() == t) {
+                                       if ((acv_content->start() <= test_content->start() && test_content->start() <= acv_content->end()) ||
+                                           (acv_content->start() <= test_content->end()   && test_content->end()   <= acv_content->end())) {
+                                               /* we have an overlap on track `t' */
+                                               ++t;
+                                               break;
+                                       }
+                               }
+                               
+                               ++j;
+                       }
+
+                       if (j == _views.end ()) {
+                               /* no overlap on `t' */
+                               break;
+                       }
+               }
+
+               acv->set_track (t);
+               _tracks = max (_tracks, t + 1);
+       }
+
+       _time_axis_view->set_y (tracks() * track_height() + 32);
+}
+
+int
+Timeline::tracks () const
+{
+       return _tracks;
+}
+
+void
+Timeline::setup_pixels_per_time_unit ()
+{
+       shared_ptr<const Film> film = _film.lock ();
+       if (!film) {
+               return;
+       }
+
+       _pixels_per_time_unit = static_cast<double>(width() - x_offset() * 2) / film->length();
+}
+
+void
+Timeline::left_down (wxMouseEvent& ev)
+{
+       list<shared_ptr<View> >::iterator i = _views.begin();
+       Position const p (ev.GetX(), ev.GetY());
+       while (i != _views.end() && !(*i)->bbox().contains (p)) {
+               ++i;
+       }
+
+       _down_view.reset ();
+
+       if (i != _views.end ()) {
+               shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
+               if (cv) {
+                       _down_view = cv;
+                       shared_ptr<Content> c = cv->content().lock();
+                       assert (c);
+                       _down_view_start = c->start ();
+               }
+       }
+
+       for (list<shared_ptr<View> >::iterator j = _views.begin(); j != _views.end(); ++j) {
+               shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*j);
+               if (cv) {
+                       cv->set_selected (i == j);
+                       if (i == j) {
+                               _film_editor->set_selection (cv->content ());
+                       }
+               }
+       }
+
+       _left_down = true;
+       _down_point = ev.GetPosition ();
+       _first_move = false;
+}
+
+void
+Timeline::left_up (wxMouseEvent &)
+{
+       _left_down = false;
+}
+
+void
+Timeline::mouse_moved (wxMouseEvent& ev)
+{
+       if (!_left_down) {
+               return;
+       }
+
+       wxPoint const p = ev.GetPosition();
+
+       if (!_first_move) {
+               int const dist = sqrt (pow (p.x - _down_point.x, 2) + pow (p.y - _down_point.y, 2));
+               if (dist < 8) {
+                       return;
+               }
+               _first_move = true;
+       }
+
+       Time const time_diff = (p.x - _down_point.x) / _pixels_per_time_unit;
+       if (_down_view) {
+               shared_ptr<Content> c = _down_view->content().lock();
+               if (c) {
+                       c->set_start (max (static_cast<Time> (0), _down_view_start + time_diff));
+
+                       shared_ptr<Film> film = _film.lock ();
+                       assert (film);
+                       film->set_sequence_video (false);
+               }
+       }
+}
+
+void
+Timeline::force_redraw (dcpomatic::Rect const & r)
+{
+       RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
+}
+
+shared_ptr<const Film>
+Timeline::film () const
+{
+       return _film.lock ();
+}
+
+void
+Timeline::resized (wxSizeEvent &)
+{
+       setup_pixels_per_time_unit ();
+}
diff --git a/src/wx/timeline.h b/src/wx/timeline.h
new file mode 100644 (file)
index 0000000..5c25a64
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <boost/signals2.hpp>
+#include <wx/wx.h>
+#include "util.h"
+
+class Film;
+class View;
+class ContentView;
+class FilmEditor;
+class TimeAxisView;
+
+class Timeline : public wxPanel
+{
+public:
+       Timeline (wxWindow *, FilmEditor *, boost::shared_ptr<Film>);
+
+       boost::shared_ptr<const Film> film () const;
+
+       void force_redraw (dcpomatic::Rect const &);
+
+       int x_offset () const {
+               return 8;
+       }
+
+       int width () const {
+               return GetSize().GetWidth ();
+       }
+
+       int track_height () const {
+               return 48;
+       }
+
+       double pixels_per_time_unit () const {
+               return _pixels_per_time_unit;
+       }
+
+       Position tracks_position () const {
+               return Position (8, 8);
+       }
+
+       int tracks () const;
+
+private:
+       void paint (wxPaintEvent &);
+       void left_down (wxMouseEvent &);
+       void mouse_moved (wxMouseEvent &);
+       void left_up (wxMouseEvent &);
+       void playlist_changed ();
+       void setup_pixels_per_time_unit ();
+       void resized (wxSizeEvent &);
+       void assign_tracks ();
+
+       FilmEditor* _film_editor;
+       boost::weak_ptr<Film> _film;
+       std::list<boost::shared_ptr<View> > _views;
+       boost::shared_ptr<TimeAxisView> _time_axis_view;
+       int _tracks;
+       double _pixels_per_time_unit;
+       bool _left_down;
+       wxPoint _down_point;
+       boost::shared_ptr<ContentView> _down_view;
+       Time _down_view_start;
+       bool _first_move;
+
+       boost::signals2::scoped_connection _playlist_connection;
+};
diff --git a/src/wx/timeline_dialog.cc b/src/wx/timeline_dialog.cc
new file mode 100644 (file)
index 0000000..35d5eec
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+    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 <list>
+#include <wx/graphics.h>
+#include "film_editor.h"
+#include "timeline_dialog.h"
+#include "wx_util.h"
+#include "playlist.h"
+
+using std::list;
+using std::cout;
+using boost::shared_ptr;
+
+TimelineDialog::TimelineDialog (FilmEditor* ed, shared_ptr<Film> film)
+       : wxDialog (ed, wxID_ANY, _("Timeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
+       , _timeline (this, ed, film)
+{
+       wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+       
+       sizer->Add (&_timeline, 1, wxEXPAND | wxALL, 12);
+
+       SetSizer (sizer);
+       sizer->Layout ();
+       sizer->SetSizeHints (this);
+}
diff --git a/src/wx/timeline_dialog.h b/src/wx/timeline_dialog.h
new file mode 100644 (file)
index 0000000..17ca22c
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <wx/wx.h>
+#include "timeline.h"
+
+class Playlist;
+
+class TimelineDialog : public wxDialog
+{
+public:
+       TimelineDialog (FilmEditor *, boost::shared_ptr<Film>);
+
+private:
+       Timeline _timeline;
+};
index 345c02b087b10a9698780207be3dc1435525a404..1205fb21b22e833b07acd6f98ed5c06a406fc84e 100644 (file)
@@ -6,6 +6,7 @@ import i18n
 sources = """
           about_dialog.cc
           audio_dialog.cc
+          audio_mapping_view.cc
           audio_plot.cc
           config_dialog.cc
           dci_metadata_dialog.cc
@@ -15,11 +16,15 @@ sources = """
           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
           """
@@ -47,20 +52,20 @@ 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'
     if bld.env.TARGET_LINUX:
         obj.uselib += ' GTK'
-    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')
index b9e78932ae88b7664bf360b12ea3d859f5119ca1..c5887e17d80b5f3f8b5faade084fac29c73978ec 100644 (file)
@@ -76,7 +76,7 @@ add_label_to_grid_bag_sizer (wxGridBagSizer* s, wxWindow* p, wxString t, bool le
 void
 error_dialog (wxWindow* parent, wxString m)
 {
-       wxMessageDialog* d = new wxMessageDialog (parent, m, _("DVD-o-matic"), wxOK);
+       wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxOK);
        d->ShowModal ();
        d->Destroy ();
 }
@@ -84,7 +84,7 @@ error_dialog (wxWindow* parent, wxString m)
 bool
 confirm_dialog (wxWindow* parent, wxString m)
 {
-       wxMessageDialog* d = new wxMessageDialog (parent, m, _("DVD-o-matic"), wxYES_NO | wxICON_QUESTION);
+       wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxYES_NO | wxICON_QUESTION);
        int const r = d->ShowModal ();
        d->Destroy ();
        return r == wxID_YES;
@@ -231,7 +231,7 @@ checked_set (wxRadioButton* widget, bool value)
 }
 
 void
-dvdomatic_setup_i18n ()
+dcpomatic_setup_i18n ()
 {
        int language = wxLANGUAGE_DEFAULT;
 
@@ -247,12 +247,12 @@ dvdomatic_setup_i18n ()
        if (wxLocale::IsAvailable (language)) {
                locale = new wxLocale (language, wxLOCALE_LOAD_DEFAULT);
 
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
                locale->AddCatalogLookupPathPrefix (std_to_wx (mo_path().string()));
 #endif         
 
-               locale->AddCatalog (wxT ("libdvdomatic-wx"));
-               locale->AddCatalog (wxT ("dvdomatic"));
+               locale->AddCatalog (wxT ("libdcpomatic-wx"));
+               locale->AddCatalog (wxT ("dcpomatic"));
                
                if (!locale->IsOk()) {
                        delete locale;
@@ -262,6 +262,6 @@ dvdomatic_setup_i18n ()
        }
 
        if (locale) {
-               dvdomatic_setup_gettext_i18n (wx_to_std (locale->GetCanonicalName ()));
+               dcpomatic_setup_gettext_i18n (wx_to_std (locale->GetCanonicalName ()));
        }
 }
index 18a9f251c7a42c1ceba81510ce338af04bd2aa71..de6a09c35298fcc0b53460e4e8be8ef299695bed 100644 (file)
@@ -17,8 +17,8 @@
 
 */
 
-#ifndef DVDOMATIC_WX_UTIL_H
-#define DVDOMATIC_WX_UTIL_H
+#ifndef DCPOMATIC_WX_UTIL_H
+#define DCPOMATIC_WX_UTIL_H
 
 #include <wx/wx.h>
 #include <wx/gbsizer.h>
@@ -32,8 +32,8 @@ class wxFilePickerCtrl;
 class wxSpinCtrl;
 class wxGridBagSizer;
 
-#define DVDOMATIC_SIZER_X_GAP 8
-#define DVDOMATIC_SIZER_Y_GAP 8
+#define DCPOMATIC_SIZER_X_GAP 8
+#define DCPOMATIC_SIZER_Y_GAP 8
 
 /** @file src/wx/wx_util.h
  *  @brief Some utility functions and classes.
@@ -45,7 +45,7 @@ extern wxStaticText* add_label_to_sizer (wxSizer *, wxWindow *, wxString, bool l
 extern wxStaticText* add_label_to_grid_bag_sizer (wxGridBagSizer *, wxWindow *, wxString, bool, wxGBPosition, wxGBSpan span = wxDefaultSpan);
 extern std::string wx_to_std (wxString);
 extern wxString std_to_wx (std::string);
-extern void dvdomatic_setup_i18n ();
+extern void dcpomatic_setup_i18n ();
 
 /** @class ThreadedStaticText
  *
@@ -83,7 +83,7 @@ extern void checked_set (wxStaticText* widget, std::string value);
    Use our own dir picker as this is the least bad option I can think of.
 */
 #if defined(__WXMSW__) || (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION == 24 && GTK_MICRO_VERSION == 17)
-#define DVDOMATIC_USE_OWN_DIR_PICKER
+#define DCPOMATIC_USE_OWN_DIR_PICKER
 #endif
 
 #endif
diff --git a/test/black_fill_test.cc b/test/black_fill_test.cc
new file mode 100644 (file)
index 0000000..7ea031e
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+    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 "imagemagick_content.h"
+
+/** @file test/black_fill_test.cc
+ *  @brief Test insertion of black frames between video content.
+ */
+
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (black_fill_test)
+{
+       shared_ptr<Film> film = new_test_film ("black_fill_test");
+       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_name ("black_fill_test");
+       film->set_container (Ratio::from_id ("185"));
+       film->set_sequence_video (false);
+       shared_ptr<ImageMagickContent> contentA (new ImageMagickContent (film, "test/data/simple_testcard_640x480.png"));
+       contentA->set_ratio (Ratio::from_id ("185"));
+       shared_ptr<ImageMagickContent> contentB (new ImageMagickContent (film, "test/data/simple_testcard_640x480.png"));
+       contentB->set_ratio (Ratio::from_id ("185"));
+
+       film->examine_and_add_content (contentA);
+       film->examine_and_add_content (contentB);
+       while (JobManager::instance()->work_to_do ());
+
+       contentA->set_video_length (3);
+       contentA->set_start (film->video_frames_to_time (2));
+       contentB->set_video_length (1);
+       contentB->set_start (film->video_frames_to_time (7));
+
+       film->make_dcp ();
+
+       while (JobManager::instance()->work_to_do ());
+
+       BOOST_CHECK (!JobManager::instance()->errors());
+
+       boost::filesystem::path ref;
+       ref = "test";
+       ref /= "data";
+       ref /= "black_fill_test";
+
+       boost::filesystem::path check;
+       check = "build";
+       check /= "test";
+       check /= "black_fill_test";
+       check /= film->dcp_name();
+
+       check_dcp (ref.string(), check.string());
+}
+
index e5229b5ffa1794485d21d7b221507846c54c5d5f..51b52331af8c84c938132f29d86ebef06cab4d6b 100644 (file)
@@ -63,15 +63,8 @@ BOOST_AUTO_TEST_CASE (client_server_test)
        shared_ptr<DCPVideoFrame> frame (
                new DCPVideoFrame (
                        image,
-                       subtitle,
-                       libdcp::Size (1998, 1080),
-                       0,
-                       0,
-                       1,
-                       Scaler::from_id ("bicubic"),
                        0,
                        24,
-                       "",
                        0,
                        200000000,
                        log
@@ -86,7 +79,7 @@ BOOST_AUTO_TEST_CASE (client_server_test)
        new thread (boost::bind (&Server::run, server, 2));
 
        /* Let the server get itself ready */
-       dvdomatic_sleep (1);
+       dcpomatic_sleep (1);
 
        ServerDescription description ("localhost", 2);
 
diff --git a/test/dcp_test.cc b/test/dcp_test.cc
deleted file mode 100644 (file)
index 9312a20..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-BOOST_AUTO_TEST_CASE (make_dcp_test)
-{
-       shared_ptr<Film> film = new_test_film ("make_dcp_test");
-       film->set_name ("test_film2");
-       film->set_content ("../../../test/test.mp4");
-       film->set_format (Format::from_nickname ("Flat"));
-       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
-       film->make_dcp ();
-       film->write_metadata ();
-
-       while (JobManager::instance()->work_to_do ()) {
-               dvdomatic_sleep (1);
-       }
-       
-       BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
-}
-
-/** Test Film::have_dcp().  Requires the output from make_dcp_test above */
-BOOST_AUTO_TEST_CASE (have_dcp_test)
-{
-       boost::filesystem::path p = test_film_dir ("make_dcp_test");
-       Film f (p.string ());
-       BOOST_CHECK (f.have_dcp());
-
-       p /= f.dcp_name();
-       p /= f.dcp_video_mxf_filename();
-       boost::filesystem::remove (p);
-       BOOST_CHECK (!f.have_dcp ());
-}
-
-BOOST_AUTO_TEST_CASE (make_dcp_with_range_test)
-{
-       shared_ptr<Film> film = new_test_film ("make_dcp_with_range_test");
-       film->set_name ("test_film3");
-       film->set_content ("../../../test/test.mp4");
-       film->examine_content ();
-       film->set_format (Format::from_nickname ("Flat"));
-       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
-       film->set_trim_end (42);
-       film->make_dcp ();
-
-       while (JobManager::instance()->work_to_do() && !JobManager::instance()->errors()) {
-               dvdomatic_sleep (1);
-       }
-
-       BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
-}
-
diff --git a/test/ffmpeg_dcp_test.cc b/test/ffmpeg_dcp_test.cc
new file mode 100644 (file)
index 0000000..06cb56e
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+    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.
+
+*/
+
+/** @file test/ffmpeg_dcp_test.cc
+ *  @brief Test scaling and black-padding of images from a still-image source.
+ */
+
+BOOST_AUTO_TEST_CASE (ffmpeg_dcp_test)
+{
+       shared_ptr<Film> film = new_test_film ("ffmpeg_dcp_test");
+       film->set_name ("test_film2");
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/test.mp4"));
+       c->set_ratio (Ratio::from_id ("185"));
+       film->examine_and_add_content (c);
+
+       /* Wait for the examine to finish */
+       while (JobManager::instance()->work_to_do ()) {
+               dcpomatic_sleep (1);
+       }
+
+       BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
+       
+       film->set_container (Ratio::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       film->make_dcp ();
+       film->write_metadata ();
+
+       while (JobManager::instance()->work_to_do ()) {
+               dcpomatic_sleep (1);
+       }
+       
+       BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
+}
+
+/** Test Film::have_dcp().  Requires the output from ffmpeg_dcp_test above */
+BOOST_AUTO_TEST_CASE (ffmpeg_have_dcp_test)
+{
+       boost::filesystem::path p = test_film_dir ("ffmpeg_dcp_test");
+       shared_ptr<Film> f (new Film (p.string ()));
+       f->read_metadata ();
+       BOOST_CHECK (f->have_dcp());
+
+       p /= f->dcp_name();
+       p /= f->dcp_video_mxf_filename();
+       boost::filesystem::remove (p);
+       BOOST_CHECK (!f->have_dcp ());
+}
diff --git a/test/ffmpeg_examiner_test.cc b/test/ffmpeg_examiner_test.cc
new file mode 100644 (file)
index 0000000..296444b
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+    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 "ffmpeg_examiner.h"
+
+BOOST_AUTO_TEST_CASE (ffmpeg_examiner_test)
+{
+       shared_ptr<Film> film = new_test_film ("ffmpeg_examiner_test");
+       shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/count300bd24.m2ts"));
+       shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (content));
+
+       BOOST_CHECK_EQUAL (examiner->first_video().get(), 600.04166666666674);
+       BOOST_CHECK_EQUAL (examiner->audio_streams().size(), 1);
+       BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->first_audio.get(), 600);
+}
diff --git a/test/ffmpeg_pts_offset.cc b/test/ffmpeg_pts_offset.cc
new file mode 100644 (file)
index 0000000..15c2b38
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
+{
+       /* Sound == video so no offset required */
+       BOOST_CHECK_EQUAL (FFmpegDecoder::compute_pts_offset (0, 0, 24), 0);
+
+       /* Common offset should be removed */
+       BOOST_CHECK_CLOSE (FFmpegDecoder::compute_pts_offset (42, 42, 24), -42, 1e-9);
+
+       /* Video is on a frame boundary */
+       BOOST_CHECK_EQUAL (FFmpegDecoder::compute_pts_offset (1.0 / 24.0, 0, 24), 0);
+
+       /* Again, video is on a frame boundary */
+       BOOST_CHECK_EQUAL (FFmpegDecoder::compute_pts_offset (1.0 / 23.97, 0, 23.97), 0);
+
+       /* And again, video is on a frame boundary */
+       BOOST_CHECK_EQUAL (FFmpegDecoder::compute_pts_offset (3.0 / 23.97, 0, 23.97), 0);
+
+       /* Off a frame boundary */
+       BOOST_CHECK_CLOSE (FFmpegDecoder::compute_pts_offset (1.0 / 24.0 - 0.0215, 0, 24), 0.0215, 1e-9);
+}
index 0b4495b486989f3dc8b0adde516a7d936b9bf151..4a0d575343d91444969e619bca2c966e2765230e 100644 (file)
@@ -25,51 +25,26 @@ BOOST_AUTO_TEST_CASE (film_metadata_test)
                boost::filesystem::remove_all (test_film);
        }
 
-       BOOST_CHECK_THROW (new Film (test_film, true), OpenFileError);
-       
-       shared_ptr<Film> f (new Film (test_film, false));
+       shared_ptr<Film> f (new Film (test_film));
        f->_dci_date = boost::gregorian::from_undelimited_string ("20130211");
-       BOOST_CHECK (f->format() == 0);
+       BOOST_CHECK (f->container() == 0);
        BOOST_CHECK (f->dcp_content_type() == 0);
-       BOOST_CHECK (f->filters ().empty());
 
        f->set_name ("fred");
-       BOOST_CHECK_THROW (f->set_content ("jim"), OpenFileError);
        f->set_dcp_content_type (DCPContentType::from_pretty_name ("Short"));
-       f->set_format (Format::from_nickname ("Flat"));
-       f->set_left_crop (1);
-       f->set_right_crop (2);
-       f->set_top_crop (3);
-       f->set_bottom_crop (4);
-       vector<Filter const *> f_filters;
-       f_filters.push_back (Filter::from_id ("pphb"));
-       f_filters.push_back (Filter::from_id ("unsharp"));
-       f->set_filters (f_filters);
-       f->set_trim_start (42);
-       f->set_trim_end (99);
-       f->set_dcp_ab (true);
+       f->set_container (Ratio::from_id ("185"));
        f->write_metadata ();
 
        stringstream s;
-       s << "diff -u test/metadata.ref " << test_film << "/metadata";
+       s << "diff -u test/data/metadata.xml.ref " << test_film << "/metadata.xml";
        BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0);
 
-       shared_ptr<Film> g (new Film (test_film, true));
+       shared_ptr<Film> g (new Film (test_film));
+       g->read_metadata ();
 
        BOOST_CHECK_EQUAL (g->name(), "fred");
        BOOST_CHECK_EQUAL (g->dcp_content_type(), DCPContentType::from_pretty_name ("Short"));
-       BOOST_CHECK_EQUAL (g->format(), Format::from_nickname ("Flat"));
-       BOOST_CHECK_EQUAL (g->crop().left, 1);
-       BOOST_CHECK_EQUAL (g->crop().right, 2);
-       BOOST_CHECK_EQUAL (g->crop().top, 3);
-       BOOST_CHECK_EQUAL (g->crop().bottom, 4);
-       vector<Filter const *> g_filters = g->filters ();
-       BOOST_CHECK_EQUAL (g_filters.size(), 2);
-       BOOST_CHECK_EQUAL (g_filters.front(), Filter::from_id ("pphb"));
-       BOOST_CHECK_EQUAL (g_filters.back(), Filter::from_id ("unsharp"));
-       BOOST_CHECK_EQUAL (g->trim_start(), 42);
-       BOOST_CHECK_EQUAL (g->trim_end(), 99);
-       BOOST_CHECK_EQUAL (g->dcp_ab(), true);
+       BOOST_CHECK_EQUAL (g->container(), Ratio::from_id ("185"));
        
        g->write_metadata ();
        BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0);
diff --git a/test/film_test.cc b/test/film_test.cc
deleted file mode 100644 (file)
index 02dfa05..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-BOOST_AUTO_TEST_CASE (paths_test)
-{
-       shared_ptr<Film> f = new_test_film ("paths_test");
-       f->set_directory ("build/test/a/b/c/d/e");
-
-       f->_content = "/foo/bar/baz";
-       BOOST_CHECK_EQUAL (f->content_path(), "/foo/bar/baz");
-       f->_content = "foo/bar/baz";
-       BOOST_CHECK_EQUAL (f->content_path(), "build/test/a/b/c/d/e/foo/bar/baz");
-}
-
diff --git a/test/format_test.cc b/test/format_test.cc
deleted file mode 100644 (file)
index b150738..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-BOOST_AUTO_TEST_CASE (format_test)
-{
-       Format::setup_formats ();
-       
-       Format const * f = Format::from_nickname ("Flat");
-       BOOST_CHECK (f);
-       BOOST_CHECK_EQUAL (f->dcp_size().width, 1998);
-       BOOST_CHECK_EQUAL (f->dcp_size().height, 1080);
-       
-       f = Format::from_nickname ("Scope");
-       BOOST_CHECK (f);
-       BOOST_CHECK_EQUAL (f->dcp_size().width, 2048);
-       BOOST_CHECK_EQUAL (f->dcp_size().height, 858);
-}
-
-
-/* Test VariableFormat-based scaling of content */
-BOOST_AUTO_TEST_CASE (scaling_test)
-{
-       shared_ptr<Film> film (new Film (test_film_dir ("scaling_test").string(), false));
-
-       /* 4:3 ratio */
-       film->set_size (libdcp::Size (320, 240));
-
-       /* This format should preserve aspect ratio of the source */
-       Format const * format = Format::from_id ("var-185");
-
-       /* We should have enough padding that the result is 4:3,
-          which would be 1440 pixels.
-       */
-       BOOST_CHECK_EQUAL (format->dcp_padding (film), (1998 - 1440) / 2);
-       
-       /* This crops it to 1.291666667 */
-       film->set_left_crop (5);
-       film->set_right_crop (5);
-
-       /* We should now have enough padding that the result is 1.29166667,
-          which would be 1395 pixels.
-       */
-       BOOST_CHECK_EQUAL (format->dcp_padding (film), rint ((1998 - 1395) / 2.0));
-}
-
index 00700656eeba77fc5b9c2833eef61fa83893217e..8b04d3763bb83dab152be8a97da7eeb70410db8b 100644 (file)
@@ -20,6 +20,7 @@
 /* Test best_dcp_frame_rate and FrameRateConversion */
 BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test)
 {
+#if 0  
        /* Run some tests with a limited range of allowed rates */
        
        std::list<int> afr;
@@ -207,5 +208,6 @@ BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
           the 14.99 fps video to 30 and then run it slow at 25.
        */
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), rint (48000 * 2 * 14.99 / 25));
+#endif 
 }
 
index a9ec3042aba4b10023625b281b070571e296831f..9dd3a1ba4e656484777dc6c7c4ca22e58afa105d 100644 (file)
@@ -97,3 +97,13 @@ BOOST_AUTO_TEST_CASE (compact_image_test)
        delete t;
        delete u;
 }
+
+BOOST_AUTO_TEST_CASE (crop_image_test)
+{
+       /* This was to check out a bug with valgrind, and is probably not very useful */
+       shared_ptr<SimpleImage> image (new SimpleImage (PIX_FMT_YUV420P, libdcp::Size (16, 16), true));
+       image->make_black ();
+       Crop crop;
+       crop.top = 3;
+       image->crop (crop, false);
+}
index 247d4f75665895d09f243cd7c6c76a73d6ed848b..86c6dc9e342d45648ae92793be10f029d9d07eea 100644 (file)
@@ -56,9 +56,9 @@ BOOST_AUTO_TEST_CASE (job_manager_test)
        shared_ptr<TestJob> a (new TestJob (f));
 
        JobManager::instance()->add (a);
-       dvdomatic_sleep (1);
+       dcpomatic_sleep (1);
        BOOST_CHECK_EQUAL (a->running (), true);
        a->set_finished_ok ();
-       dvdomatic_sleep (2);
+       dcpomatic_sleep (2);
        BOOST_CHECK_EQUAL (a->finished_ok(), true);
 }
index 714621762bbf435e01838c7fabf07cd51fc5d67d..9bec006516d67c6d6ede9174d787e2da53de4cce 100644 (file)
@@ -43,7 +43,7 @@ BOOST_AUTO_TEST_CASE (make_black_test)
        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) {
diff --git a/test/md5.test b/test/md5.test
deleted file mode 100644 (file)
index 573ed79..0000000
Binary files a/test/md5.test and /dev/null differ
diff --git a/test/metadata.ref b/test/metadata.ref
deleted file mode 100644 (file)
index b7a0318..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-version 4
-name fred
-use_dci_name 1
-content 
-trust_content_header 1
-dcp_content_type SHR
-format 185
-left_crop 1
-right_crop 2
-top_crop 3
-bottom_crop 4
-filter pphb
-filter unsharp
-scaler bicubic
-trim_start 42
-trim_end 99
-trim_type cpl
-dcp_ab 1
-use_content_audio 1
-audio_gain 0
-audio_delay 0
-still_duration 10
-with_subtitles 0
-subtitle_offset 0
-subtitle_scale 1
-colour_lut 0
-j2k_bandwidth 200000000
-audio_language 
-subtitle_language 
-territory 
-rating 
-studio 
-facility 
-package_type 
-dci_date 20130211
-dcp_frame_rate 0
-width 0
-height 0
-length 0
-content_digest 
-external_audio_stream external 0 0
-source_frame_rate 0
index b788b321972f979a7c841dab904f28251aef62e6..fb2278fdbba39e8125bd43955a928fa3763a015e 100644 (file)
@@ -43,9 +43,6 @@ struct Case
 
 BOOST_AUTO_TEST_CASE (pixel_formats_test)
 {
-       /* This needs to happen in the first test */
-       dvdomatic_setup ();
-
        list<Case> cases;
        cases.push_back(Case(AV_PIX_FMT_RGB24,       1, 480, 480, 480, 3, 0,   0  ));
        cases.push_back(Case(AV_PIX_FMT_RGBA,        1, 480, 480, 480, 4, 0,   0  ));
@@ -65,7 +62,7 @@ BOOST_AUTO_TEST_CASE (pixel_formats_test)
                f->width = 640;
                f->height = 480;
                f->format = static_cast<int> (i->format);
-                av_frame_get_buffer (f, true);
+               av_frame_get_buffer (f, true);
                SimpleImage t (f);
                BOOST_CHECK_EQUAL(t.components(), i->components);
                BOOST_CHECK_EQUAL(t.lines(0), i->lines[0]);
diff --git a/test/ratio_test.cc b/test/ratio_test.cc
new file mode 100644 (file)
index 0000000..6311976
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+    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.
+
+*/
+
+using std::ostream;
+
+namespace libdcp {
+       
+ostream&
+operator<< (ostream& s, libdcp::Size const & t)
+{
+       s << t.width << "x" << t.height;
+       return s;
+}
+
+}
+
+BOOST_AUTO_TEST_CASE (ratio_test)
+{
+       Ratio::setup_ratios ();
+
+       Ratio const * r = Ratio::from_id ("119");
+       BOOST_CHECK (r);
+       BOOST_CHECK_EQUAL (r->size(libdcp::Size (2048, 1080)), libdcp::Size (1285, 1080));
+
+       r = Ratio::from_id ("133");
+       BOOST_CHECK (r);
+       BOOST_CHECK_EQUAL (r->size(libdcp::Size (2048, 1080)), libdcp::Size (1436, 1080));
+
+       r = Ratio::from_id ("137");
+       BOOST_CHECK (r);
+       BOOST_CHECK_EQUAL (r->size(libdcp::Size (2048, 1080)), libdcp::Size (1480, 1080));
+
+       r = Ratio::from_id ("138");
+       BOOST_CHECK (r);
+       BOOST_CHECK_EQUAL (r->size(libdcp::Size (2048, 1080)), libdcp::Size (1485, 1080));
+
+       r = Ratio::from_id ("166");
+       BOOST_CHECK (r);
+       BOOST_CHECK_EQUAL (r->size(libdcp::Size (2048, 1080)), libdcp::Size (1793, 1080));
+
+       r = Ratio::from_id ("178");
+       BOOST_CHECK (r);
+       BOOST_CHECK_EQUAL (r->size(libdcp::Size (2048, 1080)), libdcp::Size (1920, 1080));
+
+       r = Ratio::from_id ("185");
+       BOOST_CHECK (r);
+       BOOST_CHECK_EQUAL (r->size(libdcp::Size (2048, 1080)), libdcp::Size (1998, 1080));
+
+       r = Ratio::from_id ("239");
+       BOOST_CHECK (r);
+       BOOST_CHECK_EQUAL (r->size(libdcp::Size (2048, 1080)), libdcp::Size (2048, 858));
+
+       r = Ratio::from_id ("full-frame");
+       BOOST_CHECK (r);
+       BOOST_CHECK_EQUAL (r->size(libdcp::Size (2048, 1080)), libdcp::Size (2048, 1080));
+}
+
diff --git a/test/scaling_test.cc b/test/scaling_test.cc
new file mode 100644 (file)
index 0000000..dba6110
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+    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 "imagemagick_content.h"
+
+/** @file test/scaling_test.cc
+ *  @brief Test scaling and black-padding of images from a still-image source.
+ */
+
+using boost::shared_ptr;
+
+static void scaling_test_for (shared_ptr<Film> film, shared_ptr<VideoContent> content, string image, string container)
+{
+       content->set_ratio (Ratio::from_id (image));
+       film->set_container (Ratio::from_id (container));
+       film->make_dcp ();
+
+       while (JobManager::instance()->work_to_do ());
+
+       BOOST_CHECK (!JobManager::instance()->errors());
+
+       boost::filesystem::path ref;
+       ref = "test";
+       ref /= "data";
+       ref /= "scaling_test_" + image + "_" + container;
+
+       boost::filesystem::path check;
+       check = "build";
+       check /= "test";
+       check /= "scaling_test";
+       check /= film->dcp_name();
+
+       check_dcp (ref.string(), check.string());
+}
+
+BOOST_AUTO_TEST_CASE (scaling_test)
+{
+       shared_ptr<Film> film = new_test_film ("scaling_test");
+       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_name ("scaling_test");
+       shared_ptr<ImageMagickContent> imc (new ImageMagickContent (film, "test/data/simple_testcard_640x480.png"));
+
+       film->examine_and_add_content (imc);
+       while (JobManager::instance()->work_to_do ());
+       
+       imc->set_video_length (1);
+
+       scaling_test_for (film, imc, "133", "185");
+       scaling_test_for (film, imc, "185", "185");
+       scaling_test_for (film, imc, "239", "185");
+
+       scaling_test_for (film, imc, "133", "239");
+       scaling_test_for (film, imc, "185", "239");
+       scaling_test_for (film, imc, "239", "239");
+}
+
index b467dc9a2efa144e94b51e9b7c5f02391e03665b..4d97e82afbebda8e7de70b050053df2175d5476c 100644 (file)
@@ -19,6 +19,7 @@
 
 BOOST_AUTO_TEST_CASE (stream_test)
 {
+#if 0  
        FFmpegAudioStream a ("ffmpeg 4 44100 1 hello there world", boost::optional<int> (1));
        BOOST_CHECK_EQUAL (a.id(), 4);
        BOOST_CHECK_EQUAL (a.sample_rate(), 44100);
@@ -48,5 +49,6 @@ BOOST_AUTO_TEST_CASE (stream_test)
        BOOST_CHECK_EQUAL (fe->sample_rate(), 44100);
        BOOST_CHECK_EQUAL (fe->channel_layout(), 1);
        BOOST_CHECK_EQUAL (fe->to_string(), "external 44100 1");
+#endif 
 }
 
index 74d967a46c28a57128cc94a907c71bedcf90c03e..d6c7842d711692cc2acd39d296a41916af6cba57 100644 (file)
@@ -22,7 +22,8 @@
 #include <boost/filesystem.hpp>
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/date_time.hpp>
-#include "format.h"
+#include <libdcp/dcp.h>
+#include "ratio.h"
 #include "film.h"
 #include "filter.h"
 #include "job_manager.h"
 #include "scaler.h"
 #include "ffmpeg_decoder.h"
 #include "sndfile_decoder.h"
-#include "trimmer.h"
+#include "dcp_content_type.h"
 #include "ui_signaller.h"
+#include "ratio.h"
 #define BOOST_TEST_DYN_LINK
-#define BOOST_TEST_MODULE dvdomatic_test
+#define BOOST_TEST_MODULE dcpomatic_test
 #include <boost/test/unit_test.hpp>
 
 using std::string;
 using std::list;
 using std::stringstream;
 using std::vector;
+using std::min;
+using std::cout;
 using boost::shared_ptr;
 using boost::thread;
 using boost::dynamic_pointer_cast;
@@ -57,13 +61,13 @@ struct TestConfig
 {
        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_container (static_cast<Ratio*> (0));
                Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
 
                ui_signaller = new UISignaller ();
@@ -90,18 +94,78 @@ new_test_film (string name)
                boost::filesystem::remove_all (p);
        }
        
-       return shared_ptr<Film> (new Film (p.string(), false));
+       shared_ptr<Film> f = shared_ptr<Film> (new Film (p.string()));
+       f->write_metadata ();
+       return f;
 }
 
+void
+check_file (string ref, string check)
+{
+       uintmax_t N = boost::filesystem::file_size (ref);
+       BOOST_CHECK_EQUAL (N, boost::filesystem::file_size(check));
+       FILE* ref_file = fopen (ref.c_str(), "rb");
+       BOOST_CHECK (ref_file);
+       FILE* check_file = fopen (check.c_str(), "rb");
+       BOOST_CHECK (check_file);
+       
+       int const buffer_size = 65536;
+       uint8_t* ref_buffer = new uint8_t[buffer_size];
+       uint8_t* check_buffer = new uint8_t[buffer_size];
+
+       while (N) {
+               uintmax_t this_time = min (uintmax_t (buffer_size), N);
+               size_t r = fread (ref_buffer, 1, this_time, ref_file);
+               BOOST_CHECK_EQUAL (r, this_time);
+               r = fread (check_buffer, 1, this_time, check_file);
+               BOOST_CHECK_EQUAL (r, this_time);
+
+               BOOST_CHECK_EQUAL (memcmp (ref_buffer, check_buffer, this_time), 0);
+               N -= this_time;
+       }
+
+       delete[] ref_buffer;
+       delete[] check_buffer;
+
+       fclose (ref_file);
+       fclose (check_file);
+}
+
+static void
+note (libdcp::NoteType, string n)
+{
+       cout << n << "\n";
+}
+
+void
+check_dcp (string ref, string check)
+{
+       libdcp::DCP ref_dcp (ref);
+       ref_dcp.read ();
+       libdcp::DCP check_dcp (check);
+       check_dcp.read ();
+
+       libdcp::EqualityOptions options;
+       options.max_mean_pixel_error = 5;
+       options.max_std_dev_pixel_error = 5;
+       options.max_audio_sample_error = 255;
+       options.cpl_names_can_differ = true;
+       options.mxf_names_can_differ = true;
+       
+       BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2)));
+}
+
+#include "ffmpeg_pts_offset.cc"
+#include "ffmpeg_examiner_test.cc"
+#include "black_fill_test.cc"
+#include "scaling_test.cc"
+#include "ratio_test.cc"
 #include "pixel_formats_test.cc"
 #include "make_black_test.cc"
-#include "trimmer_test.cc"
 #include "film_metadata_test.cc"
 #include "stream_test.cc"
-#include "format_test.cc"
 #include "util_test.cc"
-#include "film_test.cc"
-#include "dcp_test.cc"
+#include "ffmpeg_dcp_test.cc"
 #include "frame_rate_test.cc"
 #include "job_test.cc"
 #include "client_server_test.cc"
diff --git a/test/test.mp4 b/test/test.mp4
deleted file mode 100644 (file)
index 811e397..0000000
Binary files a/test/test.mp4 and /dev/null differ
index 18c24ac3a4135e25c75295f9f289a22baba04656..f75fd0e70efd8b05e4654d4f650e7d10f121a7db 100644 (file)
@@ -36,7 +36,7 @@ BOOST_AUTO_TEST_CASE (util_test)
 
 BOOST_AUTO_TEST_CASE (md5_digest_test)
 {
-       string const t = md5_digest ("test/md5.test");
+       string const t = md5_digest ("test/data/md5.test");
        BOOST_CHECK_EQUAL (t, "15058685ba99decdc4398c7634796eb0");
 
        BOOST_CHECK_THROW (md5_digest ("foobar"), OpenFileError);
index 71636a05d7fbaed510005fce9c00d4c866a118a0..60d846aeab386e647084164e829e62de732aab22 100644 (file)
@@ -13,7 +13,7 @@ def build(bld):
     obj = bld(features = 'cxx cxxprogram')
     obj.name   = 'unit-tests'
     obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
-    obj.use    = 'libdvdomatic'
+    obj.use    = 'libdcpomatic'
     obj.source = 'test.cc'
     obj.target = 'unit-tests'
     obj.install_path = ''
diff --git a/windows/dcpomatic.bmp b/windows/dcpomatic.bmp
new file mode 100644 (file)
index 0000000..0a196f7
Binary files /dev/null and b/windows/dcpomatic.bmp differ
diff --git a/windows/dcpomatic.ico b/windows/dcpomatic.ico
new file mode 100644 (file)
index 0000000..225008c
Binary files /dev/null and b/windows/dcpomatic.ico differ
diff --git a/windows/dcpomatic.rc b/windows/dcpomatic.rc
new file mode 100644 (file)
index 0000000..3963873
--- /dev/null
@@ -0,0 +1,3 @@
+id ICON "dcpomatic.ico"
+taskbar_icon ICON "dcpomatic_taskbar.ico"
+#include "wx-2.9/wx/msw/wx.rc"
diff --git a/windows/dcpomatic_taskbar.ico b/windows/dcpomatic_taskbar.ico
new file mode 100644 (file)
index 0000000..f4489fa
Binary files /dev/null and b/windows/dcpomatic_taskbar.ico differ
diff --git a/wscript b/wscript
index 9d3a566febd6e0fcee695125049035d58beaa085..2c0a145801310f92ac5dd233206d801a20eccb35 100644 (file)
--- a/wscript
+++ b/wscript
@@ -2,8 +2,8 @@ import subprocess
 import os
 import sys
 
-APPNAME = 'dvdomatic'
-VERSION = '0.109pre'
+APPNAME = 'dcpomatic'
+VERSION = '1.00pre'
 
 def options(opt):
     opt.load('compiler_cxx')
@@ -32,7 +32,7 @@ def configure(conf):
                                        '-Wall', '-Wno-attributes', '-Wextra', '-D_FILE_OFFSET_BITS=64'])
 
     if conf.env.TARGET_WINDOWS:
-        conf.env.append_value('CXXFLAGS', ['-DDVDOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE'])
+        conf.env.append_value('CXXFLAGS', ['-DDCPOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE'])
         wxrc = os.popen('wx-config --rescomp').read().split()[1:]
         conf.env.append_value('WINRCFLAGS', wxrc)
         if conf.options.enable_debug:
@@ -46,9 +46,9 @@ def configure(conf):
         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')
@@ -56,24 +56,25 @@ def configure(conf):
     if conf.env.TARGET_LINUX:
         # libxml2 seems to be linked against this on Ubuntu but it doesn't mention it in its .pc file
         conf.env.append_value('LIB', 'lzma')
-        conf.env.append_value('CXXFLAGS', '-DDVDOMATIC_LINUX')
+        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_LINUX')
 
     if conf.env.TARGET_OSX:
-        conf.env.append_value('CXXFLAGS', '-DDVDOMATIC_OSX')
+        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_OSX')
 
     if conf.options.enable_debug:
-        conf.env.append_value('CXXFLAGS', ['-g', '-DDVDOMATIC_DEBUG'])
+        conf.env.append_value('CXXFLAGS', ['-g', '-DDCPOMATIC_DEBUG'])
     else:
         conf.env.append_value('CXXFLAGS', '-O2')
 
     if not conf.options.static:
         conf.check_cfg(package = 'libdcp', atleast_version = '0.52', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
+        conf.check_cfg(package = 'libcxml', atleast_version = '0.01', args = '--cflags --libs', uselib_store = 'CXML', mandatory = True)
         conf.check_cfg(package = 'libavformat', args = '--cflags --libs', uselib_store = 'AVFORMAT', mandatory = True)
         conf.check_cfg(package = 'libavfilter', args = '--cflags --libs', uselib_store = 'AVFILTER', mandatory = True)
         conf.check_cfg(package = 'libavcodec', args = '--cflags --libs', uselib_store = 'AVCODEC', mandatory = True)
         conf.check_cfg(package = 'libavutil', args = '--cflags --libs', uselib_store = 'AVUTIL', mandatory = True)
         conf.check_cfg(package = 'libswscale', args = '--cflags --libs', uselib_store = 'SWSCALE', mandatory = True)
-        conf.check_cfg(package = 'libswresample', args = '--cflags --libs', uselib_store = 'SWRESAMPLE', mandatory = False)
+        conf.check_cfg(package = 'libswresample', args = '--cflags --libs', uselib_store = 'SWRESAMPLE', mandatory = True)
         conf.check_cfg(package = 'libpostproc', args = '--cflags --libs', uselib_store = 'POSTPROC', mandatory = True)
     else:
         # This is hackio grotesquio for static builds (ie for .deb packages).  We need to link some things
@@ -86,7 +87,9 @@ def configure(conf):
         conf.env.HAVE_DCP = 1
         conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
         conf.env.LIB_DCP = ['glibmm-2.4', 'xml++-2.6', 'ssl', 'crypto', 'bz2']
-        conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='DCP', mandatory=True)
+        conf.env.HAVE_CXML = 1
+        conf.env.STLIB_CXML = ['cxml']
+        conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XML++', mandatory=True)
         conf.env.HAVE_AVFORMAT = 1
         conf.env.STLIB_AVFORMAT = ['avformat']
         conf.env.HAVE_AVFILTER = 1
@@ -98,14 +101,10 @@ def configure(conf):
         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)
@@ -218,10 +217,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)
 
@@ -243,14 +242,14 @@ def create_version_cc(version, cxx_flags):
 
     try:
         text =  '#include "version.h"\n'
-        text += 'char const * dvdomatic_git_commit = \"%s\";\n' % commit
-        text += 'char const * dvdomatic_version = \"%s\";\n' % version
+        text += 'char const * dcpomatic_git_commit = \"%s\";\n' % commit
+        text += 'char const * dcpomatic_version = \"%s\";\n' % version
 
         t = ''
         for f in cxx_flags:
             f = f.replace('"', '\\"')
             t += f + ' '
-        text += 'char const * dvdomatic_cxx_flags = \"%s\";\n' % t[:-1]
+        text += 'char const * dcpomatic_cxx_flags = \"%s\";\n' % t[:-1]
 
         print('Writing version information to src/lib/version.cc')
         o = open('src/lib/version.cc', 'w')