Merge 1.0 in.
authorCarl Hetherington <cth@carlh.net>
Tue, 17 Sep 2013 22:39:05 +0000 (23:39 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 17 Sep 2013 22:39:05 +0000 (23:39 +0100)
455 files changed:
.gitignore
ChangeLog
Doxyfile
README
TODO [deleted file]
branch-notes [new file with mode: 0644]
build-all-ffmpeg [deleted file]
builds/all [deleted file]
builds/all-deb [deleted file]
builds/chroot-deb [deleted file]
builds/control-12.04-32 [deleted file]
builds/control-12.04-64 [deleted file]
builds/control-12.10-32 [deleted file]
builds/control-12.10-64 [deleted file]
builds/deb [deleted file]
builds/windows-32 [deleted file]
builds/windows-64 [deleted file]
cscript [new file with mode: 0644]
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.xsl [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/diagrams/3d-left-right.svg [new file with mode: 0644]
doc/manual/diagrams/file-structure.svg [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]
doc/manual/screenshots/add-file.png [new file with mode: 0644]
doc/manual/screenshots/audio-map-eg1.png [new file with mode: 0644]
doc/manual/screenshots/audio-map-eg2.png [new file with mode: 0644]
doc/manual/screenshots/audio-map-eg3.png [new file with mode: 0644]
doc/manual/screenshots/audio-plot.png [new file with mode: 0644]
doc/manual/screenshots/audio-tab.png
doc/manual/screenshots/click-content-selector.png [deleted file]
doc/manual/screenshots/dcp-tab.png [new file with mode: 0644]
doc/manual/screenshots/examine-content.png [new file with mode: 0644]
doc/manual/screenshots/film-tab.png [deleted file]
doc/manual/screenshots/filters.png
doc/manual/screenshots/making-dcp.png
doc/manual/screenshots/prefs-metadata.png [new file with mode: 0644]
doc/manual/screenshots/prefs-misc.png [new file with mode: 0644]
doc/manual/screenshots/prefs-servers.png [new file with mode: 0644]
doc/manual/screenshots/prefs-tms.png [new file with mode: 0644]
doc/manual/screenshots/prefs.png
doc/manual/screenshots/subtitles-tab.png
doc/manual/screenshots/timing-tab.png [new file with mode: 0644]
doc/manual/screenshots/video-tab.png
dvdomatic.desktop.in [deleted file]
ffmpeg-versions [deleted file]
hacks/build-all-ffmpeg [new file with mode: 0755]
hacks/optimise/8proc.log [new file with mode: 0644]
hacks/optimise/analog [new file with mode: 0755]
hacks/optimise/plotlog [new file with mode: 0755]
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]
hacks/splitchapters [new file with mode: 0755]
i18n.py [new file with mode: 0644]
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/256x256/dvdomatic.png [new file with mode: 0644]
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/512x512/dvdomatic.png [new file with mode: 0644]
icons/64x64/dcpomatic.png [new file with mode: 0644]
icons/64x64/dvdomatic.png [deleted file]
icons/dcpomatic.icns [new file with mode: 0644]
icons/dcpomatic.iconset/icon_128x128.png [new file with mode: 0644]
icons/dcpomatic.iconset/icon_128x128@2x.png [new file with mode: 0644]
icons/dcpomatic.iconset/icon_16x16.png [new file with mode: 0644]
icons/dcpomatic.iconset/icon_16x16@2x.png [new file with mode: 0644]
icons/dcpomatic.iconset/icon_256x256.png [new file with mode: 0644]
icons/dcpomatic.iconset/icon_256x256@2x.png [new file with mode: 0644]
icons/dcpomatic.iconset/icon_32x32.png [new file with mode: 0644]
icons/dcpomatic.iconset/icon_32x32@2x.png [new file with mode: 0644]
icons/dcpomatic.iconset/icon_512x512.png [new file with mode: 0644]
icons/dcpomatic.iconset/icon_512x512@2x.png [new file with mode: 0644]
icons/make.sh [new file with mode: 0755]
icons/make_icns.sh [new file with mode: 0755]
icons/taskbar_icon.png [new file with mode: 0644]
optimise/8proc.log [deleted file]
optimise/analog [deleted file]
optimise/plotlog [deleted file]
platform/linux/control-12.04-32 [new file with mode: 0644]
platform/linux/control-12.04-64 [new file with mode: 0644]
platform/linux/control-12.10-32 [new file with mode: 0644]
platform/linux/control-12.10-64 [new file with mode: 0644]
platform/linux/control-13.04-32 [new file with mode: 0644]
platform/linux/control-13.04-64 [new file with mode: 0644]
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/wscript [new file with mode: 0644]
platform/osx/Info.plist.in [new file with mode: 0644]
platform/osx/make_dmg.sh [new file with mode: 0644]
platform/osx/waf [new file with mode: 0755]
platform/osx/wscript [new file with mode: 0644]
platform/windows/.gtkrc-2.0 [new file with mode: 0755]
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/installer.nsi.32.in [new file with mode: 0644]
platform/windows/installer.nsi.64.in [new file with mode: 0644]
platform/windows/wscript [new file with mode: 0644]
pre [deleted file]
release [deleted file]
run/alignomatic [deleted file]
run/dcpomatic [new file with mode: 0755]
run/dcpomatic.bat [new file with mode: 0644]
run/dcpomatic_cli [new file with mode: 0755]
run/dcpomatic_server_cli [new file with mode: 0755]
run/dcpomatic_server_gui [new file with mode: 0755]
run/dvdomatic [deleted file]
run/fixlengths [deleted file]
run/makedcp [deleted file]
run/playomatic [deleted file]
run/server_test [new file with mode: 0755]
run/servomatic_cli [deleted file]
run/servomatic_gui [deleted file]
run/servomatictest [deleted file]
run/tests
splitchapters [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 [new file with mode: 0644]
src/lib/analyse_audio_job.h [new file with mode: 0644]
src/lib/audio_analysis.cc [new file with mode: 0644]
src/lib/audio_analysis.h [new file with mode: 0644]
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_merger.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/check_hashes_job.cc [deleted file]
src/lib/check_hashes_job.h [deleted file]
src/lib/colour_conversion.cc [new file with mode: 0644]
src/lib/colour_conversion.h [new file with mode: 0644]
src/lib/combiner.cc [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/content_factory.cc [new file with mode: 0644]
src/lib/content_factory.h [new file with mode: 0644]
src/lib/cross.cc
src/lib/cross.h
src/lib/dci_metadata.cc [new file with mode: 0644]
src/lib/dci_metadata.h [new file with mode: 0644]
src/lib/dcp_content_type.cc
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/dolby_cp750.cc
src/lib/encoder.cc
src/lib/encoder.h
src/lib/examine_content_job.cc
src/lib/examine_content_job.h
src/lib/exceptions.cc [new file with mode: 0644]
src/lib/exceptions.h
src/lib/external_audio_decoder.cc [deleted file]
src/lib/external_audio_decoder.h [deleted file]
src/lib/ffmpeg.cc [new file with mode: 0644]
src/lib/ffmpeg.h [new file with mode: 0644]
src/lib/ffmpeg_compatibility.cc [deleted file]
src/lib/ffmpeg_compatibility.h [deleted file]
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.cc
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 [new file with mode: 0644]
src/lib/image.cc
src/lib/image.h
src/lib/imagemagick_decoder.cc [deleted file]
src/lib/imagemagick_decoder.h [deleted file]
src/lib/job.cc
src/lib/job.h
src/lib/job_manager.cc
src/lib/job_manager.h
src/lib/log.cc
src/lib/log.h
src/lib/lut.h [deleted file]
src/lib/make_dcp_job.h [deleted file]
src/lib/matcher.cc [deleted file]
src/lib/matcher.h [deleted file]
src/lib/moving_image.h [new file with mode: 0644]
src/lib/moving_image_content.cc [new file with mode: 0644]
src/lib/moving_image_content.h [new file with mode: 0644]
src/lib/moving_image_decoder.cc [new file with mode: 0644]
src/lib/moving_image_decoder.h [new file with mode: 0644]
src/lib/moving_image_examiner.cc [new file with mode: 0644]
src/lib/moving_image_examiner.h [new file with mode: 0644]
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 [new file with mode: 0644]
src/lib/po/fr_FR.po [new file with mode: 0644]
src/lib/po/it_IT.po [new file with mode: 0644]
src/lib/po/sv_SE.po [new file with mode: 0644]
src/lib/position.h [new file with mode: 0644]
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/rect.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.cc
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 [new file with mode: 0644]
src/lib/sndfile_decoder.h [new file with mode: 0644]
src/lib/sound_processor.h
src/lib/stack.cpp [new file with mode: 0644]
src/lib/stack.hpp [new file with mode: 0644]
src/lib/still_image.h [new file with mode: 0644]
src/lib/still_image_content.cc [new file with mode: 0644]
src/lib/still_image_content.h [new file with mode: 0644]
src/lib/still_image_decoder.cc [new file with mode: 0644]
src/lib/still_image_decoder.h [new file with mode: 0644]
src/lib/still_image_examiner.cc [new file with mode: 0644]
src/lib/still_image_examiner.h [new file with mode: 0644]
src/lib/stream.cc [deleted file]
src/lib/stream.h [deleted file]
src/lib/subtitle.cc [deleted file]
src/lib/subtitle.h [deleted file]
src/lib/subtitle_content.cc [new file with mode: 0644]
src/lib/subtitle_content.h [new file with mode: 0644]
src/lib/subtitle_decoder.cc [new file with mode: 0644]
src/lib/subtitle_decoder.h [new file with mode: 0644]
src/lib/timer.cc
src/lib/timer.h
src/lib/transcode_job.cc
src/lib/transcode_job.h
src/lib/transcoder.cc
src/lib/transcoder.h
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 [new file with mode: 0644]
src/lib/writer.h [new file with mode: 0644]
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/makedcp.cc [deleted file]
src/tools/po/es_ES.po [new file with mode: 0644]
src/tools/po/fr_FR.po [new file with mode: 0644]
src/tools/po/it_IT.po [new file with mode: 0644]
src/tools/po/sv_SE.po [new file with mode: 0644]
src/tools/server_test.cc [new file with mode: 0644]
src/tools/servomatic_cli.cc [deleted file]
src/tools/servomatic_gui.cc [deleted file]
src/tools/servomatictest.cc [deleted file]
src/tools/test.cc [deleted file]
src/tools/wscript
src/wscript
src/wx/about_dialog.cc [new file with mode: 0644]
src/wx/about_dialog.h [new file with mode: 0644]
src/wx/audio_dialog.cc [new file with mode: 0644]
src/wx/audio_dialog.h [new file with mode: 0644]
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_panel.cc [new file with mode: 0644]
src/wx/audio_panel.h [new file with mode: 0644]
src/wx/audio_plot.cc [new file with mode: 0644]
src/wx/audio_plot.h [new file with mode: 0644]
src/wx/cinema_dialog.cc
src/wx/colour_conversion_editor.cc [new file with mode: 0644]
src/wx/colour_conversion_editor.h [new file with mode: 0644]
src/wx/config_dialog.cc
src/wx/config_dialog.h
src/wx/content_colour_conversion_dialog.cc [new file with mode: 0644]
src/wx/content_colour_conversion_dialog.h [new file with mode: 0644]
src/wx/content_menu.cc [new file with mode: 0644]
src/wx/content_menu.h [new file with mode: 0644]
src/wx/dci_metadata_dialog.cc [new file with mode: 0644]
src/wx/dci_metadata_dialog.h [new file with mode: 0644]
src/wx/dci_name_dialog.cc [deleted file]
src/wx/dci_name_dialog.h [deleted file]
src/wx/dir_picker_ctrl.cc
src/wx/dir_picker_ctrl.h
src/wx/editable_list.h [new file with mode: 0644]
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/film_editor_panel.cc [new file with mode: 0644]
src/wx/film_editor_panel.h [new file with mode: 0644]
src/wx/film_list.cc [deleted file]
src/wx/film_list.h [deleted file]
src/wx/film_viewer.cc
src/wx/film_viewer.h
src/wx/filter_dialog.cc
src/wx/filter_dialog.h
src/wx/filter_editor.cc [new file with mode: 0644]
src/wx/filter_editor.h [new file with mode: 0644]
src/wx/filter_view.cc [deleted file]
src/wx/filter_view.h [deleted file]
src/wx/gain_calculator_dialog.cc
src/wx/job_manager_view.cc
src/wx/job_manager_view.h
src/wx/job_wrapper.cc
src/wx/job_wrapper.h
src/wx/kdm_dialog.cc
src/wx/new_film_dialog.cc
src/wx/new_film_dialog.h
src/wx/po/es_ES.po [new file with mode: 0644]
src/wx/po/fr_FR.po [new file with mode: 0644]
src/wx/po/it_IT.po [new file with mode: 0644]
src/wx/po/sv_SE.po [new file with mode: 0644]
src/wx/preset_colour_conversion_dialog.cc [new file with mode: 0644]
src/wx/preset_colour_conversion_dialog.h [new file with mode: 0644]
src/wx/properties_dialog.cc
src/wx/properties_dialog.h
src/wx/repeat_dialog.cc [new file with mode: 0644]
src/wx/repeat_dialog.h [new file with mode: 0644]
src/wx/screen_dialog.cc
src/wx/server_dialog.cc
src/wx/server_dialog.h
src/wx/subtitle_panel.cc [new file with mode: 0644]
src/wx/subtitle_panel.h [new file with mode: 0644]
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/timing_panel.cc [new file with mode: 0644]
src/wx/timing_panel.h [new file with mode: 0644]
src/wx/video_panel.cc [new file with mode: 0644]
src/wx/video_panel.h [new file with mode: 0644]
src/wx/wscript
src/wx/wx_ui_signaller.cc
src/wx/wx_ui_signaller.h
src/wx/wx_util.cc
src/wx/wx_util.h
test/4k_test.cc [new file with mode: 0644]
test/audio_delay_test.cc [new file with mode: 0644]
test/audio_merger_test.cc [new file with mode: 0644]
test/black_fill_test.cc [new file with mode: 0644]
test/client_server_test.cc [new file with mode: 0644]
test/colour_conversion_test.cc [new file with mode: 0644]
test/ffmpeg_audio_test.cc [new file with mode: 0644]
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 [new file with mode: 0644]
test/frame_rate_test.cc [new file with mode: 0644]
test/guessdcp.py [new file with mode: 0644]
test/image_test.cc [new file with mode: 0644]
test/job_test.cc [new file with mode: 0644]
test/make_black_test.cc [new file with mode: 0644]
test/md5.test [deleted file]
test/metadata.ref [deleted file]
test/pixel_formats_test.cc [new file with mode: 0644]
test/play_test.cc [new file with mode: 0644]
test/ratio_test.cc [new file with mode: 0644]
test/resampler_test.cc [new file with mode: 0644]
test/scaling_test.cc [new file with mode: 0644]
test/silence_padding_test.cc [new file with mode: 0644]
test/stream_test.cc [new file with mode: 0644]
test/test.cc
test/test.h [new file with mode: 0644]
test/test.mp4 [deleted file]
test/threed_test.cc [new file with mode: 0644]
test/torture_partial.py [new file with mode: 0644]
test/util_test.cc [new file with mode: 0644]
test/wscript
version-test.py [deleted file]
version.py [deleted file]
windows/.gtkrc-2.0 [deleted file]
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]
windows/dvdomatic.bmp [deleted file]
windows/dvdomatic.ico [deleted file]
windows/dvdomatic.rc [deleted file]
windows/dvdomatic_taskbar.ico [deleted file]
windows/installer.nsi.32.in [deleted file]
windows/installer.nsi.64.in [deleted file]
windows/wscript [deleted file]
wscript

index 4c0c1926d846114f0346326afb5f784bcf8ecce3..0fd802092b9b5052d0597fa87192fbf22c23b39d 100644 (file)
@@ -15,6 +15,14 @@ sync
 doc/manual/html
 doc/manual/pdf
 doc/manual/extensions.ent
+doc/design/*.pdf
+doc/design/*.log
+doc/design/*.aux
+doc/manual/diagrams/*.pdf
+doc/manual/diagrams/*.png
 .be/id-cache
 *.pyc
-
+GPATH
+GRTAGS
+GSYMS
+GTAGS
index c82fe2ec791eff1125291a4f40d510466318e229..c31f79993eaaf53df92297480eb7c0dc240fd33f 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
-2013-01-12  Carl Hetherington  <cth@carlh.net>
+2013-09-09  Carl Hetherington  <cth@carlh.net>
 
-       * Version 0.71beta2 released.
+       * Version 1.03 released.
 
-2013-01-12  Carl Hetherington  <cth@carlh.net>
+2013-09-02  Carl Hetherington  <cth@carlh.net>
 
-       * Version 0.71beta1 released.
+       * Add missing boost datetime dependency
+       to debian control files.
 
-2013-01-12  Carl Hetherington  <cth@carlh.net>
+2013-08-30  Carl Hetherington  <cth@carlh.net>
 
-       * Untested support for splitting DCPs
-       into multiple reels.
+       * Version 1.02 released.
 
-2013-01-09  Carl Hetherington  <cth@carlh.net>
+2013-08-29  Carl Hetherington  <cth@carlh.net>
 
-       * Try to build with 0.10.4-ish ffmpeg.
+       * Version 1.01 released.
 
-2013-01-07  Carl Hetherington  <cth@carlh.net>
+2013-08-29  Carl Hetherington  <cth@carlh.net>
 
-       * Version 0.70 released.
+       * Restore server/client operation (#202).
 
-2013-01-07  Carl Hetherington  <cth@carlh.net>
+       * Fix strange rounding of still image durations (#204).
 
-       * Fix heinous thinko in mono soundtrack mapping code.
+       * Remove limitation to numbers and periods in the
+       server host name dialogue box.
 
-2013-01-06  Carl Hetherington  <cth@carlh.net>
+       * Fix stuck-at-99% progress meters (#184).
 
-       * Version 0.70beta3 released.
+       * Version 1.01beta1 released.
 
-2013-01-06  Carl Hetherington  <cth@carlh.net>
+2013-08-29  Carl Hetherington  <cth@carlh.net>
 
-       * Postpone linking of duplicate video frames so that copies
-       don't fail on Windows.
+       * Fix emissions of large chunks of silence when
+       analysing audio in some cases.
 
-2013-01-06  Carl Hetherington  <cth@carlh.net>
+       * Use my @dcpomatic.com email address for now,
+       rather than a non-existant mailing list.
 
-       * Version 0.70beta2 released.
+2013-08-28  Carl Hetherington  <cth@carlh.net>
 
-2013-01-06  Carl Hetherington  <cth@carlh.net>
+       * Initial DCP-o-matic release.
 
-       * Version 0.70beta1 released.
-
-2013-01-06  Carl Hetherington  <cth@carlh.net>
-
-       * Put mono soundtracks on the centre speaker, rather
-       than on left (reported by Mike Blakesley).
-
-       * Add format for 16:9 without letterboxing (requested by Lilian
-       Lefranc).
-
-2012-12-23  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.69 released.
-
-2012-12-23  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.68 released.
-
-2012-12-22  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.68beta10 released.
-
-2012-12-22  Carl Hetherington  <cth@carlh.net>
-
-       * Fix wscripts to work with python 3.
-
-2012-12-21  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.68beta9 released.
-
-2012-12-21  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.68beta8 released.
-
-2012-12-21  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.68beta7 released.
-
-2012-12-21  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.68beta6 released.
-
-2012-12-21  Carl Hetherington  <cth@carlh.net>
-
-       * Fix a few bugs.
-
-       * Update the manual.
-
-2012-12-20  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.68beta5 released.
-
-2012-12-20  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.68beta4 released.
-
-2012-12-20  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.68beta3 released.
-
-2012-12-20  Carl Hetherington  <cth@carlh.net>
-
-       * Allow still-image DCPs to have external audio added to them (#13).
-
-2012-12-19  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.68beta2 released.
-
-2012-12-19  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.68beta1 released.
-
-2012-12-18  Carl Hetherington  <cth@carlh.net>
-
-       * Alter film viewer so that it is much quicker, responds instantly
-       to changes in video filtering settings, and can (roughly) play the
-       source material back.
-
-       * Make the examination of content for length optional, so that
-       if a source file has an accurate header you can trust it.
-
-2012-12-18  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.67 released.
-
-2012-12-18  Carl Hetherington  <cth@carlh.net>
-
-       * Support non-planar float and signed
-       16-bit planar audio; be less
-       crashy when unsupported audio formats
-       are found.
-
-2012-12-18  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.66 released.
-
-2012-12-18  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.65 released.
->>>>>>> master
-
-2012-12-13  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.64 released.
-
-2012-12-13  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.63 released.
-
-2012-12-13  Carl Hetherington  <cth@carlh.net>
-
-       * Re-fix reports of zero audio channels
-       with soundtracks of some source files.
-
-2012-12-13  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.62 released.
-
-2012-12-13  Carl Hetherington  <cth@carlh.net>
-
-       * Improve progress reporting during the final
-       DCP make job; should stop the bar sitting at 100%
-       for a while during digest creation.
-
-2012-12-11  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.61 released.
-
-2012-12-11  Carl Hetherington  <cth@carlh.net>
-
-       * More .deb dep tweaks.
-
-2012-12-11  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.60 released.
-
-2012-12-11  Carl Hetherington  <cth@carlh.net>
-
-       * Hopefully fix utterly broken partially-static
-       builds for .debs.
-
-       * Fix specification of architecture in .debs.
-
-2012-12-10  Carl Hetherington  <cth@carlh.net>
-
-       * Add a check-box (which defaults to on) which tells DVD-o-matic
-       not to scan new content files to work out their length, but instead
-       to trust the length from the header.  This length only matters for
-       working out what thumbnails to generate, so it isn't critical.
-       Trusting the header will speed up the "Examine Content" job by
-       a factor of about 2, which is handy for large films.
-
-2012-12-10  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.59 released.
-
-2012-12-09  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.59beta5 released.
-
-2012-12-09  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.59beta4 released.
-
-2012-12-09  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.59beta3 released.
-
-2012-12-09  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.59beta2 released.
-
-2012-12-09  Carl Hetherington  <cth@carlh.net>
-
-       * Build against libdcp compiled with -O2 instead
-       of -O3.
-
-2012-12-05  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.59beta1 released.
-
-2012-11-15  Carl Hetherington  <cth@carlh.net>
-
-       * Default to using a DCI name.
-
-       * Support for using external sound files instead
-       of the ones in the video source.
-
-2012-11-14  Carl Hetherington  <cth@carlh.net>
-
-       * Rearrange the GUI a bit to tidy things up.
-
-       * Some internal reorganisation.
-
-2012-12-03  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.58 released.
-
-2012-12-03  Carl Hetherington  <cth@carlh.net>
-
-       * DVD-o-matic and its dependencies rebuilt with
-       a newer mingw toolchain and with -O2 rather than
-       -O3 to (hopefully) improve reliability on Windows.
-
-       * Fixed problems with 7.1 audio.
-
-2012-11-10  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.57 released.
-
-2012-11-10  Carl Hetherington  <cth@carlh.net>
-
-       * Fix crash when trying to use a DCI name when there
-       is no soundtrack (yet) (reported by Wolfgang Woehl).
-
-2012-11-07  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.56 released.
-
-2012-11-05  Carl Hetherington  <cth@carlh.net>
-
-       * Remove options to black-out the video when cropping the end;
-       it complicates the code and is getting a bit close to video
-       editing.
-
-       * Add option to trim from both the start and
-       the end of the input video.
-
-       * Various bug fixes and code rearrangement.
-
-2012-10-14  Carl Hetherington  <cth@carlh.net>
-
-       * Basic support for DVD and Blu-Ray subtitles.
-
-       * Re-add DCI naming support.
-
-       * Basic support for selection of audio
-       and subtitle streams.
-
-       * Fixes for audio/video sync in some cases.
-
-       * Cope with videos with varying size and/or
-       pixel format.
-
-       * Fix bug with handling of YUV422-format videos.
-
-2012-10-09  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.55 released.
-
-2012-10-09  Carl Hetherington  <cth@carlh.net>
-
-       * Fix bug possibly causing randomly-occuring
-       black thumbnails.
-
-       * Fix problems with obtaining frame rate of
-       WMV files (reported by Anders Nordentoft-Madsen).
-
-2012-10-07  Carl Hetherington  <cth@carlh.net>
-
-       * Fix up some bugs when using limited DCP
-       range (reported by Wolfgang Woehl).
-
-       * Don't stretch still images for DCPs, just
-       scale them up and pad them as required.
-
-2012-10-02  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.54 released.
-
-2012-10-02  Carl Hetherington  <cth@carlh.net>
-
-       * When encoding 24 frames per second drop
-       frame (ie 23.976 frames per second) run the
-       video at 24 FPS and resample the audio so
-       that when it is run correspondingly (slightly) fast
-       it remains in sync.
-
-       * Some code cleanup.
-
-2012-10-01  Carl Hetherington  <cth@carlh.net>
-
-       * Fix aff/666: thumbnail scan is run twice
-       when changing the content file for a film.
-
-2012-09-28  Carl Hetherington  <cth@carlh.net>
-
-       * Fix crash bug which seems to have been
-       exposed by recent changes to ffmpeg.
-
-2012-09-27  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.53 released.
-
-2012-09-27  Carl Hetherington  <cth@carlh.net>
-
-       * Fix unrecognised capital letters on
-       still-image file extensions.
-
-       * Write hashes of frames to disk and
-       check them before making the final DCP.
-
-2012-09-24  Carl Hetherington  <cth@carlh.net>
-
-       * Fix problems with overflow on long films.
-
-2012-09-24  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.52 released.
-
-2012-09-23  Carl Hetherington  <cth@carlh.net>
-
-       * Fix alignment of frames per second count.
-
-       * Use hopefully more robust networking
-       code to survive timeouts during reads and
-       writes.
-
-       * Some fixes for bugs when loading Films
-       created on Windows in Linux.
-
-2012-09-22  Carl Hetherington  <cth@carlh.net>
-
-       * Fix bug on OK-ing gain calculation
-       dialog without entering any values.
-
-       * Improve spacing in some dialogs.
-
-2012-09-22  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.51 released.
-
-2012-09-22  Carl Hetherington  <cth@carlh.net>
-
-       * Improve transcode job progress reporting.
-
-       * Update the slow bits of the properties
-       dialog in a separate thread to improve
-       responsiveness.
-
-       * Fix edit server button on Windows.
-
-2012-09-22  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.50 released.
-
-2012-09-22  Carl Hetherington  <cth@carlh.net>
-
-       * Rename servomatic to servomatic_cli and
-       add a very basic system-tray-dwelling GUI server.
-
-       * Tweak formatting of properties dialogue
-       and add a note of how many J2K frames
-       have already been encoded.
-
-       * Correctly set up crop in the viewer
-       on reloading a film.
-
-2012-09-18  Carl Hetherington  <cth@carlh.net>
-
-       * Fix non-working removal of encode servers.
-
-       * Add GUI front-end to encode server.
-
-2012-09-17  Carl Hetherington  <cth@carlh.net>
-
-       * Include servomatic in the Windows install.
-
-       * Add a simple Properties dialog to give
-       an estimate of disk space required for an
-       encode.
-
-2012-09-17  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.49 released.
-
-2012-09-16  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.48 released.
-
-2012-09-15  Carl Hetherington  <cth@carlh.net>
-
-       * Slightly speculative fix for failure to
-       take note of audio gain changes caused by
-       the Calculate dialogue.
-
-2012-09-12  Carl Hetherington  <cth@carlh.net>
-
-       * Fix crash when FFmpeg doesn't set up the audio channel
-       layout for some reason.
-
-2012-09-01  Carl Hetherington  <cth@carlh.net>
-
-       * Add 1.66-within-flat format.
-
-2012-08-27  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.47 released.
-
-2012-08-23  Carl Hetherington  <cth@carlh.net>
-
-       * Add some more formats.
-
-       * Update to use libdcp 0.11.
-
-       * Fix build with boost filesystem version 2.
-
-2012-08-10  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.46 released.
-
-2012-08-10  Carl Hetherington  <cth@carlh.net>
-
-       * Untested fixes for failure to encode
-       content without audio.
-
-2012-08-09  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.45 released.
-
-2012-08-09  Carl Hetherington  <cth@carlh.net>
-
-       * Fix bug with padding in Scope causing corrupt
-       images.
-
-       * Fix bug when using content file names which
-       start with the name of the film directory.
-
-2012-08-05  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.44 released.
-
-2012-08-04  Carl Hetherington  <cth@carlh.net>
-
-       * Fix bug with content inside the film directory.
-
-2012-08-04  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.43 released.
-
-2012-08-04  Carl Hetherington  <cth@carlh.net>
-
-       * Use wxwidgets .rc file to make Windows version
-       look nicer.
-
-       * Hopefully improve building against different
-       versions of FFmpeg.
-
-2012-08-04  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.42 released.
-
-2012-08-04  Carl Hetherington  <cth@carlh.net>
-
-       * Request admin priviledges on install for Windows 7.
-
-       * Add some missing dependencies to the Windows package.
-
-2012-08-01  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.40 released.
-
-2012-08-01  Carl Hetherington  <cth@carlh.net>
-
-       * Fix a few bugs related to thumbnailing.
-
-       * Update for libdcp version 0.06.
-
-2012-07-31  Carl Hetherington  <cth@carlh.net>
-
-       * Add option to compute required audio gains to
-       effect the same as a sound processor fader change
-       (currently for Dolby CP750 only).
-
-2012-07-28  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.37 released.
-
-2012-07-28  Carl Hetherington  <cth@carlh.net>
-
-       * Fix missed frames when encoding caused by server
-       threads that are attempting to access non-responding
-       servers.
-
-       * Fix makedcp parsing of -v option.
-
-2012-07-28  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.36 released.
-
-2012-07-28  Carl Hetherington  <cth@carlh.net>
-
-       * Install / version tweaks.
-
-2012-07-28  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.35 released.
-
-2012-07-27  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.31 released.
-
-2012-07-27  Carl Hetherington  <cth@carlh.net>
-
-       * Speed up thumbnail display.
-
-       * Various improvements to Windows port.
-
-       * Fix TMS transfer with large files.
-
-       * Clean up audio handling code somewhat.
-
-       * Re-sample audio to 48kHz or 96kHz if necessary.
-
-       * Remove player functionality from DVD-o-matic.
-
-2012-07-22  Carl Hetherington  <cth@carlh.net>
-
-       * Port to Windows.
-
-       * Use MD5 digest to decide on the directory to put J2C files
-       in, rather than the path of the content.
-
-       * Allow building with current FFmpeg git.
-
-       * Fix problems when creating cut videos in that the audio is too
-       short; pad it with silence.
-
-2012-07-21  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.29 released.
-
-2012-07-21  Carl Hetherington  <cth@carlh.net>
-
-       * Tidy widgets and menus when there is no film loaded.
-
-       * Option to build with Ubuntu 12.04's FFmpeg libraries.
-
-       * Add dialogue box to choose DVD title when ripping.
-
-       * Always do an examine run for new content.
-
-2012-07-18  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.26 released
-
-2012-07-15  Carl Hetherington  <cth@carlh.net>
-
-       * Remove code to use `standard' format DCP long names,
-       as in the wild their use seems to be decreasing, and it
-       makes the GUI simpler.
-
-       * Fix some bugs with sending to servomatic introduced
-       in the adjustments to padding.
-
-       * Write some status text when an unknown-progress
-       job is running.
-
-       * Use new libdcp rather than OpenDCP to generate MXFs
-       and write DCP XML.
-
-2012-07-14  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.25 released.
-
-2012-07-14  Carl Hetherington  <cth@carlh.net>
-
-       * Various GUI cleanups.
-
-       * Remove player from the GUI for now.
-
-       * Fix hash down the left-hand side of encoded DCPs.
-
-       * Add option to black-out the end of an encode, in order
-       to remove unwanted frames of video whilst keeping sound.
-
-       * Fixes to copy-to-server.
-
-       * Fix name of 16:9 format.
-
-2012-07-08  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.24 released.
-
-2012-07-08  Carl Hetherington  <cth@carlh.net>
-
-       * Add support for generating static DCPs from single
-       image files.
-
-       * Add option to copy DCP to a remote server (e.g. a TMS)
-       via SCP.
-
-       * Auto-update thumbs when content changes.
-
-2012-06-10  Carl Hetherington  <cth@carlh.net>
-
-       * Fix up bad padding setup when there isn't any.
-
-       * Restore sound to playomatic; add assert for bad format.
-
-2012-05-26  Carl Hetherington  <cth@carlh.net>
-
-       * Fix crash on attempting to use a non-existant filter.
-
-       * src/lib/filter.cc: Fix typo in filter name.
-
-       * Allow configuration of the reference scalers and filters in A/B mode.
-
-       * Fix identification of formats in metadata.
-
-2012-05-26  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.23 released.
-
-2012-05-28  Carl Hetherington  <cth@carlh.net>
-
-       * src/lib/player_manager.cc: possible fix to crash when stopping
-       playback.
-
-       * Fix crash in A/B mode.
-
-2012-05-26  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.21 released.
-
-2012-05-25  Carl Hetherington  <cth@carlh.net>
-
-       * Add option to delay audio with respect to video.
-
-       * src/tools/fixlengths.cc: add a few more options.
-
-2012-05-22  Carl Hetherington  <cth@carlh.net>
-
-       * src/tools/dvdomatic.cc: fix website address.
-
-       * test: fix up a few test bits.
-
-       * README: very brief introduction to a few things.
-
-2012-05-22  Carl Hetherington  <cth@carlh.net>
-
-       * Version 0.20 released.
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/TODO b/TODO
deleted file mode 100644 (file)
index 17f02e4..0000000
--- a/TODO
+++ /dev/null
@@ -1,137 +0,0 @@
-Make a DCP with subs using subtitle edit.
-
-Look at http://liblqr.wikidot.com/en:manual
-
-EC2
-
-Small instance $0.085 ph
-Sintel Trailer 1080p @ 200000 Mbps
-1247 frames @ 24fps ie 51.96s
-Took 1h20 to encode
-
-High-CPU medium $0.186 ph
-Sintel Trailer 1080p @ 200000 Mbps
-1247 frames @ 24fps ie 51.96s
-Took 23m to encode
-
-High-CPU extra-large $0.744 ph
-Sintel Trailer 1080p @ 200000 Mbps
-1247 frames @ 24fps ie 51.96s
-
-
-Transfer in free
-Transfer out $0.120 per GB
-
-
-Port DVD rip
-
-Write still j2ks straight to a MXF.
-md5_data to use openssl
-Write all j2ks straight to a MXF?  Possible?
-
-Standardise j2c/j2k
-Format name in ~/.dvdomatic screws up with spaces; use ID or something
-Thumbnails are poorly named
-x-thread signaller
-Restartable jobs somehow
-More logging
-Nice error when trying to thumbnail with no content.
-Destroy _buffer_src_context / _buffer_sink_context
-Don't start later jobs when one breaks.
-Compute time remaining based on more recent information.
-Use lexical_cast more
-Do deps better
-
-options summary
-
-1: L
-2: R
-3: C
-4: Lfe
-5: Ls
-6: Rs
-
-City Screen
-
-Screen 1: "1.37" masking preset, projector only has DCI 133 preset.
-
-With 1480x1080 alignment in DCI 133: bottom you see purple, yellow; top purple; left and right no lines
-With 1480x1080 alignment in DCI Flat: outside masks, but you see bottom purple, yellow; left/right all; top purple
-
-
-Screen 2: no real masking preset, projector has DCI 133 and DCI 137
-
-1480x1080, DCI 133
-L yellow purple
-R none
-B purple
-T none
-1480x1080, DCI 137
-L all
-R all but blue
-T purple
-B purple
-
-
-Screen 3: projector has DCI 1.38
-
-1480x1080
-L, R, T none
-B purple + yellow
-
-
-films-0.6: Dolby Countdown looks as though it's 3D.  THX Terminator 2 fucked
-(these on default settings)
-fq/gradfun --- no obvious effect
-hqdn3d --- pretty good denoising
-ow --- no obvious effect
-tn --- interesting; much noise reduction, bad artefacts on movement, colour tint even in black
-unsharp --- worse
-
-Benchmark SWS options: lanczos ?
-hqdn3d=0:0:6 ? (turn off chroma/luma blurring)
-
-Lanczos; no visible effect on Ghostbusters.
-
-
-THX_Monster with master Intel Core 2 Duo E4600 (2.4GHz), slave Intel Core i3 M350 (2.27GHz)
-1920 x 1080 original -> DCI Flat
-240 frames
-
-[Gbit: gigabit ethernet rather than 100Mbit]
-[im-mod: after modification to memcpy RGB data then to RGB -> XYZ in the encode thread
-[hack1]: after modification to pass YUV and to swscale in the encode thread (includes im-mod)
-[hack2]: modified hack1
-                               Time            Seconds         FPS             Speedup relative to 1 local
-1 local:                               20m57           1257            0.19            x 1     
-2 local:                       11m24            684            0.35            x 1.84
-2 local [im-mod]:              13m13
-2 local + 1 slave:             6m34             394            0.61            x 3.19
-2 local + 2 slave:             5m13             313            0.77            x 4.02
-2 local + 4 slave:             5m05             303            0.79            x 4.15
-2 local + 4 slave [Gbit]:      2m50             170            1.41            x 7.39
-2 local + 4 slave [Gbit,im-mod]:2m33
-2 local + 4 slave [Gbit,hack1]: 3m20
-2 local + 4 slave [Gbit,hack2]: 2m22
-1 local + 8 slave [Gbit]:      2m28             148            1.62            x 8.49
-2 local + 8 slave [Gbit]:      2m41             161            1.49            x 7.81
-2 local + 8 slave [Gbit,im-mod]:2m35
-
-
-
-Just encode 52s
-Encode + Image create 1m27
-Encode + Image create (memcpy, not convert) 53s.
-
-THX_Monster with master Intel Core i3 M350 (2.27GHz), slave Intel Core 2 Duo E4600 (2.4GHz)
-1920 x 1080 original -> DCI Flat
-240 frames
-
-
-4 local:                       2m45
-4 local [im-mod]:              2m53
-4 local + 2 slave [Gbit]:      2m22
-4 local + 4 slave [Gbit]:      2m21
-4 local + 4 slave [Gbit,in-mod]:2m21
-
-
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/build-all-ffmpeg b/build-all-ffmpeg
deleted file mode 100755 (executable)
index a3d197c..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-FFMPEGS=/home/carl/ffmpeg
-
-# 0.6, 0.7, 0.8 need significant work, I think.
-
-for v in 0.9.2 0.10.4 0.11.1; do
-    PKG_CONFIG_PATH=$FFMPEGS/$v/lib/pkgconfig ./waf configure
-    if [ "$?" != "0" ]; then
-        echo "$v: configure FAIL"
-       exit 1
-    fi
-    ./waf
-    if [ "$?" != "0" ]; then
-        echo "$v: build FAIL"
-       exit 1
-    fi
-    echo "$v: PASS"
-done
-
diff --git a/builds/all b/builds/all
deleted file mode 100755 (executable)
index f5d64b7..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash -e
-
-OUT=/home/carl/public_html/carlh.net/software/dvdomatic
-
-./waf dist
-
-mkdir -p $OUT
-mkdir -p $OUT/12.04-32
-mkdir -p $OUT/12.04-64
-mkdir -p $OUT/12.10-32
-mkdir -p $OUT/12.10-64
-
-builds/all-deb
-cp build/deb/12.04-32/*.deb $OUT/12.04-32/
-cp build/deb/12.04-64/*.deb $OUT/12.04-64/
-cp build/deb/12.10-32/*.deb $OUT/12.10-32/
-cp build/deb/12.10-64/*.deb $OUT/12.10-64/
-
-builds/windows-32
-cp build/windows/DVD*.exe $OUT/
-builds/windows-64
-cp build/windows/DVD*.exe $OUT/
-
-
diff --git a/builds/all-deb b/builds/all-deb
deleted file mode 100755 (executable)
index 65af83e..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash -e
-
-builds/deb 12.04 64
-builds/deb 12.10 64
-builds/deb 12.04 32
-builds/deb 12.10 32
diff --git a/builds/chroot-deb b/builds/chroot-deb
deleted file mode 100755 (executable)
index 881f9f8..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/bash -e
-
-UBUNTU_VERSION=$1
-BITS=$2
-if [ "$UBUNTU_VERSION" == "" -o "$BITS" == "" ]; then
-  echo "Syntax: $0 <ubuntu version> <bits>"
-  exit 1
-fi
-
-if [ "$BITS" == 32 ]; then
-  CPU=i386
-else
-  CPU=amd64
-fi
-
-cp builds/control-$UBUNTU_VERSION-$BITS debian/control
-./waf dist
-TARBALL=`ls -1 *.tar.bz2`
-VERSION=`echo $TARBALL | sed -e 's/^dvdomatic-\(.*\).tar.bz2/\1/'`
-
-echo "dvdomatic_$VERSION-1_$CPU.deb video extra" > debian/files
-
-rm -rf build/deb
-mkdir -p build/deb
-cd build/deb
-mv ../../*.tar.bz2 .
-DEB_TARBALL="dvdomatic_$VERSION.orig.tar.bz2"
-echo "Renaming $TARBALL to $DEB_TARBALL"
-mv $TARBALL $DEB_TARBALL
-tar xjf $DEB_TARBALL
-cd dvdomatic-*
-dpkg-source -b .
-dpkg-buildpackage
-cp ../*.deb ../../../
diff --git a/builds/control-12.04-32 b/builds/control-12.04-32
deleted file mode 100644 (file)
index 8cb5ace..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-Source: dvdomatic
-Section: video
-Priority: extra
-Maintainer: Carl Hetherington <cth@carlh.net>
-Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libwxgtk2.8-dev (>= 2.8.12.1), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7)
-Standards-Version: 3.9.3
-Homepage: http://carlh.net/software/dvdomatic
-
-Package: dvdomatic
-Architecture: i386
-Depends: libc6 (>= 2.15), libwxgtk2.8-0 (>= 2.8.12.1), libssh-4 (>= 0.5.2), libboost-filesystem1.46.1 (>= 1.46.1), libboost-thread1.46.1 (>= 1.46.1), libsndfile1 (>= 1.0.25), libmagick++4 (>= 8:6.6.9.7), libxml++2.6-2 (>= 2.34.1)
-Description: Generator of Digital Cinema Packages (DCPs)
-  DVD-o-matic generates Digital Cinema Packages (DCPs) from video and audio
-  files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant
-  digital projectors.
diff --git a/builds/control-12.04-64 b/builds/control-12.04-64
deleted file mode 100644 (file)
index cdb15a8..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-Source: dvdomatic
-Section: video
-Priority: extra
-Maintainer: Carl Hetherington <cth@carlh.net>
-Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libwxgtk2.8-dev (>= 2.8.12.1), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7)
-Standards-Version: 3.9.3
-Homepage: http://carlh.net/software/dvdomatic
-
-Package: dvdomatic
-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
-  files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant
-  digital projectors.
diff --git a/builds/control-12.10-32 b/builds/control-12.10-32
deleted file mode 100644 (file)
index 1dc91b7..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-Source: dvdomatic
-Section: video
-Priority: extra
-Maintainer: Carl Hetherington <cth@carlh.net>
-Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libwxgtk2.8-dev (>= 2.8.12.1), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7)
-Standards-Version: 3.9.3
-Homepage: http://carlh.net/software/dvdomatic
-
-Package: dvdomatic
-Architecture: i386
-Depends: libc6 (>= 2.15), libwxgtk2.8-0 (>= 2.8.12.1), libssh-4 (>= 0.5.2), libboost-filesystem1.49.0 (>= 1.49.0), libboost-thread1.49.0 (>= 1.49.0), libsndfile1 (>= 1.0.25), libmagick++5 (>= 8:6.7.7.10), libxml++2.6-2 (>= 2.34.2)
-Description: Generator of Digital Cinema Packages (DCPs)
-  DVD-o-matic generates Digital Cinema Packages (DCPs) from video and audio
-  files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant
-  digital projectors.
diff --git a/builds/control-12.10-64 b/builds/control-12.10-64
deleted file mode 100644 (file)
index ed0b36b..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-Source: dvdomatic
-Section: video
-Priority: extra
-Maintainer: Carl Hetherington <cth@carlh.net>
-Build-Depends: debhelper (>= 8.0.0), python (>= 2.7.3), g++ (>= 4:4.6.3), pkg-config (>= 0.26), libwxgtk2.8-dev (>= 2.8.12.1), libssh-dev (>= 0.5.2), libboost-filesystem-dev (>= 1.46.0), libboost-thread-dev (>= 1.46.0), libsndfile1-dev (>= 1.0.25), libmagick++-dev (>= 8:6.6.9.7)
-Standards-Version: 3.9.3
-Homepage: http://carlh.net/software/dvdomatic
-
-Package: dvdomatic
-Architecture: amd64
-Depends: libc6 (>= 2.15), libwxgtk2.8-0 (>= 2.8.12.1), libssh-4 (>= 0.5.2), libboost-filesystem1.49.0 (>= 1.49.0), libboost-thread1.49.0 (>= 1.49.0), libsndfile1 (>= 1.0.25), libmagick++5 (>= 8:6.7.7.10), libxml++2.6-2 (>= 2.34.2)
-Description: Generator of Digital Cinema Packages (DCPs)
-  DVD-o-matic generates Digital Cinema Packages (DCPs) from video and audio
-  files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant
-  digital projectors.
diff --git a/builds/deb b/builds/deb
deleted file mode 100755 (executable)
index 763dd55..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-
-UBUNTU_VERSION=$1
-BITS=$2
-if [ "$UBUNTU_VERSION" == "" -o "$BITS" == "" ]; then
-  echo "Syntax: $0 <ubuntu version> <bits>"
-  exit 1
-fi
-
-if [ "$BITS" == 32 ]; then
-  CPU=i386
-else
-  CPU=amd64
-fi
-
-ID=$UBUNTU_VERSION-$BITS
-CHROOT=/home/carl/Environments/ubuntu-$ID
-TARBALL=`ls -1 *.tar.bz2`
-VERSION=`echo $TARBALL | sed -e 's/^dvdomatic-\(.*\).tar.bz2/\1/'`
-
-echo "ID: $ID"
-echo "chroot: $CHROOT"
-echo "tarball: $TARBALL"
-echo "version: $VERSION"
-
-sudo cp *.tar.bz2 $CHROOT/root/
-sudo schroot -c "ubuntu-$ID" -d /root -u root -- /bin/sh -c "tar xjf dvdomatic-$VERSION.tar.bz2 && cd dvdomatic-$VERSION && builds/chroot-deb $UBUNTU_VERSION $BITS"
-mkdir -p build/deb/$ID/
-echo "Copying $CHROOT/root/dvdomatic-$VERSION/build/deb/dvdomatic_$VERSION-1_$CPU.deb to build/deb/$ID"
-sudo ls -lh $CHROOT/root/dvdomatic-$VERSION/build/deb/dvdomatic_$VERSION-1_$CPU.deb
-sudo cp -v $CHROOT/root/dvdomatic-$VERSION/build/deb/dvdomatic_$VERSION-1_$CPU.deb build/deb/$ID/
-ls -lh build/deb/$ID
-
diff --git a/builds/windows-32 b/builds/windows-32
deleted file mode 100755 (executable)
index d2a5f43..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/bin/bash
-
-export MINGW_CXX="i686-w64-mingw32-g++"
-export MINGW_WINDRES="i686-w64-mingw32-windres"
-export MINGW_PREFIX="/mingw/i686-w64-mingw32"
-export MINGW_PATH="/mingw/bin"
-export WINDOWS_PREFIX="/home/carl/Environments/windows/32"
-
-export PKG_CONFIG_LIBDIR=$WINDOWS_PREFIX/lib/pkgconfig
-
-./waf clean
-
-export PATH=$WINDOWS_PREFIX/bin:$MINGW_PATH:$PATH
-
-echo -n "USING COMPILER "
-which i686-w64-mingw32-g++
-
-CXX=$MINGW_CXX WINRC=$MINGW_WINDRES \
-  CXXFLAGS="-I$WINDOWS_PREFIX/include -I$MINGW_PREFIX/include" \
-  LINKFLAGS="-L$WINDOWS_PREFIX/lib -L$MINGW_PREFIX/lib" \
-  ./waf configure --target-windows $*
-if [ "$?" != "0" ]; then
-  exit 1
-fi
-
-./waf
-if [ "$?" != "0" ]; then
-  exit 1
-fi
-
-d=`pwd`
-
-cp build/windows/installer.32.nsi build/windows/installer2.32.nsi
-
-sed -i "s~%resources%~$d/windows~g" build/windows/installer2.32.nsi
-sed -i "s~%deps%~$WINDOWS_PREFIX~g" build/windows/installer2.32.nsi
-sed -i "s~%binaries%~$d/build~g" build/windows/installer2.32.nsi
-sed -i "s~%bits%~32~g" build/windows/installer2.32.nsi
-
-makensis build/windows/installer2.32.nsi
diff --git a/builds/windows-64 b/builds/windows-64
deleted file mode 100755 (executable)
index 8720693..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/bash
-
-export MINGW_CXX="x86_64-w64-mingw32-g++"
-export MINGW_WINDRES="x86_64-w64-mingw32-windres"
-export MINGW_PREFIX="/mingw/x86_64-w64-mingw32"
-export MINGW_PATH="/mingw/bin"
-export WINDOWS_PREFIX="/home/carl/Environments/windows/64"
-
-export PKG_CONFIG_LIBDIR=$WINDOWS_PREFIX/lib/pkgconfig
-
-./waf clean
-
-export PATH=$WINDOWS_PREFIX/bin:$MINGW_PATH:$PATH
-
-echo -n "USING COMPILER "
-which x86_64-w64-mingw32-g++
-
-CXX=$MINGW_CXX WINRC=$MINGW_WINDRES \
-  CXXFLAGS="-I$WINDOWS_PREFIX/include -I$MINGW_PREFIX/include" \
-  LINKFLAGS="-L$WINDOWS_PREFIX/lib -L$MINGW_PREFIX/lib" \
-  PATH=$WINDOWS_PREFIX/bin:$PATH \
-  ./waf configure --target-windows $*
-if [ "$?" != "0" ]; then
-  exit 1
-fi
-
-./waf
-if [ "$?" != "0" ]; then
-  exit 1
-fi
-
-d=`pwd`
-
-cp build/windows/installer.64.nsi build/windows/installer2.64.nsi
-
-sed -i "s~%resources%~$d/windows~g" build/windows/installer2.64.nsi
-sed -i "s~%deps%~$WINDOWS_PREFIX~g" build/windows/installer2.64.nsi
-sed -i "s~%binaries%~$d/build~g" build/windows/installer2.64.nsi
-sed -i "s~%bits%~64~g" build/windows/installer2.64.nsi
-
-makensis build/windows/installer2.64.nsi
diff --git a/cscript b/cscript
new file mode 100644 (file)
index 0000000..50a9c39
--- /dev/null
+++ b/cscript
@@ -0,0 +1,74 @@
+import glob
+import shutil
+import os
+
+def dependencies(target):
+    return (('ffmpeg-cdist', '77e9115b172ec6e4f0da0a5525f32fb28bae5f09'),
+            ('libdcp', '08f4fe13bbff1a674930d55ab95fd181ebd0c265'))
+
+def build(target):
+    cmd = './waf configure --prefix=%s' % target.work_dir_cscript()
+    if target.platform == 'windows':
+        cmd += ' --target-windows'
+    elif target.platform == 'linux':
+        cmd += ' --static'
+    target.command(cmd)
+
+    target.command('./waf')
+
+    if target.platform == 'linux' or target.platform == 'osx':
+        target.command('./waf install')
+
+
+def package(target, version):
+    if target.platform == 'windows':
+        shutil.copyfile('build/platform/windows/installer.%s.nsi' % target.bits, 'build/platform/windows/installer2.%s.nsi' % target.bits)
+        target.command('sed -i "s~%%resources%%~%s/platform/windows~g" build/platform/windows/installer2.%s.nsi' % (os.getcwd(), target.bits))
+        target.command('sed -i "s~%%static_deps%%~%s~g" build/platform/windows/installer2.%s.nsi' % (target.windows_prefix, target.bits))
+        target.command('sed -i "s~%%cdist_deps%%~%s~g" build/platform/windows/installer2.%s.nsi' % (target.work_dir_cscript(), target.bits))
+        target.command('sed -i "s~%%binaries%%~%s/build~g" build/platform/windows/installer2.%s.nsi' % (os.getcwd(), target.bits))
+        target.command('sed -i "s~%%bits%%~32~g" build/platform/windows/installer2.%s.nsi' % target.bits)
+        target.command('makensis build/platform/windows/installer2.%s.nsi' % target.bits)
+        return os.path.abspath(glob.glob('build/platform/windows/*%s*.exe' % target.bits)[0])
+    elif target.platform == 'linux':
+        if target.bits == 32:
+            cpu = 'i386'
+        else:
+            cpu = 'amd64'
+
+        shutil.copyfile('platform/linux/control-%s-%d' % (target.version, target.bits), 'debian/control')
+        target.command('./waf dist')
+        f = open('debian/files', 'w')
+        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('../../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'))
+        target.set('CDIST_PKG_CONFIG_PATH', target.get('PKG_CONFIG_PATH'))
+        target.command('dpkg-buildpackage')
+        
+        debs = []
+        for p in glob.glob('../*.deb'):
+            debs.append(os.path.abspath(p))
+
+        return debs
+    elif target.platform == 'osx':
+        target.command('bash platform/osx/make_dmg.sh')
+        return os.path.abspath(glob.glob('build/platform/osx/DCP-o-matic*.dmg')[0])
+
+def make_pot(target):
+    target.command('./waf 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')
+    target.command('make')
+    return [os.path.abspath('pdf'), os.path.abspath('html')]
index b8385a4b020bdd8db606901420891afab446bbc9..0d214c5723fabc69be4a96eed9df1da4228b0618 100644 (file)
@@ -1,4 +1,364 @@
-dvdomatic (0.71beta2-1) UNRELEASED; urgency=low
+dcpomatic (1.03-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * New upstream release.
+  * New upstream release.
+  * New upstream release.
+  * New upstream release.
+  * New upstream release.
+  * New upstream release.
+
+ -- Carl Hetherington <carl@d1stkfactory>  Mon, 09 Sep 2013 23:56:05 +0100
+
+dcpomatic (0.87-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Fri, 26 Apr 2013 09:53:27 +0100
+
+dcpomatic (0.86-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 23 Apr 2013 08:13:13 +0100
+
+dcpomatic (0.85-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 23 Apr 2013 00:08:20 +0100
+
+dcpomatic (0.84-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sun, 21 Apr 2013 17:49:54 +0100
+
+dcpomatic (0.84beta5-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sun, 21 Apr 2013 00:06:12 +0100
+
+dcpomatic (0.84beta4-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Fri, 19 Apr 2013 17:41:58 +0100
+
+dcpomatic (0.84beta3-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Fri, 19 Apr 2013 11:36:37 +0100
+
+dcpomatic (0.84beta2-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Fri, 19 Apr 2013 11:12:09 +0100
+
+dcpomatic (0.84beta1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Thu, 18 Apr 2013 23:32:17 +0100
+
+dcpomatic (0.83-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Wed, 10 Apr 2013 12:48:25 +0100
+
+dcpomatic (0.82-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 09 Apr 2013 23:43:35 +0100
+
+dcpomatic (0.82beta1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 09 Apr 2013 21:48:56 +0100
+
+dcpomatic (0.81-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 09 Apr 2013 19:48:04 +0100
+
+dcpomatic (0.81beta1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 09 Apr 2013 15:37:32 +0100
+
+dcpomatic (0.80-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sun, 07 Apr 2013 23:48:12 +0100
+
+dcpomatic (0.80beta4-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sun, 07 Apr 2013 23:08:49 +0100
+
+dcpomatic (0.80beta3-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sun, 07 Apr 2013 22:44:29 +0100
+
+dcpomatic (0.80beta2-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sun, 07 Apr 2013 22:19:34 +0100
+
+dcpomatic (0.80beta1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sun, 07 Apr 2013 18:21:33 +0100
+
+dcpomatic (0.79-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Mon, 01 Apr 2013 22:37:03 +0100
+
+dcpomatic (0.78-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sun, 31 Mar 2013 02:43:03 +0100
+
+dcpomatic (0.78beta16-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Thu, 28 Mar 2013 16:28:05 +0000
+
+dcpomatic (0.78beta15-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Thu, 28 Mar 2013 14:25:56 +0000
+
+dcpomatic (0.78beta14-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Thu, 28 Mar 2013 10:38:07 +0000
+
+dcpomatic (0.78beta13-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Wed, 27 Mar 2013 12:26:55 +0000
+
+dcpomatic (0.78beta12-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 26 Mar 2013 21:13:54 +0000
+
+dcpomatic (0.78beta11-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 26 Mar 2013 17:34:49 +0000
+
+dcpomatic (0.78beta10-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 26 Mar 2013 11:35:15 +0000
+
+dcpomatic (0.78beta9-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 26 Mar 2013 10:36:05 +0000
+
+dcpomatic (0.78beta8-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 26 Mar 2013 00:59:36 +0000
+
+dcpomatic (0.78beta7-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 26 Mar 2013 00:19:21 +0000
+
+dcpomatic (0.78beta6-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Mon, 25 Mar 2013 00:08:10 +0000
+
+dcpomatic (0.78beta5-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Thu, 21 Mar 2013 16:32:21 +0000
+
+dcpomatic (0.78beta4-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Wed, 20 Mar 2013 15:01:10 +0000
+
+dcpomatic (0.78beta3-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Wed, 20 Mar 2013 10:49:17 +0000
+
+dcpomatic (0.78beta2-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 19 Mar 2013 21:35:50 +0000
+
+dcpomatic (0.78beta1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 19 Mar 2013 20:50:54 +0000
+
+dcpomatic (0.77-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Thu, 14 Mar 2013 17:12:03 +0000
+
+dcpomatic (0.77beta2-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Thu, 14 Mar 2013 15:50:43 +0000
+
+dcpomatic (0.77beta1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Thu, 14 Mar 2013 15:14:01 +0000
+
+dcpomatic (0.76-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 05 Mar 2013 13:30:28 +0000
+
+dcpomatic (0.76beta3-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Tue, 05 Mar 2013 12:47:20 +0000
+
+dcpomatic (0.76beta2-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Fri, 01 Mar 2013 18:32:16 +0000
+
+dcpomatic (0.76beta1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Fri, 01 Mar 2013 17:36:55 +0000
+
+dcpomatic (0.75-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Wed, 27 Feb 2013 11:03:07 +0000
+
+dcpomatic (0.75beta1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Wed, 27 Feb 2013 08:20:42 +0000
+
+dcpomatic (0.74-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sat, 23 Feb 2013 22:57:20 +0000
+
+dcpomatic (0.74beta1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sat, 23 Feb 2013 21:44:22 +0000
+
+dcpomatic (0.73-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Thu, 21 Feb 2013 00:43:40 +0000
+
+dcpomatic (0.73beta9-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Wed, 20 Feb 2013 23:40:24 +0000
+
+dcpomatic (0.73beta8-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Mon, 18 Feb 2013 22:35:51 +0000
+
+dcpomatic (0.73beta7-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Mon, 18 Feb 2013 20:38:51 +0000
+
+dcpomatic (0.73beta6-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sun, 17 Feb 2013 23:05:56 +0000
+
+dcpomatic (0.73beta3-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sun, 17 Feb 2013 23:05:05 +0000
+
+dcpomatic (0.73beta2-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sat, 16 Feb 2013 22:42:32 +0000
+
+dcpomatic (0.73beta1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sat, 16 Feb 2013 21:19:24 +0000
+
+dcpomatic (0.72-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Thu, 24 Jan 2013 15:31:57 +0000
+
+dcpomatic (0.71-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Thu, 24 Jan 2013 11:36:04 +0000
+
+dcpomatic (0.70-1) UNRELEASED; urgency=low
 
   * New upstream release.
   * New upstream release.
@@ -6,7 +366,7 @@ dvdomatic (0.71beta2-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.
@@ -15,13 +375,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.
@@ -31,91 +391,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 3320087a5a48cead85eaee75a2a7968ea39e97ad..29f926c31651a2feb2b76792320f0a4fce70198e 100755 (executable)
        dh $@ 
 
 override_dh_auto_configure:
-       ./waf --nocache configure --prefix=/usr --static
+       LINKFLAGS=$(CDIST_LINKFLAGS) CXXFLAGS="$(CXXFLAGS) $(CDIST_CXXFLAGS)" PKG_CONFIG_PATH=$(CDIST_PKG_CONFIG_PATH) \
+                ./waf --nocache configure --prefix=/usr --static --enable-debug
 
 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=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..d71b48f
--- /dev/null
@@ -0,0 +1,77 @@
+\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?
+
+On the other hand, having the resampler in the player is confusing.  Audio comes in
+at a frame `position', but then it gets resampled and not all of it may emerge from
+the resampler.  This means that the position is meaningless, and we want a count
+of samples out from the resampler (which can be done more elegantly by the decoder's
+\texttt{\_audio\_position}.
+
+
+\section{Options for what \texttt{Time} is a function of}
+
+I've been trying for a while with \texttt{Time} as a wall-clock
+`real-time' unit.  This means that the following is tricky:
+
+\begin{enumerate}
+\item Add content at 29.97 fps
+\item Length of this content is converted to \texttt{Time} using the
+  current DCP frame rate (which will be 29.97).
+\item Add more content at 25 fps.
+\item This causes the DCP frame rate to be changed to 25 fps, and so
+  the first piece of content is now being run slower and so its length
+  changes.
+\end{enumerate}
+
+I think this is the cause of content being overlapped in this case.
+
+It is tempting to solve this by making Time a subdivsion of DCP video
+frame rate.  This makes things nicer in many ways; you get a 1:1
+mapping of content video frames to Time in most cases, but not when
+video frames are skipped to halve the frame rate, say.  In this case
+you could have a piece of content at 50 fps which is some time $T$
+long at at DCP rate of 50 fps, but half as long at a DCP rate of 25 fps.
+
+I'm fairly sure that there is inherently not a nice representation which
+will obviate the need for things to be recalculated when DCP rate changes.
+
+On the plus side, lengths in \texttt{Time} are computed on-demand from
+lengths kept as source frames.
+
+\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..bb3c3167e7962f6e182c7f8a69c39389e97532f3 100644 (file)
@@ -1,14 +1,15 @@
-# DVD-o-matic manual makefile
+# DCP-o-matic manual makefile
 
 all:   html pdf
 
-DIAGRAMS := 
+DIAGRAMS := file-structure.svg 3d-left-right.svg
 
-SCREENSHOTS := file-new.png video-new-film.png still-new-film.png click-content-selector.png video-select-content-file.png \
-               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
+SCREENSHOTS := file-new.png video-new-film.png still-new-film.png video-select-content-file.png \
+               still-select-content-file.png examine-thumbs.png examine-content.png timing-tab.png \
+               calculate-audio-gain.png prefs.png making-dcp.png filters.png video-tab.png audio-tab.png subtitles-tab.png \
+               audio-plot.png audio-map-eg1.png audio-map-eg2.png audio-map-eg3.png
 
-XML := dvdomatic.xml
+XML := dcpomatic.xml
 
 GRAPHICS := 
 
@@ -49,6 +50,9 @@ html/screenshots/new-session.png: screenshots/new-session.png
 html/screenshots/export-dialogue.png: screenshots/export-dialogue.png
        mkdir -p html/screenshots
        convert -resize 75% $< $@
+html/screenshots/making-dcp.png: screenshots/making-dcp.png
+       mkdir -p html/screenshots
+       convert -resize 75% $< $@
 
 # For HTML: convert graphics from SVG to PNG
 graphics/%.png:        graphics/%.svg
@@ -70,7 +74,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 +84,20 @@ 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
+       mkdir -p html/diagrams
+#       mkdir -p 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 +105,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 +121,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.xsl b/doc/manual/dcpomatic-pdf.xsl
new file mode 100644 (file)
index 0000000..c4ced0d
--- /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.5</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..c52d32b
--- /dev/null
@@ -0,0 +1,1372 @@
+<?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">
+
+<!-- By good luck or good management, the scale parameter to imagedata
+     appears only to affect PDF output.  HTML scaling is done in the
+     Makefile.
+-->
+
+<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>
+
+<section>
+<title>Acknowledgements</title>
+
+<para>
+This manual uses icons from the <ulink url="http://tango.freedesktop.org/">Tango Desktop Project</ulink>, with thanks.
+</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://dcpomatic.com/">http://dcpomatic.com/</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>Mac OS X</title>
+
+<para>
+DCP-o-matic will run on Mac OS X version 10.6 (Snow Leopard) and
+higher.  To install it, download the <code>DMG</code> from <ulink
+url="http://dcpomatic.com/">http://dcpomatic.com/</ulink> and double
+click to open it.  Then drag the DCP-o-matic icon to your
+<guilabel>Applications</guilabel> folder or wherever else you would
+like to install it.
+</para>
+
+</section>
+
+<section>
+<title>Ubuntu Linux</title>
+
+<para>
+You can install DCP-o-matic on Ubuntu 12.04 (&lsquo;Precise
+Pangolin&rsquo;), 12.10 (&lsquo;Quantal Quetzal&rsquo;) or 13.04
+(&lsquo;Raring Ringtail&rsquo;) using <code>.deb</code> packages:
+download the appropriate package from <ulink
+url="http://dcpomatic.com/">http://dcpomatic.com/</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:carl@dcpomatic.com">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://dcpomatic.com/">http://dcpomatic.com/</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 the details and look at the basics.
+</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
+some pieces 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>
+
+</section>
+
+<section>
+<title>Adding content</title>
+
+<para>
+The next step is to add the content that you want to use.  DCP-o-matic
+can make DCPs from multiple pieces of content, but in this simple
+example we will just use a single piece.  Click the <guilabel>Add
+file(s)...</guilabel> button, as shown in <xref
+linkend="fig-add-file"/>, 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-add-file"> 
+  <title>Adding content files</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/add-file&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-content"/>.
+</para>
+
+<figure id="fig-examine-content"> 
+  <title>Examining the content</title>
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/examine-content&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>Making the DCP</title>
+
+<para>In most cases, some adjustments would be made to DCP-o-matic's
+settings once the content has been added.  For our simple test,
+however, the default values will suffice, so we can go straight onto
+making the DCP.</para>
+
+<para>
+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 scale="30" 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 folder.  You can then copy this to a projector via a USB
+stick, hard-drive or network connection.  See <xref
+linkend="ch-files"/> for details about the files that DCP-o-matic creates.
+</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 the preferences in <xref linkend="sec-prefs-tms"/>.
+</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 one or more still images, 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>.  Now we need to add
+the content.  As before, click <guilabel>Add file(s)...</guilabel>.
+For our example, we will add a single 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>
+As with video DCPs, most of the default settings will be fine for a
+simple test.  The one thing that you might wish to change is the
+length of the still.  Select the <guilabel>Timing</guilabel> tab and
+you will see a <guilabel>Length</guilabel> setting, as shown in <xref
+linkend="fig-timing-tab"/>.
+</para>
+
+<figure id="fig-timing-tab"> 
+  <title>The timing tab</title>
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/timing-tab&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+This length is a &lsquo;timecode&rsquo;: it consists of four numbers.
+The first is hours, the second minutes, the third seconds, and the
+fourth frames.  Enter the duration that you want and then click <guilabel>Set</guilabel>.
+</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 xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
+<title>Content settings</title>
+
+<para>
+The previous chapters showed DCP generation using the default
+settings.  DCP-o-matic offers a range of features to adjust the
+content that goes into your DCP, and this chapter describes those features in
+detail.
+</para>
+
+<section>
+<title>Adding and removing content</title>
+
+<para>
+At the top of the <guilabel>Content</guilabel> tab is a list of the
+content that will go into our DCP.  There can be as many pieces of
+content as you like, and they can be of the following types:
+</para>
+
+<itemizedlist>
+<listitem>Movie &mdash; a file containing some video, probably some
+audio and possibly some subtitles; for example, a MOV, MP4 or VOB.
+</listitem>
+
+<listitem>Sound &mdash; a file containing one or more channels of
+audio; for example, a WAV or AIFF file.
+</listitem>
+
+<listitem>Still image &mdash; a file containing a single still image; for
+example, a JPEG, PNG or TIFF file.
+</listitem>
+
+<listitem>Moving image &mdash; a directory containing many still
+images which should be treated as the frames of a video.
+</listitem>
+</itemizedlist>
+
+<para>
+To add one or more movie, sound or still-image files, select
+<guilabel>Add file(s)...</guilabel> and choose them from the selector.
+To add a directory of images, choose <guilabel>Add
+directory...</guilabel> and do similar.
+</para>
+
+<para>
+You can remove a piece of content by clicking on its name and then
+clicking the <guilabel>Remove</guilabel> button.
+</para>
+
+</section>
+<section>
+<title>Content Properties</title>
+
+<para>
+Below the content list are the controls to set content properties.  To
+adjust the properties for a piece of content, click its name in the
+content list.  The content property controls will then become active
+for that piece of content.
+</para>
+
+<para>
+The content properties are split up into four sections:
+<guilabel>Video</guilabel>, <guilabel>Audio</guilabel>,
+<guilabel>Subtitles</guilabel> and <guilabel>Timing</guilabel>.  Not
+all of these sections will be active for all content types.  The controls
+in each section are described below.
+</para>
+
+</section>
+
+<section>
+<title>Video</title>
+
+<para>
+The <guilabel>Video</guilabel> tab controls properties of the image, 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>
+
+<section>
+<title>Image type</title>
+
+<para>
+The first option on this tab is the &lsquo;type&rsquo; of the video.
+This specifies how DCP-o-matic should interpret the video's image.
+<guilabel>2D</guilabel> is the default; this just takes the video
+image as a standard 2D frame.  The other option <guilabel>3D
+left/right</guilabel> tells DCP-o-matic to interpret the frame as a
+left-right pair, as shown in <xref linkend="fig-3d-left-right"/>.
+</para>
+
+<figure id="fig-3d-left-right"> 
+  <title>3D left/right image type</title>
+  <mediaobject>
+    <imageobject> 
+       <imagedata scale="100" fileref="diagrams/3d-left-right&dia;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+This option can be used to generate a 3D DCP.  Other means of creating
+3D will be added in the future.
+</para>
+
+</section>
+
+
+<!-- ============================================================== -->
+<section>
+<title>Filtering</title>
+
+<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.  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>Other settings</title>
+
+<para>
+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 <guilabel>Scale to</guilabel> option governs the shape that
+DCP-o-matic will scale the content's image into.  Select the aspect
+ratio that your content should be presented in.
+</para>
+
+</section>
+<section>
+<title>Video description</title>
+
+<para>
+At the bottom of the video tab is a short description of what will
+happen to your video with the current settings.  In the example of
+<xref linkend="fig-video-tab"/>, DCP-o-matic is telling you that the
+video file is 1920x1080 pixels (which is a ratio of 1.78:1).  Since
+the controls specify &lsquo;Flat&rsquo; for the ratio, DCP-o-matic
+scales the content image to 1998x1080, which is the DCI flat
+resolution at 2K.
+</para>
+
+<para>
+This description also gives the frame rate of the content and what
+will happen to it when it is played at the DCP's frame rate.
+<!-- XXX: link to more detailed discussion of this -->
+</para>
+
+</section>
+
+</section>
+
+<section>
+<title>Audio</title>
+
+<para>
+The <guilabel>Audio</guilabel> tab controls properties of the image, 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>
+
+<section>
+<title>Show audio</title>
+
+<para>
+The <guilabel>Show Audio</guilabel> button will instruct DCP-o-matic
+to examine the audio in your content and plot a graph of its level
+over time.  This can be useful for getting a rough idea of how loud
+the sound will be in the cinema auditorium.  A typical plot is shown
+in <xref linkend="fig-audio-plot"/>
+</para>
+
+<figure id="fig-audio-plot"> 
+  <title>Audio plot</title>
+  <mediaobject>
+    <imageobject> 
+       <imagedata fileref="screenshots/audio-plot&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+The plot gives the audio level (vertical axis, in dB) with time
+(horizontal axis).  0dB represents full scale, so if there is anything
+near this you are in danger of clipping the projector's audio outputs.
+</para>
+
+<para>
+There are two plot types: the peak level and the RMS, which can be
+shown or hidden using the check-boxes on the right hand side of the
+window.
+</para>
+
+<para>
+The channel check-boxes will show or hide the plot(s) for
+the corresponding channels in the DCP.
+</para>
+
+<para>
+The smoothing slider applies a variable degree of temporal smoothing
+to the plots, which can make them easier to read in some cases.
+</para>
+
+<para>
+Obviously the audio plot is no substitute for listening in an
+auditorium, but it can be useful to get levels in the right rough area.
+</para>
+
+</section>
+
+<section>
+<title>The audio map</title>
+
+<para>
+The section at the bottom of the audio tab is the &lsquo;audio
+map&rsquo;.  This governs how sound from the content will be arranged
+in the DCP.
+</para>
+
+<para>
+Down the left-hand side of the map is the list of audio channels in
+the currently-selected piece of content.  Along the top is each
+channel in the DCP.  A checked box means that the corresponding
+content channel will be copied into the corresponding DCP channel.
+</para>
+
+<para>
+Consider, for example, the case in <xref linkend="fig-audio-map-eg1"/>.
+</para>
+
+<figure id="fig-audio-map-eg1">
+  <title>Audio map example 1</title>
+  <mediaobject>
+    <imageobject> 
+       <imagedata fileref="screenshots/audio-map-eg1&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+Here, we have two channels in the source which are mapped to left and
+right, respectively, in the DCP.  If we modify that as in <xref
+linkend="fig-audio-map-eg2"/>
+</para>
+
+<figure id="fig-audio-map-eg2">
+  <title>Audio map example 2</title>
+  <mediaobject>
+    <imageobject> 
+       <imagedata fileref="screenshots/audio-map-eg2&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+we now have the content's streams mapped to left and right and also
+mixed together and placed in the DCP's centre channel.
+</para>
+
+<figure id="fig-audio-map-eg3">
+  <title>Audio map example 3</title>
+  <mediaobject>
+    <imageobject> 
+       <imagedata fileref="screenshots/audio-map-eg3&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+As a final example, the map in <xref linkend="fig-audio-map-eg3"/>
+shows the mapping of a 5.1 source into a 5.1 DCP.
+</para>
+
+</section>
+
+<section>
+<title>Other controls</title>
+
+<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 of your content 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>
+<guilabel>Audio Delay</guilabel> 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>
+The <guilabel>Audio Stream</guilabel> option allows you to select the
+audio stream to use, if the content contains more than one.  There
+might be different soundtrack languages, for example.
+</para>
+
+</section>
+</section>
+
+
+<section>
+<title>Subtitles</title>
+
+<para>
+The subtitles tab contains settings related to subtitles in your
+content, 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>
+All being well, future versions of DCP-o-matic will include the option to
+use text subtitles (as is the norm with most professionally-mastered
+DCPs).
+</para>
+
+</section>
+
+<!-- XXX: timing tab -->
+
+</chapter>
+
+<chapter xml:id="ch-dcp" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
+<title>DCP settings</title>
+
+<para>
+This chapter describes the settings that apply to the whole DCP.  The
+controls for these settings are in the <guilabel>DCP</guilabel> tab of
+the main window, as shown in <xref linkend="fig-dcp-tab"/>.
+</para>
+
+<figure id="fig-dcp-tab"> 
+  <title>DCP settings tab</title>
+  <mediaobject>
+    <imageobject> 
+       <imagedata fileref="screenshots/dcp-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>Container</guilabel> option sets the ratio of the image
+in the DCP.  If this ratio is different to the ratio used for any
+content, DCP-o-matic will pad the content with black.  In simple cases
+this should be set to the same ratio as that for the the primary piece
+of video content.  Alternatively, you might want to pillarbox a small
+format into a Flat container: in this case, select the small format
+for the content's ratio and &lsquo;Flat&rsquo; for the DCP.
+</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>Frame Rate</guilabel> control sets the frame rate of
+your DCP.  This can be a little tricky to get right.  Ideally, you
+want it to be the same as the video content that you are using.  If it
+is not the same, DCP-o-matic must resort to some tricks to alter your
+content to fit the specified frame rate.  Frame rates are discussed in more detail later.
+<!--- XXX: link -->
+</para>
+
+<para>
+The <guilabel>Use best</guilabel> button sets the DCP video frame rate
+to what DCP-o-matic thinks is the best given the content that you have
+added.
+</para>
+
+<para>
+The <guilabel>Audio Channels</guilabel> control sets the number of
+audio channels that the DCP will have.  If the DCP has any channels
+for which there is no content audio they will be replaced by silence.
+</para>
+
+<para>
+The <guilabel>3D</guilabel> button will set your DCP to 3D mode if it
+is checked.  A 3D DCP will then be created, and any 2D content will be
+made 3D compatible by repeating the same frame for both left and right
+eyes.  A 3D DCP can be played back on many 3D systems (e.g.\ Dolby 3D,
+Real-D etc.) but not on a 2D system.
+</para>
+
+<para>
+The <guilabel>Resolution</guilabel> tab allows you to choose the
+resolution for your DCP.  Use 2K unless you have content that is of
+high enough resolution to be worth presenting in 4K.
+</para>
+
+<para>
+The <guilabel>JPEG2000 bandwidth</guilabel>; 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>
+
+<para>
+Finally, the <guilabel>scaler</guilabel> is the method that will be used to scale up
+your content to the required size for the DCP, if required.  Bicubic is a fine choice in
+most situations.
+</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 split into four tabs.
+</para>
+
+<section>
+<title>Miscellaneous</title>
+
+<para>
+The miscellaneous tab is shown in <xref linkend="fig-prefs-misc"/>.
+</para>
+
+<figure id="fig-prefs-misc"> 
+  <title>Miscellaneous preferences</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/prefs-misc&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<section>
+<title>Language</title>
+
+<para>
+If you tick the <guilabel>Set Language</guilabel> checkbox and choose
+a language from the list, that language will be used for DCP-o-matic.
+You will need to restart DCP-o-matic to see the new language.
+</para>
+
+<para>
+The translations for DCP-o-matic have been contributed by helpful
+users.  If your language is not on the last, head to <ulink
+url="http://dcpomatic.com/i18n.php">the DCP-o-matic website</ulink> to
+read about how to contribute a translation.
+</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>Defaults</title>
+
+<para>
+The next few options allow you to set up default values for several
+properties of new films that you create.
+</para>
+
+</section>
+</section>
+
+<section>
+<title>Encoding servers</title>
+
+<para>
+The encoding servers tab is shown in <xref linkend="fig-prefs-servers"/>.
+</para>
+
+<figure id="fig-prefs-servers"> 
+  <title>Encoding servers preferences</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/prefs-servers&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<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>
+<title>Metadata</title>
+
+<para>
+The metadata tab is shown in <xref linkend="fig-prefs-metadata"/>.
+</para>
+
+<figure id="fig-prefs-metadata"> 
+  <title>Metadata preferences</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/prefs-metadata&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+This allows you to set up a couple of identifiers that are written
+into the DCP.  The default values should cause no problems.
+</para>
+
+</section>
+
+<section xml:id="prefs-tms">
+<title>TMS</title>
+
+<para>
+The TMS tab (shown in <xref linkend="fig-prefs-tms"/>) gives some
+options for specifying details about your theatre management system
+(TMS).  If you do this, and your TMS accepts SSH connections, you can
+upload DCPs directly from DCP-o-matic to the TMS using the
+<guilabel>Send DCP to TMS</guilabel> option in the
+<guilabel>Jobs</guilabel> menu.
+</para>
+
+<figure id="fig-prefs-tms"> 
+  <title>TMS preferences</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="screenshots/prefs-tms&scs;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<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>
+</chapter>
+
+<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
+<title>Frame rates</title>
+
+<para>
+In an ideal world, a DCP would be created using content at the same
+video frame and audio sampling rates as the DCP.  This is not,
+however, always possible.
+</para>
+
+<section>
+<title>DCP rate limitations</title>
+
+<para>
+There are some limitations to video and audio rates in DCPs.  This is
+complicated by the fact that not all projectors will play DCPs at the
+same rates.  It is possible to create a DCP which one projector will
+play fine, but another (of a different type) will refuse to play, or
+even refuse to ingest.
+</para>
+
+<section>
+<title>Guaranteed rates</title>
+
+<para>
+The only rates that are (pretty much) guaranteed to work on all DCI
+projectors is 24 frames per second (fps) for video and 48kHz or 96kHz
+for audio.  If you are sending your DCPs to unknown places it wise to
+consider using these rates if at all possible.
+</para>
+
+</section>
+
+<section>
+<title>Other often-supported rates</title>
+<para>
+Many projectors now in the wild support additional video frame rates:
+25, 30 and 48 fps.
+</para>
+</section>
+
+<section>
+<title>Adapting content to fit the DCP rate</title>
+
+<para>
+DCP-o-matic has a few tricks to allow you to use content that is not
+in one of the &lsquo;approved&rsquo; rates.
+</para>
+
+<para>
+Audio is easy: DCP-o-matic can resample to 48kHz from any source rate
+with minimal loss in quality.
+</para>
+
+<para>
+Video rate conversion is harder.  DCP-o-matic's basic strategy to deal
+with a non-supported content rate is to run it at the wrong speed, and
+to adjust the audio to keep it in sync.
+</para>
+
+<para>Let us consider the example of a 25fps source for which you want
+to create a 24fps DCP.  DCP-o-matic will put the frames from the
+source directly into the DCP without modification, but will tell the
+projector to play them back at 24fps.  This means that the DCP's video
+will run slightly slower than the original.
+</para>
+
+<para>
+If DCP-o-matic did nothing else, the result of this would be that the
+audio would be running at the original speed with the video running
+slowly.  Hence the audio would drift slowly out of sync.  To avoid
+this, DCP-o-matic also resamples the audio such that the projector
+will play it too fast by the same amount.  Hence it will sound
+slightly different but will remain in sync with the video.
+</para>
+
+<para>
+For very low or high frame rates, DCP-o-matic can also skip or duplicate frames.
+</para>
+
+</section>
+</section>
+
+<section>
+<title>Setting up</title>
+
+<para>
+The <guilabel>Frame Rate</guilabel> control in the
+<guilabel>DCP</guilabel> tab sets the video frame rate that the DCP
+will use.  Clicking <guilabel>Use best</guilabel> sets the rate to
+what DVD-o-matic thinks is the best for your content.  With this
+button, DCP-o-matic assumes that the whole range of frame rates (24,
+25, 30 and 48fps) are allowable.
+</para>
+
+<para>
+After this, the <guilabel>Video</guilabel> tab for each piece of
+content will give a summary of what DCP-o-matic is doing with that
+content.
+</para>
+
+</section>
+
+</chapter>
+
+
+<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
+<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;dcpomatic_server&rsquo;.
+</para>
+
+<section>
+<title>Running the servers</title>
+
+<para>
+There are two options for the encoding server;
+<code>dcpomatic_server_cli</code>, which runs on the command line, and
+<code>dcpomatic_server</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>
+dcpomatic_server_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>
+dcpomatic_server_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>
+
+</chapter>
+
+<chapter xml:id="ch-files" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
+<title>Generated files</title>
+
+<para>
+DCP-o-matic generates a number of files as it makes a DCP.  <xref
+linkend="fig-file-structure"/> shows the files that might be generated
+after you have created a DCP for a film called &lsquo;DCP Test&rsquo;.
+</para>
+
+<figure id="fig-file-structure"> 
+  <title>Creating a new film</title> 
+  <mediaobject>
+    <imageobject> 
+      <imagedata fileref="diagrams/file-structure&dia;"/>
+    </imageobject> 
+  </mediaobject>
+</figure>
+
+<para>
+The <code>DCP Test</code> folder is the one that you specify when you
+select the <guilabel>New Film</guilabel> option from DCP-o-matic's
+menu.  Everything is stored inside this folder.
+</para>
+
+<para>
+DCP-o-matic generates some working files as it goes along.  These are as follows:
+<itemizedlist>
+
+<listitem><code>log</code> is a list of notes that DCP-o-matic makes as it goes
+along.  This can be useful for debugging purposes if something goes
+wrong.</listitem>
+
+<listitem><code>metadata</code> stores the settings that you have made
+for this film: things like cropping, output format and so on.</listitem>
+
+<listitem><code>video</code> is where DCP-o-matic writes the DCP's
+video data as it encodes it.</listitem>
+
+<listitem><code>analysis</code> is used to keep the results of audio analysis runs.</listitem>
+
+<listitem><code>info</code> contains details of each video frame that
+DCP-o-matic has written so far.  This is used when an encoding
+operation is interrupted and DCP-o-matic must resume it.</listitem>
+</itemizedlist>
+</para>
+
+<para>
+Following this is the DCP itself:
+<code>DCP-TEST_EN-XX_UK-U_51_2K_CSY_20130218_CSY_OV</code>.  This
+contains some small XML files, which describe the DCP, and two large
+MXF files, which contain the DCP's audio and video data.  This folder
+(<code>DCP-TEST_EN-XX_...</code>) is what you should ingest, or pass
+to the cinema which is showing your DCP.
+</para>
+
+</chapter>
+
+
+</book>
diff --git a/doc/manual/diagrams/3d-left-right.svg b/doc/manual/diagrams/3d-left-right.svg
new file mode 100644 (file)
index 0000000..02df709
--- /dev/null
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="744.09448819"
+   height="1052.3622047"
+   id="svg4899"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="New document 5">
+  <defs
+     id="defs4901">
+    <marker
+       inkscape:stockid="Arrow2Mstart"
+       orient="auto"
+       refY="0.0"
+       refX="0.0"
+       id="Arrow2Mstart"
+       style="overflow:visible">
+      <path
+         id="path5751"
+         style="fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round"
+         d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+         transform="scale(0.6) translate(0,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow2Mend"
+       orient="auto"
+       refY="0.0"
+       refX="0.0"
+       id="Arrow2Mend"
+       style="overflow:visible;">
+      <path
+         id="path5754"
+         style="fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+         d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+         transform="scale(0.6) rotate(180) translate(0,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0.0"
+       refX="0.0"
+       id="Arrow1Mend"
+       style="overflow:visible;">
+      <path
+         id="path5736"
+         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
+         transform="scale(0.4) rotate(180) translate(10,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow2Mstart"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow2Mstart-2"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path5751-0"
+         style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+         transform="scale(0.6,0.6)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow2Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow2Mend-0"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path5754-9"
+         style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+         transform="scale(-0.6,-0.6)" />
+    </marker>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.9220686"
+     inkscape:cx="257.32427"
+     inkscape:cy="523.31639"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:window-width="1366"
+     inkscape:window-height="714"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <sodipodi:guide
+       orientation="1,0"
+       position="132.28767,577.04795"
+       id="guide5717" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="389.64041,568.68493"
+       id="guide5719" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata4904">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <text
+       xml:space="preserve"
+       style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:inconsolata;-inkscape-font-specification:inconsolata"
+       x="182.2476"
+       y="462.34418"
+       id="text4907"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4909"
+         x="182.2476"
+         y="462.34418">Left</tspan><tspan
+         sodipodi:role="line"
+         x="182.2476"
+         y="477.34418"
+         id="tspan4911">eye</tspan><tspan
+         sodipodi:role="line"
+         x="182.2476"
+         y="492.34418"
+         id="tspan4913">image</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:inconsolata;-inkscape-font-specification:inconsolata"
+       x="310.8606"
+       y="462.3082"
+       id="text4915"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4917"
+         x="310.8606"
+         y="462.3082">Right</tspan><tspan
+         sodipodi:role="line"
+         x="310.8606"
+         y="477.3082"
+         id="tspan4919">eye</tspan><tspan
+         sodipodi:role="line"
+         x="310.8606"
+         y="492.3082"
+         id="tspan4921">image</tspan></text>
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25000000000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect5713"
+       width="256.84818"
+       height="127.95709"
+       x="132.9384"
+       y="410.08606" />
+    <path
+       style="fill:none;stroke:#4d4d4d;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:1,3;stroke-dashoffset:0"
+       d="m 261.20682,410.2865 0,127.33444"
+       id="path5715"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart);marker-end:url(#Arrow2Mend)"
+       d="m 133.81932,400.42725 254.35326,0"
+       id="path5721"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <text
+       xml:space="preserve"
+       style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:inconsolata;-inkscape-font-specification:inconsolata"
+       x="246.67448"
+       y="396.21439"
+       id="text6541"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan6543"
+         x="246.67448"
+         y="396.21439">width</tspan></text>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart);marker-end:url(#Arrow2Mend)"
+       d="m 133.71272,547.50392 127.24194,0"
+       id="path5721-5"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <text
+       xml:space="preserve"
+       style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:inconsolata;-inkscape-font-specification:inconsolata"
+       x="170.6097"
+       y="560.70441"
+       id="text6575"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan6577"
+         x="170.6097"
+         y="560.70441">width / 2</tspan></text>
+  </g>
+</svg>
diff --git a/doc/manual/diagrams/file-structure.svg b/doc/manual/diagrams/file-structure.svg
new file mode 100644 (file)
index 0000000..e726369
--- /dev/null
@@ -0,0 +1,5778 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="744.09448819"
+   height="1052.3622047"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="New document 1">
+  <defs
+     id="defs4">
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0.0"
+       refX="0.0"
+       id="Arrow1Mend"
+       style="overflow:visible;">
+      <path
+         id="path8720"
+         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
+         transform="scale(0.4) rotate(180) translate(10,0)" />
+    </marker>
+    <linearGradient
+       id="linearGradient12512">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop12513" />
+      <stop
+         style="stop-color:#fff520;stop-opacity:0.89108908;"
+         offset="0.50000000"
+         id="stop12517" />
+      <stop
+         style="stop-color:#fff300;stop-opacity:0.0000000;"
+         offset="1.0000000"
+         id="stop12514" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3983">
+      <stop
+         id="stop3984"
+         offset="0.0000000"
+         style="stop-color:#ffffff;stop-opacity:0.87628865;" />
+      <stop
+         id="stop3985"
+         offset="1.0000000"
+         style="stop-color:#fffffe;stop-opacity:0.0000000;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient1789">
+      <stop
+         id="stop1790"
+         offset="0.0000000"
+         style="stop-color:#202020;stop-opacity:1.0000000;" />
+      <stop
+         id="stop1791"
+         offset="1.0000000"
+         style="stop-color:#b9b9b9;stop-opacity:1.0000000;" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient319">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop320" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop321" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3096">
+      <stop
+         style="stop-color:#424242;stop-opacity:1;"
+         offset="0"
+         id="stop3098" />
+      <stop
+         style="stop-color:#777777;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop3100" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient9766">
+      <stop
+         id="stop9768"
+         offset="0"
+         style="stop-color:#6194cb;stop-opacity:1;" />
+      <stop
+         id="stop9770"
+         offset="1"
+         style="stop-color:#729fcf;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5048">
+      <stop
+         id="stop5050"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056" />
+      <stop
+         id="stop5052"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060"
+       inkscape:collect="always">
+      <stop
+         id="stop5062"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <inkscape:perspective
+       id="perspective71"
+       inkscape:persp3d-origin="24 : 16 : 1"
+       inkscape:vp_z="48 : 24 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 24 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.000000,0.000000,0.000000,0.284916,0.000000,30.08928)"
+       r="15.821514"
+       fy="42.07798"
+       fx="24.306795"
+       cy="42.07798"
+       cx="24.306795"
+       id="radialGradient4548"
+       xlink:href="#linearGradient4542"
+       inkscape:collect="always" />
+    <linearGradient
+       id="linearGradient259">
+      <stop
+         style="stop-color:#fafafa;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop260" />
+      <stop
+         style="stop-color:#bbbbbb;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop261" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient269">
+      <stop
+         style="stop-color:#a3a3a3;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop270" />
+      <stop
+         style="stop-color:#4c4c4c;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop271" />
+    </linearGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="114.5684"
+       fx="20.8921"
+       r="5.256"
+       cy="114.5684"
+       cx="20.8921"
+       id="aigrd2">
+      <stop
+         id="stop15566"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15568"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="64.5679"
+       fx="20.8921"
+       r="5.257"
+       cy="64.5679"
+       cx="20.8921"
+       id="aigrd3">
+      <stop
+         id="stop15573"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15575"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <linearGradient
+       id="linearGradient15662">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop15664" />
+      <stop
+         style="stop-color:#f8f8f8;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop15666" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4542"
+       inkscape:collect="always">
+      <stop
+         id="stop4544"
+         offset="0"
+         style="stop-color:#000000;stop-opacity:1;" />
+      <stop
+         id="stop4546"
+         offset="1"
+         style="stop-color:#000000;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5048-4">
+      <stop
+         id="stop5050-8"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056-8" />
+      <stop
+         id="stop5052-2"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-4"
+       inkscape:collect="always">
+      <stop
+         id="stop5062-0"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064-9"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <inkscape:perspective
+       id="perspective78"
+       inkscape:persp3d-origin="24 : 16 : 1"
+       inkscape:vp_z="48 : 24 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 24 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <linearGradient
+       id="linearGradient5048-4-1">
+      <stop
+         id="stop5050-8-7"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056-8-1" />
+      <stop
+         id="stop5052-2-1"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-4-2"
+       inkscape:collect="always">
+      <stop
+         id="stop5062-0-7"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064-9-6"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient259-2">
+      <stop
+         style="stop-color:#fafafa;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop260-3" />
+      <stop
+         style="stop-color:#bbbbbb;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop261-2" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient269-1">
+      <stop
+         style="stop-color:#a3a3a3;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop270-6" />
+      <stop
+         style="stop-color:#4c4c4c;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop271-8" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient15662-7">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop15664-6" />
+      <stop
+         style="stop-color:#f8f8f8;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop15666-1" />
+    </linearGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="114.5684"
+       fx="20.892099"
+       r="5.256"
+       cy="114.5684"
+       cx="20.892099"
+       id="aigrd2-9">
+      <stop
+         id="stop15566-2"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15568-7"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="64.567902"
+       fx="20.892099"
+       r="5.257"
+       cy="64.567902"
+       cx="20.892099"
+       id="aigrd3-5">
+      <stop
+         id="stop15573-4"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15575-3"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <linearGradient
+       id="linearGradient5048-3">
+      <stop
+         id="stop5050-4"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056-4" />
+      <stop
+         id="stop5052-6"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-6"
+       inkscape:collect="always">
+      <stop
+         id="stop5062-6"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064-1"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient1789-9">
+      <stop
+         id="stop1790-6"
+         offset="0.0000000"
+         style="stop-color:#202020;stop-opacity:1.0000000;" />
+      <stop
+         id="stop1791-3"
+         offset="1.0000000"
+         style="stop-color:#b9b9b9;stop-opacity:1.0000000;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3096-8">
+      <stop
+         style="stop-color:#424242;stop-opacity:1;"
+         offset="0"
+         id="stop3098-8" />
+      <stop
+         style="stop-color:#777777;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop3100-2" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3983-1">
+      <stop
+         id="stop3984-3"
+         offset="0.0000000"
+         style="stop-color:#ffffff;stop-opacity:0.87628865;" />
+      <stop
+         id="stop3985-5"
+         offset="1.0000000"
+         style="stop-color:#fffffe;stop-opacity:0.0000000;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient9766-8">
+      <stop
+         id="stop9768-4"
+         offset="0"
+         style="stop-color:#6194cb;stop-opacity:1;" />
+      <stop
+         id="stop9770-0"
+         offset="1"
+         style="stop-color:#729fcf;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient319-6">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop320-3" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop321-6" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5048-3-7">
+      <stop
+         id="stop5050-4-7"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056-4-3" />
+      <stop
+         id="stop5052-6-3"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-6-9"
+       inkscape:collect="always">
+      <stop
+         id="stop5062-6-9"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064-1-8"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient1789-9-2">
+      <stop
+         id="stop1790-6-6"
+         offset="0.0000000"
+         style="stop-color:#202020;stop-opacity:1.0000000;" />
+      <stop
+         id="stop1791-3-6"
+         offset="1.0000000"
+         style="stop-color:#b9b9b9;stop-opacity:1.0000000;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3096-8-3">
+      <stop
+         style="stop-color:#424242;stop-opacity:1;"
+         offset="0"
+         id="stop3098-8-8" />
+      <stop
+         style="stop-color:#777777;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop3100-2-0" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3983-1-2">
+      <stop
+         id="stop3984-3-5"
+         offset="0.0000000"
+         style="stop-color:#ffffff;stop-opacity:0.87628865;" />
+      <stop
+         id="stop3985-5-0"
+         offset="1.0000000"
+         style="stop-color:#fffffe;stop-opacity:0.0000000;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient9766-8-4">
+      <stop
+         id="stop9768-4-7"
+         offset="0"
+         style="stop-color:#6194cb;stop-opacity:1;" />
+      <stop
+         id="stop9770-0-8"
+         offset="1"
+         style="stop-color:#729fcf;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient319-6-5">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop320-3-1" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop321-6-2" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5048-3-7-4">
+      <stop
+         id="stop5050-4-7-9"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056-4-3-6" />
+      <stop
+         id="stop5052-6-3-5"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-6-9-0"
+       inkscape:collect="always">
+      <stop
+         id="stop5062-6-9-9"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064-1-8-9"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient1789-9-2-3">
+      <stop
+         id="stop1790-6-6-4"
+         offset="0.0000000"
+         style="stop-color:#202020;stop-opacity:1.0000000;" />
+      <stop
+         id="stop1791-3-6-8"
+         offset="1.0000000"
+         style="stop-color:#b9b9b9;stop-opacity:1.0000000;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3096-8-3-9">
+      <stop
+         style="stop-color:#424242;stop-opacity:1;"
+         offset="0"
+         id="stop3098-8-8-9" />
+      <stop
+         style="stop-color:#777777;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop3100-2-0-2" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3983-1-2-5">
+      <stop
+         id="stop3984-3-5-3"
+         offset="0.0000000"
+         style="stop-color:#ffffff;stop-opacity:0.87628865;" />
+      <stop
+         id="stop3985-5-0-3"
+         offset="1.0000000"
+         style="stop-color:#fffffe;stop-opacity:0.0000000;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient9766-8-4-7">
+      <stop
+         id="stop9768-4-7-4"
+         offset="0"
+         style="stop-color:#6194cb;stop-opacity:1;" />
+      <stop
+         id="stop9770-0-8-3"
+         offset="1"
+         style="stop-color:#729fcf;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient319-6-5-0">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop320-3-1-8" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop321-6-2-8" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5048-4-2">
+      <stop
+         id="stop5050-8-6"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056-8-5" />
+      <stop
+         id="stop5052-2-3"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-4-24"
+       inkscape:collect="always">
+      <stop
+         id="stop5062-0-6"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064-9-1"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient259-1">
+      <stop
+         style="stop-color:#fafafa;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop260-1" />
+      <stop
+         style="stop-color:#bbbbbb;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop261-9" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient269-6">
+      <stop
+         style="stop-color:#a3a3a3;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop270-2" />
+      <stop
+         style="stop-color:#4c4c4c;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop271-9" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient15662-0">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop15664-0" />
+      <stop
+         style="stop-color:#f8f8f8;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop15666-3" />
+    </linearGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="114.5684"
+       fx="20.892099"
+       r="5.256"
+       cy="114.5684"
+       cx="20.892099"
+       id="aigrd2-1">
+      <stop
+         id="stop15566-8"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15568-1"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="64.567902"
+       fx="20.892099"
+       r="5.257"
+       cy="64.567902"
+       cx="20.892099"
+       id="aigrd3-3">
+      <stop
+         id="stop15573-2"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15575-5"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <linearGradient
+       id="linearGradient5048-4-2-5">
+      <stop
+         id="stop5050-8-6-8"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056-8-5-2" />
+      <stop
+         id="stop5052-2-3-5"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-4-24-4"
+       inkscape:collect="always">
+      <stop
+         id="stop5062-0-6-1"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064-9-1-6"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient259-1-3">
+      <stop
+         style="stop-color:#fafafa;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop260-1-3" />
+      <stop
+         style="stop-color:#bbbbbb;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop261-9-5" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient269-6-3">
+      <stop
+         style="stop-color:#a3a3a3;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop270-2-2" />
+      <stop
+         style="stop-color:#4c4c4c;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop271-9-8" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient15662-0-5">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop15664-0-3" />
+      <stop
+         style="stop-color:#f8f8f8;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop15666-3-6" />
+    </linearGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="114.5684"
+       fx="20.892099"
+       r="5.256"
+       cy="114.5684"
+       cx="20.892099"
+       id="aigrd2-1-8">
+      <stop
+         id="stop15566-8-6"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15568-1-2"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="64.567902"
+       fx="20.892099"
+       r="5.257"
+       cy="64.567902"
+       cx="20.892099"
+       id="aigrd3-3-4">
+      <stop
+         id="stop15573-2-6"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15575-5-2"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <linearGradient
+       id="linearGradient5048-3-7-4-3">
+      <stop
+         id="stop5050-4-7-9-0"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056-4-3-6-3" />
+      <stop
+         id="stop5052-6-3-5-8"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-6-9-0-5"
+       inkscape:collect="always">
+      <stop
+         id="stop5062-6-9-9-2"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064-1-8-9-0"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient1789-9-2-3-2">
+      <stop
+         id="stop1790-6-6-4-9"
+         offset="0.0000000"
+         style="stop-color:#202020;stop-opacity:1.0000000;" />
+      <stop
+         id="stop1791-3-6-8-7"
+         offset="1.0000000"
+         style="stop-color:#b9b9b9;stop-opacity:1.0000000;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3096-8-3-9-3">
+      <stop
+         style="stop-color:#424242;stop-opacity:1;"
+         offset="0"
+         id="stop3098-8-8-9-4" />
+      <stop
+         style="stop-color:#777777;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop3100-2-0-2-3" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3983-1-2-5-1">
+      <stop
+         id="stop3984-3-5-3-1"
+         offset="0.0000000"
+         style="stop-color:#ffffff;stop-opacity:0.87628865;" />
+      <stop
+         id="stop3985-5-0-3-3"
+         offset="1.0000000"
+         style="stop-color:#fffffe;stop-opacity:0.0000000;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient9766-8-4-7-5">
+      <stop
+         id="stop9768-4-7-4-3"
+         offset="0"
+         style="stop-color:#6194cb;stop-opacity:1;" />
+      <stop
+         id="stop9770-0-8-3-5"
+         offset="1"
+         style="stop-color:#729fcf;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient319-6-5-0-2">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop320-3-1-8-2" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop321-6-2-8-2" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5048-4-2-5-6">
+      <stop
+         id="stop5050-8-6-8-8"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056-8-5-2-3" />
+      <stop
+         id="stop5052-2-3-5-1"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-4-24-4-8"
+       inkscape:collect="always">
+      <stop
+         id="stop5062-0-6-1-4"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064-9-1-6-9"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient259-1-3-7">
+      <stop
+         style="stop-color:#fafafa;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop260-1-3-0" />
+      <stop
+         style="stop-color:#bbbbbb;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop261-9-5-6" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient269-6-3-7">
+      <stop
+         style="stop-color:#a3a3a3;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop270-2-2-0" />
+      <stop
+         style="stop-color:#4c4c4c;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop271-9-8-5" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient15662-0-5-7">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop15664-0-3-3" />
+      <stop
+         style="stop-color:#f8f8f8;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop15666-3-6-8" />
+    </linearGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="114.5684"
+       fx="20.892099"
+       r="5.256"
+       cy="114.5684"
+       cx="20.892099"
+       id="aigrd2-1-8-9">
+      <stop
+         id="stop15566-8-6-2"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15568-1-2-6"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="64.567902"
+       fx="20.892099"
+       r="5.257"
+       cy="64.567902"
+       cx="20.892099"
+       id="aigrd3-3-4-7">
+      <stop
+         id="stop15573-2-6-7"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15575-5-2-4"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <linearGradient
+       id="linearGradient5048-4-2-5-6-7">
+      <stop
+         id="stop5050-8-6-8-8-0"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056-8-5-2-3-8" />
+      <stop
+         id="stop5052-2-3-5-1-9"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-4-24-4-8-0"
+       inkscape:collect="always">
+      <stop
+         id="stop5062-0-6-1-4-7"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064-9-1-6-9-4"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient259-1-3-7-3">
+      <stop
+         style="stop-color:#fafafa;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop260-1-3-0-7" />
+      <stop
+         style="stop-color:#bbbbbb;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop261-9-5-6-1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient269-6-3-7-0">
+      <stop
+         style="stop-color:#a3a3a3;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop270-2-2-0-8" />
+      <stop
+         style="stop-color:#4c4c4c;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop271-9-8-5-3" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient15662-0-5-7-5">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop15664-0-3-3-0" />
+      <stop
+         style="stop-color:#f8f8f8;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop15666-3-6-8-1" />
+    </linearGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="114.5684"
+       fx="20.892099"
+       r="5.256"
+       cy="114.5684"
+       cx="20.892099"
+       id="aigrd2-1-8-9-5">
+      <stop
+         id="stop15566-8-6-2-6"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15568-1-2-6-2"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="64.567902"
+       fx="20.892099"
+       r="5.257"
+       cy="64.567902"
+       cx="20.892099"
+       id="aigrd3-3-4-7-3">
+      <stop
+         id="stop15573-2-6-7-5"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15575-5-2-4-6"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <linearGradient
+       id="linearGradient5048-4-2-5-6-7-8">
+      <stop
+         id="stop5050-8-6-8-8-0-5"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056-8-5-2-3-8-8" />
+      <stop
+         id="stop5052-2-3-5-1-9-3"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-4-24-4-8-0-4"
+       inkscape:collect="always">
+      <stop
+         id="stop5062-0-6-1-4-7-1"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064-9-1-6-9-4-1"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient259-1-3-7-3-5">
+      <stop
+         style="stop-color:#fafafa;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop260-1-3-0-7-1" />
+      <stop
+         style="stop-color:#bbbbbb;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop261-9-5-6-1-0" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient269-6-3-7-0-4">
+      <stop
+         style="stop-color:#a3a3a3;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop270-2-2-0-8-5" />
+      <stop
+         style="stop-color:#4c4c4c;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop271-9-8-5-3-3" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient15662-0-5-7-5-8">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop15664-0-3-3-0-9" />
+      <stop
+         style="stop-color:#f8f8f8;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop15666-3-6-8-1-1" />
+    </linearGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="114.5684"
+       fx="20.892099"
+       r="5.256"
+       cy="114.5684"
+       cx="20.892099"
+       id="aigrd2-1-8-9-5-4">
+      <stop
+         id="stop15566-8-6-2-6-7"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15568-1-2-6-2-4"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="64.567902"
+       fx="20.892099"
+       r="5.257"
+       cy="64.567902"
+       cx="20.892099"
+       id="aigrd3-3-4-7-3-3">
+      <stop
+         id="stop15573-2-6-7-5-6"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15575-5-2-4-6-5"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <linearGradient
+       id="linearGradient5048-4-2-5-6-7-8-1">
+      <stop
+         id="stop5050-8-6-8-8-0-5-0"
+         offset="0"
+         style="stop-color:black;stop-opacity:0;" />
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0.5"
+         id="stop5056-8-5-2-3-8-8-4" />
+      <stop
+         id="stop5052-2-3-5-1-9-3-8"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-4-24-4-8-0-4-9"
+       inkscape:collect="always">
+      <stop
+         id="stop5062-0-6-1-4-7-1-2"
+         offset="0"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         id="stop5064-9-1-6-9-4-1-5"
+         offset="1"
+         style="stop-color:black;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient259-1-3-7-3-5-3">
+      <stop
+         style="stop-color:#fafafa;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop260-1-3-0-7-1-9" />
+      <stop
+         style="stop-color:#bbbbbb;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop261-9-5-6-1-0-5" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient269-6-3-7-0-4-2">
+      <stop
+         style="stop-color:#a3a3a3;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop270-2-2-0-8-5-7" />
+      <stop
+         style="stop-color:#4c4c4c;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop271-9-8-5-3-3-9" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient15662-0-5-7-5-8-1">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1.0000000;"
+         offset="0.0000000"
+         id="stop15664-0-3-3-0-9-7" />
+      <stop
+         style="stop-color:#f8f8f8;stop-opacity:1.0000000;"
+         offset="1.0000000"
+         id="stop15666-3-6-8-1-1-7" />
+    </linearGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="114.5684"
+       fx="20.892099"
+       r="5.256"
+       cy="114.5684"
+       cx="20.892099"
+       id="aigrd2-1-8-9-5-4-9">
+      <stop
+         id="stop15566-8-6-2-6-7-0"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15568-1-2-6-2-4-3"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       fy="64.567902"
+       fx="20.892099"
+       r="5.257"
+       cy="64.567902"
+       cx="20.892099"
+       id="aigrd3-3-4-7-3-3-1">
+      <stop
+         id="stop15573-2-6-7-5-6-7"
+         style="stop-color:#F0F0F0"
+         offset="0" />
+      <stop
+         id="stop15575-5-2-4-6-5-5"
+         style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
+         offset="1.0000000" />
+    </radialGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048-4"
+       id="linearGradient8257"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4"
+       id="radialGradient8259"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4"
+       id="radialGradient8261"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient259"
+       id="radialGradient8263"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="scale(0.960493,1.041132)"
+       cx="33.966679"
+       cy="35.736916"
+       fx="33.966679"
+       fy="35.736916"
+       r="86.708450" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient269"
+       id="radialGradient8265"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.8244190"
+       cy="3.7561285"
+       fx="8.8244190"
+       fy="3.7561285"
+       r="37.751713" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient15662"
+       id="radialGradient8267"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.1435566"
+       cy="7.2678967"
+       fx="8.1435566"
+       fy="7.2678967"
+       r="38.158695" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd2"
+       id="radialGradient8269"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.8921"
+       cy="114.5684"
+       fx="20.8921"
+       fy="114.5684"
+       r="5.256" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd3"
+       id="radialGradient8271"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.8921"
+       cy="64.5679"
+       fx="20.8921"
+       fy="64.5679"
+       r="5.257" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048-4-1"
+       id="linearGradient8273"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-2"
+       id="radialGradient8275"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-2"
+       id="radialGradient8277"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient259-2"
+       id="radialGradient8279"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="scale(0.960493,1.041132)"
+       cx="33.966679"
+       cy="35.736916"
+       fx="33.966679"
+       fy="35.736916"
+       r="86.70845" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient269-1"
+       id="radialGradient8281"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.824419"
+       cy="3.7561285"
+       fx="8.824419"
+       fy="3.7561285"
+       r="37.751713" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient15662-7"
+       id="radialGradient8283"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.1435566"
+       cy="7.2678967"
+       fx="8.1435566"
+       fy="7.2678967"
+       r="38.158695" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd2-9"
+       id="radialGradient8285"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="114.5684"
+       fx="20.892099"
+       fy="114.5684"
+       r="5.256" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd3-5"
+       id="radialGradient8287"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="64.567902"
+       fx="20.892099"
+       fy="64.567902"
+       r="5.257" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048-3"
+       id="linearGradient8289"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-6"
+       id="radialGradient8291"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-6"
+       id="radialGradient8293"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient1789-9"
+       id="radialGradient8295"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.055022,-0.02734504,0.177703,1.190929,92.718071,84.312593)"
+       cx="20.706017"
+       cy="37.517986"
+       fx="20.706017"
+       fy="37.517986"
+       r="30.905205" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3096-8"
+       id="linearGradient8297"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(96.290248,91.437894)"
+       x1="18.112709"
+       y1="31.36775"
+       x2="15.514889"
+       y2="6.1802502" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3983-1"
+       id="linearGradient8299"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.516844,0,0,0.708978,95.410675,90.119728)"
+       x1="6.2297964"
+       y1="13.773066"
+       x2="9.8980894"
+       y2="66.834053" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient9766-8"
+       id="linearGradient8301"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(96.290248,91.437894)"
+       x1="22.175976"
+       y1="36.987999"
+       x2="22.065331"
+       y2="32.050499" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient319-6"
+       id="linearGradient8303"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.317489,0,0,0.816256,95.410675,90.119728)"
+       x1="13.035696"
+       y1="32.567184"
+       x2="12.853771"
+       y2="46.689312" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048-3-7"
+       id="linearGradient8305"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-6-9"
+       id="radialGradient8307"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-6-9"
+       id="radialGradient8309"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient1789-9-2"
+       id="radialGradient8311"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.6999334,-0.01814152,0.11789353,0.79009821,218.92441,106.38252)"
+       cx="20.706017"
+       cy="37.517986"
+       fx="20.706017"
+       fy="37.517986"
+       r="30.905205" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3096-8-3"
+       id="linearGradient8313"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.66343015,0,0,0.66343015,221.2943,111.10966)"
+       x1="18.112709"
+       y1="31.36775"
+       x2="15.514889"
+       y2="6.1802502" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3983-1-2"
+       id="linearGradient8315"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.00632,0,0,0.47035738,220.71077,110.23515)"
+       x1="6.2297964"
+       y1="13.773066"
+       x2="9.8980894"
+       y2="66.834053" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient9766-8-4"
+       id="linearGradient8317"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.66343015,0,0,0.66343015,221.2943,111.10966)"
+       x1="22.175976"
+       y1="36.987999"
+       x2="22.065331"
+       y2="32.050499" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient319-6-5"
+       id="linearGradient8319"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.87406192,0,0,0.54152884,220.71077,110.23515)"
+       x1="13.035696"
+       y1="32.567184"
+       x2="12.853771"
+       y2="46.689312" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048-3-7-4"
+       id="linearGradient8321"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-6-9-0"
+       id="radialGradient8323"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-6-9-0"
+       id="radialGradient8325"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient1789-9-2-3"
+       id="radialGradient8327"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.6999334,-0.01814152,0.11789353,0.79009821,218.92441,186.11384)"
+       cx="20.706017"
+       cy="37.517986"
+       fx="20.706017"
+       fy="37.517986"
+       r="30.905205" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3096-8-3-9"
+       id="linearGradient8329"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.66343015,0,0,0.66343015,221.2943,190.84098)"
+       x1="18.112709"
+       y1="31.36775"
+       x2="15.514889"
+       y2="6.1802502" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3983-1-2-5"
+       id="linearGradient8331"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.00632,0,0,0.47035738,220.71077,189.96646)"
+       x1="6.2297964"
+       y1="13.773066"
+       x2="9.8980894"
+       y2="66.834053" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient9766-8-4-7"
+       id="linearGradient8333"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.66343015,0,0,0.66343015,221.2943,190.84098)"
+       x1="22.175976"
+       y1="36.987999"
+       x2="22.065331"
+       y2="32.050499" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient319-6-5-0"
+       id="linearGradient8335"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.87406192,0,0,0.54152884,220.71077,189.96646)"
+       x1="13.035696"
+       y1="32.567184"
+       x2="12.853771"
+       y2="46.689312" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048-3-7-4-3"
+       id="linearGradient8337"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-6-9-0-5"
+       id="radialGradient8339"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-6-9-0-5"
+       id="radialGradient8341"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient1789-9-2-3-2"
+       id="radialGradient8343"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.6999334,-0.01814152,0.11789353,0.79009821,218.92441,66.516859)"
+       cx="20.706017"
+       cy="37.517986"
+       fx="20.706017"
+       fy="37.517986"
+       r="30.905205" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3096-8-3-9-3"
+       id="linearGradient8345"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.66343015,0,0,0.66343015,221.2943,71.243999)"
+       x1="18.112709"
+       y1="31.36775"
+       x2="15.514889"
+       y2="6.1802502" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3983-1-2-5-1"
+       id="linearGradient8347"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.00632,0,0,0.47035738,220.71077,70.369488)"
+       x1="6.2297964"
+       y1="13.773066"
+       x2="9.8980894"
+       y2="66.834053" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient9766-8-4-7-5"
+       id="linearGradient8349"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.66343015,0,0,0.66343015,221.2943,71.243999)"
+       x1="22.175976"
+       y1="36.987999"
+       x2="22.065331"
+       y2="32.050499" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient319-6-5-0-2"
+       id="linearGradient8351"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.87406192,0,0,0.54152884,220.71077,70.369488)"
+       x1="13.035696"
+       y1="32.567184"
+       x2="12.853771"
+       y2="46.689312" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048-4-2"
+       id="linearGradient8609"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-24"
+       id="radialGradient8611"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-24"
+       id="radialGradient8613"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient259-1"
+       id="radialGradient8615"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="scale(0.960493,1.041132)"
+       cx="33.966679"
+       cy="35.736916"
+       fx="33.966679"
+       fy="35.736916"
+       r="86.70845" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient269-6"
+       id="radialGradient8617"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.824419"
+       cy="3.7561285"
+       fx="8.824419"
+       fy="3.7561285"
+       r="37.751713" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient15662-0"
+       id="radialGradient8619"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.1435566"
+       cy="7.2678967"
+       fx="8.1435566"
+       fy="7.2678967"
+       r="38.158695" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd2-1"
+       id="radialGradient8621"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="114.5684"
+       fx="20.892099"
+       fy="114.5684"
+       r="5.256" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd3-3"
+       id="radialGradient8623"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="64.567902"
+       fx="20.892099"
+       fy="64.567902"
+       r="5.257" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048-4-2-5"
+       id="linearGradient8625"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-24-4"
+       id="radialGradient8627"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-24-4"
+       id="radialGradient8629"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient259-1-3"
+       id="radialGradient8631"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="scale(0.960493,1.041132)"
+       cx="33.966679"
+       cy="35.736916"
+       fx="33.966679"
+       fy="35.736916"
+       r="86.70845" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient269-6-3"
+       id="radialGradient8633"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.824419"
+       cy="3.7561285"
+       fx="8.824419"
+       fy="3.7561285"
+       r="37.751713" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient15662-0-5"
+       id="radialGradient8635"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.1435566"
+       cy="7.2678967"
+       fx="8.1435566"
+       fy="7.2678967"
+       r="38.158695" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd2-1-8"
+       id="radialGradient8637"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="114.5684"
+       fx="20.892099"
+       fy="114.5684"
+       r="5.256" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd3-3-4"
+       id="radialGradient8639"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="64.567902"
+       fx="20.892099"
+       fy="64.567902"
+       r="5.257" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048-4-2-5-6"
+       id="linearGradient8641"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-24-4-8"
+       id="radialGradient8643"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-24-4-8"
+       id="radialGradient8645"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient259-1-3-7"
+       id="radialGradient8647"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="scale(0.960493,1.041132)"
+       cx="33.966679"
+       cy="35.736916"
+       fx="33.966679"
+       fy="35.736916"
+       r="86.70845" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient269-6-3-7"
+       id="radialGradient8649"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.824419"
+       cy="3.7561285"
+       fx="8.824419"
+       fy="3.7561285"
+       r="37.751713" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient15662-0-5-7"
+       id="radialGradient8651"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.1435566"
+       cy="7.2678967"
+       fx="8.1435566"
+       fy="7.2678967"
+       r="38.158695" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd2-1-8-9"
+       id="radialGradient8653"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="114.5684"
+       fx="20.892099"
+       fy="114.5684"
+       r="5.256" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd3-3-4-7"
+       id="radialGradient8655"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="64.567902"
+       fx="20.892099"
+       fy="64.567902"
+       r="5.257" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048-4-2-5-6-7"
+       id="linearGradient8657"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-24-4-8-0"
+       id="radialGradient8659"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-24-4-8-0"
+       id="radialGradient8661"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient259-1-3-7-3"
+       id="radialGradient8663"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="scale(0.960493,1.041132)"
+       cx="33.966679"
+       cy="35.736916"
+       fx="33.966679"
+       fy="35.736916"
+       r="86.70845" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient269-6-3-7-0"
+       id="radialGradient8665"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.824419"
+       cy="3.7561285"
+       fx="8.824419"
+       fy="3.7561285"
+       r="37.751713" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient15662-0-5-7-5"
+       id="radialGradient8667"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.1435566"
+       cy="7.2678967"
+       fx="8.1435566"
+       fy="7.2678967"
+       r="38.158695" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd2-1-8-9-5"
+       id="radialGradient8669"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="114.5684"
+       fx="20.892099"
+       fy="114.5684"
+       r="5.256" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd3-3-4-7-3"
+       id="radialGradient8671"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="64.567902"
+       fx="20.892099"
+       fy="64.567902"
+       r="5.257" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048-4-2-5-6-7-8"
+       id="linearGradient8673"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-24-4-8-0-4"
+       id="radialGradient8675"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-24-4-8-0-4"
+       id="radialGradient8677"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient259-1-3-7-3-5"
+       id="radialGradient8679"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="scale(0.960493,1.041132)"
+       cx="33.966679"
+       cy="35.736916"
+       fx="33.966679"
+       fy="35.736916"
+       r="86.70845" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient269-6-3-7-0-4"
+       id="radialGradient8681"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.824419"
+       cy="3.7561285"
+       fx="8.824419"
+       fy="3.7561285"
+       r="37.751713" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient15662-0-5-7-5-8"
+       id="radialGradient8683"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.1435566"
+       cy="7.2678967"
+       fx="8.1435566"
+       fy="7.2678967"
+       r="38.158695" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd2-1-8-9-5-4"
+       id="radialGradient8685"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="114.5684"
+       fx="20.892099"
+       fy="114.5684"
+       r="5.256" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd3-3-4-7-3-3"
+       id="radialGradient8687"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="64.567902"
+       fx="20.892099"
+       fy="64.567902"
+       r="5.257" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048-4-2-5-6-7-8-1"
+       id="linearGradient8689"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-24-4-8-0-4-9"
+       id="radialGradient8691"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060-4-24-4-8-0-4-9"
+       id="radialGradient8693"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient259-1-3-7-3-5-3"
+       id="radialGradient8695"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="scale(0.960493,1.041132)"
+       cx="33.966679"
+       cy="35.736916"
+       fx="33.966679"
+       fy="35.736916"
+       r="86.70845" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient269-6-3-7-0-4-2"
+       id="radialGradient8697"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.824419"
+       cy="3.7561285"
+       fx="8.824419"
+       fy="3.7561285"
+       r="37.751713" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient15662-0-5-7-5-8-1"
+       id="radialGradient8699"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0,0,1.032767,3.353553,0.646447)"
+       cx="8.1435566"
+       cy="7.2678967"
+       fx="8.1435566"
+       fy="7.2678967"
+       r="38.158695" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd2-1-8-9-5-4-9"
+       id="radialGradient8701"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="114.5684"
+       fx="20.892099"
+       fy="114.5684"
+       r="5.256" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#aigrd3-3-4-7-3-3-1"
+       id="radialGradient8703"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.229703,0,0,0.229703,4.613529,3.979808)"
+       cx="20.892099"
+       cy="64.567902"
+       fx="20.892099"
+       fy="64.567902"
+       r="5.257" />
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mend-3"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path8720-2"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+    </marker>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048"
+       id="linearGradient9204"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060"
+       id="radialGradient9206"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060"
+       id="radialGradient9208"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient1789"
+       id="radialGradient9210"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.055022,-0.02734504,0.177703,1.190929,92.718071,84.312593)"
+       cx="20.706017"
+       cy="37.517986"
+       fx="20.706017"
+       fy="37.517986"
+       r="30.905205" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3096"
+       id="linearGradient9212"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(96.290248,91.437894)"
+       x1="18.112709"
+       y1="31.367750"
+       x2="15.514889"
+       y2="6.1802502" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3983"
+       id="linearGradient9214"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.516844,0,0,0.708978,95.410675,90.119728)"
+       x1="6.2297964"
+       y1="13.773066"
+       x2="9.8980894"
+       y2="66.834053" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient9766"
+       id="linearGradient9216"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(96.290248,91.437894)"
+       x1="22.175976"
+       y1="36.987999"
+       x2="22.065331"
+       y2="32.050499" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient319"
+       id="linearGradient9218"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.317489,0,0,0.816256,95.410675,90.119728)"
+       x1="13.035696"
+       y1="32.567184"
+       x2="12.853771"
+       y2="46.689312" />
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mend-7"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path8720-4"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mend-4"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path8720-3"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mend-5"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path8720-26"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mend-9"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path8720-29"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mend-45"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path8720-0"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mend-8"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path8720-01"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mend-2"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path8720-22"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mend-0"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path8720-31"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mend-37"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path8720-09"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mend-58"
+       style="overflow:visible">
+      <path
+         inkscape:connector-curvature="0"
+         id="path8720-37"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+         transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+    </marker>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.1944008"
+     inkscape:cx="226.27324"
+     inkscape:cy="648.37715"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1280"
+     inkscape:window-height="961"
+     inkscape:window-x="1912"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <text
+       xml:space="preserve"
+       style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+       x="95.147758"
+       y="135.22092"
+       id="text2989"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan2991"
+         x="95.147758"
+         y="135.22092">DCP Test</tspan></text>
+    <g
+       id="g3213"
+       transform="matrix(0.66343015,0,0,0.66343015,-3.07933,54.924457)">
+      <g
+         style="display:inline"
+         id="g5022"
+         transform="matrix(0.02165152,0,0,0.01903841,138.70563,128.37161)">
+        <rect
+           y="-150.69685"
+           x="-1559.2523"
+           height="478.35718"
+           width="1339.6335"
+           id="rect4173"
+           style="opacity:0.40206185;color:#000000;fill:url(#linearGradient9204);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+        <path
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cccc"
+           id="path5058"
+           d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+           style="opacity:0.40206185;color:#000000;fill:url(#radialGradient9206);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+        <path
+           inkscape:connector-curvature="0"
+           style="opacity:0.40206185;color:#000000;fill:url(#radialGradient9208);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+           d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+           id="path5018"
+           sodipodi:nodetypes="cccc" />
+      </g>
+      <path
+         inkscape:connector-curvature="0"
+         d="m 100.81203,130.12531 c 0.0218,0.4163 0.4599,0.83261 0.87621,0.83261 l 31.32702,0 c 0.4163,0 0.81081,-0.41631 0.78901,-0.83261 l -0.93644,-27.22673 c -0.0218,-0.41631 -0.4599,-0.83262 -0.8762,-0.83262 l -13.27087,0 c -0.48506,0 -1.23447,-0.31559 -1.40165,-1.10663 l -0.61139,-2.893073 c -0.15547,-0.735673 -0.88221,-1.037886 -1.29851,-1.037886 l -14.77886,0 c -0.41632,0 -0.810825,0.416304 -0.789029,0.832608 l 0.970709,32.264331 z"
+         id="path216"
+         style="fill:url(#radialGradient9210);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient9212);stroke-width:1.50731766;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         sodipodi:nodetypes="ccccccssssccc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9788"
+         d="m 101.51684,114.00039 30.26558,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731802;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9784"
+         d="m 101.33242,110.00039 30.44693,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731826;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731766;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 101.27094,104.00039 30.50736,0"
+         id="path9778"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731826;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 101.67641,124.00039 30.10872,0"
+         id="path9798"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9800"
+         d="m 101.79939,126.00039 29.98775,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731802;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731826;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 101.33242,108.00039 30.44693,0"
+         id="path9782"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9780"
+         d="m 101.30168,106.00039 30.47715,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731802;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9776"
+         d="m 101.21234,102.00039 15.28082,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731802;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731742;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 101.164,100.00039 14.78374,0"
+         id="path9774"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731826;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 101.61491,120.00039 30.16921,0"
+         id="path9794"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9792"
+         d="m 101.57831,118.00039 30.20512,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731766;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731802;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 101.51684,116.00039 30.26558,0"
+         id="path9790"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.5073179;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 101.4861,112.00039 30.2958,0"
+         id="path9786"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9796"
+         d="m 101.61491,122.00039 30.16921,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731826;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731802;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 101.79939,128.00039 29.98775,0"
+         id="path9802"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.45142858;color:#000000;fill:url(#linearGradient9214);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.21380496;marker:none;visibility:visible;display:block;overflow:visible"
+         d="m 102.35859,130.30192 c 0.0163,0.31222 -0.18091,0.52038 -0.49858,0.4163 l 0,0 c -0.31769,-0.10408 -0.53673,-0.31223 -0.55309,-0.62446 L 100.3592,98.029278 c -0.0163,-0.312228 0.16519,-0.500771 0.47742,-0.500771 l 14.42205,-0.04769 c 0.31223,0 0.93194,0.300472 1.13293,1.322181 l 0.57349,2.815532 c -0.42705,-0.46526 -0.41919,-0.47962 -0.63755,-1.15671 l -0.4061,-1.259175 c -0.21905,-0.727647 -0.6982,-0.8319 -1.01043,-0.8319 l -12.88777,0 c -0.31223,0 -0.50948,0.208152 -0.49313,0.520388 l 0.938,31.514857 -0.10952,-0.10407 z"
+         id="path219"
+         sodipodi:nodetypes="cccccccccscccccc" />
+      <g
+         style="fill:#ffffff;fill-opacity:0.75706213;fill-rule:nonzero;stroke:none"
+         id="g220"
+         transform="matrix(1.040764,0,0.05449252,1.040764,87.620049,94.108488)"
+         inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
+         inkscape:export-xdpi="74.800003"
+         inkscape:export-ydpi="74.800003">
+        <path
+           inkscape:connector-curvature="0"
+           style="fill:#ffffff;fill-opacity:0.50847461"
+           d="m 42.417183,8.5151772 c 0.0051,-0.097113 -0.128161,-0.2469882 -0.235117,-0.2470056 l -13.031401,-0.00212 c 0,0 0.911714,0.5879545 2.201812,0.5962436 l 11.053497,0.07102 c 0.01109,-0.2117278 0.0027,-0.2560322 0.01121,-0.4181395 z"
+           id="path221"
+           sodipodi:nodetypes="cscscs" />
+      </g>
+      <path
+         inkscape:connector-curvature="0"
+         style="color:#000000;fill:url(#linearGradient9216);fill-opacity:1;fill-rule:nonzero;stroke:#3465a4;stroke-width:1.50731766;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:block"
+         d="m 136.07378,130.94851 c 1.14389,-0.0441 1.96308,-1.0963 2.04703,-2.321 0.79179,-11.54869 1.65937,-21.23195 1.65937,-21.23195 0.0721,-0.24748 -0.16792,-0.49497 -0.48015,-0.49497 l -34.37115,0 c 0,0 -1.85032,21.86689 -1.85032,21.86689 -0.11456,0.98207 -0.46601,1.80472 -1.54984,2.18372 l 34.54506,-0.003 z"
+         id="path233"
+         sodipodi:nodetypes="cscccscc"
+         inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
+         inkscape:export-xdpi="74.800003"
+         inkscape:export-ydpi="74.800003" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccsscsc"
+         id="path304"
+         d="m 105.91049,107.90182 32.7911,0.0648 -1.57405,20.00198 c -0.0843,1.07152 -0.45067,1.42822 -1.87265,1.42822 -1.8715,0 -28.67797,-0.0324 -31.39474,-0.0324 0.2336,-0.32081 0.33375,-0.98862 0.33509,-1.00461 l 1.71525,-20.45798 z"
+         style="opacity:0.46590911;fill:none;stroke:url(#linearGradient9218);stroke-width:1.5073173px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccccc"
+         id="path323"
+         d="m 105.9105,107.66108 -1.16665,15.64327 c 0,0 8.29615,-4.14808 18.66635,-4.14808 10.37019,0 15.55529,-11.49519 15.55529,-11.49519 l -33.05499,0 z"
+         style="fill:#ffffff;fill-opacity:0.0892857;fill-rule:evenodd;stroke:none" />
+    </g>
+    <g
+       id="layer2"
+       inkscape:label="pattern"
+       transform="translate(97.101004,91.437894)" />
+    <g
+       id="g8060"
+       transform="translate(-121.3625,166.36419)">
+      <g
+         transform="matrix(0.57237779,0,0,0.57237779,222.84924,32.550815)"
+         id="g3373">
+        <g
+           id="layer6"
+           inkscape:label="Shadow">
+          <g
+             style="display:inline"
+             transform="matrix(0.02105461,0,0,0.02086758,42.85172,41.1536)"
+             id="g6707">
+            <rect
+               style="opacity:0.40206185;color:#000000;fill:url(#linearGradient8257);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+               id="rect6709"
+               width="1339.6335"
+               height="478.35718"
+               x="-1559.2523"
+               y="-150.69685" />
+            <path
+               inkscape:connector-curvature="0"
+               style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8259);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+               d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+               id="path6711"
+               sodipodi:nodetypes="cccc" />
+            <path
+               inkscape:connector-curvature="0"
+               sodipodi:nodetypes="cccc"
+               id="path6713"
+               d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+               style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8261);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+          </g>
+        </g>
+        <g
+           style="display:inline"
+           inkscape:label="Base"
+           id="layer1-5">
+          <rect
+             style="color:#000000;fill:url(#radialGradient8263);fill-opacity:1;fill-rule:nonzero;stroke:url(#radialGradient8265);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible"
+             id="rect15391"
+             width="34.875"
+             height="40.920494"
+             x="6.6035528"
+             y="3.6464462"
+             ry="1.1490486" />
+          <rect
+             style="color:#000000;fill:none;stroke:url(#radialGradient8267);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible"
+             id="rect15660"
+             width="32.775887"
+             height="38.946384"
+             x="7.6660538"
+             y="4.5839462"
+             ry="0.14904857"
+             rx="0.14904857" />
+          <g
+             transform="translate(0.646447,-0.03798933)"
+             id="g2270">
+            <g
+               id="g1440"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.74709785;stroke-miterlimit:4"
+               transform="matrix(0.229703,0,0,0.229703,4.967081,4.244972)">
+              <radialGradient
+                 id="radialGradient1442"
+                 cx="20.892099"
+                 cy="114.5684"
+                 r="5.256"
+                 fx="20.892099"
+                 fy="114.5684"
+                 gradientUnits="userSpaceOnUse">
+                <stop
+                   offset="0"
+                   style="stop-color:#F0F0F0"
+                   id="stop1444" />
+                <stop
+                   offset="1"
+                   style="stop-color:#474747"
+                   id="stop1446" />
+              </radialGradient>
+              <path
+                 inkscape:connector-curvature="0"
+                 style="stroke:none"
+                 d="m 23.428,113.07 c 0,1.973 -1.6,3.572 -3.573,3.572 -1.974,0 -3.573,-1.6 -3.573,-3.572 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                 id="path1448" />
+              <radialGradient
+                 id="radialGradient1450"
+                 cx="20.892099"
+                 cy="64.567902"
+                 r="5.257"
+                 fx="20.892099"
+                 fy="64.567902"
+                 gradientUnits="userSpaceOnUse">
+                <stop
+                   offset="0"
+                   style="stop-color:#F0F0F0"
+                   id="stop1452" />
+                <stop
+                   offset="1"
+                   style="stop-color:#474747"
+                   id="stop1454" />
+              </radialGradient>
+              <path
+                 inkscape:connector-curvature="0"
+                 style="stroke:none"
+                 d="m 23.428,63.07 c 0,1.973 -1.6,3.573 -3.573,3.573 -1.974,0 -3.573,-1.6 -3.573,-3.573 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                 id="path1456" />
+            </g>
+            <path
+               inkscape:connector-curvature="0"
+               style="fill:url(#radialGradient8269);fill-rule:nonzero;stroke:none"
+               d="m 9.9950109,29.952326 c 0,0.453204 -0.3675248,0.820499 -0.8207288,0.820499 -0.4534338,0 -0.8207289,-0.367524 -0.8207289,-0.820499 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+               id="path15570" />
+            <path
+               inkscape:connector-curvature="0"
+               style="fill:url(#radialGradient8271);fill-rule:nonzero;stroke:none"
+               d="m 9.9950109,18.467176 c 0,0.453204 -0.3675248,0.820729 -0.8207288,0.820729 -0.4534338,0 -0.8207289,-0.367525 -0.8207289,-0.820729 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+               id="path15577" />
+          </g>
+          <path
+             inkscape:connector-curvature="0"
+             style="fill:none;stroke:#000000;stroke-width:1.72709894;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.01754384"
+             d="m 11.505723,5.4942766 0,37.9065924"
+             id="path15672"
+             sodipodi:nodetypes="cc" />
+          <path
+             inkscape:connector-curvature="0"
+             style="fill:none;stroke:#ffffff;stroke-width:1.74709785;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.20467828"
+             d="m 12.5,5.0205154 0,38.0177126"
+             id="path15674"
+             sodipodi:nodetypes="cc" />
+        </g>
+        <g
+           id="layer5"
+           inkscape:label="Text"
+           style="display:inline">
+          <g
+             transform="matrix(0.909091,0,0,1,2.363628,0)"
+             id="g2253">
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15686"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="9"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15688"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="11"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15690"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="13"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15692"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="15"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15694"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="17"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15696"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="19"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15698"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="21"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15700"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="23"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15732"
+               width="9.9000053"
+               height="1"
+               x="14.999992"
+               y="25"
+               rx="0.068204239"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15736"
+               width="22.000004"
+               height="1"
+               x="14.999992"
+               y="29"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15738"
+               width="22.000004"
+               height="1"
+               x="14.999992"
+               y="31"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15740"
+               width="22.000004"
+               height="1"
+               x="14.999992"
+               y="33"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15742"
+               width="22.000004"
+               height="1"
+               x="14.999992"
+               y="35"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15744"
+               width="15.400014"
+               height="1"
+               x="14.999992"
+               y="37"
+               rx="0.10609552"
+               ry="0.065390877" />
+          </g>
+        </g>
+      </g>
+      <text
+         sodipodi:linespacing="125%"
+         id="text3413"
+         y="51.003765"
+         x="257.82571"
+         style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+         xml:space="preserve"><tspan
+           y="51.003765"
+           x="257.82571"
+           id="tspan3415"
+           sodipodi:role="line">metadata</tspan></text>
+      <text
+         sodipodi:linespacing="125%"
+         id="text3417"
+         y="10.197531"
+         x="257.82571"
+         style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+         xml:space="preserve"><tspan
+           y="10.197531"
+           x="257.82571"
+           id="tspan3419"
+           sodipodi:role="line">log</tspan></text>
+      <g
+         transform="matrix(0.57237779,0,0,0.57237779,222.84924,-7.2851657)"
+         id="g3373-1">
+        <g
+           id="layer6-2"
+           inkscape:label="Shadow">
+          <g
+             style="display:inline"
+             transform="matrix(0.02105461,0,0,0.02086758,42.85172,41.1536)"
+             id="g6707-3">
+            <rect
+               style="opacity:0.40206185;color:#000000;fill:url(#linearGradient8273);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+               id="rect6709-3"
+               width="1339.6335"
+               height="478.35718"
+               x="-1559.2523"
+               y="-150.69685" />
+            <path
+               inkscape:connector-curvature="0"
+               style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8275);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+               d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+               id="path6711-4"
+               sodipodi:nodetypes="cccc" />
+            <path
+               inkscape:connector-curvature="0"
+               sodipodi:nodetypes="cccc"
+               id="path6713-1"
+               d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+               style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8277);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+          </g>
+        </g>
+        <g
+           style="display:inline"
+           inkscape:label="Base"
+           id="layer1-5-1">
+          <rect
+             style="color:#000000;fill:url(#radialGradient8279);fill-opacity:1;fill-rule:nonzero;stroke:url(#radialGradient8281);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible"
+             id="rect15391-3"
+             width="34.875"
+             height="40.920494"
+             x="6.6035528"
+             y="3.6464462"
+             ry="1.1490486" />
+          <rect
+             style="color:#000000;fill:none;stroke:url(#radialGradient8283);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible"
+             id="rect15660-8"
+             width="32.775887"
+             height="38.946384"
+             x="7.6660538"
+             y="4.5839462"
+             ry="0.14904857"
+             rx="0.14904857" />
+          <g
+             transform="translate(0.646447,-0.03798933)"
+             id="g2270-7">
+            <g
+               id="g1440-4"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.74709785;stroke-miterlimit:4"
+               transform="matrix(0.229703,0,0,0.229703,4.967081,4.244972)">
+              <radialGradient
+                 id="radialGradient1442-2"
+                 cx="20.892099"
+                 cy="114.5684"
+                 r="5.256"
+                 fx="20.892099"
+                 fy="114.5684"
+                 gradientUnits="userSpaceOnUse">
+                <stop
+                   offset="0"
+                   style="stop-color:#F0F0F0"
+                   id="stop1444-7" />
+                <stop
+                   offset="1"
+                   style="stop-color:#474747"
+                   id="stop1446-7" />
+              </radialGradient>
+              <path
+                 inkscape:connector-curvature="0"
+                 style="stroke:none"
+                 d="m 23.428,113.07 c 0,1.973 -1.6,3.572 -3.573,3.572 -1.974,0 -3.573,-1.6 -3.573,-3.572 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                 id="path1448-9" />
+              <radialGradient
+                 id="radialGradient1450-3"
+                 cx="20.892099"
+                 cy="64.567902"
+                 r="5.257"
+                 fx="20.892099"
+                 fy="64.567902"
+                 gradientUnits="userSpaceOnUse">
+                <stop
+                   offset="0"
+                   style="stop-color:#F0F0F0"
+                   id="stop1452-1" />
+                <stop
+                   offset="1"
+                   style="stop-color:#474747"
+                   id="stop1454-9" />
+              </radialGradient>
+              <path
+                 inkscape:connector-curvature="0"
+                 style="stroke:none"
+                 d="m 23.428,63.07 c 0,1.973 -1.6,3.573 -3.573,3.573 -1.974,0 -3.573,-1.6 -3.573,-3.573 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                 id="path1456-8" />
+            </g>
+            <path
+               inkscape:connector-curvature="0"
+               style="fill:url(#radialGradient8285);fill-rule:nonzero;stroke:none"
+               d="m 9.9950109,29.952326 c 0,0.453204 -0.3675248,0.820499 -0.8207288,0.820499 -0.4534338,0 -0.8207289,-0.367524 -0.8207289,-0.820499 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+               id="path15570-6" />
+            <path
+               inkscape:connector-curvature="0"
+               style="fill:url(#radialGradient8287);fill-rule:nonzero;stroke:none"
+               d="m 9.9950109,18.467176 c 0,0.453204 -0.3675248,0.820729 -0.8207288,0.820729 -0.4534338,0 -0.8207289,-0.367525 -0.8207289,-0.820729 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+               id="path15577-5" />
+          </g>
+          <path
+             inkscape:connector-curvature="0"
+             style="fill:none;stroke:#000000;stroke-width:1.72709894;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.01754384"
+             d="m 11.505723,5.4942766 0,37.9065924"
+             id="path15672-0"
+             sodipodi:nodetypes="cc" />
+          <path
+             inkscape:connector-curvature="0"
+             style="fill:none;stroke:#ffffff;stroke-width:1.74709785;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.20467828"
+             d="m 12.5,5.0205154 0,38.0177126"
+             id="path15674-2"
+             sodipodi:nodetypes="cc" />
+        </g>
+        <g
+           id="layer5-8"
+           inkscape:label="Text"
+           style="display:inline">
+          <g
+             transform="matrix(0.909091,0,0,1,2.363628,0)"
+             id="g2253-6">
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15686-0"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="9"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15688-2"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="11"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15690-4"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="13"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15692-8"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="15"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15694-6"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="17"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15696-5"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="19"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15698-0"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="21"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15700-9"
+               width="22.000004"
+               height="1"
+               x="15.000002"
+               y="23"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15732-0"
+               width="9.9000053"
+               height="1"
+               x="14.999992"
+               y="25"
+               rx="0.068204239"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15736-0"
+               width="22.000004"
+               height="1"
+               x="14.999992"
+               y="29"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15738-6"
+               width="22.000004"
+               height="1"
+               x="14.999992"
+               y="31"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15740-1"
+               width="22.000004"
+               height="1"
+               x="14.999992"
+               y="33"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15742-3"
+               width="22.000004"
+               height="1"
+               x="14.999992"
+               y="35"
+               rx="0.15156493"
+               ry="0.065390877" />
+            <rect
+               style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible"
+               id="rect15744-8"
+               width="15.400014"
+               height="1"
+               x="14.999992"
+               y="37"
+               rx="0.10609552"
+               ry="0.065390877" />
+          </g>
+        </g>
+      </g>
+      <g
+         transform="matrix(0.66343015,0,0,0.66343015,157.41245,90.312666)"
+         id="g3213-1">
+        <g
+           transform="matrix(0.02165152,0,0,0.01903841,138.70563,128.37161)"
+           id="g5022-5"
+           style="display:inline">
+          <rect
+             style="opacity:0.40206185;color:#000000;fill:url(#linearGradient8289);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+             id="rect4173-4"
+             width="1339.6335"
+             height="478.35718"
+             x="-1559.2523"
+             y="-150.69685" />
+          <path
+             style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8291);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+             d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+             id="path5058-2"
+             sodipodi:nodetypes="cccc"
+             inkscape:connector-curvature="0" />
+          <path
+             sodipodi:nodetypes="cccc"
+             id="path5018-0"
+             d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+             style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8293);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+             inkscape:connector-curvature="0" />
+        </g>
+        <path
+           sodipodi:nodetypes="ccccccssssccc"
+           style="fill:url(#radialGradient8295);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient8297);stroke-width:1.50731766;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+           id="path216-9"
+           d="m 100.81203,130.12531 c 0.0218,0.4163 0.4599,0.83261 0.87621,0.83261 l 31.32702,0 c 0.4163,0 0.81081,-0.41631 0.78901,-0.83261 l -0.93644,-27.22673 c -0.0218,-0.41631 -0.4599,-0.83262 -0.8762,-0.83262 l -13.27087,0 c -0.48506,0 -1.23447,-0.31559 -1.40165,-1.10663 l -0.61139,-2.893073 c -0.15547,-0.735673 -0.88221,-1.037886 -1.29851,-1.037886 l -14.77886,0 c -0.41632,0 -0.810825,0.416304 -0.789029,0.832608 l 0.970709,32.264331 z"
+           inkscape:connector-curvature="0" />
+        <path
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731802;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           d="m 101.51684,114.00039 30.26558,0"
+           id="path9788-7"
+           sodipodi:nodetypes="cc"
+           inkscape:connector-curvature="0" />
+        <path
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731826;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           d="m 101.33242,110.00039 30.44693,0"
+           id="path9784-3"
+           sodipodi:nodetypes="cc"
+           inkscape:connector-curvature="0" />
+        <path
+           sodipodi:nodetypes="cc"
+           id="path9778-7"
+           d="m 101.27094,104.00039 30.50736,0"
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731766;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           inkscape:connector-curvature="0" />
+        <path
+           sodipodi:nodetypes="cc"
+           id="path9798-2"
+           d="m 101.67641,124.00039 30.10872,0"
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731826;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           inkscape:connector-curvature="0" />
+        <path
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731802;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           d="m 101.79939,126.00039 29.98775,0"
+           id="path9800-6"
+           sodipodi:nodetypes="cc"
+           inkscape:connector-curvature="0" />
+        <path
+           sodipodi:nodetypes="cc"
+           id="path9782-0"
+           d="m 101.33242,108.00039 30.44693,0"
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731826;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           inkscape:connector-curvature="0" />
+        <path
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731802;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           d="m 101.30168,106.00039 30.47715,0"
+           id="path9780-1"
+           sodipodi:nodetypes="cc"
+           inkscape:connector-curvature="0" />
+        <path
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731802;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           d="m 101.21234,102.00039 15.28082,0"
+           id="path9776-6"
+           sodipodi:nodetypes="cc"
+           inkscape:connector-curvature="0" />
+        <path
+           sodipodi:nodetypes="cc"
+           id="path9774-5"
+           d="m 101.164,100.00039 14.78374,0"
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731742;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           inkscape:connector-curvature="0" />
+        <path
+           sodipodi:nodetypes="cc"
+           id="path9794-7"
+           d="m 101.61491,120.00039 30.16921,0"
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731826;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           inkscape:connector-curvature="0" />
+        <path
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731766;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           d="m 101.57831,118.00039 30.20512,0"
+           id="path9792-5"
+           sodipodi:nodetypes="cc"
+           inkscape:connector-curvature="0" />
+        <path
+           sodipodi:nodetypes="cc"
+           id="path9790-4"
+           d="m 101.51684,116.00039 30.26558,0"
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731802;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           inkscape:connector-curvature="0" />
+        <path
+           sodipodi:nodetypes="cc"
+           id="path9786-1"
+           d="m 101.4861,112.00039 30.2958,0"
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.5073179;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           inkscape:connector-curvature="0" />
+        <path
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731826;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           d="m 101.61491,122.00039 30.16921,0"
+           id="path9796-2"
+           sodipodi:nodetypes="cc"
+           inkscape:connector-curvature="0" />
+        <path
+           sodipodi:nodetypes="cc"
+           id="path9802-0"
+           d="m 101.79939,128.00039 29.98775,0"
+           style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.50731802;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+           inkscape:connector-curvature="0" />
+        <path
+           sodipodi:nodetypes="cccccccccscccccc"
+           id="path219-0"
+           d="m 102.35859,130.30192 c 0.0163,0.31222 -0.18091,0.52038 -0.49858,0.4163 l 0,0 c -0.31769,-0.10408 -0.53673,-0.31223 -0.55309,-0.62446 L 100.3592,98.029278 c -0.0163,-0.312228 0.16519,-0.500771 0.47742,-0.500771 l 14.42205,-0.04769 c 0.31223,0 0.93194,0.300472 1.13293,1.322181 l 0.57349,2.815532 c -0.42705,-0.46526 -0.41919,-0.47962 -0.63755,-1.15671 l -0.4061,-1.259175 c -0.21905,-0.727647 -0.6982,-0.8319 -1.01043,-0.8319 l -12.88777,0 c -0.31223,0 -0.50948,0.208152 -0.49313,0.520388 l 0.938,31.514857 -0.10952,-0.10407 z"
+           style="opacity:0.45142858;color:#000000;fill:url(#linearGradient8299);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.21380496;marker:none;visibility:visible;display:block;overflow:visible"
+           inkscape:connector-curvature="0" />
+        <g
+           inkscape:export-ydpi="74.800003"
+           inkscape:export-xdpi="74.800003"
+           inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
+           transform="matrix(1.040764,0,0.05449252,1.040764,87.620049,94.108488)"
+           id="g220-1"
+           style="fill:#ffffff;fill-opacity:0.75706213;fill-rule:nonzero;stroke:none">
+          <path
+             sodipodi:nodetypes="cscscs"
+             id="path221-4"
+             d="m 42.417183,8.5151772 c 0.0051,-0.097113 -0.128161,-0.2469882 -0.235117,-0.2470056 l -13.031401,-0.00212 c 0,0 0.911714,0.5879545 2.201812,0.5962436 l 11.053497,0.07102 c 0.01109,-0.2117278 0.0027,-0.2560322 0.01121,-0.4181395 z"
+             style="fill:#ffffff;fill-opacity:0.50847461"
+             inkscape:connector-curvature="0" />
+        </g>
+        <path
+           inkscape:export-ydpi="74.800003"
+           inkscape:export-xdpi="74.800003"
+           inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
+           sodipodi:nodetypes="cscccscc"
+           id="path233-6"
+           d="m 136.07378,130.94851 c 1.14389,-0.0441 1.96308,-1.0963 2.04703,-2.321 0.79179,-11.54869 1.65937,-21.23195 1.65937,-21.23195 0.0721,-0.24748 -0.16792,-0.49497 -0.48015,-0.49497 l -34.37115,0 c 0,0 -1.85032,21.86689 -1.85032,21.86689 -0.11456,0.98207 -0.46601,1.80472 -1.54984,2.18372 l 34.54506,-0.003 z"
+           style="color:#000000;fill:url(#linearGradient8301);fill-opacity:1;fill-rule:nonzero;stroke:#3465a4;stroke-width:1.50731766;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:block"
+           inkscape:connector-curvature="0" />
+        <path
+           style="opacity:0.46590911;fill:none;stroke:url(#linearGradient8303);stroke-width:1.5073173px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 105.91049,107.90182 32.7911,0.0648 -1.57405,20.00198 c -0.0843,1.07152 -0.45067,1.42822 -1.87265,1.42822 -1.8715,0 -28.67797,-0.0324 -31.39474,-0.0324 0.2336,-0.32081 0.33375,-0.98862 0.33509,-1.00461 l 1.71525,-20.45798 z"
+           id="path304-0"
+           sodipodi:nodetypes="ccsscsc"
+           inkscape:connector-curvature="0" />
+        <path
+           style="fill:#ffffff;fill-opacity:0.0892857;fill-rule:evenodd;stroke:none"
+           d="m 105.9105,107.66108 -1.16665,15.64327 c 0,0 8.29615,-4.14808 18.66635,-4.14808 10.37019,0 15.55529,-11.49519 15.55529,-11.49519 l -33.05499,0 z"
+           id="path323-7"
+           sodipodi:nodetypes="ccccc"
+           inkscape:connector-curvature="0" />
+      </g>
+      <text
+         sodipodi:linespacing="125%"
+         id="text2989-1"
+         y="170.85364"
+         x="257.82571"
+         style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+         xml:space="preserve"><tspan
+           y="170.85364"
+           x="257.82571"
+           id="tspan2991-7"
+           sodipodi:role="line">info</tspan></text>
+      <g
+         style="display:inline"
+         id="g5022-5-1"
+         transform="matrix(0.01436427,0,0,0.01263066,249.43395,135.6126)">
+        <rect
+           y="-150.69685"
+           x="-1559.2523"
+           height="478.35718"
+           width="1339.6335"
+           id="rect4173-4-6"
+           style="opacity:0.40206185;color:#000000;fill:url(#linearGradient8305);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+        <path
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cccc"
+           id="path5058-2-4"
+           d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+           style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8307);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+        <path
+           inkscape:connector-curvature="0"
+           style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8309);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+           d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+           id="path5018-0-0"
+           sodipodi:nodetypes="cccc" />
+      </g>
+      <path
+         inkscape:connector-curvature="0"
+         d="m 224.29419,136.77606 c 0.0145,0.27618 0.30511,0.55238 0.5813,0.55238 l 20.78329,0 c 0.27619,0 0.53792,-0.2762 0.52346,-0.55238 l -0.62127,-18.06304 c -0.0145,-0.27619 -0.30511,-0.55238 -0.58129,-0.55238 l -8.8043,0 c -0.3218,0 -0.81898,-0.20937 -0.9299,-0.73417 l -0.40561,-1.91935 c -0.10314,-0.48807 -0.58528,-0.68857 -0.86147,-0.68857 l -9.80474,0 c -0.2762,0 -0.53793,0.27619 -0.52347,0.55238 l 0.644,21.40513 z"
+         id="path216-9-6"
+         style="fill:url(#radialGradient8311);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient8313);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         sodipodi:nodetypes="ccccccssssccc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9788-7-1"
+         d="m 224.76178,126.0783 20.0791,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9784-3-8"
+         d="m 224.63943,123.42458 20.19941,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.59864,119.444 20.23951,0"
+         id="path9778-7-9"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.86765,132.7126 19.97503,0"
+         id="path9798-2-8"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9800-6-4"
+         d="m 224.94923,134.03946 19.89478,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.63943,122.09772 20.19941,0"
+         id="path9782-0-1"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9780-1-4"
+         d="m 224.61904,120.77086 20.21946,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9776-6-3"
+         d="m 224.55977,118.11714 10.13775,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.99999982;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.5277,116.79028 9.80798,0"
+         id="path9774-5-9"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.82684,130.05888 20.01517,0"
+         id="path9794-7-8"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9792-5-8"
+         d="m 224.80256,128.73202 20.03899,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.76178,127.40516 20.0791,0"
+         id="path9790-4-0"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.74139,124.75144 20.09915,0"
+         id="path9786-1-8"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9796-2-7"
+         d="m 224.82684,131.38574 20.01517,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.94923,135.36632 19.89478,0"
+         id="path9802-0-7"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.45142858;color:#000000;fill:url(#linearGradient8315);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.21380496;marker:none;visibility:visible;display:block;overflow:visible"
+         d="m 225.32022,136.89323 c 0.0108,0.20713 -0.12002,0.34523 -0.33077,0.27618 l 0,0 c -0.21076,-0.0691 -0.35608,-0.20714 -0.36693,-0.41428 l -0.62875,-21.27255 c -0.0108,-0.20714 0.10959,-0.33222 0.31673,-0.33222 l 9.56803,-0.0316 c 0.20714,0 0.61827,0.19934 0.75162,0.87717 l 0.38047,1.86791 c -0.28332,-0.30867 -0.27811,-0.31819 -0.42297,-0.7674 l -0.26942,-0.83537 c -0.14533,-0.48274 -0.46321,-0.55191 -0.67035,-0.55191 l -8.55014,0 c -0.20714,0 -0.338,0.1381 -0.32715,0.34524 l 0.62229,20.90791 -0.0727,-0.069 z"
+         id="path219-0-8"
+         sodipodi:nodetypes="cccccccccscccccc" />
+      <g
+         style="fill:#ffffff;fill-opacity:0.75706213;fill-rule:nonzero;stroke:none"
+         id="g220-1-3"
+         transform="matrix(0.69047422,0,0.03615198,0.69047422,215.54223,112.88141)"
+         inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
+         inkscape:export-xdpi="74.800003"
+         inkscape:export-ydpi="74.800003">
+        <path
+           inkscape:connector-curvature="0"
+           style="fill:#ffffff;fill-opacity:0.50847461"
+           d="m 42.417183,8.5151772 c 0.0051,-0.097113 -0.128161,-0.2469882 -0.235117,-0.2470056 l -13.031401,-0.00212 c 0,0 0.911714,0.5879545 2.201812,0.5962436 l 11.053497,0.07102 c 0.01109,-0.2117278 0.0027,-0.2560322 0.01121,-0.4181395 z"
+           id="path221-4-8"
+           sodipodi:nodetypes="cscscs" />
+      </g>
+      <path
+         inkscape:connector-curvature="0"
+         style="color:#000000;fill:url(#linearGradient8317);fill-opacity:1;fill-rule:nonzero;stroke:#3465a4;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:block"
+         d="m 247.6879,137.32219 c 0.75889,-0.0293 1.30236,-0.72731 1.35806,-1.53982 0.5253,-7.66175 1.10088,-14.08591 1.10088,-14.08591 0.0478,-0.16419 -0.11141,-0.32838 -0.31855,-0.32838 l -22.80286,0 c 0,0 -1.22756,14.50715 -1.22756,14.50715 -0.076,0.65154 -0.30916,1.19731 -1.02821,1.44875 l 22.91824,-0.002 z"
+         id="path233-6-3"
+         sodipodi:nodetypes="cscccscc"
+         inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
+         inkscape:export-xdpi="74.800003"
+         inkscape:export-ydpi="74.800003" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccsscsc"
+         id="path304-0-7"
+         d="m 227.67666,122.03232 21.75461,0.043 -1.04428,13.26992 c -0.0559,0.71088 -0.29898,0.94753 -1.24237,0.94753 -1.24161,0 -19.02583,-0.0215 -20.82822,-0.0215 0.15498,-0.21283 0.22142,-0.65588 0.22231,-0.66649 l 1.13795,-13.57244 z"
+         style="opacity:0.46590911;fill:none;stroke:url(#linearGradient8319);stroke-width:0.99999976px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccccc"
+         id="path323-7-1"
+         d="m 227.67667,121.87261 -0.77399,10.37822 c 0,0 5.50391,-2.75196 12.38382,-2.75196 6.87989,0 10.31985,-7.62626 10.31985,-7.62626 l -21.92968,0 z"
+         style="fill:#ffffff;fill-opacity:0.0892857;fill-rule:evenodd;stroke:none" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text2989-1-0"
+         y="129.97945"
+         x="257.82571"
+         style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+         xml:space="preserve"><tspan
+           y="129.97945"
+           x="257.82571"
+           id="tspan2991-7-7"
+           sodipodi:role="line">analysis</tspan></text>
+      <text
+         sodipodi:linespacing="125%"
+         id="text4093"
+         y="210.07005"
+         x="257.82571"
+         style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+         xml:space="preserve"><tspan
+           y="210.07005"
+           x="257.82571"
+           id="tspan4095"
+           sodipodi:role="line">DCP-TEST_EN-XX_UK-U_51_2K_CSY_20130218_CSY_OV</tspan></text>
+      <g
+         style="display:inline"
+         id="g5022-5-1-6"
+         transform="matrix(0.01436427,0,0,0.01263066,249.43395,215.34392)">
+        <rect
+           y="-150.69685"
+           x="-1559.2523"
+           height="478.35718"
+           width="1339.6335"
+           id="rect4173-4-6-8"
+           style="opacity:0.40206185;color:#000000;fill:url(#linearGradient8321);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+        <path
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cccc"
+           id="path5058-2-4-1"
+           d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+           style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8323);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+        <path
+           inkscape:connector-curvature="0"
+           style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8325);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+           d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+           id="path5018-0-0-9"
+           sodipodi:nodetypes="cccc" />
+      </g>
+      <path
+         inkscape:connector-curvature="0"
+         d="m 224.29419,216.50737 c 0.0145,0.27619 0.30511,0.55238 0.5813,0.55238 l 20.78329,0 c 0.27619,0 0.53792,-0.27619 0.52346,-0.55238 l -0.62127,-18.06303 c -0.0145,-0.27619 -0.30511,-0.55238 -0.58129,-0.55238 l -8.8043,0 c -0.3218,0 -0.81898,-0.20938 -0.9299,-0.73418 l -0.40561,-1.91935 c -0.10314,-0.48807 -0.58528,-0.68856 -0.86147,-0.68856 l -9.80474,0 c -0.2762,0 -0.53793,0.27619 -0.52347,0.55237 l 0.644,21.40513 z"
+         id="path216-9-6-8"
+         style="fill:url(#radialGradient8327);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient8329);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         sodipodi:nodetypes="ccccccssssccc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9788-7-1-9"
+         d="m 224.76178,205.80962 20.0791,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9784-3-8-7"
+         d="m 224.63943,203.1559 20.19941,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.59864,199.17531 20.23951,0"
+         id="path9778-7-9-2"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.86765,212.44392 19.97503,0"
+         id="path9798-2-8-2"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9800-6-4-8"
+         d="m 224.94923,213.77078 19.89478,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.63943,201.82903 20.19941,0"
+         id="path9782-0-1-2"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9780-1-4-8"
+         d="m 224.61904,200.50217 20.21946,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9776-6-3-9"
+         d="m 224.55977,197.84845 10.13775,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.99999982;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.5277,196.52159 9.80798,0"
+         id="path9774-5-9-0"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.82684,209.7902 20.01517,0"
+         id="path9794-7-8-7"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9792-5-8-8"
+         d="m 224.80256,208.46334 20.03899,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.76178,207.13648 20.0791,0"
+         id="path9790-4-0-1"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.74139,204.48276 20.09915,0"
+         id="path9786-1-8-5"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9796-2-7-8"
+         d="m 224.82684,211.11706 20.01517,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.94923,215.09764 19.89478,0"
+         id="path9802-0-7-6"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.45142858;color:#000000;fill:url(#linearGradient8331);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.21380496;marker:none;visibility:visible;display:block;overflow:visible"
+         d="m 225.32022,216.62454 c 0.0108,0.20714 -0.12002,0.34524 -0.33077,0.27619 l 0,0 c -0.21076,-0.0691 -0.35608,-0.20714 -0.36693,-0.41429 l -0.62875,-21.27254 c -0.0108,-0.20714 0.10959,-0.33223 0.31673,-0.33223 l 9.56803,-0.0316 c 0.20714,0 0.61827,0.19935 0.75162,0.87718 l 0.38047,1.86791 c -0.28332,-0.30867 -0.27811,-0.3182 -0.42297,-0.7674 l -0.26942,-0.83537 c -0.14533,-0.48275 -0.46321,-0.55191 -0.67035,-0.55191 l -8.55014,0 c -0.20714,0 -0.338,0.13809 -0.32715,0.34524 l 0.62229,20.90791 -0.0727,-0.0691 z"
+         id="path219-0-8-1"
+         sodipodi:nodetypes="cccccccccscccccc" />
+      <g
+         style="fill:#ffffff;fill-opacity:0.75706213;fill-rule:nonzero;stroke:none"
+         id="g220-1-3-2"
+         transform="matrix(0.69047422,0,0.03615198,0.69047422,215.54223,192.61273)"
+         inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
+         inkscape:export-xdpi="74.800003"
+         inkscape:export-ydpi="74.800003">
+        <path
+           inkscape:connector-curvature="0"
+           style="fill:#ffffff;fill-opacity:0.50847461"
+           d="m 42.417183,8.5151772 c 0.0051,-0.097113 -0.128161,-0.2469882 -0.235117,-0.2470056 l -13.031401,-0.00212 c 0,0 0.911714,0.5879545 2.201812,0.5962436 l 11.053497,0.07102 c 0.01109,-0.2117278 0.0027,-0.2560322 0.01121,-0.4181395 z"
+           id="path221-4-8-4"
+           sodipodi:nodetypes="cscscs" />
+      </g>
+      <path
+         inkscape:connector-curvature="0"
+         style="color:#000000;fill:url(#linearGradient8333);fill-opacity:1;fill-rule:nonzero;stroke:#3465a4;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:block"
+         d="m 247.6879,217.05351 c 0.75889,-0.0293 1.30236,-0.72732 1.35806,-1.53982 0.5253,-7.66175 1.10088,-14.08592 1.10088,-14.08592 0.0478,-0.16418 -0.11141,-0.32838 -0.31855,-0.32838 l -22.80286,0 c 0,0 -1.22756,14.50716 -1.22756,14.50716 -0.076,0.65153 -0.30916,1.1973 -1.02821,1.44874 l 22.91824,-0.002 z"
+         id="path233-6-3-2"
+         sodipodi:nodetypes="cscccscc"
+         inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
+         inkscape:export-xdpi="74.800003"
+         inkscape:export-ydpi="74.800003" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccsscsc"
+         id="path304-0-7-5"
+         d="m 227.67666,201.76364 21.75461,0.043 -1.04428,13.26992 c -0.0559,0.71088 -0.29898,0.94752 -1.24237,0.94752 -1.24161,0 -19.02583,-0.0215 -20.82822,-0.0215 0.15498,-0.21284 0.22142,-0.65588 0.22231,-0.66649 l 1.13795,-13.57244 z"
+         style="opacity:0.46590911;fill:none;stroke:url(#linearGradient8335);stroke-width:0.99999976px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccccc"
+         id="path323-7-1-8"
+         d="m 227.67667,201.60393 -0.77399,10.37821 c 0,0 5.50391,-2.75196 12.38382,-2.75196 6.87989,0 10.31985,-7.62625 10.31985,-7.62625 l -21.92968,0 z"
+         style="fill:#ffffff;fill-opacity:0.0892857;fill-rule:evenodd;stroke:none" />
+      <g
+         style="display:inline"
+         id="g5022-5-1-6-4"
+         transform="matrix(0.01436427,0,0,0.01263066,249.43395,95.746939)">
+        <rect
+           y="-150.69685"
+           x="-1559.2523"
+           height="478.35718"
+           width="1339.6335"
+           id="rect4173-4-6-8-0"
+           style="opacity:0.40206185;color:#000000;fill:url(#linearGradient8337);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+        <path
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cccc"
+           id="path5058-2-4-1-7"
+           d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+           style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8339);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+        <path
+           inkscape:connector-curvature="0"
+           style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8341);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+           d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+           id="path5018-0-0-9-0"
+           sodipodi:nodetypes="cccc" />
+      </g>
+      <path
+         inkscape:connector-curvature="0"
+         d="m 224.29419,96.910397 c 0.0145,0.276186 0.30511,0.552379 0.5813,0.552379 l 20.78329,0 c 0.27619,0 0.53792,-0.276193 0.52346,-0.552379 l -0.62127,-18.063034 c -0.0145,-0.276192 -0.30511,-0.552385 -0.58129,-0.552385 l -8.8043,0 c -0.3218,0 -0.81898,-0.209372 -0.9299,-0.734172 l -0.40561,-1.919351 c -0.10314,-0.488068 -0.58528,-0.688565 -0.86147,-0.688565 l -9.80474,0 c -0.2762,0 -0.53793,0.276188 -0.52347,0.552377 l 0.644,21.40513 z"
+         id="path216-9-6-8-3"
+         style="fill:url(#radialGradient8343);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient8345);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         sodipodi:nodetypes="ccccccssssccc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9788-7-1-9-4"
+         d="m 224.76178,86.212639 20.0791,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9784-3-8-7-0"
+         d="m 224.63943,83.558918 20.19941,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.59864,79.578337 20.23951,0"
+         id="path9778-7-9-2-0"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.86765,92.84694 19.97503,0"
+         id="path9798-2-8-2-5"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9800-6-4-8-3"
+         d="m 224.94923,94.173801 19.89478,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.63943,82.232058 20.19941,0"
+         id="path9782-0-1-2-1"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9780-1-4-8-0"
+         d="m 224.61904,80.905198 20.21946,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9776-6-3-9-6"
+         d="m 224.55977,78.251477 10.13775,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.99999982;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.5277,76.924617 9.80798,0"
+         id="path9774-5-9-0-7"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.82684,90.19322 20.01517,0"
+         id="path9794-7-8-7-5"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9792-5-8-8-0"
+         d="m 224.80256,88.866359 20.03899,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.76178,87.539499 20.0791,0"
+         id="path9790-4-0-1-0"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.74139,84.885779 20.09915,0"
+         id="path9786-1-8-5-4"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc"
+         id="path9796-2-7-8-5"
+         d="m 224.82684,91.52008 20.01517,0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+         d="m 224.94923,95.500661 19.89478,0"
+         id="path9802-0-7-6-4"
+         sodipodi:nodetypes="cc" />
+      <path
+         inkscape:connector-curvature="0"
+         style="opacity:0.45142858;color:#000000;fill:url(#linearGradient8347);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.21380496;marker:none;visibility:visible;display:block;overflow:visible"
+         d="m 225.32022,97.027565 c 0.0108,0.207136 -0.12002,0.345236 -0.33077,0.276186 l 0,0 c -0.21076,-0.06905 -0.35608,-0.207142 -0.36693,-0.414285 l -0.62875,-21.272544 c -0.0108,-0.207142 0.10959,-0.332227 0.31673,-0.332227 l 9.56803,-0.03164 c 0.20714,0 0.61827,0.199342 0.75162,0.877175 l 0.38047,1.867909 c -0.28332,-0.308668 -0.27811,-0.318195 -0.42297,-0.767397 l -0.26942,-0.835374 c -0.14533,-0.482743 -0.46321,-0.551908 -0.67035,-0.551908 l -8.55014,0 c -0.20714,0 -0.338,0.138094 -0.32715,0.345241 l 0.62229,20.907907 -0.0727,-0.06904 z"
+         id="path219-0-8-1-9"
+         sodipodi:nodetypes="cccccccccscccccc" />
+      <g
+         style="fill:#ffffff;fill-opacity:0.75706213;fill-rule:nonzero;stroke:none"
+         id="g220-1-3-2-5"
+         transform="matrix(0.69047422,0,0.03615198,0.69047422,215.54223,73.015751)"
+         inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
+         inkscape:export-xdpi="74.800003"
+         inkscape:export-ydpi="74.800003">
+        <path
+           inkscape:connector-curvature="0"
+           style="fill:#ffffff;fill-opacity:0.50847461"
+           d="m 42.417183,8.5151772 c 0.0051,-0.097113 -0.128161,-0.2469882 -0.235117,-0.2470056 l -13.031401,-0.00212 c 0,0 0.911714,0.5879545 2.201812,0.5962436 l 11.053497,0.07102 c 0.01109,-0.2117278 0.0027,-0.2560322 0.01121,-0.4181395 z"
+           id="path221-4-8-4-6"
+           sodipodi:nodetypes="cscscs" />
+      </g>
+      <path
+         inkscape:connector-curvature="0"
+         style="color:#000000;fill:url(#linearGradient8349);fill-opacity:1;fill-rule:nonzero;stroke:#3465a4;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:block"
+         d="m 247.6879,97.456533 c 0.75889,-0.02926 1.30236,-0.727319 1.35806,-1.539822 0.5253,-7.661749 1.10088,-14.085916 1.10088,-14.085916 0.0478,-0.164185 -0.11141,-0.328378 -0.31855,-0.328378 l -22.80286,0 c 0,0 -1.22756,14.507155 -1.22756,14.507155 -0.076,0.651534 -0.30916,1.197305 -1.02821,1.448745 l 22.91824,-0.002 z"
+         id="path233-6-3-2-4"
+         sodipodi:nodetypes="cscccscc"
+         inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
+         inkscape:export-xdpi="74.800003"
+         inkscape:export-ydpi="74.800003" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccsscsc"
+         id="path304-0-7-5-8"
+         d="m 227.67666,82.166664 21.75461,0.04299 -1.04428,13.269916 c -0.0559,0.710879 -0.29898,0.947525 -1.24237,0.947525 -1.24161,0 -19.02583,-0.02149 -20.82822,-0.02149 0.15498,-0.212835 0.22142,-0.655881 0.22231,-0.666489 l 1.13795,-13.572441 z"
+         style="opacity:0.46590911;fill:none;stroke:url(#linearGradient8351);stroke-width:0.99999976px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccccc"
+         id="path323-7-1-8-2"
+         d="m 227.67667,82.006949 -0.77399,10.378217 c 0,0 5.50391,-2.751961 12.38382,-2.751961 6.87989,0 10.31985,-7.626256 10.31985,-7.626256 l -21.92968,0 z"
+         style="fill:#ffffff;fill-opacity:0.0892857;fill-rule:evenodd;stroke:none" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text4999"
+         y="91.08889"
+         x="257.82571"
+         style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+         xml:space="preserve"><tspan
+           y="91.08889"
+           x="257.82571"
+           id="tspan5001"
+           sodipodi:role="line">video</tspan></text>
+    </g>
+    <g
+       id="g8353"
+       transform="translate(-205.70245,18.492801)">
+      <g
+         transform="translate(420.76771,186.50829)"
+         id="g6116">
+        <g
+           id="g3373-2"
+           transform="matrix(0.57237779,0,0,0.57237779,-68.034766,192.93901)">
+          <g
+             inkscape:label="Shadow"
+             id="layer6-5">
+            <g
+               id="g6707-8"
+               transform="matrix(0.02105461,0,0,0.02086758,42.85172,41.1536)"
+               style="display:inline">
+              <rect
+                 y="-150.69685"
+                 x="-1559.2523"
+                 height="478.35718"
+                 width="1339.6335"
+                 id="rect6709-6"
+                 style="opacity:0.40206185;color:#000000;fill:url(#linearGradient8609);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+              <path
+                 sodipodi:nodetypes="cccc"
+                 id="path6711-7"
+                 d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+                 style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8611);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+                 inkscape:connector-curvature="0" />
+              <path
+                 style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8613);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+                 d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+                 id="path6713-7"
+                 sodipodi:nodetypes="cccc"
+                 inkscape:connector-curvature="0" />
+            </g>
+          </g>
+          <g
+             id="layer1-5-2"
+             inkscape:label="Base"
+             style="display:inline">
+            <rect
+               ry="1.1490486"
+               y="3.6464462"
+               x="6.6035528"
+               height="40.920494"
+               width="34.875"
+               id="rect15391-2"
+               style="color:#000000;fill:url(#radialGradient8615);fill-opacity:1;fill-rule:nonzero;stroke:url(#radialGradient8617);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible" />
+            <rect
+               rx="0.14904857"
+               ry="0.14904857"
+               y="4.5839462"
+               x="7.6660538"
+               height="38.946384"
+               width="32.775887"
+               id="rect15660-9"
+               style="color:#000000;fill:none;stroke:url(#radialGradient8619);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible" />
+            <g
+               id="g2270-4"
+               transform="translate(0.646447,-0.03798933)">
+              <g
+                 transform="matrix(0.229703,0,0,0.229703,4.967081,4.244972)"
+                 style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.74709785;stroke-miterlimit:4"
+                 id="g1440-1">
+                <radialGradient
+                   gradientUnits="userSpaceOnUse"
+                   fy="114.5684"
+                   fx="20.892099"
+                   r="5.256"
+                   cy="114.5684"
+                   cx="20.892099"
+                   id="radialGradient1442-9">
+                  <stop
+                     id="stop1444-6"
+                     style="stop-color:#F0F0F0"
+                     offset="0" />
+                  <stop
+                     id="stop1446-9"
+                     style="stop-color:#474747"
+                     offset="1" />
+                </radialGradient>
+                <path
+                   id="path1448-8"
+                   d="m 23.428,113.07 c 0,1.973 -1.6,3.572 -3.573,3.572 -1.974,0 -3.573,-1.6 -3.573,-3.572 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                   style="stroke:none"
+                   inkscape:connector-curvature="0" />
+                <radialGradient
+                   gradientUnits="userSpaceOnUse"
+                   fy="64.567902"
+                   fx="20.892099"
+                   r="5.257"
+                   cy="64.567902"
+                   cx="20.892099"
+                   id="radialGradient1450-2">
+                  <stop
+                     id="stop1452-5"
+                     style="stop-color:#F0F0F0"
+                     offset="0" />
+                  <stop
+                     id="stop1454-5"
+                     style="stop-color:#474747"
+                     offset="1" />
+                </radialGradient>
+                <path
+                   id="path1456-4"
+                   d="m 23.428,63.07 c 0,1.973 -1.6,3.573 -3.573,3.573 -1.974,0 -3.573,-1.6 -3.573,-3.573 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                   style="stroke:none"
+                   inkscape:connector-curvature="0" />
+              </g>
+              <path
+                 id="path15570-9"
+                 d="m 9.9950109,29.952326 c 0,0.453204 -0.3675248,0.820499 -0.8207288,0.820499 -0.4534338,0 -0.8207289,-0.367524 -0.8207289,-0.820499 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+                 style="fill:url(#radialGradient8621);fill-rule:nonzero;stroke:none"
+                 inkscape:connector-curvature="0" />
+              <path
+                 id="path15577-1"
+                 d="m 9.9950109,18.467176 c 0,0.453204 -0.3675248,0.820729 -0.8207288,0.820729 -0.4534338,0 -0.8207289,-0.367525 -0.8207289,-0.820729 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+                 style="fill:url(#radialGradient8623);fill-rule:nonzero;stroke:none"
+                 inkscape:connector-curvature="0" />
+            </g>
+            <path
+               sodipodi:nodetypes="cc"
+               id="path15672-2"
+               d="m 11.505723,5.4942766 0,37.9065924"
+               style="fill:none;stroke:#000000;stroke-width:1.72709894;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.01754384"
+               inkscape:connector-curvature="0" />
+            <path
+               sodipodi:nodetypes="cc"
+               id="path15674-5"
+               d="m 12.5,5.0205154 0,38.0177126"
+               style="fill:none;stroke:#ffffff;stroke-width:1.74709785;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.20467828"
+               inkscape:connector-curvature="0" />
+          </g>
+          <g
+             style="display:inline"
+             inkscape:label="Text"
+             id="layer5-0">
+            <g
+               id="g2253-8"
+               transform="matrix(0.909091,0,0,1,2.363628,0)">
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="9"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15686-3"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="11"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15688-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="13"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15690-3"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="15"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15692-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="17"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15694-67"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="19"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15696-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="21"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15698-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="23"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15700-7"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.068204239"
+                 y="25"
+                 x="14.999992"
+                 height="1"
+                 width="9.9000053"
+                 id="rect15732-6"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="29"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15736-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="31"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15738-3"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="33"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15740-5"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="35"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15742-7"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.10609552"
+                 y="37"
+                 x="14.999992"
+                 height="1"
+                 width="15.400014"
+                 id="rect15744-6"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+            </g>
+          </g>
+        </g>
+        <text
+           xml:space="preserve"
+           style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+           x="-39.249401"
+           y="210.45995"
+           id="text4529"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan4531"
+             x="-39.249401"
+             y="210.45995">17318961-7a15-434c-b4a3-3126784bae8d_pkl.xml</tspan></text>
+      </g>
+      <g
+         transform="translate(420.76771,189.64935)"
+         id="g6073">
+        <g
+           id="g3373-2-9"
+           transform="matrix(0.57237779,0,0,0.57237779,-68.034766,229.08978)">
+          <g
+             inkscape:label="Shadow"
+             id="layer6-5-1">
+            <g
+               id="g6707-8-5"
+               transform="matrix(0.02105461,0,0,0.02086758,42.85172,41.1536)"
+               style="display:inline">
+              <rect
+                 y="-150.69685"
+                 x="-1559.2523"
+                 height="478.35718"
+                 width="1339.6335"
+                 id="rect6709-6-0"
+                 style="opacity:0.40206185;color:#000000;fill:url(#linearGradient8625);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+              <path
+                 sodipodi:nodetypes="cccc"
+                 id="path6711-7-3"
+                 d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+                 style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8627);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+                 inkscape:connector-curvature="0" />
+              <path
+                 style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8629);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+                 d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+                 id="path6713-7-6"
+                 sodipodi:nodetypes="cccc"
+                 inkscape:connector-curvature="0" />
+            </g>
+          </g>
+          <g
+             id="layer1-5-2-4"
+             inkscape:label="Base"
+             style="display:inline">
+            <rect
+               ry="1.1490486"
+               y="3.6464462"
+               x="6.6035528"
+               height="40.920494"
+               width="34.875"
+               id="rect15391-2-9"
+               style="color:#000000;fill:url(#radialGradient8631);fill-opacity:1;fill-rule:nonzero;stroke:url(#radialGradient8633);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible" />
+            <rect
+               rx="0.14904857"
+               ry="0.14904857"
+               y="4.5839462"
+               x="7.6660538"
+               height="38.946384"
+               width="32.775887"
+               id="rect15660-9-2"
+               style="color:#000000;fill:none;stroke:url(#radialGradient8635);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible" />
+            <g
+               id="g2270-4-9"
+               transform="translate(0.646447,-0.03798933)">
+              <g
+                 transform="matrix(0.229703,0,0,0.229703,4.967081,4.244972)"
+                 style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.74709785;stroke-miterlimit:4"
+                 id="g1440-1-3">
+                <radialGradient
+                   gradientUnits="userSpaceOnUse"
+                   fy="114.5684"
+                   fx="20.892099"
+                   r="5.256"
+                   cy="114.5684"
+                   cx="20.892099"
+                   id="radialGradient1442-9-4">
+                  <stop
+                     id="stop1444-6-4"
+                     style="stop-color:#F0F0F0"
+                     offset="0" />
+                  <stop
+                     id="stop1446-9-0"
+                     style="stop-color:#474747"
+                     offset="1" />
+                </radialGradient>
+                <path
+                   id="path1448-8-5"
+                   d="m 23.428,113.07 c 0,1.973 -1.6,3.572 -3.573,3.572 -1.974,0 -3.573,-1.6 -3.573,-3.572 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                   style="stroke:none"
+                   inkscape:connector-curvature="0" />
+                <radialGradient
+                   gradientUnits="userSpaceOnUse"
+                   fy="64.567902"
+                   fx="20.892099"
+                   r="5.257"
+                   cy="64.567902"
+                   cx="20.892099"
+                   id="radialGradient1450-2-9">
+                  <stop
+                     id="stop1452-5-6"
+                     style="stop-color:#F0F0F0"
+                     offset="0" />
+                  <stop
+                     id="stop1454-5-3"
+                     style="stop-color:#474747"
+                     offset="1" />
+                </radialGradient>
+                <path
+                   id="path1456-4-4"
+                   d="m 23.428,63.07 c 0,1.973 -1.6,3.573 -3.573,3.573 -1.974,0 -3.573,-1.6 -3.573,-3.573 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                   style="stroke:none"
+                   inkscape:connector-curvature="0" />
+              </g>
+              <path
+                 id="path15570-9-2"
+                 d="m 9.9950109,29.952326 c 0,0.453204 -0.3675248,0.820499 -0.8207288,0.820499 -0.4534338,0 -0.8207289,-0.367524 -0.8207289,-0.820499 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+                 style="fill:url(#radialGradient8637);fill-rule:nonzero;stroke:none"
+                 inkscape:connector-curvature="0" />
+              <path
+                 id="path15577-1-8"
+                 d="m 9.9950109,18.467176 c 0,0.453204 -0.3675248,0.820729 -0.8207288,0.820729 -0.4534338,0 -0.8207289,-0.367525 -0.8207289,-0.820729 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+                 style="fill:url(#radialGradient8639);fill-rule:nonzero;stroke:none"
+                 inkscape:connector-curvature="0" />
+            </g>
+            <path
+               sodipodi:nodetypes="cc"
+               id="path15672-2-8"
+               d="m 11.505723,5.4942766 0,37.9065924"
+               style="fill:none;stroke:#000000;stroke-width:1.72709894;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.01754384"
+               inkscape:connector-curvature="0" />
+            <path
+               sodipodi:nodetypes="cc"
+               id="path15674-5-7"
+               d="m 12.5,5.0205154 0,38.0177126"
+               style="fill:none;stroke:#ffffff;stroke-width:1.74709785;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.20467828"
+               inkscape:connector-curvature="0" />
+          </g>
+          <g
+             style="display:inline"
+             inkscape:label="Text"
+             id="layer5-0-5">
+            <g
+               id="g2253-8-8"
+               transform="matrix(0.909091,0,0,1,2.363628,0)">
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="9"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15686-3-1"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="11"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15688-9-2"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="13"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15690-3-5"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="15"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15692-9-7"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="17"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15694-67-4"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="19"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15696-9-4"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="21"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15698-9-4"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="23"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15700-7-4"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.068204239"
+                 y="25"
+                 x="14.999992"
+                 height="1"
+                 width="9.9000053"
+                 id="rect15732-6-4"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="29"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15736-9-2"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="31"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15738-3-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="33"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15740-5-3"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="35"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15742-7-0"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.10609552"
+                 y="37"
+                 x="14.999992"
+                 height="1"
+                 width="15.400014"
+                 id="rect15744-6-7"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+            </g>
+          </g>
+        </g>
+        <text
+           xml:space="preserve"
+           style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+           x="-39.249401"
+           y="246.64413"
+           id="text4529-7"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             x="-39.249401"
+             y="246.64413"
+             id="tspan4777">7ae68dd6-8347-43f4-91ca-331341378efb_cpl.xml</tspan></text>
+      </g>
+      <g
+         transform="translate(420.76771,192.57832)"
+         id="g5986">
+        <text
+           xml:space="preserve"
+           style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+           x="-39.249401"
+           y="283.90015"
+           id="text4781"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan4783"
+             x="-39.249401"
+             y="283.90015">ASSETMAP.xml</tspan></text>
+        <g
+           id="g3373-2-9-5"
+           transform="matrix(0.57237779,0,0,0.57237779,-68.034766,265.4526)">
+          <g
+             inkscape:label="Shadow"
+             id="layer6-5-1-7">
+            <g
+               id="g6707-8-5-9"
+               transform="matrix(0.02105461,0,0,0.02086758,42.85172,41.1536)"
+               style="display:inline">
+              <rect
+                 y="-150.69685"
+                 x="-1559.2523"
+                 height="478.35718"
+                 width="1339.6335"
+                 id="rect6709-6-0-8"
+                 style="opacity:0.40206185;color:#000000;fill:url(#linearGradient8641);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+              <path
+                 sodipodi:nodetypes="cccc"
+                 id="path6711-7-3-3"
+                 d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+                 style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8643);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+                 inkscape:connector-curvature="0" />
+              <path
+                 style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8645);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+                 d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+                 id="path6713-7-6-0"
+                 sodipodi:nodetypes="cccc"
+                 inkscape:connector-curvature="0" />
+            </g>
+          </g>
+          <g
+             id="layer1-5-2-4-2"
+             inkscape:label="Base"
+             style="display:inline">
+            <rect
+               ry="1.1490486"
+               y="3.6464462"
+               x="6.6035528"
+               height="40.920494"
+               width="34.875"
+               id="rect15391-2-9-2"
+               style="color:#000000;fill:url(#radialGradient8647);fill-opacity:1;fill-rule:nonzero;stroke:url(#radialGradient8649);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible" />
+            <rect
+               rx="0.14904857"
+               ry="0.14904857"
+               y="4.5839462"
+               x="7.6660538"
+               height="38.946384"
+               width="32.775887"
+               id="rect15660-9-2-3"
+               style="color:#000000;fill:none;stroke:url(#radialGradient8651);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible" />
+            <g
+               id="g2270-4-9-6"
+               transform="translate(0.646447,-0.03798933)">
+              <g
+                 transform="matrix(0.229703,0,0,0.229703,4.967081,4.244972)"
+                 style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.74709785;stroke-miterlimit:4"
+                 id="g1440-1-3-7">
+                <radialGradient
+                   gradientUnits="userSpaceOnUse"
+                   fy="114.5684"
+                   fx="20.892099"
+                   r="5.256"
+                   cy="114.5684"
+                   cx="20.892099"
+                   id="radialGradient1442-9-4-1">
+                  <stop
+                     id="stop1444-6-4-5"
+                     style="stop-color:#F0F0F0"
+                     offset="0" />
+                  <stop
+                     id="stop1446-9-0-5"
+                     style="stop-color:#474747"
+                     offset="1" />
+                </radialGradient>
+                <path
+                   id="path1448-8-5-4"
+                   d="m 23.428,113.07 c 0,1.973 -1.6,3.572 -3.573,3.572 -1.974,0 -3.573,-1.6 -3.573,-3.572 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                   style="stroke:none"
+                   inkscape:connector-curvature="0" />
+                <radialGradient
+                   gradientUnits="userSpaceOnUse"
+                   fy="64.567902"
+                   fx="20.892099"
+                   r="5.257"
+                   cy="64.567902"
+                   cx="20.892099"
+                   id="radialGradient1450-2-9-0">
+                  <stop
+                     id="stop1452-5-6-2"
+                     style="stop-color:#F0F0F0"
+                     offset="0" />
+                  <stop
+                     id="stop1454-5-3-6"
+                     style="stop-color:#474747"
+                     offset="1" />
+                </radialGradient>
+                <path
+                   id="path1456-4-4-0"
+                   d="m 23.428,63.07 c 0,1.973 -1.6,3.573 -3.573,3.573 -1.974,0 -3.573,-1.6 -3.573,-3.573 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                   style="stroke:none"
+                   inkscape:connector-curvature="0" />
+              </g>
+              <path
+                 id="path15570-9-2-6"
+                 d="m 9.9950109,29.952326 c 0,0.453204 -0.3675248,0.820499 -0.8207288,0.820499 -0.4534338,0 -0.8207289,-0.367524 -0.8207289,-0.820499 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+                 style="fill:url(#radialGradient8653);fill-rule:nonzero;stroke:none"
+                 inkscape:connector-curvature="0" />
+              <path
+                 id="path15577-1-8-1"
+                 d="m 9.9950109,18.467176 c 0,0.453204 -0.3675248,0.820729 -0.8207288,0.820729 -0.4534338,0 -0.8207289,-0.367525 -0.8207289,-0.820729 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+                 style="fill:url(#radialGradient8655);fill-rule:nonzero;stroke:none"
+                 inkscape:connector-curvature="0" />
+            </g>
+            <path
+               sodipodi:nodetypes="cc"
+               id="path15672-2-8-1"
+               d="m 11.505723,5.4942766 0,37.9065924"
+               style="fill:none;stroke:#000000;stroke-width:1.72709894;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.01754384"
+               inkscape:connector-curvature="0" />
+            <path
+               sodipodi:nodetypes="cc"
+               id="path15674-5-7-1"
+               d="m 12.5,5.0205154 0,38.0177126"
+               style="fill:none;stroke:#ffffff;stroke-width:1.74709785;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.20467828"
+               inkscape:connector-curvature="0" />
+          </g>
+          <g
+             style="display:inline"
+             inkscape:label="Text"
+             id="layer5-0-5-7">
+            <g
+               id="g2253-8-8-3"
+               transform="matrix(0.909091,0,0,1,2.363628,0)">
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="9"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15686-3-1-0"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="11"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15688-9-2-0"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="13"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15690-3-5-4"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="15"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15692-9-7-8"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="17"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15694-67-4-3"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="19"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15696-9-4-0"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="21"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15698-9-4-1"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="23"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15700-7-4-2"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.068204239"
+                 y="25"
+                 x="14.999992"
+                 height="1"
+                 width="9.9000053"
+                 id="rect15732-6-4-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="29"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15736-9-2-0"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="31"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15738-3-9-3"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="33"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15740-5-3-8"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="35"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15742-7-0-8"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.10609552"
+                 y="37"
+                 x="14.999992"
+                 height="1"
+                 width="15.400014"
+                 id="rect15744-6-7-5"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+            </g>
+          </g>
+        </g>
+      </g>
+      <g
+         transform="translate(420.76771,188.62887)"
+         id="g6029">
+        <text
+           xml:space="preserve"
+           style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+           x="-39.249401"
+           y="326.2482"
+           id="text4785"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan4787"
+             x="-39.249401"
+             y="326.2482">dcp_test_audio.mxf</tspan><tspan
+             sodipodi:role="line"
+             x="-39.249401"
+             y="341.2482"
+             id="tspan4789" /></text>
+        <g
+           id="g3373-2-9-5-6"
+           transform="matrix(0.57237779,0,0,0.57237779,-68.034766,308.69385)">
+          <g
+             inkscape:label="Shadow"
+             id="layer6-5-1-7-6">
+            <g
+               id="g6707-8-5-9-7"
+               transform="matrix(0.02105461,0,0,0.02086758,42.85172,41.1536)"
+               style="display:inline">
+              <rect
+                 y="-150.69685"
+                 x="-1559.2523"
+                 height="478.35718"
+                 width="1339.6335"
+                 id="rect6709-6-0-8-6"
+                 style="opacity:0.40206185;color:#000000;fill:url(#linearGradient8657);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+              <path
+                 sodipodi:nodetypes="cccc"
+                 id="path6711-7-3-3-7"
+                 d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+                 style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8659);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+                 inkscape:connector-curvature="0" />
+              <path
+                 style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8661);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+                 d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+                 id="path6713-7-6-0-7"
+                 sodipodi:nodetypes="cccc"
+                 inkscape:connector-curvature="0" />
+            </g>
+          </g>
+          <g
+             id="layer1-5-2-4-2-1"
+             inkscape:label="Base"
+             style="display:inline">
+            <rect
+               ry="1.1490486"
+               y="3.6464462"
+               x="6.6035528"
+               height="40.920494"
+               width="34.875"
+               id="rect15391-2-9-2-9"
+               style="color:#000000;fill:url(#radialGradient8663);fill-opacity:1;fill-rule:nonzero;stroke:url(#radialGradient8665);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible" />
+            <rect
+               rx="0.14904857"
+               ry="0.14904857"
+               y="4.5839462"
+               x="7.6660538"
+               height="38.946384"
+               width="32.775887"
+               id="rect15660-9-2-3-1"
+               style="color:#000000;fill:none;stroke:url(#radialGradient8667);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible" />
+            <g
+               id="g2270-4-9-6-6"
+               transform="translate(0.646447,-0.03798933)">
+              <g
+                 transform="matrix(0.229703,0,0,0.229703,4.967081,4.244972)"
+                 style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.74709785;stroke-miterlimit:4"
+                 id="g1440-1-3-7-2">
+                <radialGradient
+                   gradientUnits="userSpaceOnUse"
+                   fy="114.5684"
+                   fx="20.892099"
+                   r="5.256"
+                   cy="114.5684"
+                   cx="20.892099"
+                   id="radialGradient1442-9-4-1-7">
+                  <stop
+                     id="stop1444-6-4-5-2"
+                     style="stop-color:#F0F0F0"
+                     offset="0" />
+                  <stop
+                     id="stop1446-9-0-5-7"
+                     style="stop-color:#474747"
+                     offset="1" />
+                </radialGradient>
+                <path
+                   id="path1448-8-5-4-6"
+                   d="m 23.428,113.07 c 0,1.973 -1.6,3.572 -3.573,3.572 -1.974,0 -3.573,-1.6 -3.573,-3.572 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                   style="stroke:none"
+                   inkscape:connector-curvature="0" />
+                <radialGradient
+                   gradientUnits="userSpaceOnUse"
+                   fy="64.567902"
+                   fx="20.892099"
+                   r="5.257"
+                   cy="64.567902"
+                   cx="20.892099"
+                   id="radialGradient1450-2-9-0-5">
+                  <stop
+                     id="stop1452-5-6-2-1"
+                     style="stop-color:#F0F0F0"
+                     offset="0" />
+                  <stop
+                     id="stop1454-5-3-6-3"
+                     style="stop-color:#474747"
+                     offset="1" />
+                </radialGradient>
+                <path
+                   id="path1456-4-4-0-9"
+                   d="m 23.428,63.07 c 0,1.973 -1.6,3.573 -3.573,3.573 -1.974,0 -3.573,-1.6 -3.573,-3.573 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                   style="stroke:none"
+                   inkscape:connector-curvature="0" />
+              </g>
+              <path
+                 id="path15570-9-2-6-9"
+                 d="m 9.9950109,29.952326 c 0,0.453204 -0.3675248,0.820499 -0.8207288,0.820499 -0.4534338,0 -0.8207289,-0.367524 -0.8207289,-0.820499 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+                 style="fill:url(#radialGradient8669);fill-rule:nonzero;stroke:none"
+                 inkscape:connector-curvature="0" />
+              <path
+                 id="path15577-1-8-1-5"
+                 d="m 9.9950109,18.467176 c 0,0.453204 -0.3675248,0.820729 -0.8207288,0.820729 -0.4534338,0 -0.8207289,-0.367525 -0.8207289,-0.820729 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+                 style="fill:url(#radialGradient8671);fill-rule:nonzero;stroke:none"
+                 inkscape:connector-curvature="0" />
+            </g>
+            <path
+               sodipodi:nodetypes="cc"
+               id="path15672-2-8-1-9"
+               d="m 11.505723,5.4942766 0,37.9065924"
+               style="fill:none;stroke:#000000;stroke-width:1.72709894;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.01754384"
+               inkscape:connector-curvature="0" />
+            <path
+               sodipodi:nodetypes="cc"
+               id="path15674-5-7-1-2"
+               d="m 12.5,5.0205154 0,38.0177126"
+               style="fill:none;stroke:#ffffff;stroke-width:1.74709785;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.20467828"
+               inkscape:connector-curvature="0" />
+          </g>
+          <g
+             style="display:inline"
+             inkscape:label="Text"
+             id="layer5-0-5-7-6">
+            <g
+               id="g2253-8-8-3-5"
+               transform="matrix(0.909091,0,0,1,2.363628,0)">
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="9"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15686-3-1-0-3"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="11"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15688-9-2-0-2"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="13"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15690-3-5-4-4"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="15"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15692-9-7-8-4"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="17"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15694-67-4-3-4"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="19"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15696-9-4-0-2"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="21"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15698-9-4-1-7"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="23"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15700-7-4-2-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.068204239"
+                 y="25"
+                 x="14.999992"
+                 height="1"
+                 width="9.9000053"
+                 id="rect15732-6-4-9-2"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="29"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15736-9-2-0-3"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="31"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15738-3-9-3-7"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="33"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15740-5-3-8-3"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="35"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15742-7-0-8-1"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.10609552"
+                 y="37"
+                 x="14.999992"
+                 height="1"
+                 width="15.400014"
+                 id="rect15744-6-7-5-5"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+            </g>
+          </g>
+        </g>
+      </g>
+      <g
+         transform="translate(420.76771,183.50693)"
+         id="g6159">
+        <text
+           xml:space="preserve"
+           style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+           x="-39.249401"
+           y="370.66199"
+           id="text4791"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan4793"
+             x="-39.249401"
+             y="370.66199">dcp_test_video.mxf</tspan><tspan
+             sodipodi:role="line"
+             x="-39.249401"
+             y="385.66199"
+             id="tspan4795" /></text>
+        <g
+           id="g3373-2-9-5-6-2"
+           transform="matrix(0.57237779,0,0,0.57237779,-68.034766,353.10764)">
+          <g
+             inkscape:label="Shadow"
+             id="layer6-5-1-7-6-8">
+            <g
+               id="g6707-8-5-9-7-1"
+               transform="matrix(0.02105461,0,0,0.02086758,42.85172,41.1536)"
+               style="display:inline">
+              <rect
+                 y="-150.69685"
+                 x="-1559.2523"
+                 height="478.35718"
+                 width="1339.6335"
+                 id="rect6709-6-0-8-6-0"
+                 style="opacity:0.40206185;color:#000000;fill:url(#linearGradient8673);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+              <path
+                 sodipodi:nodetypes="cccc"
+                 id="path6711-7-3-3-7-3"
+                 d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+                 style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8675);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+                 inkscape:connector-curvature="0" />
+              <path
+                 style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8677);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+                 d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+                 id="path6713-7-6-0-7-1"
+                 sodipodi:nodetypes="cccc"
+                 inkscape:connector-curvature="0" />
+            </g>
+          </g>
+          <g
+             id="layer1-5-2-4-2-1-5"
+             inkscape:label="Base"
+             style="display:inline">
+            <rect
+               ry="1.1490486"
+               y="3.6464462"
+               x="6.6035528"
+               height="40.920494"
+               width="34.875"
+               id="rect15391-2-9-2-9-5"
+               style="color:#000000;fill:url(#radialGradient8679);fill-opacity:1;fill-rule:nonzero;stroke:url(#radialGradient8681);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible" />
+            <rect
+               rx="0.14904857"
+               ry="0.14904857"
+               y="4.5839462"
+               x="7.6660538"
+               height="38.946384"
+               width="32.775887"
+               id="rect15660-9-2-3-1-2"
+               style="color:#000000;fill:none;stroke:url(#radialGradient8683);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible" />
+            <g
+               id="g2270-4-9-6-6-2"
+               transform="translate(0.646447,-0.03798933)">
+              <g
+                 transform="matrix(0.229703,0,0,0.229703,4.967081,4.244972)"
+                 style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.74709785;stroke-miterlimit:4"
+                 id="g1440-1-3-7-2-1">
+                <radialGradient
+                   gradientUnits="userSpaceOnUse"
+                   fy="114.5684"
+                   fx="20.892099"
+                   r="5.256"
+                   cy="114.5684"
+                   cx="20.892099"
+                   id="radialGradient1442-9-4-1-7-7">
+                  <stop
+                     id="stop1444-6-4-5-2-4"
+                     style="stop-color:#F0F0F0"
+                     offset="0" />
+                  <stop
+                     id="stop1446-9-0-5-7-8"
+                     style="stop-color:#474747"
+                     offset="1" />
+                </radialGradient>
+                <path
+                   id="path1448-8-5-4-6-2"
+                   d="m 23.428,113.07 c 0,1.973 -1.6,3.572 -3.573,3.572 -1.974,0 -3.573,-1.6 -3.573,-3.572 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                   style="stroke:none"
+                   inkscape:connector-curvature="0" />
+                <radialGradient
+                   gradientUnits="userSpaceOnUse"
+                   fy="64.567902"
+                   fx="20.892099"
+                   r="5.257"
+                   cy="64.567902"
+                   cx="20.892099"
+                   id="radialGradient1450-2-9-0-5-3">
+                  <stop
+                     id="stop1452-5-6-2-1-6"
+                     style="stop-color:#F0F0F0"
+                     offset="0" />
+                  <stop
+                     id="stop1454-5-3-6-3-3"
+                     style="stop-color:#474747"
+                     offset="1" />
+                </radialGradient>
+                <path
+                   id="path1456-4-4-0-9-0"
+                   d="m 23.428,63.07 c 0,1.973 -1.6,3.573 -3.573,3.573 -1.974,0 -3.573,-1.6 -3.573,-3.573 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                   style="stroke:none"
+                   inkscape:connector-curvature="0" />
+              </g>
+              <path
+                 id="path15570-9-2-6-9-3"
+                 d="m 9.9950109,29.952326 c 0,0.453204 -0.3675248,0.820499 -0.8207288,0.820499 -0.4534338,0 -0.8207289,-0.367524 -0.8207289,-0.820499 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+                 style="fill:url(#radialGradient8685);fill-rule:nonzero;stroke:none"
+                 inkscape:connector-curvature="0" />
+              <path
+                 id="path15577-1-8-1-5-5"
+                 d="m 9.9950109,18.467176 c 0,0.453204 -0.3675248,0.820729 -0.8207288,0.820729 -0.4534338,0 -0.8207289,-0.367525 -0.8207289,-0.820729 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+                 style="fill:url(#radialGradient8687);fill-rule:nonzero;stroke:none"
+                 inkscape:connector-curvature="0" />
+            </g>
+            <path
+               sodipodi:nodetypes="cc"
+               id="path15672-2-8-1-9-2"
+               d="m 11.505723,5.4942766 0,37.9065924"
+               style="fill:none;stroke:#000000;stroke-width:1.72709894;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.01754384"
+               inkscape:connector-curvature="0" />
+            <path
+               sodipodi:nodetypes="cc"
+               id="path15674-5-7-1-2-8"
+               d="m 12.5,5.0205154 0,38.0177126"
+               style="fill:none;stroke:#ffffff;stroke-width:1.74709785;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.20467828"
+               inkscape:connector-curvature="0" />
+          </g>
+          <g
+             style="display:inline"
+             inkscape:label="Text"
+             id="layer5-0-5-7-6-0">
+            <g
+               id="g2253-8-8-3-5-7"
+               transform="matrix(0.909091,0,0,1,2.363628,0)">
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="9"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15686-3-1-0-3-2"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="11"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15688-9-2-0-2-2"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="13"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15690-3-5-4-4-5"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="15"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15692-9-7-8-4-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="17"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15694-67-4-3-4-1"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="19"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15696-9-4-0-2-0"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="21"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15698-9-4-1-7-8"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="23"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15700-7-4-2-9-5"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.068204239"
+                 y="25"
+                 x="14.999992"
+                 height="1"
+                 width="9.9000053"
+                 id="rect15732-6-4-9-2-0"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="29"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15736-9-2-0-3-7"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="31"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15738-3-9-3-7-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="33"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15740-5-3-8-3-0"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="35"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15742-7-0-8-1-5"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.10609552"
+                 y="37"
+                 x="14.999992"
+                 height="1"
+                 width="15.400014"
+                 id="rect15744-6-7-5-5-3"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+            </g>
+          </g>
+        </g>
+      </g>
+      <g
+         transform="translate(420.76771,186.50828)"
+         id="g6203">
+        <text
+           xml:space="preserve"
+           style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+           x="-39.249401"
+           y="407.84564"
+           id="text5705"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan5707"
+             x="-39.249401"
+             y="407.84564">VOLINDEX.xml</tspan></text>
+        <g
+           id="g3373-2-9-5-6-2-3"
+           transform="matrix(0.57237779,0,0,0.57237779,-68.034766,389.3981)">
+          <g
+             inkscape:label="Shadow"
+             id="layer6-5-1-7-6-8-3">
+            <g
+               id="g6707-8-5-9-7-1-2"
+               transform="matrix(0.02105461,0,0,0.02086758,42.85172,41.1536)"
+               style="display:inline">
+              <rect
+                 y="-150.69685"
+                 x="-1559.2523"
+                 height="478.35718"
+                 width="1339.6335"
+                 id="rect6709-6-0-8-6-0-4"
+                 style="opacity:0.40206185;color:#000000;fill:url(#linearGradient8689);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+              <path
+                 sodipodi:nodetypes="cccc"
+                 id="path6711-7-3-3-7-3-1"
+                 d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+                 style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8691);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+                 inkscape:connector-curvature="0" />
+              <path
+                 style="opacity:0.40206185;color:#000000;fill:url(#radialGradient8693);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+                 d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+                 id="path6713-7-6-0-7-1-2"
+                 sodipodi:nodetypes="cccc"
+                 inkscape:connector-curvature="0" />
+            </g>
+          </g>
+          <g
+             id="layer1-5-2-4-2-1-5-9"
+             inkscape:label="Base"
+             style="display:inline">
+            <rect
+               ry="1.1490486"
+               y="3.6464462"
+               x="6.6035528"
+               height="40.920494"
+               width="34.875"
+               id="rect15391-2-9-2-9-5-0"
+               style="color:#000000;fill:url(#radialGradient8695);fill-opacity:1;fill-rule:nonzero;stroke:url(#radialGradient8697);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible" />
+            <rect
+               rx="0.14904857"
+               ry="0.14904857"
+               y="4.5839462"
+               x="7.6660538"
+               height="38.946384"
+               width="32.775887"
+               id="rect15660-9-2-3-1-2-8"
+               style="color:#000000;fill:none;stroke:url(#radialGradient8699);stroke-width:1.74709785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible" />
+            <g
+               id="g2270-4-9-6-6-2-7"
+               transform="translate(0.646447,-0.03798933)">
+              <g
+                 transform="matrix(0.229703,0,0,0.229703,4.967081,4.244972)"
+                 style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.74709785;stroke-miterlimit:4"
+                 id="g1440-1-3-7-2-1-4">
+                <radialGradient
+                   gradientUnits="userSpaceOnUse"
+                   fy="114.5684"
+                   fx="20.892099"
+                   r="5.256"
+                   cy="114.5684"
+                   cx="20.892099"
+                   id="radialGradient1442-9-4-1-7-7-5">
+                  <stop
+                     id="stop1444-6-4-5-2-4-6"
+                     style="stop-color:#F0F0F0"
+                     offset="0" />
+                  <stop
+                     id="stop1446-9-0-5-7-8-8"
+                     style="stop-color:#474747"
+                     offset="1" />
+                </radialGradient>
+                <path
+                   id="path1448-8-5-4-6-2-0"
+                   d="m 23.428,113.07 c 0,1.973 -1.6,3.572 -3.573,3.572 -1.974,0 -3.573,-1.6 -3.573,-3.572 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                   style="stroke:none"
+                   inkscape:connector-curvature="0" />
+                <radialGradient
+                   gradientUnits="userSpaceOnUse"
+                   fy="64.567902"
+                   fx="20.892099"
+                   r="5.257"
+                   cy="64.567902"
+                   cx="20.892099"
+                   id="radialGradient1450-2-9-0-5-3-7">
+                  <stop
+                     id="stop1452-5-6-2-1-6-7"
+                     style="stop-color:#F0F0F0"
+                     offset="0" />
+                  <stop
+                     id="stop1454-5-3-6-3-3-4"
+                     style="stop-color:#474747"
+                     offset="1" />
+                </radialGradient>
+                <path
+                   id="path1456-4-4-0-9-0-3"
+                   d="m 23.428,63.07 c 0,1.973 -1.6,3.573 -3.573,3.573 -1.974,0 -3.573,-1.6 -3.573,-3.573 0,-1.974 1.6,-3.573 3.573,-3.573 1.973,0 3.573,1.6 3.573,3.573 z"
+                   style="stroke:none"
+                   inkscape:connector-curvature="0" />
+              </g>
+              <path
+                 id="path15570-9-2-6-9-3-1"
+                 d="m 9.9950109,29.952326 c 0,0.453204 -0.3675248,0.820499 -0.8207288,0.820499 -0.4534338,0 -0.8207289,-0.367524 -0.8207289,-0.820499 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+                 style="fill:url(#radialGradient8701);fill-rule:nonzero;stroke:none"
+                 inkscape:connector-curvature="0" />
+              <path
+                 id="path15577-1-8-1-5-5-3"
+                 d="m 9.9950109,18.467176 c 0,0.453204 -0.3675248,0.820729 -0.8207288,0.820729 -0.4534338,0 -0.8207289,-0.367525 -0.8207289,-0.820729 0,-0.453434 0.3675248,-0.820729 0.8207289,-0.820729 0.453204,0 0.8207288,0.367525 0.8207288,0.820729 z"
+                 style="fill:url(#radialGradient8703);fill-rule:nonzero;stroke:none"
+                 inkscape:connector-curvature="0" />
+            </g>
+            <path
+               sodipodi:nodetypes="cc"
+               id="path15672-2-8-1-9-2-6"
+               d="m 11.505723,5.4942766 0,37.9065924"
+               style="fill:none;stroke:#000000;stroke-width:1.72709894;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.01754384"
+               inkscape:connector-curvature="0" />
+            <path
+               sodipodi:nodetypes="cc"
+               id="path15674-5-7-1-2-8-3"
+               d="m 12.5,5.0205154 0,38.0177126"
+               style="fill:none;stroke:#ffffff;stroke-width:1.74709785;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.20467828"
+               inkscape:connector-curvature="0" />
+          </g>
+          <g
+             style="display:inline"
+             inkscape:label="Text"
+             id="layer5-0-5-7-6-0-0">
+            <g
+               id="g2253-8-8-3-5-7-1"
+               transform="matrix(0.909091,0,0,1,2.363628,0)">
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="9"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15686-3-1-0-3-2-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="11"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15688-9-2-0-2-2-0"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="13"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15690-3-5-4-4-5-4"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="15"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15692-9-7-8-4-9-2"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="17"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15694-67-4-3-4-1-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="19"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15696-9-4-0-2-0-3"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="21"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15698-9-4-1-7-8-1"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="23"
+                 x="15.000002"
+                 height="1"
+                 width="22.000004"
+                 id="rect15700-7-4-2-9-5-4"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.068204239"
+                 y="25"
+                 x="14.999992"
+                 height="1"
+                 width="9.9000053"
+                 id="rect15732-6-4-9-2-0-8"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="29"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15736-9-2-0-3-7-2"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="31"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15738-3-9-3-7-9-0"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="33"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15740-5-3-8-3-0-5"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.15156493"
+                 y="35"
+                 x="14.999992"
+                 height="1"
+                 width="22.000004"
+                 id="rect15742-7-0-8-1-5-5"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+              <rect
+                 ry="0.065390877"
+                 rx="0.10609552"
+                 y="37"
+                 x="14.999992"
+                 height="1"
+                 width="15.400014"
+                 id="rect15744-6-7-5-5-3-9"
+                 style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970757;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible" />
+            </g>
+          </g>
+        </g>
+      </g>
+    </g>
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 76.217328,153.89436 0,218.95716 21.253829,0"
+       id="path8705"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 114.88266,395.32148 0,213.17695 21.25382,0"
+       id="path8705-8"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 76.803595,172.74211 21.253829,0"
+       id="path8705-81"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 76.803595,213.01043 21.25383,0"
+       id="path8705-81-5"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 76.803595,252.0674 21.25383,0"
+       id="path8705-81-8"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 76.803595,292.85467 21.25383,0"
+       id="path8705-81-59"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 76.803595,332.67077 21.25383,0"
+       id="path8705-81-58"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 115.96554,410.92217 21.25383,0"
+       id="path8705-81-58-3"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 115.96554,453.15907 21.25383,0"
+       id="path8705-81-58-3-7"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 115.96554,491.71557 21.25383,0"
+       id="path8705-81-58-3-3"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 115.96554,530.09682 21.25383,0"
+       id="path8705-81-58-3-5"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 115.96554,568.88355 21.25383,0"
+       id="path8705-81-58-3-8"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#8f5902;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 181.68106,157.3527 12.5586,0 0,192.56519 -13.39584,0"
+       id="path9382"
+       inkscape:connector-curvature="0" />
+    <text
+       xml:space="preserve"
+       style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+       x="205.96103"
+       y="241.07671"
+       id="text9384"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan9386"
+         x="205.96103"
+         y="241.07671">DCP-o-matic's</tspan><tspan
+         sodipodi:role="line"
+         x="205.96103"
+         y="256.07669"
+         id="tspan9388">working</tspan><tspan
+         sodipodi:role="line"
+         x="205.96103"
+         y="271.07669"
+         id="tspan9390">files</tspan></text>
+    <path
+       style="color:#000000;fill:none;stroke:#8f5902;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 444.5744,360.802 12.5586,0 0,272.94023 -13.39584,0"
+       id="path9382-3"
+       inkscape:connector-curvature="0" />
+    <text
+       xml:space="preserve"
+       style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+       x="468.01709"
+       y="500.62109"
+       id="text9410"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan9412"
+         x="468.01709"
+         y="500.62109">DCP</tspan></text>
+  </g>
+</svg>
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 753358c..0000000
+++ /dev/null
@@ -1,939 +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 Totem, mplayer or VLC.
-</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>
-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 <guilabel>L</guilabel>,
-<guilabel>R</guilabel>, <guilabel>T</guilabel> and
-<guilabel>B</guilabel> settings correspond to the left, right, top and
-bottom of the image respectively.  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 cropping in action.
-</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>
-
-</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 right hand
-corner 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>Colour look-up table</title>
-
-<para>
-This 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>
-
-</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>
-As it stands, these filters are somewhat disorganised!  Work is
-ongoing to test them with various content and choose a selection which
-work well for cinema applications.
-</para>
-
-<para>
-If you want to examine them yourself, you may find the A/B option (see
-<xref linkend="sec-ab"/>) useful.
-</para>
-
-<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/doc/manual/screenshots/add-file.png b/doc/manual/screenshots/add-file.png
new file mode 100644 (file)
index 0000000..86901de
Binary files /dev/null and b/doc/manual/screenshots/add-file.png differ
diff --git a/doc/manual/screenshots/audio-map-eg1.png b/doc/manual/screenshots/audio-map-eg1.png
new file mode 100644 (file)
index 0000000..246c2e2
Binary files /dev/null and b/doc/manual/screenshots/audio-map-eg1.png differ
diff --git a/doc/manual/screenshots/audio-map-eg2.png b/doc/manual/screenshots/audio-map-eg2.png
new file mode 100644 (file)
index 0000000..e2a9e1b
Binary files /dev/null and b/doc/manual/screenshots/audio-map-eg2.png differ
diff --git a/doc/manual/screenshots/audio-map-eg3.png b/doc/manual/screenshots/audio-map-eg3.png
new file mode 100644 (file)
index 0000000..a1ebe38
Binary files /dev/null and b/doc/manual/screenshots/audio-map-eg3.png differ
diff --git a/doc/manual/screenshots/audio-plot.png b/doc/manual/screenshots/audio-plot.png
new file mode 100644 (file)
index 0000000..80e2fe0
Binary files /dev/null and b/doc/manual/screenshots/audio-plot.png differ
index 650f67b3cfd17e403554dbe1d83d5c575c791aec..78b216793c20f2b5672930f6f3e10dd75f423218 100644 (file)
Binary files a/doc/manual/screenshots/audio-tab.png and b/doc/manual/screenshots/audio-tab.png differ
diff --git a/doc/manual/screenshots/click-content-selector.png b/doc/manual/screenshots/click-content-selector.png
deleted file mode 100644 (file)
index d8ff34c..0000000
Binary files a/doc/manual/screenshots/click-content-selector.png and /dev/null differ
diff --git a/doc/manual/screenshots/dcp-tab.png b/doc/manual/screenshots/dcp-tab.png
new file mode 100644 (file)
index 0000000..049b58e
Binary files /dev/null and b/doc/manual/screenshots/dcp-tab.png differ
diff --git a/doc/manual/screenshots/examine-content.png b/doc/manual/screenshots/examine-content.png
new file mode 100644 (file)
index 0000000..f3e98e1
Binary files /dev/null and b/doc/manual/screenshots/examine-content.png differ
diff --git a/doc/manual/screenshots/film-tab.png b/doc/manual/screenshots/film-tab.png
deleted file mode 100644 (file)
index 184a17b..0000000
Binary files a/doc/manual/screenshots/film-tab.png and /dev/null differ
index 690abe70b74d3b4c47b8df650a5c70f9a36d56f5..3f3c1e6c08a7bb691fb5112d71683a6c5479ad3d 100644 (file)
Binary files a/doc/manual/screenshots/filters.png and b/doc/manual/screenshots/filters.png differ
index 291e5f4055dc60e41d3ca8a20251ec626ef8f621..29e1f5d338843f0717fa628e6b88c8f5b6a307ad 100644 (file)
Binary files a/doc/manual/screenshots/making-dcp.png and b/doc/manual/screenshots/making-dcp.png differ
diff --git a/doc/manual/screenshots/prefs-metadata.png b/doc/manual/screenshots/prefs-metadata.png
new file mode 100644 (file)
index 0000000..bcfc19e
Binary files /dev/null and b/doc/manual/screenshots/prefs-metadata.png differ
diff --git a/doc/manual/screenshots/prefs-misc.png b/doc/manual/screenshots/prefs-misc.png
new file mode 100644 (file)
index 0000000..ce6e85e
Binary files /dev/null and b/doc/manual/screenshots/prefs-misc.png differ
diff --git a/doc/manual/screenshots/prefs-servers.png b/doc/manual/screenshots/prefs-servers.png
new file mode 100644 (file)
index 0000000..3b7e8e7
Binary files /dev/null and b/doc/manual/screenshots/prefs-servers.png differ
diff --git a/doc/manual/screenshots/prefs-tms.png b/doc/manual/screenshots/prefs-tms.png
new file mode 100644 (file)
index 0000000..ed38783
Binary files /dev/null and b/doc/manual/screenshots/prefs-tms.png differ
index 28706dad1d3813108dd3c3019d2b5a34330345f1..a1e0e083e22d2215a79792c3671f3c46e71e6c22 100644 (file)
Binary files a/doc/manual/screenshots/prefs.png and b/doc/manual/screenshots/prefs.png differ
index 09a8b8edb44a3ac5833fb00734c5e544d8a67dce..4880d8391ad3813fc19688134de6700abf6e3e6a 100644 (file)
Binary files a/doc/manual/screenshots/subtitles-tab.png and b/doc/manual/screenshots/subtitles-tab.png differ
diff --git a/doc/manual/screenshots/timing-tab.png b/doc/manual/screenshots/timing-tab.png
new file mode 100644 (file)
index 0000000..122d01d
Binary files /dev/null and b/doc/manual/screenshots/timing-tab.png differ
index a3e21c9d354b53176e7bbb12dbf8e76699041b3b..31b120a52336d09bcd983a85a1a03943e657590e 100644 (file)
Binary files a/doc/manual/screenshots/video-tab.png and b/doc/manual/screenshots/video-tab.png differ
diff --git a/dvdomatic.desktop.in b/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/ffmpeg-versions b/ffmpeg-versions
deleted file mode 100644 (file)
index caf166c..0000000
+++ /dev/null
@@ -1 +0,0 @@
-6912e7a008acd1464a63b0a00779a3de81b9a8ab       0.64
diff --git a/hacks/build-all-ffmpeg b/hacks/build-all-ffmpeg
new file mode 100755 (executable)
index 0000000..a3d197c
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+FFMPEGS=/home/carl/ffmpeg
+
+# 0.6, 0.7, 0.8 need significant work, I think.
+
+for v in 0.9.2 0.10.4 0.11.1; do
+    PKG_CONFIG_PATH=$FFMPEGS/$v/lib/pkgconfig ./waf configure
+    if [ "$?" != "0" ]; then
+        echo "$v: configure FAIL"
+       exit 1
+    fi
+    ./waf
+    if [ "$?" != "0" ]; then
+        echo "$v: build FAIL"
+       exit 1
+    fi
+    echo "$v: PASS"
+done
+
diff --git a/hacks/optimise/8proc.log b/hacks/optimise/8proc.log
new file mode 100644 (file)
index 0000000..edc40d9
--- /dev/null
@@ -0,0 +1,2427 @@
+Fri Jan 11 19:38:14 2013: DVD-o-matic 0.71pre git d8106aabb6 using libopenjpeg 1.5.0, libavcodec 54.86.100, libavfilter 3.32.100, libavformat 54.59.107, libavutil 52.13.100, libpostproc 52.2.100, libswscale 2.1.103, ImageMagick 6.6.9-7 2012-08-17 Q16 http://www.imagemagick.org, libssh 0.5.2/openssl/zlib, libdcp 0.36pre git e651d843c5
+Fri Jan 11 19:38:14 2013: Starting to make DCP on ip-10-240-125-92
+Fri Jan 11 19:38:14 2013: Content is /mnt/boon_telly.mkv; type video
+Fri Jan 11 19:38:14 2013: Content length 1
+Fri Jan 11 19:38:14 2013: Content digest 72332980e2f9b2fec52e665d9de67f5d
+Fri Jan 11 19:38:14 2013: 8 threads
+Fri Jan 11 19:38:14 2013: J2K bandwidth 200000000
+Fri Jan 11 19:38:14 2013: Transcode job starting
+Fri Jan 11 19:38:14 2013: Audio delay is 0ms
+Fri Jan 11 19:38:14 2013: Will resample audio from 44100 to 47952
+1357933094:340201 encoder thread 0x7fe358024570 sleeps
+1357933094:340494 encoder thread 0x7fe3580a45f0 sleeps
+1357933094:340739 encoder thread 0x7fe3580a4940 sleeps
+1357933094:340894 encoder thread 0x7fe3580a4c90 sleeps
+1357933094:341042 encoder thread 0x7fe3580a5680 sleeps
+1357933094:341172 encoder thread 0x7fe3580a5330 sleeps
+1357933094:341331 encoder thread 0x7fe3580a4fe0 sleeps
+1357933094:341502 encoder thread 0x7fe3580a6090 sleeps
+1357933094:341656 encoder thread 0x7fe3580a63e0 sleeps
+1357933094:341814 encoder thread 0x7fe3580a59d0 sleeps
+1357933094:342030 encoder thread 0x7fe3580a6a80 sleeps
+1357933094:342242 encoder thread 0x7fe3580a6730 sleeps
+1357933094:342402 encoder thread 0x7fe3580a5d40 sleeps
+1357933094:342670 writer sleeps with a queue of 0
+1357933094:342784 encoder thread 0x7fe358031200 sleeps
+1357933094:342894 encoder thread 0x7fe358031550 sleeps
+1357933094:343025 encoder thread 0x7fe358030eb0 sleeps
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0, output at 0
+Fri Jan 11 19:38:14 2013: New graph for 320x240, pixel format 0
+1357933094:359750 Decoder emits 0
+1357933094:359959 adding to queue of 0
+1357933094:360201 encoder thread 0x7fe358024570 wakes with queue of 1
+Fri Jan 11 19:38:14 2013: First video at 0, first audio at 0.279, pushing 12304 audio frames of silence for 2 channels (4 bytes per sample)
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358024570 pops frame 0 from queue
+1357933094:360518 encoder thread 0x7fe358024570 begins local encode of 0
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.033, output at 0.0333667
+1357933094:364961 Decoder emits 1
+1357933094:365141 adding to queue of 0
+1357933094:365349 encoder thread 0x7fe3580a45f0 wakes with queue of 1
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a45f0 pops frame 1 from queue
+1357933094:365531 encoder thread 0x7fe3580a45f0 begins local encode of 1
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.067, output at 0.0667333
+1357933094:366438 Decoder emits 2
+1357933094:366640 adding to queue of 0
+1357933094:366797 encoder thread 0x7fe3580a4940 wakes with queue of 1
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a4940 pops frame 2 from queue
+1357933094:366971 encoder thread 0x7fe3580a4940 begins local encode of 2
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.1, output at 0.1001
+1357933094:367910 Decoder emits 3
+1357933094:368090 adding to queue of 0
+1357933094:368247 encoder thread 0x7fe3580a4c90 wakes with queue of 1
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a4c90 pops frame 3 from queue
+1357933094:368480 encoder thread 0x7fe3580a4c90 begins local encode of 3
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.133, output at 0.133467
+1357933094:369833 Decoder emits 4
+1357933094:369996 adding to queue of 0
+1357933094:370158 encoder thread 0x7fe358031550 wakes with queue of 1
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358031550 pops frame 4 from queue
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.167, output at 0.166833
+1357933094:371634 Decoder emits 5
+1357933094:371799 adding to queue of 0
+1357933094:372031 encoder thread 0x7fe3580a6a80 wakes with queue of 1
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a6a80 pops frame 5 from queue
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.2, output at 0.2002
+1357933094:373749 Decoder emits 6
+1357933094:374043 adding to queue of 0
+1357933094:374300 encoder thread 0x7fe3580a4fe0 wakes with queue of 1
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a4fe0 pops frame 6 from queue
+1357933094:374572 encoder thread 0x7fe3580a4fe0 begins local encode of 6
+Fri Jan 11 19:38:14 2013: Remote encode of 4 on shankly failed (Host not found (authoritative)); thread sleeping for 10s
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358031550 pushes frame 4 back onto queue after failure
+Fri Jan 11 19:38:14 2013: Remote encode of 5 on shankly failed (Host not found (authoritative)); thread sleeping for 10s
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.234, output at 0.233567
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a6a80 pushes frame 5 back onto queue after failure
+1357933094:376385 Decoder emits 7
+1357933094:376576 adding to queue of 2
+1357933094:376741 encoder thread 0x7fe3580a6090 wakes with queue of 3
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a6090 pops frame 5 from queue
+1357933094:376955 encoder thread 0x7fe358030eb0 wakes with queue of 2
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358030eb0 pops frame 4 from queue
+1357933094:377373 encoder thread 0x7fe3580a59d0 wakes with queue of 1
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.267, output at 0.266933
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a59d0 pops frame 7 from queue
+Fri Jan 11 19:38:14 2013: Remote encode of 5 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 10s
+1357933094:378831 encoder thread 0x7fe3580a59d0 begins local encode of 7
+Fri Jan 11 19:38:14 2013: Remote encode of 4 on shankly failed (Host not found (authoritative)); thread sleeping for 10s
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a6090 pushes frame 5 back onto queue after failure
+1357933094:379408 Decoder emits 8
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358030eb0 pushes frame 4 back onto queue after failure
+1357933094:381559 adding to queue of 2
+1357933094:381743 encoder thread 0x7fe3580a5680 wakes with queue of 3
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a5680 pops frame 4 from queue
+1357933094:382048 encoder thread 0x7fe3580a5680 begins local encode of 4
+1357933094:382177 encoder thread 0x7fe3580a5d40 wakes with queue of 2
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a5d40 pops frame 5 from queue
+1357933094:382637 encoder thread 0x7fe3580a6730 wakes with queue of 1
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a6730 pops frame 8 from queue
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.3, output at 0.3003
+Fri Jan 11 19:38:14 2013: Remote encode of 5 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 10s
+1357933094:385624 Decoder emits 9
+Fri Jan 11 19:38:14 2013: Remote encode of 8 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 10s
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a5d40 pushes frame 5 back onto queue after failure
+1357933094:386344 adding to queue of 1
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a6730 pushes frame 8 back onto queue after failure
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.334, output at 0.333667
+1357933094:387790 encoder thread 0x7fe358031200 wakes with queue of 3
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358031200 pops frame 8 from queue
+1357933094:388454 encoder thread 0x7fe3580a63e0 wakes with queue of 2
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a63e0 pops frame 5 from queue
+1357933094:389086 Decoder emits 10
+1357933094:389317 encoder thread 0x7fe3580a5330 wakes with queue of 1
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a5330 pops frame 9 from queue
+1357933094:390132 encoder thread 0x7fe3580a5330 begins local encode of 9
+Fri Jan 11 19:38:14 2013: Remote encode of 8 on shankly failed (Host not found (authoritative)); thread sleeping for 10s
+1357933094:391076 adding to queue of 0
+Fri Jan 11 19:38:14 2013: Remote encode of 5 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 10s
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358031200 pushes frame 8 back onto queue after failure
+Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a63e0 pushes frame 5 back onto queue after failure
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.367, output at 0.367033
+1357933094:393896 Decoder emits 11
+1357933094:394191 adding to queue of 3
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.4, output at 0.4004
+1357933094:396333 Decoder emits 12
+1357933094:396622 adding to queue of 4
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.434, output at 0.433767
+1357933094:398815 Decoder emits 13
+1357933094:399193 adding to queue of 5
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.467, output at 0.467133
+1357933094:401444 Decoder emits 14
+1357933094:401823 adding to queue of 6
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.501, output at 0.5005
+1357933094:404225 Decoder emits 15
+1357933094:404539 adding to queue of 7
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.534, output at 0.533867
+1357933094:406560 Decoder emits 16
+1357933094:406843 adding to queue of 8
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.567, output at 0.567233
+1357933094:408774 Decoder emits 17
+1357933094:409048 adding to queue of 9
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.601, output at 0.6006
+1357933094:411389 Decoder emits 18
+1357933094:411689 adding to queue of 10
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.634, output at 0.633967
+1357933094:413810 Decoder emits 19
+1357933094:414127 adding to queue of 11
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.667, output at 0.667333
+1357933094:416830 Decoder emits 20
+1357933094:417080 adding to queue of 12
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.701, output at 0.7007
+1357933094:419640 Decoder emits 21
+1357933094:419904 adding to queue of 13
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.734, output at 0.734067
+1357933094:421940 Decoder emits 22
+1357933094:422174 adding to queue of 14
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.767, output at 0.767433
+1357933094:423850 Decoder emits 23
+1357933094:424200 adding to queue of 15
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.801, output at 0.8008
+1357933094:425983 Decoder emits 24
+1357933094:426251 adding to queue of 16
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.834, output at 0.834167
+1357933094:428361 Decoder emits 25
+1357933094:428579 adding to queue of 17
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.868, output at 0.867533
+1357933094:430320 Decoder emits 26
+1357933094:430564 adding to queue of 18
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.901, output at 0.9009
+1357933094:432926 Decoder emits 27
+1357933094:433234 adding to queue of 19
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.934, output at 0.934267
+1357933094:435586 Decoder emits 28
+1357933094:435813 adding to queue of 20
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.968, output at 0.967633
+1357933094:438455 Decoder emits 29
+1357933094:440090 adding to queue of 21
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.001, output at 1.001
+1357933094:444491 Decoder emits 30
+1357933094:444860 adding to queue of 22
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.034, output at 1.03437
+1357933094:448724 Decoder emits 31
+1357933094:448976 adding to queue of 23
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.068, output at 1.06773
+1357933094:451685 Decoder emits 32
+1357933094:451946 adding to queue of 24
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.101, output at 1.1011
+1357933094:454092 Decoder emits 33
+1357933094:454334 adding to queue of 25
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.134, output at 1.13447
+1357933094:465182 Decoder emits 34
+1357933094:465635 adding to queue of 26
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.168, output at 1.16783
+1357933094:469982 Decoder emits 35
+1357933094:470438 adding to queue of 27
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.201, output at 1.2012
+1357933094:489678 Decoder emits 36
+1357933094:490046 adding to queue of 28
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.235, output at 1.23457
+1357933094:492329 Decoder emits 37
+1357933094:492583 adding to queue of 29
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.268, output at 1.26793
+1357933094:494702 Decoder emits 38
+1357933094:494982 adding to queue of 30
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.301, output at 1.3013
+1357933094:497256 Decoder emits 39
+1357933094:497654 adding to queue of 31
+Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.335, output at 1.33467
+1357933094:501057 Decoder emits 40
+1357933094:501413 decoder sleeps with queue of 32
+Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 3
+Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 1
+Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 6
+Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 4
+Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 2
+Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 0
+Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 9
+Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 7
+1357933101:993117 encoder thread 0x7fe3580a4c90 finishes local encode of 3
+1357933101:993423 writer wakes with a queue of 1
+1357933102:3937 encoder thread 0x7fe3580a45f0 finishes local encode of 1
+1357933102:7985 decoder wakes with queue of 32
+1357933102:10662 encoder thread 0x7fe3580a4c90 sleeps
+1357933102:10887 encoder thread 0x7fe3580a4fe0 finishes local encode of 6
+1357933102:11039 decoder sleeps with queue of 32
+1357933102:18971 writer sleeps with a queue of 2
+1357933102:22602 encoder thread 0x7fe3580a45f0 sleeps
+1357933102:22763 encoder thread 0x7fe3580a4c90 wakes with queue of 32
+1357933102:25582 writer wakes with a queue of 2
+1357933102:28203 encoder thread 0x7fe3580a5680 finishes local encode of 4
+Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a4c90 pops frame 5 from queue
+1357933102:31001 encoder thread 0x7fe3580a59d0 finishes local encode of 7
+1357933102:37630 encoder thread 0x7fe358024570 finishes local encode of 0
+1357933102:44369 encoder thread 0x7fe3580a4c90 begins local encode of 5
+1357933102:47717 encoder thread 0x7fe3580a4940 finishes local encode of 2
+1357933102:47823 writer sleeps with a queue of 3
+1357933102:47929 encoder thread 0x7fe3580a4fe0 sleeps
+1357933102:47995 decoder wakes with queue of 31
+1357933102:48092 encoder thread 0x7fe3580a5330 finishes local encode of 9
+1357933102:48178 writer wakes with a queue of 3
+1357933102:48251 adding to queue of 31
+1357933102:48377 encoder thread 0x7fe3580a45f0 wakes with queue of 32
+Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a45f0 pops frame 8 from queue
+1357933102:48663 encoder thread 0x7fe3580a45f0 begins local encode of 8
+1357933102:52712 encoder thread 0x7fe3580a5680 sleeps
+Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.368, output at 1.36803
+1357933102:60215 writer sleeps with a queue of 5
+1357933102:60296 encoder thread 0x7fe3580a59d0 sleeps
+1357933102:60355 encoder thread 0x7fe3580a4fe0 wakes with queue of 31
+1357933102:60518 writer wakes with a queue of 5
+1357933102:60573 Decoder emits 41
+Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a4fe0 pops frame 10 from queue
+1357933102:60790 encoder thread 0x7fe3580a4fe0 begins local encode of 10
+1357933102:71805 writer sleeps with a queue of 4
+1357933102:76222 encoder thread 0x7fe358024570 sleeps
+1357933102:83571 writer wakes with a queue of 4
+1357933102:87934 encoder thread 0x7fe3580a4940 sleeps
+1357933102:94813 encoder thread 0x7fe3580a5330 sleeps
+1357933102:94919 encoder thread 0x7fe3580a5680 wakes with queue of 30
+Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a5680 pops frame 11 from queue
+1357933102:95137 encoder thread 0x7fe3580a5680 begins local encode of 11
+1357933102:95206 encoder thread 0x7fe3580a59d0 wakes with queue of 29
+Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a59d0 pops frame 12 from queue
+1357933102:95350 encoder thread 0x7fe3580a59d0 begins local encode of 12
+1357933102:95450 adding to queue of 28
+1357933102:95638 encoder thread 0x7fe358024570 wakes with queue of 29
+Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe358024570 pops frame 13 from queue
+1357933102:95827 encoder thread 0x7fe358024570 begins local encode of 13
+1357933102:95961 encoder thread 0x7fe3580a4940 wakes with queue of 28
+Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a4940 pops frame 14 from queue
+1357933102:96324 encoder thread 0x7fe3580a4940 begins local encode of 14
+Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.401, output at 1.4014
+1357933102:96884 writer sleeps with a queue of 3
+1357933102:97030 Decoder emits 42
+1357933102:109515 encoder thread 0x7fe3580a5330 wakes with queue of 27
+1357933102:109759 writer wakes with a queue of 3
+Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a5330 pops frame 15 from queue
+1357933102:110205 adding to queue of 26
+Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.435, output at 1.43477
+1357933102:112797 Decoder emits 43
+1357933102:113092 adding to queue of 27
+1357933102:113721 writer sleeps with a queue of 2
+1357933102:113967 encoder thread 0x7fe3580a5330 begins local encode of 15
+1357933102:114180 writer wakes with a queue of 2
+Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.468, output at 1.46813
+1357933102:115092 Decoder emits 44
+1357933102:115964 adding to queue of 28
+Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.502, output at 1.5015
+1357933102:118372 Decoder emits 45
+1357933102:118553 writer sleeps with a queue of 1
+1357933102:118736 writer wakes with a queue of 1
+1357933102:118932 adding to queue of 29
+Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.535, output at 1.53487
+1357933102:120998 Decoder emits 46
+1357933102:121257 adding to queue of 30
+1357933102:122434 writer sleeps with a queue of 0
+Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.568, output at 1.56823
+1357933102:123122 Decoder emits 47
+1357933102:123390 adding to queue of 31
+Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.602, output at 1.6016
+1357933102:125079 Decoder emits 48
+1357933102:125306 decoder sleeps with queue of 32
+1357933104:375629 decoder wakes with queue of 32
+1357933104:376068 encoder thread 0x7fe358031550 sleeps
+1357933104:376536 decoder sleeps with queue of 32
+1357933104:376946 encoder thread 0x7fe3580a6a80 sleeps
+1357933104:377378 decoder wakes with queue of 32
+1357933104:377797 decoder sleeps with queue of 32
+1357933104:378186 encoder thread 0x7fe358031550 wakes with queue of 32
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe358031550 pops frame 16 from queue
+1357933104:379015 encoder thread 0x7fe3580a6a80 wakes with queue of 31
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a6a80 pops frame 17 from queue
+1357933104:379776 encoder thread 0x7fe3580a6090 sleeps
+1357933104:380137 decoder wakes with queue of 30
+Fri Jan 11 19:38:24 2013: Remote encode of 16 on shankly failed (Host not found (authoritative)); thread sleeping for 20s
+1357933104:380914 adding to queue of 30
+Fri Jan 11 19:38:24 2013: Remote encode of 17 on shankly failed (Host not found (authoritative)); thread sleeping for 20s
+Fri Jan 11 19:38:24 2013: Source video frame ready; source at 1.635, output at 1.63497
+1357933104:393590 encoder thread 0x7fe358030eb0 sleeps
+1357933104:393986 encoder thread 0x7fe3580a6090 wakes with queue of 31
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a6090 pops frame 18 from queue
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe358031550 pushes frame 16 back onto queue after failure
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a6a80 pushes frame 17 back onto queue after failure
+1357933104:395915 encoder thread 0x7fe3580a5d40 sleeps
+1357933104:396286 encoder thread 0x7fe3580a6730 sleeps
+1357933104:396655 encoder thread 0x7fe358031200 sleeps
+1357933104:397018 encoder thread 0x7fe3580a63e0 sleeps
+1357933104:397354 encoder thread 0x7fe358030eb0 wakes with queue of 32
+Fri Jan 11 19:38:24 2013: Remote encode of 18 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 20s
+1357933104:398091 Decoder emits 49
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe358030eb0 pops frame 17 from queue
+1357933104:398770 encoder thread 0x7fe3580a5d40 wakes with queue of 31
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a5d40 pops frame 16 from queue
+1357933104:399433 encoder thread 0x7fe3580a6730 wakes with queue of 30
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a6730 pops frame 19 from queue
+Fri Jan 11 19:38:24 2013: Remote encode of 17 on shankly failed (Host not found (authoritative)); thread sleeping for 20s
+Fri Jan 11 19:38:24 2013: Remote encode of 16 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 20s
+1357933104:400921 encoder thread 0x7fe358031200 wakes with queue of 29
+Fri Jan 11 19:38:24 2013: Remote encode of 19 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 20s
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe358031200 pops frame 20 from queue
+1357933104:402087 encoder thread 0x7fe3580a63e0 wakes with queue of 28
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a63e0 pops frame 21 from queue
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a6090 pushes frame 18 back onto queue after failure
+Fri Jan 11 19:38:24 2013: Remote encode of 20 on shankly failed (Host not found (authoritative)); thread sleeping for 20s
+1357933104:403658 adding to queue of 28
+Fri Jan 11 19:38:24 2013: Remote encode of 21 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 20s
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe358030eb0 pushes frame 17 back onto queue after failure
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a5d40 pushes frame 16 back onto queue after failure
+Fri Jan 11 19:38:24 2013: Source video frame ready; source at 1.668, output at 1.66833
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a6730 pushes frame 19 back onto queue after failure
+1357933104:405879 Decoder emits 50
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe358031200 pushes frame 20 back onto queue after failure
+Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a63e0 pushes frame 21 back onto queue after failure
+1357933104:406940 decoder sleeps with queue of 34
+Fri Jan 11 19:38:28 2013: Finished locally-encoded frame 8
+1357933108:903758 encoder thread 0x7fe3580a45f0 finishes local encode of 8
+1357933108:904086 writer wakes with a queue of 1
+1357933108:922214 encoder thread 0x7fe3580a45f0 sleeps
+1357933108:922312 decoder wakes with queue of 34
+1357933108:922452 decoder sleeps with queue of 34
+1357933108:922559 encoder thread 0x7fe3580a45f0 wakes with queue of 34
+Fri Jan 11 19:38:28 2013: Encoder thread 0x7fe3580a45f0 pops frame 21 from queue
+1357933108:922709 encoder thread 0x7fe3580a45f0 begins local encode of 21
+1357933108:926212 writer sleeps with a queue of 0
+Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 5
+1357933109:181344 encoder thread 0x7fe3580a4c90 finishes local encode of 5
+Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 15
+Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 13
+Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 12
+Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 10
+1357933109:273438 writer wakes with a queue of 1
+Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 14
+Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 11
+1357933109:274120 encoder thread 0x7fe3580a4c90 sleeps
+1357933109:274347 decoder wakes with queue of 33
+1357933109:274616 encoder thread 0x7fe3580a5330 finishes local encode of 15
+1357933109:274815 encoder thread 0x7fe358024570 finishes local encode of 13
+1357933109:275027 decoder sleeps with queue of 33
+1357933109:275243 encoder thread 0x7fe3580a4c90 wakes with queue of 33
+Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe3580a4c90 pops frame 20 from queue
+1357933109:275540 encoder thread 0x7fe3580a4fe0 finishes local encode of 10
+1357933109:302943 encoder thread 0x7fe3580a59d0 finishes local encode of 12
+1357933109:303092 encoder thread 0x7fe3580a4940 finishes local encode of 14
+1357933109:303216 encoder thread 0x7fe3580a5680 finishes local encode of 11
+1357933109:303343 encoder thread 0x7fe3580a4c90 begins local encode of 20
+1357933109:305539 writer sleeps with a queue of 2
+1357933109:305645 encoder thread 0x7fe3580a5330 sleeps
+1357933109:305779 encoder thread 0x7fe358024570 sleeps
+1357933109:305845 decoder wakes with queue of 32
+1357933109:305940 writer wakes with a queue of 2
+1357933109:306010 decoder sleeps with queue of 32
+1357933109:319371 encoder thread 0x7fe358024570 wakes with queue of 32
+1357933109:319478 encoder thread 0x7fe3580a5680 sleeps
+Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe358024570 pops frame 19 from queue
+1357933109:319729 encoder thread 0x7fe358024570 begins local encode of 19
+1357933109:331703 writer sleeps with a queue of 5
+1357933109:345077 encoder thread 0x7fe3580a59d0 sleeps
+1357933109:358909 encoder thread 0x7fe3580a4fe0 sleeps
+1357933109:359054 writer wakes with a queue of 5
+1357933109:359167 encoder thread 0x7fe3580a5330 wakes with queue of 31
+1357933109:359292 encoder thread 0x7fe3580a4940 sleeps
+Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe3580a5330 pops frame 16 from queue
+1357933109:359566 encoder thread 0x7fe3580a5330 begins local encode of 16
+1357933109:359626 decoder wakes with queue of 30
+1357933109:359802 adding to queue of 30
+1357933109:359993 encoder thread 0x7fe3580a5680 wakes with queue of 31
+Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe3580a5680 pops frame 17 from queue
+1357933109:360297 encoder thread 0x7fe3580a5680 begins local encode of 17
+1357933109:360414 encoder thread 0x7fe3580a59d0 wakes with queue of 30
+Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe3580a59d0 pops frame 18 from queue
+1357933109:360637 encoder thread 0x7fe3580a59d0 begins local encode of 18
+Fri Jan 11 19:38:29 2013: Source video frame ready; source at 1.702, output at 1.7017
+1357933109:361593 Decoder emits 51
+1357933109:362224 writer sleeps with a queue of 4
+1357933109:362368 encoder thread 0x7fe3580a4fe0 wakes with queue of 29
+1357933109:362457 writer wakes with a queue of 4
+Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe3580a4fe0 pops frame 22 from queue
+1357933109:362689 encoder thread 0x7fe3580a4fe0 begins local encode of 22
+1357933109:363838 encoder thread 0x7fe3580a4940 wakes with queue of 28
+Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe3580a4940 pops frame 23 from queue
+1357933109:364204 encoder thread 0x7fe3580a4940 begins local encode of 23
+1357933109:364383 adding to queue of 27
+1357933109:366117 writer sleeps with a queue of 3
+Fri Jan 11 19:38:29 2013: Source video frame ready; source at 1.735, output at 1.73507
+1357933109:366765 Decoder emits 52
+1357933109:367035 adding to queue of 28
+Fri Jan 11 19:38:29 2013: Source video frame ready; source at 1.768, output at 1.76843
+1357933109:368911 Decoder emits 53
+1357933109:369164 adding to queue of 29
+Fri Jan 11 19:38:29 2013: Source video frame ready; source at 1.802, output at 1.8018
+1357933109:371579 writer wakes with a queue of 3
+1357933109:371810 Decoder emits 54
+1357933109:372115 adding to queue of 30
+Fri Jan 11 19:38:29 2013: Source video frame ready; source at 1.835, output at 1.83517
+1357933109:374135 Decoder emits 55
+1357933109:374415 adding to queue of 31
+Fri Jan 11 19:38:29 2013: Source video frame ready; source at 1.869, output at 1.86853
+1357933109:376060 Decoder emits 56
+1357933109:376403 decoder sleeps with queue of 32
+1357933109:389508 writer sleeps with a queue of 2
+1357933109:389828 writer wakes with a queue of 2
+1357933109:393643 writer sleeps with a queue of 1
+1357933109:393940 writer wakes with a queue of 1
+1357933109:397571 writer sleeps with a queue of 0
+Fri Jan 11 19:38:33 2013: Finished locally-encoded frame 21
+1357933113:380530 encoder thread 0x7fe3580a45f0 finishes local encode of 21
+1357933113:380913 encoder thread 0x7fe3580a45f0 sleeps
+1357933113:381429 decoder wakes with queue of 32
+1357933113:381828 writer wakes with a queue of 1
+1357933113:382145 decoder sleeps with queue of 32
+1357933113:382500 encoder thread 0x7fe3580a45f0 wakes with queue of 32
+Fri Jan 11 19:38:33 2013: Encoder thread 0x7fe3580a45f0 pops frame 24 from queue
+1357933113:383231 encoder thread 0x7fe3580a45f0 begins local encode of 24
+1357933113:385816 writer sleeps with a queue of 0
+Fri Jan 11 19:38:35 2013: Finished locally-encoded frame 16
+1357933115:890275 encoder thread 0x7fe3580a5330 finishes local encode of 16
+Fri Jan 11 19:38:35 2013: Finished locally-encoded frame 22
+1357933116:26523 encoder thread 0x7fe3580a5330 sleeps
+Fri Jan 11 19:38:36 2013: Finished locally-encoded frame 23
+1357933116:103278 writer wakes with a queue of 1
+Fri Jan 11 19:38:36 2013: Finished locally-encoded frame 18
+Fri Jan 11 19:38:36 2013: Finished locally-encoded frame 20
+1357933116:136045 encoder thread 0x7fe3580a4fe0 finishes local encode of 22
+Fri Jan 11 19:38:36 2013: Finished locally-encoded frame 19
+1357933116:136315 decoder wakes with queue of 31
+Fri Jan 11 19:38:36 2013: Finished locally-encoded frame 17
+1357933116:136580 encoder thread 0x7fe3580a4940 finishes local encode of 23
+Fri Jan 11 19:38:36 2013: Finished locally-encoded frame 24
+1357933116:136865 adding to queue of 31
+1357933116:136974 encoder thread 0x7fe3580a4c90 finishes local encode of 20
+1357933116:137088 encoder thread 0x7fe3580a59d0 finishes local encode of 18
+1357933116:137241 encoder thread 0x7fe3580a5330 wakes with queue of 32
+Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a5330 pops frame 25 from queue
+1357933116:137426 encoder thread 0x7fe358024570 finishes local encode of 19
+1357933116:137535 encoder thread 0x7fe3580a5330 begins local encode of 25
+1357933116:151350 encoder thread 0x7fe3580a45f0 finishes local encode of 24
+1357933116:164741 encoder thread 0x7fe3580a4940 sleeps
+1357933116:164866 writer sleeps with a queue of 5
+Fri Jan 11 19:38:36 2013: Source video frame ready; source at 1.902, output at 1.9019
+1357933116:165039 encoder thread 0x7fe3580a4fe0 sleeps
+1357933116:165109 encoder thread 0x7fe3580a4c90 sleeps
+1357933116:165172 encoder thread 0x7fe3580a5680 finishes local encode of 17
+1357933116:165240 encoder thread 0x7fe3580a59d0 sleeps
+1357933116:165297 encoder thread 0x7fe358024570 sleeps
+1357933116:165361 encoder thread 0x7fe3580a4940 wakes with queue of 31
+1357933116:165433 writer wakes with a queue of 5
+1357933116:165505 Decoder emits 57
+Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a4940 pops frame 26 from queue
+1357933116:165660 encoder thread 0x7fe3580a4940 begins local encode of 26
+1357933116:165704 encoder thread 0x7fe3580a4fe0 wakes with queue of 30
+Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a4fe0 pops frame 27 from queue
+1357933116:165850 encoder thread 0x7fe3580a4fe0 begins local encode of 27
+1357933116:165901 encoder thread 0x7fe3580a4c90 wakes with queue of 29
+Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a4c90 pops frame 28 from queue
+1357933116:166098 encoder thread 0x7fe3580a4c90 begins local encode of 28
+1357933116:166146 encoder thread 0x7fe3580a59d0 wakes with queue of 28
+Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a59d0 pops frame 29 from queue
+1357933116:166347 encoder thread 0x7fe3580a59d0 begins local encode of 29
+1357933116:166441 encoder thread 0x7fe358024570 wakes with queue of 27
+Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe358024570 pops frame 30 from queue
+1357933116:166582 encoder thread 0x7fe358024570 begins local encode of 30
+1357933116:176479 adding to queue of 26
+Fri Jan 11 19:38:36 2013: Source video frame ready; source at 1.935, output at 1.93527
+1357933116:178524 writer sleeps with a queue of 6
+1357933116:178587 writer wakes with a queue of 6
+1357933116:178665 Decoder emits 58
+1357933116:178906 adding to queue of 27
+Fri Jan 11 19:38:36 2013: Source video frame ready; source at 1.969, output at 1.96863
+1357933116:181499 Decoder emits 59
+1357933116:181653 adding to queue of 28
+1357933116:182321 writer sleeps with a queue of 5
+1357933116:182450 encoder thread 0x7fe3580a45f0 sleeps
+1357933116:182595 encoder thread 0x7fe3580a45f0 wakes with queue of 29
+Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a45f0 pops frame 31 from queue
+1357933116:182755 encoder thread 0x7fe3580a45f0 begins local encode of 31
+Fri Jan 11 19:38:36 2013: Source video frame ready; source at 2.002, output at 2.002
+1357933116:183530 Decoder emits 60
+1357933116:183657 adding to queue of 28
+Fri Jan 11 19:38:36 2013: Source video frame ready; source at 2.035, output at 2.03537
+1357933116:186741 Decoder emits 61
+1357933116:187020 adding to queue of 29
+1357933116:188457 encoder thread 0x7fe3580a5680 sleeps
+1357933116:189864 writer wakes with a queue of 5
+Fri Jan 11 19:38:36 2013: Source video frame ready; source at 2.069, output at 2.06873
+1357933116:192759 encoder thread 0x7fe3580a5680 wakes with queue of 30
+Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a5680 pops frame 32 from queue
+1357933116:193167 encoder thread 0x7fe3580a5680 begins local encode of 32
+1357933116:193338 Decoder emits 62
+1357933116:193598 adding to queue of 29
+1357933116:194932 writer sleeps with a queue of 4
+1357933116:195245 writer wakes with a queue of 4
+Fri Jan 11 19:38:36 2013: Source video frame ready; source at 2.102, output at 2.1021
+1357933116:196145 Decoder emits 63
+1357933116:196502 adding to queue of 30
+Fri Jan 11 19:38:36 2013: Source video frame ready; source at 2.135, output at 2.13547
+1357933116:198708 Decoder emits 64
+1357933116:198947 adding to queue of 31
+Fri Jan 11 19:38:36 2013: Source video frame ready; source at 2.169, output at 2.16883
+1357933116:200702 writer sleeps with a queue of 3
+1357933116:200906 writer wakes with a queue of 3
+1357933116:201299 Decoder emits 65
+1357933116:201648 decoder sleeps with queue of 32
+1357933116:204559 writer sleeps with a queue of 2
+1357933116:204726 writer wakes with a queue of 2
+1357933116:208531 writer sleeps with a queue of 1
+1357933116:208702 writer wakes with a queue of 1
+1357933116:212392 writer sleeps with a queue of 0
+Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 31
+1357933123:218645 encoder thread 0x7fe3580a45f0 finishes local encode of 31
+Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 30
+Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 28
+Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 26
+1357933123:407719 writer wakes with a queue of 1
+Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 27
+1357933123:407921 encoder thread 0x7fe3580a45f0 sleeps
+1357933123:408055 decoder wakes with queue of 32
+Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 29
+1357933123:408313 encoder thread 0x7fe358024570 finishes local encode of 30
+Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 25
+Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 32
+1357933123:408728 encoder thread 0x7fe3580a4c90 finishes local encode of 28
+1357933123:408877 decoder sleeps with queue of 32
+1357933123:409054 encoder thread 0x7fe3580a45f0 wakes with queue of 32
+Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a45f0 pops frame 33 from queue
+1357933123:409261 encoder thread 0x7fe3580a45f0 begins local encode of 33
+1357933123:409461 encoder thread 0x7fe3580a4940 finishes local encode of 26
+1357933123:423780 encoder thread 0x7fe3580a4fe0 finishes local encode of 27
+1357933123:438064 encoder thread 0x7fe3580a5680 finishes local encode of 32
+1357933123:438162 decoder wakes with queue of 31
+1357933123:438320 encoder thread 0x7fe3580a5330 finishes local encode of 25
+1357933123:438404 encoder thread 0x7fe358024570 sleeps
+1357933123:438491 writer sleeps with a queue of 3
+1357933123:438571 encoder thread 0x7fe3580a4940 sleeps
+1357933123:438643 encoder thread 0x7fe3580a59d0 finishes local encode of 29
+1357933123:438723 adding to queue of 31
+1357933123:438820 writer wakes with a queue of 3
+Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.202, output at 2.2022
+1357933123:452255 encoder thread 0x7fe3580a4c90 sleeps
+1357933123:452389 encoder thread 0x7fe358024570 wakes with queue of 32
+1357933123:452482 Decoder emits 66
+Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe358024570 pops frame 34 from queue
+1357933123:452712 encoder thread 0x7fe358024570 begins local encode of 34
+1357933123:452767 encoder thread 0x7fe3580a4940 wakes with queue of 31
+Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a4940 pops frame 35 from queue
+1357933123:452989 encoder thread 0x7fe3580a4940 begins local encode of 35
+1357933123:466394 writer sleeps with a queue of 6
+1357933123:476131 encoder thread 0x7fe3580a4fe0 sleeps
+1357933123:486712 writer wakes with a queue of 6
+1357933123:486858 encoder thread 0x7fe3580a5680 sleeps
+1357933123:487013 encoder thread 0x7fe3580a4fe0 wakes with queue of 30
+1357933123:487118 encoder thread 0x7fe3580a5330 sleeps
+Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a4fe0 pops frame 36 from queue
+1357933123:487345 encoder thread 0x7fe3580a4fe0 begins local encode of 36
+1357933123:487428 encoder thread 0x7fe3580a4c90 wakes with queue of 29
+Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a4c90 pops frame 37 from queue
+1357933123:487643 encoder thread 0x7fe3580a4c90 begins local encode of 37
+1357933123:487765 adding to queue of 28
+Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.236, output at 2.23557
+1357933123:498635 encoder thread 0x7fe3580a59d0 sleeps
+1357933123:498886 Decoder emits 67
+1357933123:499017 encoder thread 0x7fe3580a5680 wakes with queue of 29
+Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a5680 pops frame 38 from queue
+1357933123:499271 encoder thread 0x7fe3580a5680 begins local encode of 38
+1357933123:499364 encoder thread 0x7fe3580a5330 wakes with queue of 28
+Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a5330 pops frame 39 from queue
+1357933123:499644 encoder thread 0x7fe3580a5330 begins local encode of 39
+1357933123:500692 writer sleeps with a queue of 5
+1357933123:500910 writer wakes with a queue of 5
+1357933123:501054 encoder thread 0x7fe3580a59d0 wakes with queue of 27
+Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a59d0 pops frame 40 from queue
+1357933123:501365 encoder thread 0x7fe3580a59d0 begins local encode of 40
+1357933123:501474 adding to queue of 26
+Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.269, output at 2.26893
+1357933123:503979 Decoder emits 68
+1357933123:504243 adding to queue of 27
+1357933123:504600 writer sleeps with a queue of 4
+1357933123:504728 writer wakes with a queue of 4
+Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.302, output at 2.3023
+1357933123:507138 Decoder emits 69
+1357933123:507367 adding to queue of 28
+1357933123:509074 writer sleeps with a queue of 3
+1357933123:509376 writer wakes with a queue of 3
+Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.336, output at 2.33567
+1357933123:510128 Decoder emits 70
+1357933123:510398 adding to queue of 29
+Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.369, output at 2.36903
+1357933123:512603 Decoder emits 71
+1357933123:512861 adding to queue of 30
+Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.402, output at 2.4024
+1357933123:515123 Decoder emits 72
+1357933123:515389 adding to queue of 31
+Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.436, output at 2.43577
+1357933123:517884 Decoder emits 73
+1357933123:518124 decoder sleeps with queue of 32
+1357933123:524203 writer sleeps with a queue of 2
+1357933123:524484 writer wakes with a queue of 2
+1357933123:529054 writer sleeps with a queue of 1
+1357933123:529365 writer wakes with a queue of 1
+1357933123:533776 writer sleeps with a queue of 0
+1357933124:395531 decoder wakes with queue of 32
+1357933124:395971 encoder thread 0x7fe358031550 sleeps
+1357933124:396417 decoder sleeps with queue of 32
+1357933124:396762 encoder thread 0x7fe3580a6a80 sleeps
+1357933124:397154 decoder wakes with queue of 32
+1357933124:397593 decoder sleeps with queue of 32
+1357933124:397957 encoder thread 0x7fe358031550 wakes with queue of 32
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe358031550 pops frame 41 from queue
+1357933124:398719 encoder thread 0x7fe3580a6a80 wakes with queue of 31
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a6a80 pops frame 42 from queue
+Fri Jan 11 19:38:44 2013: Remote encode of 41 on shankly failed (Host not found (authoritative)); thread sleeping for 30s
+Fri Jan 11 19:38:44 2013: Remote encode of 42 on shankly failed (Host not found (authoritative)); thread sleeping for 30s
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe358031550 pushes frame 41 back onto queue after failure
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a6a80 pushes frame 42 back onto queue after failure
+1357933124:403437 encoder thread 0x7fe3580a6090 sleeps
+1357933124:403804 decoder wakes with queue of 32
+1357933124:404217 decoder sleeps with queue of 32
+1357933124:404693 encoder thread 0x7fe3580a6090 wakes with queue of 32
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a6090 pops frame 42 from queue
+1357933124:405429 encoder thread 0x7fe358030eb0 sleeps
+1357933124:405849 decoder wakes with queue of 31
+1357933124:406590 adding to queue of 31
+Fri Jan 11 19:38:44 2013: Remote encode of 42 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 30s
+1357933124:410919 encoder thread 0x7fe3580a5d40 sleeps
+1357933124:411494 encoder thread 0x7fe358030eb0 wakes with queue of 32
+Fri Jan 11 19:38:44 2013: Source video frame ready; source at 2.469, output at 2.46913
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe358030eb0 pops frame 41 from queue
+1357933124:413762 Decoder emits 74
+1357933124:414500 encoder thread 0x7fe3580a6730 sleeps
+1357933124:427061 encoder thread 0x7fe358031200 sleeps
+Fri Jan 11 19:38:44 2013: Remote encode of 41 on shankly failed (Host not found (authoritative)); thread sleeping for 30s
+1357933124:428843 encoder thread 0x7fe3580a63e0 sleeps
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a6090 pushes frame 42 back onto queue after failure
+1357933124:429727 encoder thread 0x7fe3580a5d40 wakes with queue of 32
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a5d40 pops frame 42 from queue
+1357933124:430875 adding to queue of 31
+1357933124:431481 encoder thread 0x7fe3580a6730 wakes with queue of 32
+Fri Jan 11 19:38:44 2013: Remote encode of 42 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 30s
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a6730 pops frame 43 from queue
+1357933124:436047 encoder thread 0x7fe358031200 wakes with queue of 31
+Fri Jan 11 19:38:44 2013: Source video frame ready; source at 2.503, output at 2.5025
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe358031200 pops frame 44 from queue
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe358030eb0 pushes frame 41 back onto queue after failure
+1357933124:439474 Decoder emits 75
+Fri Jan 11 19:38:44 2013: Remote encode of 43 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 30s
+1357933124:440343 encoder thread 0x7fe3580a63e0 wakes with queue of 31
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a63e0 pops frame 41 from queue
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a5d40 pushes frame 42 back onto queue after failure
+Fri Jan 11 19:38:44 2013: Remote encode of 44 on shankly failed (Host not found (authoritative)); thread sleeping for 30s
+1357933124:442532 adding to queue of 31
+Fri Jan 11 19:38:44 2013: Remote encode of 41 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 30s
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a6730 pushes frame 43 back onto queue after failure
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe358031200 pushes frame 44 back onto queue after failure
+Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a63e0 pushes frame 41 back onto queue after failure
+Fri Jan 11 19:38:44 2013: Source video frame ready; source at 2.536, output at 2.53587
+1357933124:447003 Decoder emits 76
+1357933124:447526 decoder sleeps with queue of 35
+Fri Jan 11 19:38:48 2013: Finished locally-encoded frame 33
+1357933128:522199 encoder thread 0x7fe3580a45f0 finishes local encode of 33
+1357933128:522522 writer wakes with a queue of 1
+1357933128:570490 decoder wakes with queue of 35
+1357933128:572143 decoder sleeps with queue of 35
+1357933128:572227 encoder thread 0x7fe3580a45f0 sleeps
+1357933128:572348 encoder thread 0x7fe3580a45f0 wakes with queue of 35
+Fri Jan 11 19:38:48 2013: Encoder thread 0x7fe3580a45f0 pops frame 41 from queue
+1357933128:572508 encoder thread 0x7fe3580a45f0 begins local encode of 41
+1357933128:574402 writer sleeps with a queue of 0
+Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 34
+1357933129:154610 encoder thread 0x7fe358024570 finishes local encode of 34
+1357933129:155254 writer wakes with a queue of 1
+1357933129:167760 encoder thread 0x7fe358024570 sleeps
+1357933129:169522 decoder wakes with queue of 34
+1357933129:172190 decoder sleeps with queue of 34
+1357933129:172446 writer sleeps with a queue of 0
+1357933129:172631 encoder thread 0x7fe358024570 wakes with queue of 34
+Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe358024570 pops frame 44 from queue
+1357933129:172910 encoder thread 0x7fe358024570 begins local encode of 44
+Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 39
+1357933129:223266 encoder thread 0x7fe3580a5330 finishes local encode of 39
+1357933129:223556 writer wakes with a queue of 1
+1357933129:294239 encoder thread 0x7fe3580a5330 sleeps
+1357933129:294364 decoder wakes with queue of 33
+1357933129:294441 decoder sleeps with queue of 33
+1357933129:294532 encoder thread 0x7fe3580a5330 wakes with queue of 33
+Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe3580a5330 pops frame 43 from queue
+1357933129:294693 encoder thread 0x7fe3580a5330 begins local encode of 43
+1357933129:297852 writer sleeps with a queue of 0
+Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 38
+Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 40
+1357933129:466490 encoder thread 0x7fe3580a5680 finishes local encode of 38
+Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 36
+1357933129:522282 encoder thread 0x7fe3580a59d0 finishes local encode of 40
+Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 35
+Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 37
+1357933129:523059 writer wakes with a queue of 1
+1357933129:523326 encoder thread 0x7fe3580a5680 sleeps
+1357933129:523649 decoder wakes with queue of 32
+1357933129:523876 decoder sleeps with queue of 32
+1357933129:523999 encoder thread 0x7fe3580a4940 finishes local encode of 35
+1357933129:528241 encoder thread 0x7fe3580a4fe0 finishes local encode of 36
+1357933129:549790 encoder thread 0x7fe3580a4c90 finishes local encode of 37
+1357933129:549940 encoder thread 0x7fe3580a59d0 sleeps
+1357933129:550044 encoder thread 0x7fe3580a5680 wakes with queue of 32
+Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe3580a5680 pops frame 42 from queue
+1357933129:550316 encoder thread 0x7fe3580a5680 begins local encode of 42
+1357933129:550420 decoder wakes with queue of 31
+1357933129:550552 adding to queue of 31
+1357933129:551794 writer sleeps with a queue of 4
+Fri Jan 11 19:38:49 2013: Source video frame ready; source at 2.569, output at 2.56923
+1357933129:592770 encoder thread 0x7fe3580a4940 sleeps
+1357933129:592953 Decoder emits 77
+1357933129:593076 writer wakes with a queue of 4
+1357933129:593222 encoder thread 0x7fe3580a4c90 sleeps
+1357933129:593369 encoder thread 0x7fe3580a59d0 wakes with queue of 32
+1357933129:593512 encoder thread 0x7fe3580a4fe0 sleeps
+Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe3580a59d0 pops frame 45 from queue
+1357933129:593796 encoder thread 0x7fe3580a59d0 begins local encode of 45
+1357933129:593909 encoder thread 0x7fe3580a4940 wakes with queue of 31
+Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe3580a4940 pops frame 46 from queue
+1357933129:594182 encoder thread 0x7fe3580a4940 begins local encode of 46
+1357933129:594319 adding to queue of 30
+Fri Jan 11 19:38:49 2013: Source video frame ready; source at 2.603, output at 2.6026
+1357933129:596326 encoder thread 0x7fe3580a4c90 wakes with queue of 31
+1357933129:598370 writer sleeps with a queue of 3
+1357933129:598521 Decoder emits 78
+Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe3580a4c90 pops frame 47 from queue
+1357933129:598799 writer wakes with a queue of 3
+1357933129:599014 encoder thread 0x7fe3580a4c90 begins local encode of 47
+1357933129:600090 encoder thread 0x7fe3580a4fe0 wakes with queue of 30
+Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe3580a4fe0 pops frame 48 from queue
+1357933129:600552 adding to queue of 29
+1357933129:601030 encoder thread 0x7fe3580a4fe0 begins local encode of 48
+1357933129:602301 writer sleeps with a queue of 2
+1357933129:604481 writer wakes with a queue of 2
+Fri Jan 11 19:38:49 2013: Source video frame ready; source at 2.636, output at 2.63597
+1357933129:605223 Decoder emits 79
+1357933129:605653 adding to queue of 30
+Fri Jan 11 19:38:49 2013: Source video frame ready; source at 2.669, output at 2.66933
+1357933129:608174 Decoder emits 80
+1357933129:608533 adding to queue of 31
+1357933129:608773 writer sleeps with a queue of 1
+1357933129:609027 writer wakes with a queue of 1
+Fri Jan 11 19:38:49 2013: Source video frame ready; source at 2.703, output at 2.7027
+1357933129:611833 Decoder emits 81
+1357933129:612122 decoder sleeps with queue of 32
+1357933129:613890 writer sleeps with a queue of 0
+Fri Jan 11 19:38:51 2013: Finished locally-encoded frame 41
+1357933131:643683 encoder thread 0x7fe3580a45f0 finishes local encode of 41
+1357933131:644053 encoder thread 0x7fe3580a45f0 sleeps
+1357933131:644379 writer wakes with a queue of 1
+1357933131:644845 decoder wakes with queue of 32
+1357933131:645233 decoder sleeps with queue of 32
+1357933131:645607 encoder thread 0x7fe3580a45f0 wakes with queue of 32
+Fri Jan 11 19:38:51 2013: Encoder thread 0x7fe3580a45f0 pops frame 49 from queue
+1357933131:646289 encoder thread 0x7fe3580a45f0 begins local encode of 49
+1357933131:648638 writer sleeps with a queue of 0
+Fri Jan 11 19:38:54 2013: Finished locally-encoded frame 43
+1357933134:553870 encoder thread 0x7fe3580a5330 finishes local encode of 43
+1357933134:553990 writer wakes with a queue of 1
+1357933134:556931 writer sleeps with a queue of 0
+1357933134:557016 encoder thread 0x7fe3580a5330 sleeps
+1357933134:557111 decoder wakes with queue of 31
+1357933134:557261 adding to queue of 31
+1357933134:557388 encoder thread 0x7fe3580a5330 wakes with queue of 32
+Fri Jan 11 19:38:54 2013: Encoder thread 0x7fe3580a5330 pops frame 50 from queue
+1357933134:557536 encoder thread 0x7fe3580a5330 begins local encode of 50
+Fri Jan 11 19:38:54 2013: Source video frame ready; source at 2.736, output at 2.73607
+1357933134:558867 Decoder emits 82
+1357933134:558981 adding to queue of 31
+Fri Jan 11 19:38:54 2013: Source video frame ready; source at 2.769, output at 2.76943
+1357933134:560985 Decoder emits 83
+1357933134:561079 decoder sleeps with queue of 32
+Fri Jan 11 19:38:54 2013: Finished locally-encoded frame 44
+1357933134:782210 encoder thread 0x7fe358024570 finishes local encode of 44
+1357933134:782324 writer wakes with a queue of 1
+1357933134:782389 decoder wakes with queue of 32
+1357933134:782452 decoder sleeps with queue of 32
+1357933134:782494 encoder thread 0x7fe358024570 sleeps
+1357933134:782545 encoder thread 0x7fe358024570 wakes with queue of 32
+Fri Jan 11 19:38:54 2013: Encoder thread 0x7fe358024570 pops frame 51 from queue
+1357933134:782649 encoder thread 0x7fe358024570 begins local encode of 51
+1357933134:785342 writer sleeps with a queue of 0
+Fri Jan 11 19:38:55 2013: Finished locally-encoded frame 47
+1357933136:19792 encoder thread 0x7fe3580a4c90 finishes local encode of 47
+Fri Jan 11 19:38:56 2013: Finished locally-encoded frame 48
+Fri Jan 11 19:38:56 2013: Finished locally-encoded frame 46
+1357933136:121135 writer wakes with a queue of 1
+Fri Jan 11 19:38:56 2013: Finished locally-encoded frame 45
+1357933136:121397 encoder thread 0x7fe3580a4c90 sleeps
+1357933136:121508 decoder wakes with queue of 31
+Fri Jan 11 19:38:56 2013: Finished locally-encoded frame 42
+1357933136:121681 adding to queue of 31
+1357933136:121738 encoder thread 0x7fe3580a4fe0 finishes local encode of 48
+1357933136:122320 encoder thread 0x7fe3580a59d0 finishes local encode of 45
+1357933136:136126 encoder thread 0x7fe3580a5680 finishes local encode of 42
+Fri Jan 11 19:38:56 2013: Source video frame ready; source at 2.803, output at 2.8028
+1357933136:136343 encoder thread 0x7fe3580a4940 finishes local encode of 46
+1357933136:136434 encoder thread 0x7fe3580a4fe0 sleeps
+1357933136:136495 encoder thread 0x7fe3580a4c90 wakes with queue of 32
+1357933136:136551 Decoder emits 84
+Fri Jan 11 19:38:56 2013: Encoder thread 0x7fe3580a4c90 pops frame 52 from queue
+1357933136:136700 encoder thread 0x7fe3580a4c90 begins local encode of 52
+1357933136:166270 encoder thread 0x7fe3580a59d0 sleeps
+1357933136:187294 encoder thread 0x7fe3580a5680 sleeps
+1357933136:198771 writer sleeps with a queue of 4
+1357933136:198855 encoder thread 0x7fe3580a4940 sleeps
+1357933136:198942 encoder thread 0x7fe3580a4fe0 wakes with queue of 31
+Fri Jan 11 19:38:56 2013: Finished locally-encoded frame 49
+1357933136:199171 writer wakes with a queue of 4
+Fri Jan 11 19:38:56 2013: Encoder thread 0x7fe3580a4fe0 pops frame 53 from queue
+1357933136:199328 encoder thread 0x7fe3580a4fe0 begins local encode of 53
+1357933136:199413 adding to queue of 30
+1357933136:199592 encoder thread 0x7fe3580a59d0 wakes with queue of 31
+Fri Jan 11 19:38:56 2013: Encoder thread 0x7fe3580a59d0 pops frame 54 from queue
+1357933136:199726 encoder thread 0x7fe3580a45f0 finishes local encode of 49
+1357933136:199812 encoder thread 0x7fe3580a5680 wakes with queue of 30
+Fri Jan 11 19:38:56 2013: Encoder thread 0x7fe3580a5680 pops frame 55 from queue
+1357933136:199929 encoder thread 0x7fe3580a5680 begins local encode of 55
+1357933136:200006 encoder thread 0x7fe3580a4940 wakes with queue of 29
+Fri Jan 11 19:38:56 2013: Encoder thread 0x7fe3580a4940 pops frame 56 from queue
+1357933136:200167 encoder thread 0x7fe3580a4940 begins local encode of 56
+Fri Jan 11 19:38:56 2013: Source video frame ready; source at 2.836, output at 2.83617
+1357933136:211315 encoder thread 0x7fe3580a45f0 sleeps
+1357933136:211455 encoder thread 0x7fe3580a45f0 wakes with queue of 28
+1357933136:211508 writer sleeps with a queue of 4
+1357933136:211626 Decoder emits 85
+1357933136:211714 encoder thread 0x7fe3580a59d0 begins local encode of 54
+Fri Jan 11 19:38:56 2013: Encoder thread 0x7fe3580a45f0 pops frame 57 from queue
+1357933136:211907 writer wakes with a queue of 4
+1357933136:211990 adding to queue of 27
+Fri Jan 11 19:38:56 2013: Source video frame ready; source at 2.87, output at 2.86953
+1357933136:213971 Decoder emits 86
+1357933136:214237 adding to queue of 28
+1357933136:215566 writer sleeps with a queue of 3
+1357933136:216640 writer wakes with a queue of 3
+Fri Jan 11 19:38:56 2013: Source video frame ready; source at 2.903, output at 2.9029
+1357933136:217159 Decoder emits 87
+1357933136:217403 adding to queue of 29
+1357933136:218028 encoder thread 0x7fe3580a45f0 begins local encode of 57
+Fri Jan 11 19:38:56 2013: Source video frame ready; source at 2.936, output at 2.93627
+1357933136:219867 Decoder emits 88
+1357933136:220093 adding to queue of 30
+Fri Jan 11 19:38:56 2013: Source video frame ready; source at 2.97, output at 2.96963
+1357933136:222272 Decoder emits 89
+1357933136:222510 adding to queue of 31
+Fri Jan 11 19:38:56 2013: Source video frame ready; source at 3.003, output at 3.003
+1357933136:224745 Decoder emits 90
+1357933136:224989 decoder sleeps with queue of 32
+1357933136:233519 writer sleeps with a queue of 2
+1357933136:233767 writer wakes with a queue of 2
+1357933136:237516 writer sleeps with a queue of 1
+1357933136:237868 writer wakes with a queue of 1
+1357933136:242268 writer sleeps with a queue of 0
+Fri Jan 11 19:38:57 2013: Finished locally-encoded frame 50
+1357933137:284424 encoder thread 0x7fe3580a5330 finishes local encode of 50
+1357933137:284730 writer wakes with a queue of 1
+1357933137:292610 encoder thread 0x7fe3580a5330 sleeps
+1357933137:292956 decoder wakes with queue of 32
+1357933137:293271 decoder sleeps with queue of 32
+1357933137:293578 encoder thread 0x7fe3580a5330 wakes with queue of 32
+Fri Jan 11 19:38:57 2013: Encoder thread 0x7fe3580a5330 pops frame 58 from queue
+1357933137:294222 encoder thread 0x7fe3580a5330 begins local encode of 58
+1357933137:296253 writer sleeps with a queue of 0
+Fri Jan 11 19:38:57 2013: Finished locally-encoded frame 51
+1357933137:372268 encoder thread 0x7fe358024570 finishes local encode of 51
+1357933137:372706 writer wakes with a queue of 1
+1357933137:386056 encoder thread 0x7fe358024570 sleeps
+1357933137:386379 decoder wakes with queue of 31
+1357933137:386777 adding to queue of 31
+1357933137:387177 encoder thread 0x7fe358024570 wakes with queue of 32
+Fri Jan 11 19:38:57 2013: Encoder thread 0x7fe358024570 pops frame 59 from queue
+1357933137:387861 encoder thread 0x7fe358024570 begins local encode of 59
+Fri Jan 11 19:38:57 2013: Source video frame ready; source at 3.036, output at 3.03637
+1357933137:389941 writer sleeps with a queue of 0
+1357933137:390254 Decoder emits 91
+1357933137:390637 adding to queue of 31
+Fri Jan 11 19:38:57 2013: Source video frame ready; source at 3.07, output at 3.06973
+1357933137:392303 Decoder emits 92
+1357933137:392631 decoder sleeps with queue of 32
+Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 55
+1357933142:115338 encoder thread 0x7fe3580a5680 finishes local encode of 55
+1357933142:115697 writer wakes with a queue of 1
+1357933142:131598 writer sleeps with a queue of 0
+1357933142:131861 encoder thread 0x7fe3580a5680 sleeps
+1357933142:131999 decoder wakes with queue of 32
+1357933142:132227 decoder sleeps with queue of 32
+1357933142:132353 encoder thread 0x7fe3580a5680 wakes with queue of 32
+Fri Jan 11 19:39:02 2013: Encoder thread 0x7fe3580a5680 pops frame 60 from queue
+1357933142:132509 encoder thread 0x7fe3580a5680 begins local encode of 60
+Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 57
+1357933142:652200 encoder thread 0x7fe3580a45f0 finishes local encode of 57
+1357933142:652478 writer wakes with a queue of 1
+1357933142:670430 encoder thread 0x7fe3580a45f0 sleeps
+1357933142:672161 decoder wakes with queue of 31
+1357933142:672536 adding to queue of 31
+1357933142:674747 writer sleeps with a queue of 0
+Fri Jan 11 19:39:02 2013: Source video frame ready; source at 3.103, output at 3.1031
+1357933142:674991 encoder thread 0x7fe3580a45f0 wakes with queue of 32
+Fri Jan 11 19:39:02 2013: Encoder thread 0x7fe3580a45f0 pops frame 61 from queue
+1357933142:675160 encoder thread 0x7fe3580a45f0 begins local encode of 61
+1357933142:675251 Decoder emits 93
+1357933142:675422 adding to queue of 31
+Fri Jan 11 19:39:02 2013: Source video frame ready; source at 3.136, output at 3.13647
+1357933142:678756 Decoder emits 94
+1357933142:678875 decoder sleeps with queue of 32
+Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 56
+Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 52
+Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 53
+1357933142:884663 encoder thread 0x7fe3580a4940 finishes local encode of 56
+Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 54
+1357933142:899652 encoder thread 0x7fe3580a4c90 finishes local encode of 52
+1357933142:899866 writer wakes with a queue of 1
+1357933142:900134 encoder thread 0x7fe3580a4940 sleeps
+1357933142:900310 encoder thread 0x7fe3580a59d0 finishes local encode of 54
+1357933142:900505 decoder wakes with queue of 32
+1357933142:900675 decoder sleeps with queue of 32
+1357933142:914829 encoder thread 0x7fe3580a4c90 sleeps
+1357933142:915010 encoder thread 0x7fe3580a4940 wakes with queue of 32
+Fri Jan 11 19:39:02 2013: Encoder thread 0x7fe3580a4940 pops frame 62 from queue
+1357933142:915345 encoder thread 0x7fe3580a4940 begins local encode of 62
+1357933142:936039 encoder thread 0x7fe3580a59d0 sleeps
+1357933142:974305 decoder wakes with queue of 31
+1357933142:987770 writer sleeps with a queue of 2
+Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 58
+Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 59
+1357933142:988424 adding to queue of 31
+1357933142:988714 writer wakes with a queue of 2
+1357933142:989115 encoder thread 0x7fe3580a4c90 wakes with queue of 32
+Fri Jan 11 19:39:02 2013: Encoder thread 0x7fe3580a4c90 pops frame 63 from queue
+1357933142:989600 encoder thread 0x7fe3580a4c90 begins local encode of 63
+1357933142:989810 encoder thread 0x7fe3580a5330 finishes local encode of 58
+1357933142:990161 encoder thread 0x7fe3580a59d0 wakes with queue of 31
+1357933142:990494 encoder thread 0x7fe358024570 finishes local encode of 59
+Fri Jan 11 19:39:02 2013: Source video frame ready; source at 3.17, output at 3.16983
+1357933142:991069 Decoder emits 95
+Fri Jan 11 19:39:02 2013: Encoder thread 0x7fe3580a59d0 pops frame 64 from queue
+1357933142:991534 encoder thread 0x7fe3580a59d0 begins local encode of 64
+1357933143:2971 writer sleeps with a queue of 3
+1357933143:9064 encoder thread 0x7fe3580a5330 sleeps
+1357933143:9313 encoder thread 0x7fe358024570 sleeps
+1357933143:9452 adding to queue of 30
+1357933143:9608 writer wakes with a queue of 3
+1357933143:9804 encoder thread 0x7fe3580a4fe0 finishes local encode of 53
+1357933143:9990 encoder thread 0x7fe3580a5330 wakes with queue of 31
+Fri Jan 11 19:39:03 2013: Encoder thread 0x7fe3580a5330 pops frame 65 from queue
+1357933143:10511 encoder thread 0x7fe3580a5330 begins local encode of 65
+1357933143:10803 encoder thread 0x7fe358024570 wakes with queue of 30
+Fri Jan 11 19:39:03 2013: Encoder thread 0x7fe358024570 pops frame 66 from queue
+Fri Jan 11 19:39:03 2013: Source video frame ready; source at 3.203, output at 3.2032
+1357933143:11848 Decoder emits 96
+1357933143:12061 encoder thread 0x7fe3580a4fe0 sleeps
+1357933143:12370 adding to queue of 29
+1357933143:12447 encoder thread 0x7fe358024570 begins local encode of 66
+1357933143:13252 writer sleeps with a queue of 3
+1357933143:13540 encoder thread 0x7fe3580a4fe0 wakes with queue of 30
+1357933143:13753 writer wakes with a queue of 3
+Fri Jan 11 19:39:03 2013: Source video frame ready; source at 3.237, output at 3.23657
+1357933143:14476 Decoder emits 97
+Fri Jan 11 19:39:03 2013: Encoder thread 0x7fe3580a4fe0 pops frame 67 from queue
+1357933143:15875 encoder thread 0x7fe3580a4fe0 begins local encode of 67
+1357933143:16079 adding to queue of 29
+Fri Jan 11 19:39:03 2013: Source video frame ready; source at 3.27, output at 3.26993
+1357933143:18286 Decoder emits 98
+1357933143:18493 writer sleeps with a queue of 2
+1357933143:18734 adding to queue of 30
+Fri Jan 11 19:39:03 2013: Source video frame ready; source at 3.303, output at 3.3033
+1357933143:21059 Decoder emits 99
+1357933143:21320 adding to queue of 31
+Fri Jan 11 19:39:03 2013: Source video frame ready; source at 3.337, output at 3.33667
+1357933143:27968 writer wakes with a queue of 2
+1357933143:28422 Decoder emits 100
+1357933143:29904 decoder sleeps with queue of 32
+1357933143:32086 writer sleeps with a queue of 1
+1357933143:32329 writer wakes with a queue of 1
+1357933143:46423 writer sleeps with a queue of 0
+Fri Jan 11 19:39:05 2013: Finished locally-encoded frame 60
+1357933145:40526 encoder thread 0x7fe3580a5680 finishes local encode of 60
+1357933145:40958 writer wakes with a queue of 1
+1357933145:54534 encoder thread 0x7fe3580a5680 sleeps
+1357933145:54905 decoder wakes with queue of 32
+1357933145:55214 decoder sleeps with queue of 32
+1357933145:55589 encoder thread 0x7fe3580a5680 wakes with queue of 32
+Fri Jan 11 19:39:05 2013: Encoder thread 0x7fe3580a5680 pops frame 68 from queue
+1357933145:56270 encoder thread 0x7fe3580a5680 begins local encode of 68
+1357933145:58332 writer sleeps with a queue of 0
+Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 65
+1357933149:516036 encoder thread 0x7fe3580a5330 finishes local encode of 65
+Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 61
+1357933149:544443 decoder wakes with queue of 31
+1357933149:544635 encoder thread 0x7fe3580a5330 sleeps
+1357933149:544726 adding to queue of 31
+1357933149:544802 writer wakes with a queue of 1
+1357933149:545457 encoder thread 0x7fe3580a45f0 finishes local encode of 61
+Fri Jan 11 19:39:09 2013: Source video frame ready; source at 3.37, output at 3.37003
+1357933149:563307 encoder thread 0x7fe3580a5330 wakes with queue of 32
+Fri Jan 11 19:39:09 2013: Encoder thread 0x7fe3580a5330 pops frame 69 from queue
+1357933149:563572 encoder thread 0x7fe3580a5330 begins local encode of 69
+1357933149:563682 Decoder emits 101
+1357933149:563935 adding to queue of 31
+1357933149:565648 writer sleeps with a queue of 1
+1357933149:565771 writer wakes with a queue of 1
+Fri Jan 11 19:39:09 2013: Source video frame ready; source at 3.403, output at 3.4034
+1357933149:567160 Decoder emits 102
+1357933149:567387 decoder sleeps with queue of 32
+1357933149:567530 encoder thread 0x7fe3580a45f0 sleeps
+1357933149:567617 encoder thread 0x7fe3580a45f0 wakes with queue of 32
+Fri Jan 11 19:39:09 2013: Encoder thread 0x7fe3580a45f0 pops frame 70 from queue
+1357933149:567830 encoder thread 0x7fe3580a45f0 begins local encode of 70
+1357933149:581549 writer sleeps with a queue of 0
+Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 63
+Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 62
+1357933149:935739 encoder thread 0x7fe3580a4c90 finishes local encode of 63
+Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 68
+Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 64
+1357933149:995417 encoder thread 0x7fe3580a4940 finishes local encode of 62
+Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 67
+1357933149:995680 writer wakes with a queue of 1
+Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 66
+1357933149:995936 encoder thread 0x7fe3580a5680 finishes local encode of 68
+1357933149:996084 decoder wakes with queue of 31
+1357933149:996281 encoder thread 0x7fe3580a4c90 sleeps
+1357933149:996491 adding to queue of 31
+1357933149:997365 encoder thread 0x7fe358024570 finishes local encode of 66
+1357933150:10655 encoder thread 0x7fe3580a59d0 finishes local encode of 64
+1357933150:23649 encoder thread 0x7fe3580a4fe0 finishes local encode of 67
+Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.437, output at 3.43677
+1357933150:23841 encoder thread 0x7fe3580a4940 sleeps
+1357933150:23931 writer sleeps with a queue of 3
+1357933150:23995 encoder thread 0x7fe3580a5680 sleeps
+1357933150:24056 encoder thread 0x7fe3580a4c90 wakes with queue of 32
+1357933150:24114 Decoder emits 103
+1357933150:24194 writer wakes with a queue of 3
+Fri Jan 11 19:39:10 2013: Encoder thread 0x7fe3580a4c90 pops frame 71 from queue
+1357933150:24342 encoder thread 0x7fe3580a4c90 begins local encode of 71
+1357933150:27909 encoder thread 0x7fe358024570 sleeps
+1357933150:28026 encoder thread 0x7fe3580a4940 wakes with queue of 31
+Fri Jan 11 19:39:10 2013: Encoder thread 0x7fe3580a4940 pops frame 72 from queue
+1357933150:28216 encoder thread 0x7fe3580a4940 begins local encode of 72
+1357933150:28275 encoder thread 0x7fe3580a5680 wakes with queue of 30
+Fri Jan 11 19:39:10 2013: Encoder thread 0x7fe3580a5680 pops frame 73 from queue
+1357933150:28418 encoder thread 0x7fe3580a5680 begins local encode of 73
+1357933150:28475 adding to queue of 29
+Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.47, output at 3.47013
+1357933150:53366 writer sleeps with a queue of 4
+1357933150:53618 encoder thread 0x7fe3580a59d0 sleeps
+1357933150:53732 encoder thread 0x7fe3580a4fe0 sleeps
+1357933150:53830 encoder thread 0x7fe358024570 wakes with queue of 30
+1357933150:53957 writer wakes with a queue of 4
+1357933150:54040 Decoder emits 104
+Fri Jan 11 19:39:10 2013: Encoder thread 0x7fe358024570 pops frame 74 from queue
+1357933150:54310 encoder thread 0x7fe358024570 begins local encode of 74
+1357933150:54398 encoder thread 0x7fe3580a59d0 wakes with queue of 29
+Fri Jan 11 19:39:10 2013: Encoder thread 0x7fe3580a59d0 pops frame 75 from queue
+1357933150:54635 encoder thread 0x7fe3580a59d0 begins local encode of 75
+1357933150:54754 encoder thread 0x7fe3580a4fe0 wakes with queue of 28
+Fri Jan 11 19:39:10 2013: Encoder thread 0x7fe3580a4fe0 pops frame 76 from queue
+1357933150:55191 encoder thread 0x7fe3580a4fe0 begins local encode of 76
+1357933150:55375 adding to queue of 27
+Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.504, output at 3.5035
+1357933150:58456 Decoder emits 105
+1357933150:58844 adding to queue of 28
+Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.537, output at 3.53687
+1357933150:60887 Decoder emits 106
+1357933150:61233 adding to queue of 29
+Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.57, output at 3.57023
+1357933150:64285 Decoder emits 107
+1357933150:64633 adding to queue of 30
+Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.604, output at 3.6036
+1357933150:66537 Decoder emits 108
+1357933150:66676 writer sleeps with a queue of 3
+1357933150:67009 writer wakes with a queue of 3
+1357933150:68451 adding to queue of 31
+Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.637, output at 3.63697
+1357933150:72083 Decoder emits 109
+1357933150:72341 decoder sleeps with queue of 32
+1357933150:81537 writer sleeps with a queue of 2
+1357933150:81991 writer wakes with a queue of 2
+1357933150:87900 writer sleeps with a queue of 1
+1357933150:90060 writer wakes with a queue of 1
+1357933150:94749 writer sleeps with a queue of 0
+1357933154:401667 encoder thread 0x7fe358031550 sleeps
+1357933154:402110 decoder wakes with queue of 32
+1357933154:402594 decoder sleeps with queue of 32
+1357933154:403034 encoder thread 0x7fe3580a6a80 sleeps
+1357933154:403384 encoder thread 0x7fe358031550 wakes with queue of 32
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe358031550 pops frame 77 from queue
+1357933154:404149 decoder wakes with queue of 31
+1357933154:404586 adding to queue of 31
+1357933154:405021 encoder thread 0x7fe3580a6a80 wakes with queue of 32
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a6a80 pops frame 78 from queue
+Fri Jan 11 19:39:14 2013: Remote encode of 77 on shankly failed (Host not found (authoritative)); thread sleeping for 40s
+Fri Jan 11 19:39:14 2013: Source video frame ready; source at 3.67, output at 3.67033
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe358031550 pushes frame 77 back onto queue after failure
+1357933154:407061 Decoder emits 110
+Fri Jan 11 19:39:14 2013: Remote encode of 78 on shankly failed (Host not found (authoritative)); thread sleeping for 40s
+1357933154:407920 decoder sleeps with queue of 32
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a6a80 pushes frame 78 back onto queue after failure
+1357933154:429739 encoder thread 0x7fe3580a6090 sleeps
+1357933154:430165 decoder wakes with queue of 33
+1357933154:430503 decoder sleeps with queue of 33
+1357933154:430863 encoder thread 0x7fe3580a6090 wakes with queue of 33
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a6090 pops frame 78 from queue
+Fri Jan 11 19:39:14 2013: Remote encode of 78 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 40s
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a6090 pushes frame 78 back onto queue after failure
+1357933154:439557 encoder thread 0x7fe358030eb0 sleeps
+1357933154:439866 decoder wakes with queue of 33
+1357933154:440151 decoder sleeps with queue of 33
+1357933154:440511 encoder thread 0x7fe358030eb0 wakes with queue of 33
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe358030eb0 pops frame 78 from queue
+1357933154:441997 encoder thread 0x7fe3580a5d40 sleeps
+1357933154:442300 decoder wakes with queue of 32
+Fri Jan 11 19:39:14 2013: Remote encode of 78 on shankly failed (Host not found (authoritative)); thread sleeping for 40s
+1357933154:443063 decoder sleeps with queue of 32
+1357933154:443393 encoder thread 0x7fe3580a5d40 wakes with queue of 32
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a5d40 pops frame 77 from queue
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe358030eb0 pushes frame 78 back onto queue after failure
+1357933154:444645 decoder wakes with queue of 32
+1357933154:445028 encoder thread 0x7fe3580a6730 sleeps
+1357933154:445343 decoder sleeps with queue of 32
+Fri Jan 11 19:39:14 2013: Remote encode of 77 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 40s
+1357933154:445962 encoder thread 0x7fe358031200 sleeps
+1357933154:446264 encoder thread 0x7fe3580a6730 wakes with queue of 32
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a6730 pops frame 78 from queue
+1357933154:446887 decoder wakes with queue of 31
+1357933154:447235 adding to queue of 31
+1357933154:447642 encoder thread 0x7fe3580a63e0 sleeps
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a5d40 pushes frame 77 back onto queue after failure
+Fri Jan 11 19:39:14 2013: Remote encode of 78 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 40s
+1357933154:448719 encoder thread 0x7fe358031200 wakes with queue of 33
+Fri Jan 11 19:39:14 2013: Source video frame ready; source at 3.704, output at 3.7037
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe358031200 pops frame 77 from queue
+1357933154:449617 Decoder emits 111
+1357933154:449964 encoder thread 0x7fe3580a63e0 wakes with queue of 32
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a63e0 pops frame 79 from queue
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a6730 pushes frame 78 back onto queue after failure
+Fri Jan 11 19:39:14 2013: Remote encode of 77 on shankly failed (Host not found (authoritative)); thread sleeping for 40s
+1357933154:451408 decoder sleeps with queue of 32
+Fri Jan 11 19:39:14 2013: Remote encode of 79 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 40s
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe358031200 pushes frame 77 back onto queue after failure
+Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a63e0 pushes frame 79 back onto queue after failure
+Fri Jan 11 19:39:16 2013: Finished locally-encoded frame 69
+1357933156:856781 encoder thread 0x7fe3580a5330 finishes local encode of 69
+1357933156:866630 encoder thread 0x7fe3580a5330 sleeps
+1357933156:866855 decoder wakes with queue of 34
+1357933156:866944 decoder sleeps with queue of 34
+1357933156:867031 encoder thread 0x7fe3580a5330 wakes with queue of 34
+Fri Jan 11 19:39:16 2013: Encoder thread 0x7fe3580a5330 pops frame 79 from queue
+1357933156:867173 encoder thread 0x7fe3580a5330 begins local encode of 79
+1357933156:904295 writer wakes with a queue of 1
+1357933156:909270 writer sleeps with a queue of 0
+Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 70
+Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 74
+1357933157:96701 encoder thread 0x7fe3580a45f0 finishes local encode of 70
+Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 73
+1357933157:188013 encoder thread 0x7fe358024570 finishes local encode of 74
+1357933157:223228 writer wakes with a queue of 1
+Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 76
+Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 75
+1357933157:269634 encoder thread 0x7fe3580a5680 finishes local encode of 73
+1357933157:269830 decoder wakes with queue of 33
+1357933157:270010 encoder thread 0x7fe3580a45f0 sleeps
+Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 72
+Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 71
+1357933157:270414 encoder thread 0x7fe3580a4fe0 finishes local encode of 76
+1357933157:270539 decoder sleeps with queue of 33
+1357933157:270694 encoder thread 0x7fe3580a59d0 finishes local encode of 75
+1357933157:274635 encoder thread 0x7fe3580a4c90 finishes local encode of 71
+1357933157:301852 encoder thread 0x7fe3580a45f0 wakes with queue of 33
+1357933157:301968 encoder thread 0x7fe358024570 sleeps
+1357933157:302077 encoder thread 0x7fe3580a5680 sleeps
+1357933157:302195 writer sleeps with a queue of 3
+Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe3580a45f0 pops frame 77 from queue
+1357933157:302423 writer wakes with a queue of 3
+1357933157:316504 encoder thread 0x7fe3580a45f0 begins local encode of 77
+1357933157:316677 encoder thread 0x7fe3580a4fe0 sleeps
+1357933157:316810 decoder wakes with queue of 32
+1357933157:316930 decoder sleeps with queue of 32
+1357933157:317071 encoder thread 0x7fe358024570 wakes with queue of 32
+Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe358024570 pops frame 78 from queue
+1357933157:317338 encoder thread 0x7fe358024570 begins local encode of 78
+1357933157:317453 encoder thread 0x7fe3580a5680 wakes with queue of 31
+Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe3580a5680 pops frame 80 from queue
+1357933157:317752 encoder thread 0x7fe3580a5680 begins local encode of 80
+1357933157:344719 encoder thread 0x7fe3580a59d0 sleeps
+1357933157:344933 writer sleeps with a queue of 4
+1357933157:345033 encoder thread 0x7fe3580a4c90 sleeps
+1357933157:345123 encoder thread 0x7fe3580a4fe0 wakes with queue of 30
+1357933157:345226 encoder thread 0x7fe3580a4940 finishes local encode of 72
+1357933157:345377 writer wakes with a queue of 4
+Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe3580a4fe0 pops frame 81 from queue
+1357933157:345640 encoder thread 0x7fe3580a4fe0 begins local encode of 81
+1357933157:345736 decoder wakes with queue of 29
+1357933157:345883 adding to queue of 29
+1357933157:346083 encoder thread 0x7fe3580a59d0 wakes with queue of 30
+Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe3580a59d0 pops frame 82 from queue
+1357933157:346331 encoder thread 0x7fe3580a59d0 begins local encode of 82
+1357933157:346915 encoder thread 0x7fe3580a4c90 wakes with queue of 29
+Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe3580a4c90 pops frame 83 from queue
+1357933157:347248 encoder thread 0x7fe3580a4c90 begins local encode of 83
+1357933157:347463 encoder thread 0x7fe3580a4940 sleeps
+Fri Jan 11 19:39:17 2013: Source video frame ready; source at 3.737, output at 3.73707
+1357933157:349555 Decoder emits 112
+1357933157:349668 encoder thread 0x7fe3580a4940 wakes with queue of 28
+Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe3580a4940 pops frame 84 from queue
+1357933157:349977 encoder thread 0x7fe3580a4940 begins local encode of 84
+1357933157:350093 writer sleeps with a queue of 4
+1357933157:350265 adding to queue of 27
+Fri Jan 11 19:39:17 2013: Source video frame ready; source at 3.77, output at 3.77043
+1357933157:352268 Decoder emits 113
+1357933157:352530 adding to queue of 28
+Fri Jan 11 19:39:17 2013: Source video frame ready; source at 3.804, output at 3.8038
+1357933157:363455 Decoder emits 114
+1357933157:363872 adding to queue of 29
+Fri Jan 11 19:39:17 2013: Source video frame ready; source at 3.837, output at 3.83717
+1357933157:366499 Decoder emits 115
+1357933157:366809 adding to queue of 30
+Fri Jan 11 19:39:17 2013: Source video frame ready; source at 3.871, output at 3.87053
+1357933157:369799 Decoder emits 116
+1357933157:370025 writer wakes with a queue of 4
+1357933157:370281 adding to queue of 31
+Fri Jan 11 19:39:17 2013: Source video frame ready; source at 3.904, output at 3.9039
+1357933157:372780 Decoder emits 117
+1357933157:373003 decoder sleeps with queue of 32
+1357933157:374537 writer sleeps with a queue of 3
+1357933157:374715 writer wakes with a queue of 3
+1357933157:378775 writer sleeps with a queue of 2
+1357933157:379025 writer wakes with a queue of 2
+1357933157:398922 writer sleeps with a queue of 1
+1357933157:399123 writer wakes with a queue of 1
+1357933157:403505 writer sleeps with a queue of 0
+Fri Jan 11 19:39:22 2013: Finished locally-encoded frame 79
+1357933162:300558 encoder thread 0x7fe3580a5330 finishes local encode of 79
+1357933162:300646 encoder thread 0x7fe3580a5330 sleeps
+1357933162:300695 decoder wakes with queue of 32
+1357933162:300770 writer wakes with a queue of 1
+1357933162:300888 decoder sleeps with queue of 32
+1357933162:300962 encoder thread 0x7fe3580a5330 wakes with queue of 32
+Fri Jan 11 19:39:22 2013: Encoder thread 0x7fe3580a5330 pops frame 85 from queue
+1357933162:301073 encoder thread 0x7fe3580a5330 begins local encode of 85
+1357933162:304126 writer sleeps with a queue of 0
+Fri Jan 11 19:39:23 2013: Finished locally-encoded frame 84
+1357933163:759332 encoder thread 0x7fe3580a4940 finishes local encode of 84
+1357933163:759615 writer wakes with a queue of 1
+1357933163:778025 encoder thread 0x7fe3580a4940 sleeps
+1357933163:778119 decoder wakes with queue of 31
+1357933163:778328 adding to queue of 31
+1357933163:778494 encoder thread 0x7fe3580a4940 wakes with queue of 32
+Fri Jan 11 19:39:23 2013: Encoder thread 0x7fe3580a4940 pops frame 86 from queue
+1357933163:778654 encoder thread 0x7fe3580a4940 begins local encode of 86
+Fri Jan 11 19:39:23 2013: Source video frame ready; source at 3.937, output at 3.93727
+1357933163:781912 writer sleeps with a queue of 0
+1357933163:782018 Decoder emits 118
+1357933163:782253 adding to queue of 31
+Fri Jan 11 19:39:23 2013: Source video frame ready; source at 3.971, output at 3.97063
+1357933163:784596 Decoder emits 119
+1357933163:784843 decoder sleeps with queue of 32
+Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 82
+Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 80
+1357933164:244969 encoder thread 0x7fe3580a59d0 finishes local encode of 82
+Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 78
+Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 81
+Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 83
+Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 77
+1357933164:264693 writer wakes with a queue of 1
+1357933164:264886 encoder thread 0x7fe3580a59d0 sleeps
+1357933164:265091 decoder wakes with queue of 32
+1357933164:265315 encoder thread 0x7fe358024570 finishes local encode of 78
+1357933164:265582 encoder thread 0x7fe3580a4fe0 finishes local encode of 81
+1357933164:265740 decoder sleeps with queue of 32
+1357933164:265936 encoder thread 0x7fe3580a59d0 wakes with queue of 32
+Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe3580a59d0 pops frame 87 from queue
+1357933164:266248 encoder thread 0x7fe3580a59d0 begins local encode of 87
+1357933164:266399 encoder thread 0x7fe3580a4c90 finishes local encode of 83
+1357933164:278864 encoder thread 0x7fe3580a45f0 finishes local encode of 77
+1357933164:279034 encoder thread 0x7fe358024570 sleeps
+1357933164:279158 encoder thread 0x7fe3580a4fe0 sleeps
+1357933164:279258 decoder wakes with queue of 31
+1357933164:279402 adding to queue of 31
+1357933164:279905 encoder thread 0x7fe3580a5680 finishes local encode of 80
+1357933164:294035 writer sleeps with a queue of 4
+Fri Jan 11 19:39:24 2013: Source video frame ready; source at 4.004, output at 4.004
+1357933164:294328 encoder thread 0x7fe3580a45f0 sleeps
+1357933164:294452 encoder thread 0x7fe3580a4c90 sleeps
+1357933164:294537 encoder thread 0x7fe358024570 wakes with queue of 32
+1357933164:294620 writer wakes with a queue of 4
+1357933164:294704 Decoder emits 120
+Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe358024570 pops frame 88 from queue
+1357933164:294928 encoder thread 0x7fe358024570 begins local encode of 88
+1357933164:295016 encoder thread 0x7fe3580a4fe0 wakes with queue of 31
+Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe3580a4fe0 pops frame 89 from queue
+1357933164:295295 encoder thread 0x7fe3580a4fe0 begins local encode of 89
+1357933164:295415 encoder thread 0x7fe3580a45f0 wakes with queue of 30
+Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe3580a45f0 pops frame 90 from queue
+1357933164:295651 encoder thread 0x7fe3580a45f0 begins local encode of 90
+1357933164:295748 encoder thread 0x7fe3580a4c90 wakes with queue of 29
+Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe3580a4c90 pops frame 91 from queue
+1357933164:296038 encoder thread 0x7fe3580a4c90 begins local encode of 91
+1357933164:298408 writer sleeps with a queue of 4
+1357933164:298530 adding to queue of 28
+Fri Jan 11 19:39:24 2013: Source video frame ready; source at 4.037, output at 4.03737
+1357933164:301288 Decoder emits 121
+1357933164:301533 adding to queue of 29
+1357933164:301787 writer wakes with a queue of 4
+1357933164:301913 encoder thread 0x7fe3580a5680 sleeps
+1357933164:302318 encoder thread 0x7fe3580a5680 wakes with queue of 30
+Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe3580a5680 pops frame 92 from queue
+1357933164:302822 encoder thread 0x7fe3580a5680 begins local encode of 92
+Fri Jan 11 19:39:24 2013: Source video frame ready; source at 4.071, output at 4.07073
+1357933164:304770 Decoder emits 122
+1357933164:304991 adding to queue of 29
+1357933164:306671 writer sleeps with a queue of 3
+Fri Jan 11 19:39:24 2013: Source video frame ready; source at 4.104, output at 4.1041
+1357933164:307658 Decoder emits 123
+1357933164:307843 writer wakes with a queue of 3
+1357933164:308164 adding to queue of 30
+Fri Jan 11 19:39:24 2013: Source video frame ready; source at 4.137, output at 4.13747
+1357933164:311097 Decoder emits 124
+1357933164:311403 adding to queue of 31
+1357933164:313011 writer sleeps with a queue of 2
+1357933164:314222 writer wakes with a queue of 2
+Fri Jan 11 19:39:24 2013: Source video frame ready; source at 4.171, output at 4.17083
+1357933164:315088 Decoder emits 125
+1357933164:315362 decoder sleeps with queue of 32
+1357933164:318075 writer sleeps with a queue of 1
+1357933164:318317 writer wakes with a queue of 1
+1357933164:333907 writer sleeps with a queue of 0
+Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 85
+1357933164:943495 encoder thread 0x7fe3580a5330 finishes local encode of 85
+1357933164:943772 writer wakes with a queue of 1
+1357933164:951312 encoder thread 0x7fe3580a5330 sleeps
+1357933164:951576 decoder wakes with queue of 32
+1357933164:951869 decoder sleeps with queue of 32
+1357933164:952173 encoder thread 0x7fe3580a5330 wakes with queue of 32
+Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe3580a5330 pops frame 93 from queue
+1357933164:952848 encoder thread 0x7fe3580a5330 begins local encode of 93
+1357933164:954847 writer sleeps with a queue of 0
+Fri Jan 11 19:39:28 2013: Finished locally-encoded frame 86
+1357933168:218472 encoder thread 0x7fe3580a4940 finishes local encode of 86
+1357933168:218894 writer wakes with a queue of 1
+1357933168:234453 encoder thread 0x7fe3580a4940 sleeps
+1357933168:234819 decoder wakes with queue of 31
+1357933168:235189 adding to queue of 31
+1357933168:235585 encoder thread 0x7fe3580a4940 wakes with queue of 32
+Fri Jan 11 19:39:28 2013: Encoder thread 0x7fe3580a4940 pops frame 94 from queue
+Fri Jan 11 19:39:28 2013: Source video frame ready; source at 4.204, output at 4.2042
+1357933168:236976 encoder thread 0x7fe3580a4940 begins local encode of 94
+1357933168:237346 Decoder emits 126
+1357933168:237769 adding to queue of 31
+1357933168:238332 writer sleeps with a queue of 0
+Fri Jan 11 19:39:28 2013: Source video frame ready; source at 4.238, output at 4.23757
+1357933168:239315 Decoder emits 127
+1357933168:239673 decoder sleeps with queue of 32
+Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 88
+1357933171:278675 encoder thread 0x7fe358024570 finishes local encode of 88
+Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 89
+1357933171:389144 encoder thread 0x7fe358024570 sleeps
+Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 91
+1357933171:486733 decoder wakes with queue of 32
+1357933171:500331 writer wakes with a queue of 1
+1357933171:511296 encoder thread 0x7fe3580a4fe0 finishes local encode of 89
+Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 87
+Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 90
+1357933171:536686 encoder thread 0x7fe3580a4c90 finishes local encode of 91
+Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 92
+Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 93
+1357933171:536987 decoder sleeps with queue of 32
+Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 94
+1357933171:537223 encoder thread 0x7fe3580a59d0 finishes local encode of 87
+1357933171:537285 encoder thread 0x7fe358024570 wakes with queue of 32
+Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe358024570 pops frame 95 from queue
+1357933171:537438 encoder thread 0x7fe358024570 begins local encode of 95
+1357933171:550605 encoder thread 0x7fe3580a45f0 finishes local encode of 90
+1357933171:566467 encoder thread 0x7fe3580a5680 finishes local encode of 92
+1357933171:579791 encoder thread 0x7fe3580a5330 finishes local encode of 93
+1357933171:579924 decoder wakes with queue of 31
+1357933171:580091 writer sleeps with a queue of 3
+1357933171:580171 adding to queue of 31
+1357933171:580224 encoder thread 0x7fe3580a4fe0 sleeps
+1357933171:580318 encoder thread 0x7fe3580a59d0 sleeps
+1357933171:580411 encoder thread 0x7fe3580a4940 finishes local encode of 94
+1357933171:580486 writer wakes with a queue of 3
+1357933171:580539 encoder thread 0x7fe3580a4c90 sleeps
+1357933171:580609 encoder thread 0x7fe3580a4fe0 wakes with queue of 32
+Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a4fe0 pops frame 96 from queue
+1357933171:580805 encoder thread 0x7fe3580a4fe0 begins local encode of 96
+1357933171:580865 encoder thread 0x7fe3580a59d0 wakes with queue of 31
+Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a59d0 pops frame 97 from queue
+1357933171:581232 encoder thread 0x7fe3580a59d0 begins local encode of 97
+1357933171:589731 encoder thread 0x7fe3580a45f0 sleeps
+1357933171:589881 encoder thread 0x7fe3580a4c90 wakes with queue of 30
+Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.271, output at 4.27093
+Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a4c90 pops frame 98 from queue
+1357933171:590335 encoder thread 0x7fe3580a4c90 begins local encode of 98
+1357933171:605346 Decoder emits 128
+1357933171:627210 writer sleeps with a queue of 6
+1357933171:627432 encoder thread 0x7fe3580a5680 sleeps
+1357933171:627569 encoder thread 0x7fe3580a5330 sleeps
+1357933171:627650 encoder thread 0x7fe3580a45f0 wakes with queue of 29
+1357933171:627736 encoder thread 0x7fe3580a4940 sleeps
+1357933171:627844 writer wakes with a queue of 6
+Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a45f0 pops frame 99 from queue
+1357933171:628058 encoder thread 0x7fe3580a45f0 begins local encode of 99
+1357933171:628124 adding to queue of 28
+1357933171:628508 encoder thread 0x7fe3580a5680 wakes with queue of 29
+Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a5680 pops frame 100 from queue
+1357933171:628769 encoder thread 0x7fe3580a5680 begins local encode of 100
+Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.304, output at 4.3043
+1357933171:630460 Decoder emits 129
+1357933171:632234 writer sleeps with a queue of 5
+1357933171:632351 encoder thread 0x7fe3580a5330 wakes with queue of 28
+1357933171:632446 writer wakes with a queue of 5
+Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a5330 pops frame 101 from queue
+1357933171:632713 encoder thread 0x7fe3580a5330 begins local encode of 101
+1357933171:633893 encoder thread 0x7fe3580a4940 wakes with queue of 27
+Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a4940 pops frame 102 from queue
+1357933171:634248 encoder thread 0x7fe3580a4940 begins local encode of 102
+1357933171:634389 adding to queue of 26
+1357933171:636023 writer sleeps with a queue of 4
+Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.338, output at 4.33767
+1357933171:637681 Decoder emits 130
+1357933171:637913 adding to queue of 27
+Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.371, output at 4.37103
+1357933171:639817 Decoder emits 131
+1357933171:639950 writer wakes with a queue of 4
+1357933171:640304 adding to queue of 28
+Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.404, output at 4.4044
+1357933171:642416 Decoder emits 132
+1357933171:642667 adding to queue of 29
+Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.438, output at 4.43777
+1357933171:644252 Decoder emits 133
+1357933171:644505 adding to queue of 30
+Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.471, output at 4.47113
+1357933171:646288 Decoder emits 134
+1357933171:646421 writer sleeps with a queue of 3
+1357933171:646627 writer wakes with a queue of 3
+1357933171:646831 adding to queue of 31
+Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.505, output at 4.5045
+1357933171:648695 Decoder emits 135
+1357933171:649000 decoder sleeps with queue of 32
+1357933171:651037 writer sleeps with a queue of 2
+1357933171:651208 writer wakes with a queue of 2
+1357933171:655192 writer sleeps with a queue of 1
+1357933171:655373 writer wakes with a queue of 1
+1357933171:659901 writer sleeps with a queue of 0
+Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 100
+1357933178:754229 encoder thread 0x7fe3580a5680 finishes local encode of 100
+Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 98
+Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 96
+Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 102
+1357933178:982783 encoder thread 0x7fe3580a5680 sleeps
+1357933178:982892 decoder wakes with queue of 32
+1357933178:982989 writer wakes with a queue of 1
+1357933178:983117 encoder thread 0x7fe3580a4c90 finishes local encode of 98
+1357933178:983215 encoder thread 0x7fe3580a4940 finishes local encode of 102
+Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 95
+Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 97
+1357933178:983485 encoder thread 0x7fe3580a4fe0 finishes local encode of 96
+Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 101
+1357933178:983766 decoder sleeps with queue of 32
+Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 99
+1357933178:984038 encoder thread 0x7fe358024570 finishes local encode of 95
+1357933178:984101 encoder thread 0x7fe3580a5680 wakes with queue of 32
+Fri Jan 11 19:39:38 2013: Encoder thread 0x7fe3580a5680 pops frame 103 from queue
+1357933178:984251 encoder thread 0x7fe3580a5680 begins local encode of 103
+1357933178:998083 encoder thread 0x7fe3580a4c90 sleeps
+1357933179:25471 encoder thread 0x7fe3580a59d0 finishes local encode of 97
+1357933179:25602 encoder thread 0x7fe3580a45f0 finishes local encode of 99
+1357933179:25679 encoder thread 0x7fe3580a5330 finishes local encode of 101
+1357933179:25761 encoder thread 0x7fe3580a4940 sleeps
+1357933179:25826 writer sleeps with a queue of 4
+1357933179:25893 encoder thread 0x7fe3580a4fe0 sleeps
+1357933179:25957 encoder thread 0x7fe3580a4c90 wakes with queue of 31
+1357933179:26019 writer wakes with a queue of 4
+Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe3580a4c90 pops frame 104 from queue
+1357933179:26182 encoder thread 0x7fe3580a4c90 begins local encode of 104
+1357933179:36490 encoder thread 0x7fe3580a5330 sleeps
+1357933179:47899 writer sleeps with a queue of 6
+1357933179:47981 encoder thread 0x7fe3580a5330 wakes with queue of 30
+1357933179:48052 encoder thread 0x7fe358024570 sleeps
+1357933179:48130 writer wakes with a queue of 6
+Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe3580a5330 pops frame 105 from queue
+1357933179:48294 encoder thread 0x7fe3580a5330 begins local encode of 105
+1357933179:48351 encoder thread 0x7fe3580a4fe0 wakes with queue of 29
+Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe3580a4fe0 pops frame 106 from queue
+1357933179:48520 encoder thread 0x7fe3580a4fe0 begins local encode of 106
+1357933179:60923 encoder thread 0x7fe3580a59d0 sleeps
+1357933179:68886 writer sleeps with a queue of 5
+1357933179:68981 encoder thread 0x7fe3580a45f0 sleeps
+1357933179:69115 decoder wakes with queue of 28
+1357933179:69186 writer wakes with a queue of 5
+1357933179:69249 adding to queue of 28
+1357933179:69426 encoder thread 0x7fe3580a4940 wakes with queue of 29
+Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe3580a4940 pops frame 107 from queue
+1357933179:69682 encoder thread 0x7fe3580a4940 begins local encode of 107
+1357933179:69790 encoder thread 0x7fe358024570 wakes with queue of 28
+Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe358024570 pops frame 108 from queue
+1357933179:70118 encoder thread 0x7fe358024570 begins local encode of 108
+1357933179:70215 encoder thread 0x7fe3580a59d0 wakes with queue of 27
+Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe3580a59d0 pops frame 109 from queue
+Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.538, output at 4.53787
+1357933179:71090 Decoder emits 136
+1357933179:71356 adding to queue of 26
+1357933179:71570 encoder thread 0x7fe3580a45f0 wakes with queue of 27
+Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe3580a45f0 pops frame 110 from queue
+1357933179:73111 encoder thread 0x7fe3580a45f0 begins local encode of 110
+Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.571, output at 4.57123
+1357933179:73729 Decoder emits 137
+1357933179:73966 adding to queue of 26
+Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.605, output at 4.6046
+1357933179:75609 Decoder emits 138
+1357933179:75774 adding to queue of 27
+Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.638, output at 4.63797
+1357933179:78084 encoder thread 0x7fe3580a59d0 begins local encode of 109
+1357933179:78312 writer sleeps with a queue of 4
+1357933179:78504 writer wakes with a queue of 4
+1357933179:78753 Decoder emits 139
+1357933179:79174 adding to queue of 28
+Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.671, output at 4.67133
+1357933179:83761 writer sleeps with a queue of 3
+1357933179:83985 writer wakes with a queue of 3
+1357933179:84375 Decoder emits 140
+1357933179:84843 adding to queue of 29
+Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.705, output at 4.7047
+1357933179:87382 Decoder emits 141
+1357933179:87678 adding to queue of 30
+1357933179:89018 writer sleeps with a queue of 2
+1357933179:89318 writer wakes with a queue of 2
+Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.738, output at 4.73807
+1357933179:90013 Decoder emits 142
+1357933179:90337 adding to queue of 31
+Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.771, output at 4.77143
+1357933179:92900 Decoder emits 143
+1357933179:93086 writer sleeps with a queue of 1
+1357933179:93279 writer wakes with a queue of 1
+1357933179:93506 decoder sleeps with queue of 32
+1357933179:96759 writer sleeps with a queue of 0
+Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 104
+Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 105
+Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 108
+1357933186:274915 encoder thread 0x7fe3580a4c90 finishes local encode of 104
+Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 110
+1357933186:288567 decoder wakes with queue of 32
+1357933186:288641 encoder thread 0x7fe3580a5330 finishes local encode of 105
+Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 106
+Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 103
+Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 107
+1357933186:289078 writer wakes with a queue of 1
+1357933186:289183 encoder thread 0x7fe358024570 finishes local encode of 108
+1357933186:289272 encoder thread 0x7fe3580a4c90 sleeps
+Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 109
+1357933186:289514 decoder sleeps with queue of 32
+1357933186:289653 encoder thread 0x7fe3580a45f0 finishes local encode of 110
+1357933186:289938 encoder thread 0x7fe3580a4fe0 finishes local encode of 106
+1357933186:300780 encoder thread 0x7fe3580a5680 finishes local encode of 103
+1357933186:308513 writer sleeps with a queue of 4
+1357933186:308578 writer wakes with a queue of 4
+1357933186:308620 encoder thread 0x7fe3580a5330 sleeps
+1357933186:308690 encoder thread 0x7fe358024570 sleeps
+1357933186:308767 encoder thread 0x7fe3580a4c90 wakes with queue of 32
+1357933186:308828 encoder thread 0x7fe3580a4940 finishes local encode of 107
+1357933186:308906 encoder thread 0x7fe3580a59d0 finishes local encode of 109
+Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a4c90 pops frame 111 from queue
+1357933186:309072 encoder thread 0x7fe3580a4c90 begins local encode of 111
+1357933186:316420 encoder thread 0x7fe3580a45f0 sleeps
+1357933186:316521 decoder wakes with queue of 31
+1357933186:316643 adding to queue of 31
+Fri Jan 11 19:39:46 2013: Source video frame ready; source at 4.805, output at 4.8048
+1357933186:330616 writer sleeps with a queue of 6
+1357933186:344739 encoder thread 0x7fe3580a5680 sleeps
+1357933186:344810 encoder thread 0x7fe3580a4fe0 sleeps
+1357933186:344879 writer wakes with a queue of 6
+1357933186:344962 encoder thread 0x7fe3580a5330 wakes with queue of 32
+1357933186:345032 Decoder emits 144
+Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a5330 pops frame 112 from queue
+1357933186:345232 encoder thread 0x7fe3580a5330 begins local encode of 112
+1357933186:345303 encoder thread 0x7fe358024570 wakes with queue of 31
+Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe358024570 pops frame 113 from queue
+1357933186:345461 encoder thread 0x7fe358024570 begins local encode of 113
+1357933186:368710 encoder thread 0x7fe3580a4940 sleeps
+1357933186:368835 encoder thread 0x7fe3580a59d0 sleeps
+1357933186:368898 encoder thread 0x7fe3580a45f0 wakes with queue of 30
+Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a45f0 pops frame 114 from queue
+1357933186:369117 encoder thread 0x7fe3580a45f0 begins local encode of 114
+1357933186:369170 encoder thread 0x7fe3580a5680 wakes with queue of 29
+Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a5680 pops frame 115 from queue
+1357933186:369342 encoder thread 0x7fe3580a5680 begins local encode of 115
+1357933186:375527 encoder thread 0x7fe3580a4fe0 wakes with queue of 28
+1357933186:375745 writer sleeps with a queue of 5
+1357933186:375813 writer wakes with a queue of 5
+Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a4fe0 pops frame 116 from queue
+1357933186:376196 encoder thread 0x7fe3580a4fe0 begins local encode of 116
+1357933186:376453 adding to queue of 27
+1357933186:377212 encoder thread 0x7fe3580a4940 wakes with queue of 28
+Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a4940 pops frame 117 from queue
+1357933186:377543 encoder thread 0x7fe3580a4940 begins local encode of 117
+Fri Jan 11 19:39:46 2013: Source video frame ready; source at 4.838, output at 4.83817
+1357933186:379255 writer sleeps with a queue of 4
+1357933186:379395 encoder thread 0x7fe3580a59d0 wakes with queue of 27
+1357933186:379501 Decoder emits 145
+1357933186:379616 writer wakes with a queue of 4
+Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a59d0 pops frame 118 from queue
+1357933186:381383 encoder thread 0x7fe3580a59d0 begins local encode of 118
+1357933186:381547 adding to queue of 26
+1357933186:383259 writer sleeps with a queue of 3
+Fri Jan 11 19:39:46 2013: Source video frame ready; source at 4.872, output at 4.87153
+1357933186:384579 Decoder emits 146
+1357933186:384821 adding to queue of 27
+Fri Jan 11 19:39:46 2013: Source video frame ready; source at 4.905, output at 4.9049
+1357933186:386708 Decoder emits 147
+1357933186:386964 adding to queue of 28
+Fri Jan 11 19:39:46 2013: Source video frame ready; source at 4.938, output at 4.93827
+1357933186:389785 Decoder emits 148
+1357933186:390035 adding to queue of 29
+Fri Jan 11 19:39:46 2013: Source video frame ready; source at 4.972, output at 4.97163
+1357933186:392171 writer wakes with a queue of 3
+1357933186:392523 Decoder emits 149
+1357933186:392861 adding to queue of 30
+Fri Jan 11 19:39:46 2013: Source video frame ready; source at 5.005, output at 5.005
+1357933186:395163 Decoder emits 150
+1357933186:395456 adding to queue of 31
+1357933186:396558 writer sleeps with a queue of 2
+1357933186:396964 writer wakes with a queue of 2
+Fri Jan 11 19:39:46 2013: Source video frame ready; source at 5.038, output at 5.03837
+1357933186:397779 Decoder emits 151
+1357933186:398028 decoder sleeps with queue of 32
+1357933186:401782 writer sleeps with a queue of 1
+1357933186:402018 writer wakes with a queue of 1
+1357933186:409277 writer sleeps with a queue of 0
+Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 116
+1357933193:349837 encoder thread 0x7fe3580a4fe0 finishes local encode of 116
+1357933193:350054 writer wakes with a queue of 1
+1357933193:354234 writer sleeps with a queue of 0
+1357933193:374690 encoder thread 0x7fe3580a4fe0 sleeps
+1357933193:374857 decoder wakes with queue of 32
+1357933193:374940 decoder sleeps with queue of 32
+1357933193:375099 encoder thread 0x7fe3580a4fe0 wakes with queue of 32
+Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a4fe0 pops frame 119 from queue
+1357933193:375279 encoder thread 0x7fe3580a4fe0 begins local encode of 119
+Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 118
+Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 117
+Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 111
+Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 115
+1357933193:678184 encoder thread 0x7fe3580a59d0 finishes local encode of 118
+Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 112
+Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 114
+Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 113
+1357933193:692105 writer wakes with a queue of 1
+1357933193:692327 encoder thread 0x7fe3580a4c90 finishes local encode of 111
+1357933193:692453 encoder thread 0x7fe3580a5680 finishes local encode of 115
+1357933193:692702 encoder thread 0x7fe3580a59d0 sleeps
+1357933193:692856 decoder wakes with queue of 31
+1357933193:692940 encoder thread 0x7fe3580a5330 finishes local encode of 112
+1357933193:693030 encoder thread 0x7fe358024570 finishes local encode of 113
+1357933193:693139 encoder thread 0x7fe3580a45f0 finishes local encode of 114
+1357933193:693272 adding to queue of 31
+1357933193:693783 encoder thread 0x7fe3580a4940 finishes local encode of 117
+Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.072, output at 5.07173
+1357933193:720807 writer sleeps with a queue of 6
+1357933193:720912 writer wakes with a queue of 6
+1357933193:721000 encoder thread 0x7fe3580a59d0 wakes with queue of 32
+1357933193:721076 encoder thread 0x7fe3580a4c90 sleeps
+1357933193:721173 encoder thread 0x7fe3580a5680 sleeps
+1357933193:721247 Decoder emits 152
+Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a59d0 pops frame 120 from queue
+1357933193:721409 encoder thread 0x7fe3580a59d0 begins local encode of 120
+1357933193:735893 encoder thread 0x7fe3580a5330 sleeps
+1357933193:743408 writer sleeps with a queue of 5
+1357933193:743490 writer wakes with a queue of 5
+1357933193:743572 encoder thread 0x7fe3580a4940 sleeps
+1357933193:743668 encoder thread 0x7fe3580a4c90 wakes with queue of 31
+1357933193:743742 encoder thread 0x7fe358024570 sleeps
+1357933193:743820 encoder thread 0x7fe3580a45f0 sleeps
+Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a4c90 pops frame 121 from queue
+1357933193:743992 encoder thread 0x7fe3580a4c90 begins local encode of 121
+1357933193:744106 encoder thread 0x7fe3580a5680 wakes with queue of 30
+Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a5680 pops frame 122 from queue
+1357933193:744319 encoder thread 0x7fe3580a5680 begins local encode of 122
+1357933193:744418 adding to queue of 29
+1357933193:744568 encoder thread 0x7fe3580a5330 wakes with queue of 30
+Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a5330 pops frame 123 from queue
+1357933193:744742 encoder thread 0x7fe3580a5330 begins local encode of 123
+1357933193:744818 encoder thread 0x7fe3580a4940 wakes with queue of 29
+Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a4940 pops frame 124 from queue
+1357933193:744997 encoder thread 0x7fe3580a4940 begins local encode of 124
+Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.105, output at 5.1051
+1357933193:746131 Decoder emits 153
+1357933193:746595 writer sleeps with a queue of 4
+1357933193:746652 writer wakes with a queue of 4
+1357933193:749908 writer sleeps with a queue of 3
+1357933193:750034 encoder thread 0x7fe358024570 wakes with queue of 28
+1357933193:750133 writer wakes with a queue of 3
+Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe358024570 pops frame 125 from queue
+1357933193:750396 encoder thread 0x7fe358024570 begins local encode of 125
+1357933193:750519 encoder thread 0x7fe3580a45f0 wakes with queue of 27
+Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a45f0 pops frame 126 from queue
+1357933193:750936 encoder thread 0x7fe3580a45f0 begins local encode of 126
+1357933193:751094 adding to queue of 26
+Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.138, output at 5.13847
+1357933193:758910 Decoder emits 154
+1357933193:759158 adding to queue of 27
+1357933193:760609 writer sleeps with a queue of 2
+1357933193:760878 writer wakes with a queue of 2
+Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.172, output at 5.17183
+1357933193:761568 Decoder emits 155
+1357933193:761830 adding to queue of 28
+Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.205, output at 5.2052
+1357933193:763729 Decoder emits 156
+1357933193:763941 adding to queue of 29
+Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.239, output at 5.23857
+1357933193:765932 Decoder emits 157
+1357933193:766178 adding to queue of 30
+Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.272, output at 5.27193
+1357933193:767794 Decoder emits 158
+1357933193:768019 adding to queue of 31
+Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.305, output at 5.3053
+1357933193:769709 Decoder emits 159
+1357933193:769913 decoder sleeps with queue of 32
+1357933193:781529 writer sleeps with a queue of 1
+1357933193:782078 writer wakes with a queue of 1
+1357933193:792101 writer sleeps with a queue of 0
+1357933194:407280 encoder thread 0x7fe358031550 sleeps
+1357933194:407760 decoder wakes with queue of 32
+1357933194:408135 decoder sleeps with queue of 32
+1357933194:408503 encoder thread 0x7fe358031550 wakes with queue of 32
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe358031550 pops frame 127 from queue
+1357933194:409338 encoder thread 0x7fe3580a6a80 sleeps
+1357933194:409728 decoder wakes with queue of 31
+1357933194:410215 adding to queue of 31
+1357933194:410644 encoder thread 0x7fe3580a6a80 wakes with queue of 32
+Fri Jan 11 19:39:54 2013: Remote encode of 127 on shankly failed (Host not found (authoritative)); thread sleeping for 50s
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a6a80 pops frame 128 from queue
+Fri Jan 11 19:39:54 2013: Source video frame ready; source at 5.339, output at 5.33867
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe358031550 pushes frame 127 back onto queue after failure
+1357933194:412750 Decoder emits 160
+Fri Jan 11 19:39:54 2013: Remote encode of 128 on shankly failed (Host not found (authoritative)); thread sleeping for 50s
+1357933194:413532 decoder sleeps with queue of 32
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a6a80 pushes frame 128 back onto queue after failure
+1357933194:433562 encoder thread 0x7fe3580a6090 sleeps
+1357933194:434003 decoder wakes with queue of 33
+1357933194:434361 decoder sleeps with queue of 33
+1357933194:434694 encoder thread 0x7fe3580a6090 wakes with queue of 33
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a6090 pops frame 128 from queue
+Fri Jan 11 19:39:54 2013: Remote encode of 128 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 50s
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a6090 pushes frame 128 back onto queue after failure
+1357933194:444618 encoder thread 0x7fe358030eb0 sleeps
+1357933194:444958 decoder wakes with queue of 33
+1357933194:445318 decoder sleeps with queue of 33
+1357933194:445690 encoder thread 0x7fe358030eb0 wakes with queue of 33
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe358030eb0 pops frame 128 from queue
+Fri Jan 11 19:39:54 2013: Remote encode of 128 on shankly failed (Host not found (authoritative)); thread sleeping for 50s
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe358030eb0 pushes frame 128 back onto queue after failure
+1357933194:448439 encoder thread 0x7fe3580a5d40 sleeps
+1357933194:448778 decoder wakes with queue of 33
+1357933194:449180 decoder sleeps with queue of 33
+1357933194:449576 encoder thread 0x7fe3580a5d40 wakes with queue of 33
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a5d40 pops frame 128 from queue
+Fri Jan 11 19:39:54 2013: Remote encode of 128 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 50s
+1357933194:451904 encoder thread 0x7fe3580a6730 sleeps
+1357933194:452236 decoder wakes with queue of 32
+1357933194:452577 decoder sleeps with queue of 32
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a5d40 pushes frame 128 back onto queue after failure
+1357933194:453428 encoder thread 0x7fe3580a6730 wakes with queue of 33
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a6730 pops frame 128 from queue
+1357933194:454393 encoder thread 0x7fe358031200 sleeps
+1357933194:454941 encoder thread 0x7fe3580a63e0 sleeps
+1357933194:455524 decoder wakes with queue of 32
+1357933194:456348 decoder sleeps with queue of 32
+Fri Jan 11 19:39:54 2013: Remote encode of 128 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 50s
+1357933194:457470 encoder thread 0x7fe358031200 wakes with queue of 32
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe358031200 pops frame 127 from queue
+1357933194:458437 encoder thread 0x7fe3580a63e0 wakes with queue of 31
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a63e0 pops frame 129 from queue
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a6730 pushes frame 128 back onto queue after failure
+Fri Jan 11 19:39:54 2013: Remote encode of 127 on shankly failed (Host not found (authoritative)); thread sleeping for 50s
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe358031200 pushes frame 127 back onto queue after failure
+Fri Jan 11 19:39:54 2013: Remote encode of 129 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 50s
+Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a63e0 pushes frame 129 back onto queue after failure
+Fri Jan 11 19:39:58 2013: Finished locally-encoded frame 123
+1357933198:967025 encoder thread 0x7fe3580a5330 finishes local encode of 123
+1357933198:967382 writer wakes with a queue of 1
+1357933198:982912 writer sleeps with a queue of 0
+1357933198:983079 encoder thread 0x7fe3580a5330 sleeps
+1357933198:983205 decoder wakes with queue of 33
+1357933198:983404 decoder sleeps with queue of 33
+1357933198:983524 encoder thread 0x7fe3580a5330 wakes with queue of 33
+Fri Jan 11 19:39:58 2013: Encoder thread 0x7fe3580a5330 pops frame 129 from queue
+1357933198:983688 encoder thread 0x7fe3580a5330 begins local encode of 129
+Fri Jan 11 19:39:59 2013: Finished locally-encoded frame 126
+Fri Jan 11 19:39:59 2013: Finished locally-encoded frame 125
+1357933199:496055 encoder thread 0x7fe3580a45f0 finishes local encode of 126
+1357933199:496328 encoder thread 0x7fe358024570 finishes local encode of 125
+1357933199:506491 writer wakes with a queue of 1
+1357933199:506810 encoder thread 0x7fe3580a45f0 sleeps
+1357933199:507491 decoder wakes with queue of 32
+1357933199:507687 decoder sleeps with queue of 32
+1357933199:517739 encoder thread 0x7fe358024570 sleeps
+1357933199:520670 writer sleeps with a queue of 1
+1357933199:520966 encoder thread 0x7fe3580a45f0 wakes with queue of 32
+1357933199:551489 writer wakes with a queue of 1
+Fri Jan 11 19:39:59 2013: Encoder thread 0x7fe3580a45f0 pops frame 127 from queue
+1357933199:551904 encoder thread 0x7fe3580a45f0 begins local encode of 127
+1357933199:552048 decoder wakes with queue of 31
+1357933199:552437 adding to queue of 31
+Fri Jan 11 19:39:59 2013: Source video frame ready; source at 5.372, output at 5.37203
+1357933199:555815 Decoder emits 161
+1357933199:585975 writer sleeps with a queue of 0
+1357933199:586174 encoder thread 0x7fe358024570 wakes with queue of 32
+Fri Jan 11 19:39:59 2013: Encoder thread 0x7fe358024570 pops frame 128 from queue
+1357933199:589103 encoder thread 0x7fe358024570 begins local encode of 128
+1357933199:589197 adding to queue of 31
+Fri Jan 11 19:39:59 2013: Source video frame ready; source at 5.405, output at 5.4054
+1357933199:592859 Decoder emits 162
+1357933199:593024 decoder sleeps with queue of 32
+Fri Jan 11 19:40:00 2013: Finished locally-encoded frame 122
+Fri Jan 11 19:40:00 2013: Finished locally-encoded frame 121
+Fri Jan 11 19:40:00 2013: Finished locally-encoded frame 120
+Fri Jan 11 19:40:00 2013: Finished locally-encoded frame 124
+Fri Jan 11 19:40:00 2013: Finished locally-encoded frame 119
+1357933200:131149 encoder thread 0x7fe3580a5680 finishes local encode of 122
+1357933200:131381 encoder thread 0x7fe3580a4c90 finishes local encode of 121
+1357933200:145988 encoder thread 0x7fe3580a59d0 finishes local encode of 120
+1357933200:146160 writer wakes with a queue of 1
+1357933200:146319 encoder thread 0x7fe3580a4940 finishes local encode of 124
+1357933200:146479 encoder thread 0x7fe3580a4fe0 finishes local encode of 119
+1357933200:146602 decoder wakes with queue of 32
+1357933200:146736 encoder thread 0x7fe3580a5680 sleeps
+1357933200:146838 decoder sleeps with queue of 32
+1357933200:161572 encoder thread 0x7fe3580a4c90 sleeps
+1357933200:172938 writer sleeps with a queue of 4
+1357933200:200744 encoder thread 0x7fe3580a4fe0 sleeps
+1357933200:200941 encoder thread 0x7fe3580a4940 sleeps
+1357933200:201094 encoder thread 0x7fe3580a5680 wakes with queue of 32
+1357933200:201215 writer wakes with a queue of 4
+1357933200:201334 encoder thread 0x7fe3580a59d0 sleeps
+Fri Jan 11 19:40:00 2013: Encoder thread 0x7fe3580a5680 pops frame 130 from queue
+1357933200:201644 encoder thread 0x7fe3580a5680 begins local encode of 130
+1357933200:201754 decoder wakes with queue of 31
+1357933200:201936 adding to queue of 31
+1357933200:202122 encoder thread 0x7fe3580a4c90 wakes with queue of 32
+Fri Jan 11 19:40:00 2013: Encoder thread 0x7fe3580a4c90 pops frame 131 from queue
+1357933200:202401 encoder thread 0x7fe3580a4c90 begins local encode of 131
+1357933200:202557 encoder thread 0x7fe3580a4fe0 wakes with queue of 31
+Fri Jan 11 19:40:00 2013: Encoder thread 0x7fe3580a4fe0 pops frame 132 from queue
+1357933200:202851 encoder thread 0x7fe3580a4fe0 begins local encode of 132
+1357933200:203068 encoder thread 0x7fe3580a4940 wakes with queue of 30
+Fri Jan 11 19:40:00 2013: Encoder thread 0x7fe3580a4940 pops frame 133 from queue
+Fri Jan 11 19:40:00 2013: Source video frame ready; source at 5.439, output at 5.43877
+1357933200:203879 Decoder emits 163
+1357933200:205621 encoder thread 0x7fe3580a59d0 wakes with queue of 29
+1357933200:205741 encoder thread 0x7fe3580a4940 begins local encode of 133
+1357933200:205843 writer sleeps with a queue of 3
+Fri Jan 11 19:40:00 2013: Encoder thread 0x7fe3580a59d0 pops frame 134 from queue
+1357933200:206112 writer wakes with a queue of 3
+1357933200:206218 adding to queue of 28
+Fri Jan 11 19:40:00 2013: Source video frame ready; source at 5.472, output at 5.47213
+1357933200:208814 Decoder emits 164
+1357933200:209172 adding to queue of 29
+1357933200:209351 encoder thread 0x7fe3580a59d0 begins local encode of 134
+1357933200:210101 writer sleeps with a queue of 2
+1357933200:210371 writer wakes with a queue of 2
+Fri Jan 11 19:40:00 2013: Source video frame ready; source at 5.506, output at 5.5055
+1357933200:211088 Decoder emits 165
+1357933200:211382 adding to queue of 30
+Fri Jan 11 19:40:00 2013: Source video frame ready; source at 5.539, output at 5.53887
+1357933200:214538 Decoder emits 166
+1357933200:214791 adding to queue of 31
+Fri Jan 11 19:40:00 2013: Source video frame ready; source at 5.572, output at 5.57223
+1357933200:216922 Decoder emits 167
+1357933200:217157 decoder sleeps with queue of 32
+1357933200:220148 writer sleeps with a queue of 1
+1357933200:220554 writer wakes with a queue of 1
+1357933200:224544 writer sleeps with a queue of 0
+Fri Jan 11 19:40:02 2013: Finished locally-encoded frame 129
+1357933202:83650 encoder thread 0x7fe3580a5330 finishes local encode of 129
+1357933202:83981 encoder thread 0x7fe3580a5330 sleeps
+1357933202:84243 writer wakes with a queue of 1
+1357933202:84556 decoder wakes with queue of 32
+1357933202:84913 decoder sleeps with queue of 32
+1357933202:85326 encoder thread 0x7fe3580a5330 wakes with queue of 32
+Fri Jan 11 19:40:02 2013: Encoder thread 0x7fe3580a5330 pops frame 135 from queue
+1357933202:86077 encoder thread 0x7fe3580a5330 begins local encode of 135
+1357933202:88210 writer sleeps with a queue of 0
+Fri Jan 11 19:40:05 2013: Finished locally-encoded frame 127
+1357933205:288446 encoder thread 0x7fe3580a45f0 finishes local encode of 127
+1357933205:288531 encoder thread 0x7fe3580a45f0 sleeps
+1357933205:288582 decoder wakes with queue of 31
+1357933205:288689 adding to queue of 31
+1357933205:288792 encoder thread 0x7fe3580a45f0 wakes with queue of 32
+Fri Jan 11 19:40:05 2013: Source video frame ready; source at 5.606, output at 5.6056
+1357933205:364162 Decoder emits 168
+1357933205:394427 writer wakes with a queue of 1
+Fri Jan 11 19:40:05 2013: Encoder thread 0x7fe3580a45f0 pops frame 136 from queue
+Fri Jan 11 19:40:05 2013: Finished locally-encoded frame 128
+1357933205:394722 adding to queue of 31
+1357933205:395192 encoder thread 0x7fe358024570 finishes local encode of 128
+1357933205:395260 encoder thread 0x7fe358024570 sleeps
+1357933205:395316 encoder thread 0x7fe358024570 wakes with queue of 32
+Fri Jan 11 19:40:05 2013: Encoder thread 0x7fe358024570 pops frame 137 from queue
+1357933205:395421 encoder thread 0x7fe358024570 begins local encode of 137
+Fri Jan 11 19:40:05 2013: Source video frame ready; source at 5.639, output at 5.63897
+1357933205:396291 Decoder emits 169
+1357933205:396402 adding to queue of 31
+Fri Jan 11 19:40:05 2013: Source video frame ready; source at 5.672, output at 5.67233
+1357933205:397317 Decoder emits 170
+1357933205:397391 decoder sleeps with queue of 32
+1357933205:397443 encoder thread 0x7fe3580a45f0 begins local encode of 136
+1357933205:397742 writer sleeps with a queue of 1
+1357933205:397790 writer wakes with a queue of 1
+1357933205:401013 writer sleeps with a queue of 0
+Fri Jan 11 19:40:06 2013: Finished locally-encoded frame 130
+1357933206:832217 encoder thread 0x7fe3580a5680 finishes local encode of 130
+Fri Jan 11 19:40:06 2013: Finished locally-encoded frame 134
+1357933206:952915 writer wakes with a queue of 1
+Fri Jan 11 19:40:07 2013: Finished locally-encoded frame 131
+1357933207:11661 encoder thread 0x7fe3580a5680 sleeps
+1357933207:11768 decoder wakes with queue of 32
+Fri Jan 11 19:40:07 2013: Finished locally-encoded frame 133
+Fri Jan 11 19:40:07 2013: Finished locally-encoded frame 132
+Fri Jan 11 19:40:07 2013: Finished locally-encoded frame 135
+1357933207:12501 decoder sleeps with queue of 32
+1357933207:12770 encoder thread 0x7fe3580a4c90 finishes local encode of 131
+1357933207:13108 encoder thread 0x7fe3580a5680 wakes with queue of 32
+Fri Jan 11 19:40:07 2013: Encoder thread 0x7fe3580a5680 pops frame 138 from queue
+1357933207:13585 encoder thread 0x7fe3580a4fe0 finishes local encode of 132
+1357933207:26871 encoder thread 0x7fe3580a4940 finishes local encode of 133
+1357933207:27003 encoder thread 0x7fe3580a5680 begins local encode of 138
+1357933207:27106 encoder thread 0x7fe3580a5330 finishes local encode of 135
+1357933207:27221 encoder thread 0x7fe3580a4c90 sleeps
+1357933207:27313 decoder wakes with queue of 31
+1357933207:27478 adding to queue of 31
+1357933207:27762 encoder thread 0x7fe3580a59d0 finishes local encode of 134
+Fri Jan 11 19:40:07 2013: Source video frame ready; source at 5.706, output at 5.7057
+1357933207:55750 writer sleeps with a queue of 4
+1357933207:68468 encoder thread 0x7fe3580a4fe0 sleeps
+1357933207:68618 encoder thread 0x7fe3580a4940 sleeps
+1357933207:68697 Decoder emits 171
+1357933207:68779 writer wakes with a queue of 4
+1357933207:68845 encoder thread 0x7fe3580a5330 sleeps
+1357933207:68933 encoder thread 0x7fe3580a4c90 wakes with queue of 32
+Fri Jan 11 19:40:07 2013: Encoder thread 0x7fe3580a4c90 pops frame 139 from queue
+1357933207:69084 encoder thread 0x7fe3580a4c90 begins local encode of 139
+1357933207:69127 encoder thread 0x7fe3580a4fe0 wakes with queue of 31
+Fri Jan 11 19:40:07 2013: Encoder thread 0x7fe3580a4fe0 pops frame 140 from queue
+1357933207:69280 encoder thread 0x7fe3580a4fe0 begins local encode of 140
+1357933207:69324 encoder thread 0x7fe3580a4940 wakes with queue of 30
+Fri Jan 11 19:40:07 2013: Encoder thread 0x7fe3580a4940 pops frame 141 from queue
+1357933207:69519 encoder thread 0x7fe3580a4940 begins local encode of 141
+1357933207:69582 adding to queue of 29
+1357933207:69977 encoder thread 0x7fe3580a59d0 sleeps
+Fri Jan 11 19:40:07 2013: Source video frame ready; source at 5.739, output at 5.73907
+1357933207:72464 writer sleeps with a queue of 4
+1357933207:72574 writer wakes with a queue of 4
+1357933207:72665 Decoder emits 172
+1357933207:73511 encoder thread 0x7fe3580a5330 wakes with queue of 30
+Fri Jan 11 19:40:07 2013: Encoder thread 0x7fe3580a5330 pops frame 142 from queue
+1357933207:73795 encoder thread 0x7fe3580a5330 begins local encode of 142
+1357933207:75724 writer sleeps with a queue of 3
+1357933207:75872 writer wakes with a queue of 3
+1357933207:77044 encoder thread 0x7fe3580a59d0 wakes with queue of 29
+Fri Jan 11 19:40:07 2013: Encoder thread 0x7fe3580a59d0 pops frame 143 from queue
+1357933207:77457 encoder thread 0x7fe3580a59d0 begins local encode of 143
+1357933207:77615 adding to queue of 28
+1357933207:91384 writer sleeps with a queue of 2
+Fri Jan 11 19:40:07 2013: Source video frame ready; source at 5.772, output at 5.77243
+1357933207:91734 Decoder emits 173
+1357933207:91867 adding to queue of 29
+Fri Jan 11 19:40:07 2013: Source video frame ready; source at 5.806, output at 5.8058
+1357933207:93716 Decoder emits 174
+1357933207:93887 adding to queue of 30
+Fri Jan 11 19:40:07 2013: Source video frame ready; source at 5.839, output at 5.83917
+1357933207:95435 Decoder emits 175
+1357933207:95568 adding to queue of 31
+Fri Jan 11 19:40:07 2013: Source video frame ready; source at 5.873, output at 5.87253
+1357933207:97226 writer wakes with a queue of 2
+1357933207:97514 Decoder emits 176
+1357933207:98891 decoder sleeps with queue of 32
+1357933207:101158 writer sleeps with a queue of 1
+1357933207:101363 writer wakes with a queue of 1
+1357933207:116791 writer sleeps with a queue of 0
+Fri Jan 11 19:40:08 2013: Finished locally-encoded frame 137
+1357933208:437367 encoder thread 0x7fe358024570 finishes local encode of 137
+1357933208:437839 writer wakes with a queue of 1
+1357933208:451320 encoder thread 0x7fe358024570 sleeps
+1357933208:451732 decoder wakes with queue of 32
+1357933208:452078 decoder sleeps with queue of 32
+1357933208:452430 encoder thread 0x7fe358024570 wakes with queue of 32
+Fri Jan 11 19:40:08 2013: Encoder thread 0x7fe358024570 pops frame 144 from queue
+1357933208:453028 encoder thread 0x7fe358024570 begins local encode of 144
+1357933208:454944 writer sleeps with a queue of 0
+Fri Jan 11 19:40:08 2013: Finished locally-encoded frame 136
+1357933208:509695 encoder thread 0x7fe3580a45f0 finishes local encode of 136
+1357933208:510026 writer wakes with a queue of 1
+1357933208:516892 encoder thread 0x7fe3580a45f0 sleeps
+1357933208:517230 decoder wakes with queue of 31
+1357933208:517631 adding to queue of 31
+1357933208:518098 encoder thread 0x7fe3580a45f0 wakes with queue of 32
+Fri Jan 11 19:40:08 2013: Encoder thread 0x7fe3580a45f0 pops frame 145 from queue
+1357933208:518943 encoder thread 0x7fe3580a45f0 begins local encode of 145
+Fri Jan 11 19:40:08 2013: Source video frame ready; source at 5.906, output at 5.9059
+1357933208:519693 Decoder emits 177
+1357933208:520028 adding to queue of 31
+1357933208:520685 writer sleeps with a queue of 0
+Fri Jan 11 19:40:08 2013: Source video frame ready; source at 5.939, output at 5.93927
+1357933208:527563 Decoder emits 178
+1357933208:527928 decoder sleeps with queue of 32
+Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 141
+1357933213:290129 encoder thread 0x7fe3580a4940 finishes local encode of 141
+1357933213:290422 writer wakes with a queue of 1
+1357933213:308220 encoder thread 0x7fe3580a4940 sleeps
+1357933213:308315 decoder wakes with queue of 32
+1357933213:308388 decoder sleeps with queue of 32
+1357933213:308467 encoder thread 0x7fe3580a4940 wakes with queue of 32
+Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a4940 pops frame 146 from queue
+1357933213:308630 encoder thread 0x7fe3580a4940 begins local encode of 146
+1357933213:312482 writer sleeps with a queue of 0
+Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 143
+Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 139
+1357933213:773670 encoder thread 0x7fe3580a59d0 finishes local encode of 143
+Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 138
+Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 140
+Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 142
+1357933213:790346 writer wakes with a queue of 1
+1357933213:790508 encoder thread 0x7fe3580a4c90 finishes local encode of 139
+1357933213:790799 encoder thread 0x7fe3580a59d0 sleeps
+1357933213:791023 decoder wakes with queue of 31
+1357933213:791375 encoder thread 0x7fe3580a5680 finishes local encode of 138
+1357933213:791614 encoder thread 0x7fe3580a5330 finishes local encode of 142
+1357933213:791759 adding to queue of 31
+1357933213:791896 encoder thread 0x7fe3580a4fe0 finishes local encode of 140
+Fri Jan 11 19:40:13 2013: Source video frame ready; source at 5.973, output at 5.97263
+1357933213:808217 encoder thread 0x7fe3580a4c90 sleeps
+1357933213:808325 encoder thread 0x7fe3580a59d0 wakes with queue of 32
+Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a59d0 pops frame 147 from queue
+1357933213:808557 Decoder emits 179
+1357933213:825154 encoder thread 0x7fe3580a59d0 begins local encode of 147
+1357933213:839728 encoder thread 0x7fe3580a5330 sleeps
+1357933213:855320 writer sleeps with a queue of 4
+1357933213:863567 encoder thread 0x7fe3580a5680 sleeps
+1357933213:892870 encoder thread 0x7fe3580a4fe0 sleeps
+1357933213:900683 encoder thread 0x7fe3580a4c90 wakes with queue of 31
+Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 144
+Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 145
+1357933213:927093 writer wakes with a queue of 4
+Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a4c90 pops frame 148 from queue
+1357933213:927469 encoder thread 0x7fe3580a4c90 begins local encode of 148
+1357933213:927654 encoder thread 0x7fe358024570 finishes local encode of 144
+1357933213:927782 adding to queue of 30
+1357933213:928043 encoder thread 0x7fe3580a5330 wakes with queue of 31
+1357933213:928216 encoder thread 0x7fe3580a45f0 finishes local encode of 145
+Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a5330 pops frame 149 from queue
+1357933213:928494 encoder thread 0x7fe3580a5330 begins local encode of 149
+1357933213:928602 encoder thread 0x7fe3580a5680 wakes with queue of 30
+Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a5680 pops frame 150 from queue
+1357933213:928910 encoder thread 0x7fe3580a5680 begins local encode of 150
+1357933213:929087 encoder thread 0x7fe3580a4fe0 wakes with queue of 29
+Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a4fe0 pops frame 151 from queue
+Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.006, output at 6.006
+1357933213:937591 encoder thread 0x7fe358024570 sleeps
+1357933213:948264 encoder thread 0x7fe3580a4fe0 begins local encode of 151
+1357933213:948443 writer sleeps with a queue of 5
+1357933213:948574 Decoder emits 180
+1357933213:948709 encoder thread 0x7fe358024570 wakes with queue of 28
+1357933213:948812 encoder thread 0x7fe3580a45f0 sleeps
+1357933213:948916 writer wakes with a queue of 5
+Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe358024570 pops frame 152 from queue
+1357933213:949248 adding to queue of 27
+1357933213:950629 encoder thread 0x7fe3580a45f0 wakes with queue of 28
+Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.039, output at 6.03937
+1357933213:951268 Decoder emits 181
+1357933213:952749 writer sleeps with a queue of 4
+1357933213:952928 writer wakes with a queue of 4
+1357933213:953026 encoder thread 0x7fe358024570 begins local encode of 152
+1357933213:961791 writer sleeps with a queue of 3
+1357933213:961983 writer wakes with a queue of 3
+Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a45f0 pops frame 153 from queue
+1357933213:962553 encoder thread 0x7fe3580a45f0 begins local encode of 153
+1357933213:962748 adding to queue of 27
+Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.073, output at 6.07273
+1357933213:965536 Decoder emits 182
+1357933213:965806 adding to queue of 28
+Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.106, output at 6.1061
+1357933213:967723 Decoder emits 183
+1357933213:967969 adding to queue of 29
+1357933213:968815 writer sleeps with a queue of 2
+1357933213:969070 writer wakes with a queue of 2
+Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.139, output at 6.13947
+1357933213:970704 Decoder emits 184
+1357933213:971035 adding to queue of 30
+1357933213:972881 writer sleeps with a queue of 1
+1357933213:973063 writer wakes with a queue of 1
+Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.173, output at 6.17283
+1357933213:974744 Decoder emits 185
+1357933213:975182 adding to queue of 31
+Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.206, output at 6.2062
+1357933213:978576 writer sleeps with a queue of 0
+1357933213:978832 Decoder emits 186
+1357933213:979201 decoder sleeps with queue of 32
+Fri Jan 11 19:40:18 2013: Finished locally-encoded frame 146
+1357933218:137842 encoder thread 0x7fe3580a4940 finishes local encode of 146
+1357933218:138235 writer wakes with a queue of 1
+1357933218:151181 encoder thread 0x7fe3580a4940 sleeps
+1357933218:151542 decoder wakes with queue of 32
+1357933218:151859 decoder sleeps with queue of 32
+1357933218:152173 encoder thread 0x7fe3580a4940 wakes with queue of 32
+Fri Jan 11 19:40:18 2013: Encoder thread 0x7fe3580a4940 pops frame 154 from queue
+1357933218:152898 encoder thread 0x7fe3580a4940 begins local encode of 154
+1357933218:154726 writer sleeps with a queue of 0
+Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 149
+1357933220:603254 encoder thread 0x7fe3580a5330 finishes local encode of 149
+1357933220:603651 writer wakes with a queue of 1
+1357933220:620330 writer sleeps with a queue of 0
+1357933220:629730 encoder thread 0x7fe3580a5330 sleeps
+1357933220:629935 decoder wakes with queue of 31
+1357933220:630139 adding to queue of 31
+1357933220:630289 encoder thread 0x7fe3580a5330 wakes with queue of 32
+Fri Jan 11 19:40:20 2013: Encoder thread 0x7fe3580a5330 pops frame 155 from queue
+1357933220:630509 encoder thread 0x7fe3580a5330 begins local encode of 155
+Fri Jan 11 19:40:20 2013: Source video frame ready; source at 6.24, output at 6.23957
+1357933220:633105 Decoder emits 187
+1357933220:633352 adding to queue of 31
+Fri Jan 11 19:40:20 2013: Source video frame ready; source at 6.273, output at 6.27293
+1357933220:636497 Decoder emits 188
+1357933220:636642 decoder sleeps with queue of 32
+Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 148
+Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 152
+Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 150
+1357933220:910037 encoder thread 0x7fe3580a4c90 finishes local encode of 148
+Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 147
+Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 151
+1357933220:954556 encoder thread 0x7fe358024570 finishes local encode of 152
+Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 153
+1357933220:960042 writer wakes with a queue of 1
+1357933220:960114 encoder thread 0x7fe3580a5680 finishes local encode of 150
+1357933220:960268 encoder thread 0x7fe3580a4c90 sleeps
+1357933220:960449 decoder wakes with queue of 32
+1357933220:960604 encoder thread 0x7fe3580a59d0 finishes local encode of 147
+1357933220:960684 encoder thread 0x7fe3580a4fe0 finishes local encode of 151
+Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 154
+1357933220:960878 encoder thread 0x7fe3580a45f0 finishes local encode of 153
+1357933220:960988 decoder sleeps with queue of 32
+1357933220:961542 encoder thread 0x7fe3580a4940 finishes local encode of 154
+1357933220:984624 encoder thread 0x7fe358024570 sleeps
+1357933220:984728 encoder thread 0x7fe3580a5680 sleeps
+1357933220:984842 encoder thread 0x7fe3580a4c90 wakes with queue of 32
+Fri Jan 11 19:40:20 2013: Encoder thread 0x7fe3580a4c90 pops frame 156 from queue
+1357933220:985115 encoder thread 0x7fe3580a4c90 begins local encode of 156
+1357933221:10659 encoder thread 0x7fe3580a59d0 sleeps
+1357933221:12559 encoder thread 0x7fe3580a4fe0 sleeps
+1357933221:20062 writer sleeps with a queue of 6
+1357933221:20161 encoder thread 0x7fe3580a45f0 sleeps
+1357933221:20265 decoder wakes with queue of 31
+1357933221:20375 writer wakes with a queue of 6
+1357933221:20470 adding to queue of 31
+Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.306, output at 6.3063
+1357933221:32732 encoder thread 0x7fe3580a4940 sleeps
+1357933221:32845 encoder thread 0x7fe358024570 wakes with queue of 32
+1357933221:32957 Decoder emits 189
+Fri Jan 11 19:40:21 2013: Encoder thread 0x7fe358024570 pops frame 157 from queue
+1357933221:33133 encoder thread 0x7fe358024570 begins local encode of 157
+1357933221:33238 encoder thread 0x7fe3580a5680 wakes with queue of 31
+Fri Jan 11 19:40:21 2013: Encoder thread 0x7fe3580a5680 pops frame 158 from queue
+1357933221:33440 encoder thread 0x7fe3580a5680 begins local encode of 158
+1357933221:33542 encoder thread 0x7fe3580a59d0 wakes with queue of 30
+Fri Jan 11 19:40:21 2013: Encoder thread 0x7fe3580a59d0 pops frame 159 from queue
+1357933221:33735 encoder thread 0x7fe3580a59d0 begins local encode of 159
+1357933221:33816 encoder thread 0x7fe3580a4fe0 wakes with queue of 29
+Fri Jan 11 19:40:21 2013: Encoder thread 0x7fe3580a4fe0 pops frame 160 from queue
+1357933221:34033 encoder thread 0x7fe3580a4fe0 begins local encode of 160
+1357933221:34104 encoder thread 0x7fe3580a45f0 wakes with queue of 28
+Fri Jan 11 19:40:21 2013: Encoder thread 0x7fe3580a45f0 pops frame 161 from queue
+1357933221:34361 encoder thread 0x7fe3580a45f0 begins local encode of 161
+1357933221:34708 writer sleeps with a queue of 5
+1357933221:34872 writer wakes with a queue of 5
+1357933221:35022 encoder thread 0x7fe3580a4940 wakes with queue of 27
+Fri Jan 11 19:40:21 2013: Encoder thread 0x7fe3580a4940 pops frame 162 from queue
+1357933221:40856 encoder thread 0x7fe3580a4940 begins local encode of 162
+1357933221:40985 adding to queue of 26
+Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.34, output at 6.33967
+1357933221:43235 Decoder emits 190
+1357933221:43454 adding to queue of 27
+Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.373, output at 6.37303
+1357933221:45029 Decoder emits 191
+1357933221:45259 adding to queue of 28
+Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.406, output at 6.4064
+1357933221:46908 writer sleeps with a queue of 4
+1357933221:47159 Decoder emits 192
+1357933221:47486 adding to queue of 29
+1357933221:47643 writer wakes with a queue of 4
+Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.44, output at 6.43977
+1357933221:49982 Decoder emits 193
+1357933221:50252 adding to queue of 30
+1357933221:51546 writer sleeps with a queue of 3
+1357933221:51717 writer wakes with a queue of 3
+Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.473, output at 6.47313
+1357933221:53353 Decoder emits 194
+1357933221:53691 adding to queue of 31
+Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.507, output at 6.5065
+1357933221:55670 Decoder emits 195
+1357933221:55911 decoder sleeps with queue of 32
+1357933221:56324 writer sleeps with a queue of 2
+1357933221:56496 writer wakes with a queue of 2
+1357933221:59946 writer sleeps with a queue of 1
+1357933221:60134 writer wakes with a queue of 1
+1357933221:64145 writer sleeps with a queue of 0
+Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 156
+1357933228:335212 encoder thread 0x7fe3580a4c90 finishes local encode of 156
+Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 155
+Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 162
+Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 160
+Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 158
+Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 157
+Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 161
+Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 159
+1357933228:431256 encoder thread 0x7fe3580a5330 finishes local encode of 155
+1357933228:431360 encoder thread 0x7fe3580a4c90 sleeps
+1357933228:431509 decoder wakes with queue of 32
+1357933228:431607 writer wakes with a queue of 1
+1357933228:431694 decoder sleeps with queue of 32
+1357933228:432503 encoder thread 0x7fe3580a4fe0 finishes local encode of 160
+1357933228:439720 encoder thread 0x7fe3580a5680 finishes local encode of 158
+1357933228:439794 encoder thread 0x7fe3580a59d0 finishes local encode of 159
+1357933228:439898 encoder thread 0x7fe358024570 finishes local encode of 157
+1357933228:439985 encoder thread 0x7fe3580a45f0 finishes local encode of 161
+1357933228:440058 encoder thread 0x7fe3580a4940 finishes local encode of 162
+1357933228:440122 encoder thread 0x7fe3580a5330 sleeps
+1357933228:440182 decoder wakes with queue of 32
+1357933228:440252 decoder sleeps with queue of 32
+1357933228:440345 encoder thread 0x7fe3580a4c90 wakes with queue of 32
+Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a4c90 pops frame 163 from queue
+1357933228:440487 encoder thread 0x7fe3580a4c90 begins local encode of 163
+1357933228:454795 encoder thread 0x7fe3580a4fe0 sleeps
+1357933228:484602 encoder thread 0x7fe3580a5680 sleeps
+1357933228:498923 writer sleeps with a queue of 7
+1357933228:510758 writer wakes with a queue of 7
+1357933228:510821 encoder thread 0x7fe358024570 sleeps
+1357933228:510982 encoder thread 0x7fe3580a45f0 sleeps
+1357933228:511047 encoder thread 0x7fe3580a59d0 sleeps
+1357933228:511116 encoder thread 0x7fe3580a4940 sleeps
+1357933228:511197 encoder thread 0x7fe3580a5330 wakes with queue of 31
+Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a5330 pops frame 164 from queue
+1357933228:511389 encoder thread 0x7fe3580a5330 begins local encode of 164
+1357933228:511460 decoder wakes with queue of 30
+1357933228:511586 adding to queue of 30
+1357933228:511706 encoder thread 0x7fe3580a4fe0 wakes with queue of 31
+Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a4fe0 pops frame 165 from queue
+1357933228:511927 encoder thread 0x7fe3580a4fe0 begins local encode of 165
+1357933228:511981 encoder thread 0x7fe3580a5680 wakes with queue of 30
+Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a5680 pops frame 166 from queue
+1357933228:512240 encoder thread 0x7fe3580a5680 begins local encode of 166
+1357933228:512316 encoder thread 0x7fe358024570 wakes with queue of 29
+Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe358024570 pops frame 167 from queue
+1357933228:512498 encoder thread 0x7fe358024570 begins local encode of 167
+1357933228:512587 encoder thread 0x7fe3580a45f0 wakes with queue of 28
+Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a45f0 pops frame 168 from queue
+1357933228:512815 encoder thread 0x7fe3580a45f0 begins local encode of 168
+1357933228:513053 encoder thread 0x7fe3580a59d0 wakes with queue of 27
+Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.54, output at 6.53987
+1357933228:513470 Decoder emits 196
+Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a59d0 pops frame 169 from queue
+1357933228:513710 encoder thread 0x7fe3580a59d0 begins local encode of 169
+1357933228:513885 writer sleeps with a queue of 6
+1357933228:514017 writer wakes with a queue of 6
+1357933228:517170 writer sleeps with a queue of 5
+1357933228:517363 writer wakes with a queue of 5
+1357933228:517495 encoder thread 0x7fe3580a4940 wakes with queue of 26
+Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a4940 pops frame 170 from queue
+1357933228:517910 encoder thread 0x7fe3580a4940 begins local encode of 170
+1357933228:518072 adding to queue of 25
+Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.573, output at 6.57323
+1357933228:520309 Decoder emits 197
+1357933228:520653 adding to queue of 26
+1357933228:520887 writer sleeps with a queue of 4
+1357933228:521063 writer wakes with a queue of 4
+Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.607, output at 6.6066
+1357933228:522919 Decoder emits 198
+1357933228:523164 adding to queue of 27
+Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.64, output at 6.63997
+1357933228:525230 Decoder emits 199
+1357933228:525450 adding to queue of 28
+Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.673, output at 6.67333
+1357933228:527251 Decoder emits 200
+1357933228:527472 adding to queue of 29
+1357933228:528187 writer sleeps with a queue of 3
+1357933228:528611 writer wakes with a queue of 3
+Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.707, output at 6.7067
+1357933228:529589 Decoder emits 201
+1357933228:529794 adding to queue of 30
+Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.74, output at 6.74007
+1357933228:531614 Decoder emits 202
+1357933228:531836 adding to queue of 31
+Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.773, output at 6.77343
+1357933228:534921 Decoder emits 203
+1357933228:535135 decoder sleeps with queue of 32
+1357933228:541575 writer sleeps with a queue of 2
+1357933228:541984 writer wakes with a queue of 2
+1357933228:549575 writer sleeps with a queue of 1
+1357933228:549917 writer wakes with a queue of 1
+1357933228:553777 writer sleeps with a queue of 0
+Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 167
+1357933235:537904 encoder thread 0x7fe358024570 finishes local encode of 167
+Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 170
+1357933235:584070 writer wakes with a queue of 1
+1357933235:680813 encoder thread 0x7fe358024570 sleeps
+Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 168
+1357933235:783272 encoder thread 0x7fe3580a4940 finishes local encode of 170
+Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 164
+Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 169
+Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 163
+Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 165
+1357933235:783772 decoder wakes with queue of 32
+1357933235:783892 decoder sleeps with queue of 32
+Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 166
+1357933235:784139 encoder thread 0x7fe358024570 wakes with queue of 32
+Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe358024570 pops frame 171 from queue
+1357933235:784307 encoder thread 0x7fe3580a45f0 finishes local encode of 168
+1357933235:784377 encoder thread 0x7fe358024570 begins local encode of 171
+1357933235:802457 writer sleeps with a queue of 2
+1357933235:809820 decoder wakes with queue of 31
+1357933235:809914 encoder thread 0x7fe3580a59d0 finishes local encode of 169
+1357933235:809998 encoder thread 0x7fe3580a5330 finishes local encode of 164
+1357933235:810063 encoder thread 0x7fe3580a5680 finishes local encode of 166
+1357933235:810131 encoder thread 0x7fe3580a4940 sleeps
+1357933235:810192 encoder thread 0x7fe3580a4c90 finishes local encode of 163
+1357933235:810263 encoder thread 0x7fe3580a45f0 sleeps
+1357933235:810336 encoder thread 0x7fe3580a4fe0 finishes local encode of 165
+1357933235:810408 writer wakes with a queue of 2
+1357933235:810469 adding to queue of 31
+Fri Jan 11 19:40:35 2013: Source video frame ready; source at 6.807, output at 6.8068
+1357933235:825103 encoder thread 0x7fe3580a45f0 wakes with queue of 32
+1357933235:825196 encoder thread 0x7fe3580a5680 sleeps
+1357933235:825248 Decoder emits 204
+Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a45f0 pops frame 172 from queue
+1357933235:825406 encoder thread 0x7fe3580a45f0 begins local encode of 172
+1357933235:840303 writer sleeps with a queue of 6
+1357933235:854047 encoder thread 0x7fe3580a59d0 sleeps
+1357933235:854156 writer wakes with a queue of 6
+1357933235:854248 encoder thread 0x7fe3580a5330 sleeps
+1357933235:854396 encoder thread 0x7fe3580a4940 wakes with queue of 31
+Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a4940 pops frame 173 from queue
+1357933235:854638 encoder thread 0x7fe3580a4940 begins local encode of 173
+1357933235:863490 encoder thread 0x7fe3580a4c90 sleeps
+1357933235:877655 encoder thread 0x7fe3580a4fe0 sleeps
+1357933235:877791 encoder thread 0x7fe3580a5680 wakes with queue of 30
+Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a5680 pops frame 174 from queue
+1357933235:878054 encoder thread 0x7fe3580a5680 begins local encode of 174
+1357933235:878150 adding to queue of 29
+1357933235:878340 encoder thread 0x7fe3580a59d0 wakes with queue of 30
+Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a59d0 pops frame 175 from queue
+1357933235:878548 encoder thread 0x7fe3580a59d0 begins local encode of 175
+1357933235:878678 encoder thread 0x7fe3580a5330 wakes with queue of 29
+Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a5330 pops frame 176 from queue
+1357933235:879013 encoder thread 0x7fe3580a5330 begins local encode of 176
+1357933235:879172 encoder thread 0x7fe3580a4c90 wakes with queue of 28
+Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a4c90 pops frame 177 from queue
+1357933235:879618 encoder thread 0x7fe3580a4c90 begins local encode of 177
+1357933235:879797 writer sleeps with a queue of 5
+1357933235:880021 writer wakes with a queue of 5
+Fri Jan 11 19:40:35 2013: Source video frame ready; source at 6.84, output at 6.84017
+1357933235:881619 Decoder emits 205
+1357933235:882570 encoder thread 0x7fe3580a4fe0 wakes with queue of 27
+Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a4fe0 pops frame 178 from queue
+1357933235:883015 adding to queue of 26
+Fri Jan 11 19:40:35 2013: Source video frame ready; source at 6.874, output at 6.87353
+1357933235:885209 Decoder emits 206
+1357933235:885316 encoder thread 0x7fe3580a4fe0 begins local encode of 178
+1357933235:896831 writer sleeps with a queue of 4
+1357933235:897273 adding to queue of 27
+Fri Jan 11 19:40:35 2013: Source video frame ready; source at 6.907, output at 6.9069
+1357933235:901894 Decoder emits 207
+1357933235:902468 adding to queue of 28
+Fri Jan 11 19:40:35 2013: Source video frame ready; source at 6.94, output at 6.94027
+1357933235:906674 Decoder emits 208
+1357933235:907081 adding to queue of 29
+Fri Jan 11 19:40:35 2013: Source video frame ready; source at 6.974, output at 6.97363
+1357933235:909295 writer wakes with a queue of 4
+1357933235:909557 Decoder emits 209
+1357933235:909873 adding to queue of 30
+Fri Jan 11 19:40:35 2013: Source video frame ready; source at 7.007, output at 7.007
+1357933235:912676 Decoder emits 210
+1357933235:912943 adding to queue of 31
+Fri Jan 11 19:40:35 2013: Source video frame ready; source at 7.04, output at 7.04037
+1357933235:914787 writer sleeps with a queue of 3
+1357933235:915006 writer wakes with a queue of 3
+1357933235:915169 Decoder emits 211
+1357933235:915486 decoder sleeps with queue of 32
+1357933235:918704 writer sleeps with a queue of 2
+1357933235:918904 writer wakes with a queue of 2
+1357933235:922506 writer sleeps with a queue of 1
+1357933235:922648 writer wakes with a queue of 1
+1357933235:926325 writer sleeps with a queue of 0
diff --git a/hacks/optimise/analog b/hacks/optimise/analog
new file mode 100755 (executable)
index 0000000..1743008
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/python
+
+import sys
+
+class Encoder:
+      def __init__(self):
+            self.awake = 0
+            self.asleep = 0
+            self.last_event = 0
+            self.state = None
+
+encoders = dict()
+
+f = open(sys.argv[1], 'r')
+while 1:
+      l = f.readline()
+      if l == '':
+        break
+
+      s = l.split()
+      if len(s) == 0:
+            continue
+
+      t = s[0].split(':')
+      if len(t) != 2:
+            continue
+
+      secs = float(t[0]) + float(t[1]) / 1e6
+      if s[1] == 'encoder' and s[2] == 'thread' and s[4] == 'finishes':
+            tid = s[3]
+            if not tid in encoders:
+                  encoders[tid] = Encoder()
+
+            assert(encoders[tid].state == None or encoders[tid].state == 'awake')
+            if encoders[tid].state == 'awake':
+                  encoders[tid].awake += (secs - encoders[tid].last_event)
+
+            encoders[tid].state = 'asleep'
+            encoders[tid].last_event = secs
+
+      elif s[1] == 'encoder' and s[2] == 'thread' and s[4] == 'begins':
+            tid = s[3]
+            if not tid in encoders:
+                  encoders[tid] = Encoder()
+
+            if encoders[tid].state is not None:
+                  encoders[tid].asleep += (secs - encoders[tid].last_event)
+
+            encoders[tid].state = 'awake'
+            encoders[tid].last_event = secs
+
+for k, v in encoders.iteritems():
+      print '%s: awake %f asleep %f' % (k, v.awake, v.asleep)
diff --git a/hacks/optimise/plotlog b/hacks/optimise/plotlog
new file mode 100755 (executable)
index 0000000..55b6fb8
--- /dev/null
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+
+from pylab import *
+import sys
+
+class Point:
+    def __init__(self, t, a):
+        self.time = t
+        self.awake = a
+
+decoder = []
+writer = []
+encoder = dict()
+
+f = open(sys.argv[1], 'r')
+for l in f.readlines():
+    l = l.strip()
+    s = l.split()
+    if len(s) == 0:
+        continue
+
+    t = s[0].split(':')
+    if len(t) != 2:
+        continue
+
+    secs = float(t[0]) + float(t[1]) / 1e6
+    if s[1] == 'decoder' and s[2] == 'sleeps':
+        decoder.append(Point(secs, False))
+    elif s[1] == 'decoder' and s[2] == 'wakes':
+        decoder.append(Point(secs, True))
+    elif s[1] == 'encoder' and s[2] == 'thread' and s[4] == 'finishes':
+        if s[3] not in encoder:
+            print 'new encoder %s' % s[3]
+            encoder[s[3]] = []
+        encoder[str(s[3])].append(Point(secs, False))
+    elif s[1] == 'encoder' and s[2] == 'thread' and s[4] == 'begins':
+        if s[3] not in encoder:
+            print 'new encoder %s' % s[3]
+            encoder[s[3]] = []
+        encoder[s[3]].append(Point(secs, True))
+    elif s[1] == 'writer' and s[2] == 'sleeps':
+        writer.append(Point(secs, False))
+    elif s[1] == 'writer' and s[2] == 'wakes':
+        writer.append(Point(secs, True))
+
+def do_a_plot(points, tit, pos):
+    x = []
+    y = []
+    awake = False
+    for p in points:
+        if p.awake != awake:
+            x.append(p.time)
+            y.append(int(awake) + pos)
+            x.append(p.time)
+            y.append(int(p.awake) + pos)
+            awake = p.awake
+
+    plot(x, y)
+#    fill_between(x, y, 0, color='0.8')
+    title(tit)
+
+figure()
+
+N = len(encoder) + 2
+
+do_a_plot(decoder, 'dec', 0)
+do_a_plot(writer, 'wri', 1)
+
+encoder_list = []
+for k, v in encoder.iteritems():
+    encoder_list.append(v)
+
+print len(encoder_list)
+
+y = 2
+for e in encoder_list:
+    do_a_plot(e, 'enc', y)
+    y += 1
+
+show()
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/hacks/splitchapters b/hacks/splitchapters
new file mode 100755 (executable)
index 0000000..1e5cff0
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/python
+
+import os
+import sys
+
+if len(sys.argv) < 2:
+    print 'Syntax: %s <DVD-image>' % sys.argv[0]
+    sys.exit(1)
+
+lsdvd = os.popen('lsdvd -c "%s"' % sys.argv[1])
+lines = lsdvd.readlines()
+
+N = None
+
+for l in lines:
+    w = l.split()
+    if len(w) > 5 and w[4] == 'Chapters:':
+        N = int(w[5][:-1])
+
+if N == None:
+    print 'Could not get chapter count.'
+    sys.exit(1)
+
+for i in range(1, N + 1):
+    os.mkdir('%d' % i)
+    c = 'mplayer dvd:// -chapter %d-%d -dvd-device "%s" -dumpstream -dumpfile %d/%d.vob' % (i, i, sys.argv[1], i, i)
+    print c
+    os.system(c)
+
+
diff --git a/i18n.py b/i18n.py
new file mode 100644 (file)
index 0000000..807aedd
--- /dev/null
+++ b/i18n.py
@@ -0,0 +1,36 @@
+import glob
+import os
+from waflib import Logs
+
+def command(c):
+    print c
+    os.system(c)
+
+def pot(dir, sources, name):
+    s = ""
+    for f in sources.split('\n'):
+        t = f.strip()
+        if len(t) > 0:
+            s += (os.path.join(dir, t)) + " "
+
+    Logs.info('Making %s.pot' % os.path.join('build', dir, name))
+    d = os.path.join('build', dir)
+    try:
+        os.makedirs(d)
+    except:
+        pass
+
+    command('xgettext -d %s -s --keyword=_ --add-comments=/ -p %s -o %s.pot %s' % (name, d, name, s))
+
+def pot_merge(dir, name):
+    for f in glob.glob(os.path.join(os.getcwd(), dir, 'po', '*.po')):
+        command('msgmerge %s %s.pot -o %s' % (f, os.path.join('build', dir, name), f))
+
+def po_to_mo(dir, name, bld):
+    for f in glob.glob(os.path.join(os.getcwd(), dir, 'po', '*.po')):
+        lang = os.path.basename(f).replace('.po', '')
+        po = os.path.join('po', '%s.po' % lang)
+        mo = os.path.join('mo', lang, '%s.mo' % name)
+
+        bld(rule = 'msgfmt -f ${SRC} -o ${TGT}', source = bld.path.make_node(po), target = bld.path.get_bld().make_node(mo))
+        bld.install_files(os.path.join('${PREFIX}', 'share', 'locale', lang, 'LC_MESSAGES'), mo)
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/256x256/dvdomatic.png b/icons/256x256/dvdomatic.png
new file mode 100644 (file)
index 0000000..ea02e12
Binary files /dev/null and b/icons/256x256/dvdomatic.png 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/512x512/dvdomatic.png b/icons/512x512/dvdomatic.png
new file mode 100644 (file)
index 0000000..65b5012
Binary files /dev/null and b/icons/512x512/dvdomatic.png 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
diff --git a/icons/dcpomatic.icns b/icons/dcpomatic.icns
new file mode 100644 (file)
index 0000000..ef1208e
Binary files /dev/null and b/icons/dcpomatic.icns differ
diff --git a/icons/dcpomatic.iconset/icon_128x128.png b/icons/dcpomatic.iconset/icon_128x128.png
new file mode 100644 (file)
index 0000000..9936b39
Binary files /dev/null and b/icons/dcpomatic.iconset/icon_128x128.png differ
diff --git a/icons/dcpomatic.iconset/icon_128x128@2x.png b/icons/dcpomatic.iconset/icon_128x128@2x.png
new file mode 100644 (file)
index 0000000..9936b39
Binary files /dev/null and b/icons/dcpomatic.iconset/icon_128x128@2x.png differ
diff --git a/icons/dcpomatic.iconset/icon_16x16.png b/icons/dcpomatic.iconset/icon_16x16.png
new file mode 100644 (file)
index 0000000..3c5a10f
Binary files /dev/null and b/icons/dcpomatic.iconset/icon_16x16.png differ
diff --git a/icons/dcpomatic.iconset/icon_16x16@2x.png b/icons/dcpomatic.iconset/icon_16x16@2x.png
new file mode 100644 (file)
index 0000000..3c5a10f
Binary files /dev/null and b/icons/dcpomatic.iconset/icon_16x16@2x.png differ
diff --git a/icons/dcpomatic.iconset/icon_256x256.png b/icons/dcpomatic.iconset/icon_256x256.png
new file mode 100644 (file)
index 0000000..ea02e12
Binary files /dev/null and b/icons/dcpomatic.iconset/icon_256x256.png differ
diff --git a/icons/dcpomatic.iconset/icon_256x256@2x.png b/icons/dcpomatic.iconset/icon_256x256@2x.png
new file mode 100644 (file)
index 0000000..ea02e12
Binary files /dev/null and b/icons/dcpomatic.iconset/icon_256x256@2x.png differ
diff --git a/icons/dcpomatic.iconset/icon_32x32.png b/icons/dcpomatic.iconset/icon_32x32.png
new file mode 100644 (file)
index 0000000..8cecf08
Binary files /dev/null and b/icons/dcpomatic.iconset/icon_32x32.png differ
diff --git a/icons/dcpomatic.iconset/icon_32x32@2x.png b/icons/dcpomatic.iconset/icon_32x32@2x.png
new file mode 100644 (file)
index 0000000..8cecf08
Binary files /dev/null and b/icons/dcpomatic.iconset/icon_32x32@2x.png differ
diff --git a/icons/dcpomatic.iconset/icon_512x512.png b/icons/dcpomatic.iconset/icon_512x512.png
new file mode 100644 (file)
index 0000000..65b5012
Binary files /dev/null and b/icons/dcpomatic.iconset/icon_512x512.png differ
diff --git a/icons/dcpomatic.iconset/icon_512x512@2x.png b/icons/dcpomatic.iconset/icon_512x512@2x.png
new file mode 100644 (file)
index 0000000..65b5012
Binary files /dev/null and b/icons/dcpomatic.iconset/icon_512x512@2x.png differ
diff --git a/icons/make.sh b/icons/make.sh
new file mode 100755 (executable)
index 0000000..36129d6
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+width=$1
+height=$2
+output=$3
+
+inkscape -z -e $output -w $width -h $height -D finish-trace.svg
diff --git a/icons/make_icns.sh b/icons/make_icns.sh
new file mode 100755 (executable)
index 0000000..522e907
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+iconutil --convert icns --output dcpomatic.icns dcpomatic.iconset/
+
diff --git a/icons/taskbar_icon.png b/icons/taskbar_icon.png
new file mode 100644 (file)
index 0000000..fd6b6e9
Binary files /dev/null and b/icons/taskbar_icon.png differ
diff --git a/optimise/8proc.log b/optimise/8proc.log
deleted file mode 100644 (file)
index edc40d9..0000000
+++ /dev/null
@@ -1,2427 +0,0 @@
-Fri Jan 11 19:38:14 2013: DVD-o-matic 0.71pre git d8106aabb6 using libopenjpeg 1.5.0, libavcodec 54.86.100, libavfilter 3.32.100, libavformat 54.59.107, libavutil 52.13.100, libpostproc 52.2.100, libswscale 2.1.103, ImageMagick 6.6.9-7 2012-08-17 Q16 http://www.imagemagick.org, libssh 0.5.2/openssl/zlib, libdcp 0.36pre git e651d843c5
-Fri Jan 11 19:38:14 2013: Starting to make DCP on ip-10-240-125-92
-Fri Jan 11 19:38:14 2013: Content is /mnt/boon_telly.mkv; type video
-Fri Jan 11 19:38:14 2013: Content length 1
-Fri Jan 11 19:38:14 2013: Content digest 72332980e2f9b2fec52e665d9de67f5d
-Fri Jan 11 19:38:14 2013: 8 threads
-Fri Jan 11 19:38:14 2013: J2K bandwidth 200000000
-Fri Jan 11 19:38:14 2013: Transcode job starting
-Fri Jan 11 19:38:14 2013: Audio delay is 0ms
-Fri Jan 11 19:38:14 2013: Will resample audio from 44100 to 47952
-1357933094:340201 encoder thread 0x7fe358024570 sleeps
-1357933094:340494 encoder thread 0x7fe3580a45f0 sleeps
-1357933094:340739 encoder thread 0x7fe3580a4940 sleeps
-1357933094:340894 encoder thread 0x7fe3580a4c90 sleeps
-1357933094:341042 encoder thread 0x7fe3580a5680 sleeps
-1357933094:341172 encoder thread 0x7fe3580a5330 sleeps
-1357933094:341331 encoder thread 0x7fe3580a4fe0 sleeps
-1357933094:341502 encoder thread 0x7fe3580a6090 sleeps
-1357933094:341656 encoder thread 0x7fe3580a63e0 sleeps
-1357933094:341814 encoder thread 0x7fe3580a59d0 sleeps
-1357933094:342030 encoder thread 0x7fe3580a6a80 sleeps
-1357933094:342242 encoder thread 0x7fe3580a6730 sleeps
-1357933094:342402 encoder thread 0x7fe3580a5d40 sleeps
-1357933094:342670 writer sleeps with a queue of 0
-1357933094:342784 encoder thread 0x7fe358031200 sleeps
-1357933094:342894 encoder thread 0x7fe358031550 sleeps
-1357933094:343025 encoder thread 0x7fe358030eb0 sleeps
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0, output at 0
-Fri Jan 11 19:38:14 2013: New graph for 320x240, pixel format 0
-1357933094:359750 Decoder emits 0
-1357933094:359959 adding to queue of 0
-1357933094:360201 encoder thread 0x7fe358024570 wakes with queue of 1
-Fri Jan 11 19:38:14 2013: First video at 0, first audio at 0.279, pushing 12304 audio frames of silence for 2 channels (4 bytes per sample)
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358024570 pops frame 0 from queue
-1357933094:360518 encoder thread 0x7fe358024570 begins local encode of 0
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.033, output at 0.0333667
-1357933094:364961 Decoder emits 1
-1357933094:365141 adding to queue of 0
-1357933094:365349 encoder thread 0x7fe3580a45f0 wakes with queue of 1
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a45f0 pops frame 1 from queue
-1357933094:365531 encoder thread 0x7fe3580a45f0 begins local encode of 1
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.067, output at 0.0667333
-1357933094:366438 Decoder emits 2
-1357933094:366640 adding to queue of 0
-1357933094:366797 encoder thread 0x7fe3580a4940 wakes with queue of 1
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a4940 pops frame 2 from queue
-1357933094:366971 encoder thread 0x7fe3580a4940 begins local encode of 2
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.1, output at 0.1001
-1357933094:367910 Decoder emits 3
-1357933094:368090 adding to queue of 0
-1357933094:368247 encoder thread 0x7fe3580a4c90 wakes with queue of 1
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a4c90 pops frame 3 from queue
-1357933094:368480 encoder thread 0x7fe3580a4c90 begins local encode of 3
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.133, output at 0.133467
-1357933094:369833 Decoder emits 4
-1357933094:369996 adding to queue of 0
-1357933094:370158 encoder thread 0x7fe358031550 wakes with queue of 1
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358031550 pops frame 4 from queue
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.167, output at 0.166833
-1357933094:371634 Decoder emits 5
-1357933094:371799 adding to queue of 0
-1357933094:372031 encoder thread 0x7fe3580a6a80 wakes with queue of 1
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a6a80 pops frame 5 from queue
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.2, output at 0.2002
-1357933094:373749 Decoder emits 6
-1357933094:374043 adding to queue of 0
-1357933094:374300 encoder thread 0x7fe3580a4fe0 wakes with queue of 1
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a4fe0 pops frame 6 from queue
-1357933094:374572 encoder thread 0x7fe3580a4fe0 begins local encode of 6
-Fri Jan 11 19:38:14 2013: Remote encode of 4 on shankly failed (Host not found (authoritative)); thread sleeping for 10s
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358031550 pushes frame 4 back onto queue after failure
-Fri Jan 11 19:38:14 2013: Remote encode of 5 on shankly failed (Host not found (authoritative)); thread sleeping for 10s
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.234, output at 0.233567
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a6a80 pushes frame 5 back onto queue after failure
-1357933094:376385 Decoder emits 7
-1357933094:376576 adding to queue of 2
-1357933094:376741 encoder thread 0x7fe3580a6090 wakes with queue of 3
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a6090 pops frame 5 from queue
-1357933094:376955 encoder thread 0x7fe358030eb0 wakes with queue of 2
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358030eb0 pops frame 4 from queue
-1357933094:377373 encoder thread 0x7fe3580a59d0 wakes with queue of 1
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.267, output at 0.266933
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a59d0 pops frame 7 from queue
-Fri Jan 11 19:38:14 2013: Remote encode of 5 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 10s
-1357933094:378831 encoder thread 0x7fe3580a59d0 begins local encode of 7
-Fri Jan 11 19:38:14 2013: Remote encode of 4 on shankly failed (Host not found (authoritative)); thread sleeping for 10s
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a6090 pushes frame 5 back onto queue after failure
-1357933094:379408 Decoder emits 8
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358030eb0 pushes frame 4 back onto queue after failure
-1357933094:381559 adding to queue of 2
-1357933094:381743 encoder thread 0x7fe3580a5680 wakes with queue of 3
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a5680 pops frame 4 from queue
-1357933094:382048 encoder thread 0x7fe3580a5680 begins local encode of 4
-1357933094:382177 encoder thread 0x7fe3580a5d40 wakes with queue of 2
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a5d40 pops frame 5 from queue
-1357933094:382637 encoder thread 0x7fe3580a6730 wakes with queue of 1
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a6730 pops frame 8 from queue
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.3, output at 0.3003
-Fri Jan 11 19:38:14 2013: Remote encode of 5 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 10s
-1357933094:385624 Decoder emits 9
-Fri Jan 11 19:38:14 2013: Remote encode of 8 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 10s
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a5d40 pushes frame 5 back onto queue after failure
-1357933094:386344 adding to queue of 1
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a6730 pushes frame 8 back onto queue after failure
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.334, output at 0.333667
-1357933094:387790 encoder thread 0x7fe358031200 wakes with queue of 3
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358031200 pops frame 8 from queue
-1357933094:388454 encoder thread 0x7fe3580a63e0 wakes with queue of 2
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a63e0 pops frame 5 from queue
-1357933094:389086 Decoder emits 10
-1357933094:389317 encoder thread 0x7fe3580a5330 wakes with queue of 1
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a5330 pops frame 9 from queue
-1357933094:390132 encoder thread 0x7fe3580a5330 begins local encode of 9
-Fri Jan 11 19:38:14 2013: Remote encode of 8 on shankly failed (Host not found (authoritative)); thread sleeping for 10s
-1357933094:391076 adding to queue of 0
-Fri Jan 11 19:38:14 2013: Remote encode of 5 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 10s
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe358031200 pushes frame 8 back onto queue after failure
-Fri Jan 11 19:38:14 2013: Encoder thread 0x7fe3580a63e0 pushes frame 5 back onto queue after failure
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.367, output at 0.367033
-1357933094:393896 Decoder emits 11
-1357933094:394191 adding to queue of 3
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.4, output at 0.4004
-1357933094:396333 Decoder emits 12
-1357933094:396622 adding to queue of 4
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.434, output at 0.433767
-1357933094:398815 Decoder emits 13
-1357933094:399193 adding to queue of 5
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.467, output at 0.467133
-1357933094:401444 Decoder emits 14
-1357933094:401823 adding to queue of 6
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.501, output at 0.5005
-1357933094:404225 Decoder emits 15
-1357933094:404539 adding to queue of 7
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.534, output at 0.533867
-1357933094:406560 Decoder emits 16
-1357933094:406843 adding to queue of 8
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.567, output at 0.567233
-1357933094:408774 Decoder emits 17
-1357933094:409048 adding to queue of 9
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.601, output at 0.6006
-1357933094:411389 Decoder emits 18
-1357933094:411689 adding to queue of 10
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.634, output at 0.633967
-1357933094:413810 Decoder emits 19
-1357933094:414127 adding to queue of 11
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.667, output at 0.667333
-1357933094:416830 Decoder emits 20
-1357933094:417080 adding to queue of 12
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.701, output at 0.7007
-1357933094:419640 Decoder emits 21
-1357933094:419904 adding to queue of 13
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.734, output at 0.734067
-1357933094:421940 Decoder emits 22
-1357933094:422174 adding to queue of 14
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.767, output at 0.767433
-1357933094:423850 Decoder emits 23
-1357933094:424200 adding to queue of 15
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.801, output at 0.8008
-1357933094:425983 Decoder emits 24
-1357933094:426251 adding to queue of 16
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.834, output at 0.834167
-1357933094:428361 Decoder emits 25
-1357933094:428579 adding to queue of 17
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.868, output at 0.867533
-1357933094:430320 Decoder emits 26
-1357933094:430564 adding to queue of 18
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.901, output at 0.9009
-1357933094:432926 Decoder emits 27
-1357933094:433234 adding to queue of 19
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.934, output at 0.934267
-1357933094:435586 Decoder emits 28
-1357933094:435813 adding to queue of 20
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 0.968, output at 0.967633
-1357933094:438455 Decoder emits 29
-1357933094:440090 adding to queue of 21
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.001, output at 1.001
-1357933094:444491 Decoder emits 30
-1357933094:444860 adding to queue of 22
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.034, output at 1.03437
-1357933094:448724 Decoder emits 31
-1357933094:448976 adding to queue of 23
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.068, output at 1.06773
-1357933094:451685 Decoder emits 32
-1357933094:451946 adding to queue of 24
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.101, output at 1.1011
-1357933094:454092 Decoder emits 33
-1357933094:454334 adding to queue of 25
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.134, output at 1.13447
-1357933094:465182 Decoder emits 34
-1357933094:465635 adding to queue of 26
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.168, output at 1.16783
-1357933094:469982 Decoder emits 35
-1357933094:470438 adding to queue of 27
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.201, output at 1.2012
-1357933094:489678 Decoder emits 36
-1357933094:490046 adding to queue of 28
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.235, output at 1.23457
-1357933094:492329 Decoder emits 37
-1357933094:492583 adding to queue of 29
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.268, output at 1.26793
-1357933094:494702 Decoder emits 38
-1357933094:494982 adding to queue of 30
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.301, output at 1.3013
-1357933094:497256 Decoder emits 39
-1357933094:497654 adding to queue of 31
-Fri Jan 11 19:38:14 2013: Source video frame ready; source at 1.335, output at 1.33467
-1357933094:501057 Decoder emits 40
-1357933094:501413 decoder sleeps with queue of 32
-Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 3
-Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 1
-Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 6
-Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 4
-Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 2
-Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 0
-Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 9
-Fri Jan 11 19:38:21 2013: Finished locally-encoded frame 7
-1357933101:993117 encoder thread 0x7fe3580a4c90 finishes local encode of 3
-1357933101:993423 writer wakes with a queue of 1
-1357933102:3937 encoder thread 0x7fe3580a45f0 finishes local encode of 1
-1357933102:7985 decoder wakes with queue of 32
-1357933102:10662 encoder thread 0x7fe3580a4c90 sleeps
-1357933102:10887 encoder thread 0x7fe3580a4fe0 finishes local encode of 6
-1357933102:11039 decoder sleeps with queue of 32
-1357933102:18971 writer sleeps with a queue of 2
-1357933102:22602 encoder thread 0x7fe3580a45f0 sleeps
-1357933102:22763 encoder thread 0x7fe3580a4c90 wakes with queue of 32
-1357933102:25582 writer wakes with a queue of 2
-1357933102:28203 encoder thread 0x7fe3580a5680 finishes local encode of 4
-Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a4c90 pops frame 5 from queue
-1357933102:31001 encoder thread 0x7fe3580a59d0 finishes local encode of 7
-1357933102:37630 encoder thread 0x7fe358024570 finishes local encode of 0
-1357933102:44369 encoder thread 0x7fe3580a4c90 begins local encode of 5
-1357933102:47717 encoder thread 0x7fe3580a4940 finishes local encode of 2
-1357933102:47823 writer sleeps with a queue of 3
-1357933102:47929 encoder thread 0x7fe3580a4fe0 sleeps
-1357933102:47995 decoder wakes with queue of 31
-1357933102:48092 encoder thread 0x7fe3580a5330 finishes local encode of 9
-1357933102:48178 writer wakes with a queue of 3
-1357933102:48251 adding to queue of 31
-1357933102:48377 encoder thread 0x7fe3580a45f0 wakes with queue of 32
-Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a45f0 pops frame 8 from queue
-1357933102:48663 encoder thread 0x7fe3580a45f0 begins local encode of 8
-1357933102:52712 encoder thread 0x7fe3580a5680 sleeps
-Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.368, output at 1.36803
-1357933102:60215 writer sleeps with a queue of 5
-1357933102:60296 encoder thread 0x7fe3580a59d0 sleeps
-1357933102:60355 encoder thread 0x7fe3580a4fe0 wakes with queue of 31
-1357933102:60518 writer wakes with a queue of 5
-1357933102:60573 Decoder emits 41
-Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a4fe0 pops frame 10 from queue
-1357933102:60790 encoder thread 0x7fe3580a4fe0 begins local encode of 10
-1357933102:71805 writer sleeps with a queue of 4
-1357933102:76222 encoder thread 0x7fe358024570 sleeps
-1357933102:83571 writer wakes with a queue of 4
-1357933102:87934 encoder thread 0x7fe3580a4940 sleeps
-1357933102:94813 encoder thread 0x7fe3580a5330 sleeps
-1357933102:94919 encoder thread 0x7fe3580a5680 wakes with queue of 30
-Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a5680 pops frame 11 from queue
-1357933102:95137 encoder thread 0x7fe3580a5680 begins local encode of 11
-1357933102:95206 encoder thread 0x7fe3580a59d0 wakes with queue of 29
-Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a59d0 pops frame 12 from queue
-1357933102:95350 encoder thread 0x7fe3580a59d0 begins local encode of 12
-1357933102:95450 adding to queue of 28
-1357933102:95638 encoder thread 0x7fe358024570 wakes with queue of 29
-Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe358024570 pops frame 13 from queue
-1357933102:95827 encoder thread 0x7fe358024570 begins local encode of 13
-1357933102:95961 encoder thread 0x7fe3580a4940 wakes with queue of 28
-Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a4940 pops frame 14 from queue
-1357933102:96324 encoder thread 0x7fe3580a4940 begins local encode of 14
-Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.401, output at 1.4014
-1357933102:96884 writer sleeps with a queue of 3
-1357933102:97030 Decoder emits 42
-1357933102:109515 encoder thread 0x7fe3580a5330 wakes with queue of 27
-1357933102:109759 writer wakes with a queue of 3
-Fri Jan 11 19:38:22 2013: Encoder thread 0x7fe3580a5330 pops frame 15 from queue
-1357933102:110205 adding to queue of 26
-Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.435, output at 1.43477
-1357933102:112797 Decoder emits 43
-1357933102:113092 adding to queue of 27
-1357933102:113721 writer sleeps with a queue of 2
-1357933102:113967 encoder thread 0x7fe3580a5330 begins local encode of 15
-1357933102:114180 writer wakes with a queue of 2
-Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.468, output at 1.46813
-1357933102:115092 Decoder emits 44
-1357933102:115964 adding to queue of 28
-Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.502, output at 1.5015
-1357933102:118372 Decoder emits 45
-1357933102:118553 writer sleeps with a queue of 1
-1357933102:118736 writer wakes with a queue of 1
-1357933102:118932 adding to queue of 29
-Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.535, output at 1.53487
-1357933102:120998 Decoder emits 46
-1357933102:121257 adding to queue of 30
-1357933102:122434 writer sleeps with a queue of 0
-Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.568, output at 1.56823
-1357933102:123122 Decoder emits 47
-1357933102:123390 adding to queue of 31
-Fri Jan 11 19:38:22 2013: Source video frame ready; source at 1.602, output at 1.6016
-1357933102:125079 Decoder emits 48
-1357933102:125306 decoder sleeps with queue of 32
-1357933104:375629 decoder wakes with queue of 32
-1357933104:376068 encoder thread 0x7fe358031550 sleeps
-1357933104:376536 decoder sleeps with queue of 32
-1357933104:376946 encoder thread 0x7fe3580a6a80 sleeps
-1357933104:377378 decoder wakes with queue of 32
-1357933104:377797 decoder sleeps with queue of 32
-1357933104:378186 encoder thread 0x7fe358031550 wakes with queue of 32
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe358031550 pops frame 16 from queue
-1357933104:379015 encoder thread 0x7fe3580a6a80 wakes with queue of 31
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a6a80 pops frame 17 from queue
-1357933104:379776 encoder thread 0x7fe3580a6090 sleeps
-1357933104:380137 decoder wakes with queue of 30
-Fri Jan 11 19:38:24 2013: Remote encode of 16 on shankly failed (Host not found (authoritative)); thread sleeping for 20s
-1357933104:380914 adding to queue of 30
-Fri Jan 11 19:38:24 2013: Remote encode of 17 on shankly failed (Host not found (authoritative)); thread sleeping for 20s
-Fri Jan 11 19:38:24 2013: Source video frame ready; source at 1.635, output at 1.63497
-1357933104:393590 encoder thread 0x7fe358030eb0 sleeps
-1357933104:393986 encoder thread 0x7fe3580a6090 wakes with queue of 31
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a6090 pops frame 18 from queue
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe358031550 pushes frame 16 back onto queue after failure
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a6a80 pushes frame 17 back onto queue after failure
-1357933104:395915 encoder thread 0x7fe3580a5d40 sleeps
-1357933104:396286 encoder thread 0x7fe3580a6730 sleeps
-1357933104:396655 encoder thread 0x7fe358031200 sleeps
-1357933104:397018 encoder thread 0x7fe3580a63e0 sleeps
-1357933104:397354 encoder thread 0x7fe358030eb0 wakes with queue of 32
-Fri Jan 11 19:38:24 2013: Remote encode of 18 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 20s
-1357933104:398091 Decoder emits 49
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe358030eb0 pops frame 17 from queue
-1357933104:398770 encoder thread 0x7fe3580a5d40 wakes with queue of 31
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a5d40 pops frame 16 from queue
-1357933104:399433 encoder thread 0x7fe3580a6730 wakes with queue of 30
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a6730 pops frame 19 from queue
-Fri Jan 11 19:38:24 2013: Remote encode of 17 on shankly failed (Host not found (authoritative)); thread sleeping for 20s
-Fri Jan 11 19:38:24 2013: Remote encode of 16 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 20s
-1357933104:400921 encoder thread 0x7fe358031200 wakes with queue of 29
-Fri Jan 11 19:38:24 2013: Remote encode of 19 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 20s
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe358031200 pops frame 20 from queue
-1357933104:402087 encoder thread 0x7fe3580a63e0 wakes with queue of 28
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a63e0 pops frame 21 from queue
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a6090 pushes frame 18 back onto queue after failure
-Fri Jan 11 19:38:24 2013: Remote encode of 20 on shankly failed (Host not found (authoritative)); thread sleeping for 20s
-1357933104:403658 adding to queue of 28
-Fri Jan 11 19:38:24 2013: Remote encode of 21 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 20s
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe358030eb0 pushes frame 17 back onto queue after failure
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a5d40 pushes frame 16 back onto queue after failure
-Fri Jan 11 19:38:24 2013: Source video frame ready; source at 1.668, output at 1.66833
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a6730 pushes frame 19 back onto queue after failure
-1357933104:405879 Decoder emits 50
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe358031200 pushes frame 20 back onto queue after failure
-Fri Jan 11 19:38:24 2013: Encoder thread 0x7fe3580a63e0 pushes frame 21 back onto queue after failure
-1357933104:406940 decoder sleeps with queue of 34
-Fri Jan 11 19:38:28 2013: Finished locally-encoded frame 8
-1357933108:903758 encoder thread 0x7fe3580a45f0 finishes local encode of 8
-1357933108:904086 writer wakes with a queue of 1
-1357933108:922214 encoder thread 0x7fe3580a45f0 sleeps
-1357933108:922312 decoder wakes with queue of 34
-1357933108:922452 decoder sleeps with queue of 34
-1357933108:922559 encoder thread 0x7fe3580a45f0 wakes with queue of 34
-Fri Jan 11 19:38:28 2013: Encoder thread 0x7fe3580a45f0 pops frame 21 from queue
-1357933108:922709 encoder thread 0x7fe3580a45f0 begins local encode of 21
-1357933108:926212 writer sleeps with a queue of 0
-Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 5
-1357933109:181344 encoder thread 0x7fe3580a4c90 finishes local encode of 5
-Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 15
-Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 13
-Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 12
-Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 10
-1357933109:273438 writer wakes with a queue of 1
-Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 14
-Fri Jan 11 19:38:29 2013: Finished locally-encoded frame 11
-1357933109:274120 encoder thread 0x7fe3580a4c90 sleeps
-1357933109:274347 decoder wakes with queue of 33
-1357933109:274616 encoder thread 0x7fe3580a5330 finishes local encode of 15
-1357933109:274815 encoder thread 0x7fe358024570 finishes local encode of 13
-1357933109:275027 decoder sleeps with queue of 33
-1357933109:275243 encoder thread 0x7fe3580a4c90 wakes with queue of 33
-Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe3580a4c90 pops frame 20 from queue
-1357933109:275540 encoder thread 0x7fe3580a4fe0 finishes local encode of 10
-1357933109:302943 encoder thread 0x7fe3580a59d0 finishes local encode of 12
-1357933109:303092 encoder thread 0x7fe3580a4940 finishes local encode of 14
-1357933109:303216 encoder thread 0x7fe3580a5680 finishes local encode of 11
-1357933109:303343 encoder thread 0x7fe3580a4c90 begins local encode of 20
-1357933109:305539 writer sleeps with a queue of 2
-1357933109:305645 encoder thread 0x7fe3580a5330 sleeps
-1357933109:305779 encoder thread 0x7fe358024570 sleeps
-1357933109:305845 decoder wakes with queue of 32
-1357933109:305940 writer wakes with a queue of 2
-1357933109:306010 decoder sleeps with queue of 32
-1357933109:319371 encoder thread 0x7fe358024570 wakes with queue of 32
-1357933109:319478 encoder thread 0x7fe3580a5680 sleeps
-Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe358024570 pops frame 19 from queue
-1357933109:319729 encoder thread 0x7fe358024570 begins local encode of 19
-1357933109:331703 writer sleeps with a queue of 5
-1357933109:345077 encoder thread 0x7fe3580a59d0 sleeps
-1357933109:358909 encoder thread 0x7fe3580a4fe0 sleeps
-1357933109:359054 writer wakes with a queue of 5
-1357933109:359167 encoder thread 0x7fe3580a5330 wakes with queue of 31
-1357933109:359292 encoder thread 0x7fe3580a4940 sleeps
-Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe3580a5330 pops frame 16 from queue
-1357933109:359566 encoder thread 0x7fe3580a5330 begins local encode of 16
-1357933109:359626 decoder wakes with queue of 30
-1357933109:359802 adding to queue of 30
-1357933109:359993 encoder thread 0x7fe3580a5680 wakes with queue of 31
-Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe3580a5680 pops frame 17 from queue
-1357933109:360297 encoder thread 0x7fe3580a5680 begins local encode of 17
-1357933109:360414 encoder thread 0x7fe3580a59d0 wakes with queue of 30
-Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe3580a59d0 pops frame 18 from queue
-1357933109:360637 encoder thread 0x7fe3580a59d0 begins local encode of 18
-Fri Jan 11 19:38:29 2013: Source video frame ready; source at 1.702, output at 1.7017
-1357933109:361593 Decoder emits 51
-1357933109:362224 writer sleeps with a queue of 4
-1357933109:362368 encoder thread 0x7fe3580a4fe0 wakes with queue of 29
-1357933109:362457 writer wakes with a queue of 4
-Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe3580a4fe0 pops frame 22 from queue
-1357933109:362689 encoder thread 0x7fe3580a4fe0 begins local encode of 22
-1357933109:363838 encoder thread 0x7fe3580a4940 wakes with queue of 28
-Fri Jan 11 19:38:29 2013: Encoder thread 0x7fe3580a4940 pops frame 23 from queue
-1357933109:364204 encoder thread 0x7fe3580a4940 begins local encode of 23
-1357933109:364383 adding to queue of 27
-1357933109:366117 writer sleeps with a queue of 3
-Fri Jan 11 19:38:29 2013: Source video frame ready; source at 1.735, output at 1.73507
-1357933109:366765 Decoder emits 52
-1357933109:367035 adding to queue of 28
-Fri Jan 11 19:38:29 2013: Source video frame ready; source at 1.768, output at 1.76843
-1357933109:368911 Decoder emits 53
-1357933109:369164 adding to queue of 29
-Fri Jan 11 19:38:29 2013: Source video frame ready; source at 1.802, output at 1.8018
-1357933109:371579 writer wakes with a queue of 3
-1357933109:371810 Decoder emits 54
-1357933109:372115 adding to queue of 30
-Fri Jan 11 19:38:29 2013: Source video frame ready; source at 1.835, output at 1.83517
-1357933109:374135 Decoder emits 55
-1357933109:374415 adding to queue of 31
-Fri Jan 11 19:38:29 2013: Source video frame ready; source at 1.869, output at 1.86853
-1357933109:376060 Decoder emits 56
-1357933109:376403 decoder sleeps with queue of 32
-1357933109:389508 writer sleeps with a queue of 2
-1357933109:389828 writer wakes with a queue of 2
-1357933109:393643 writer sleeps with a queue of 1
-1357933109:393940 writer wakes with a queue of 1
-1357933109:397571 writer sleeps with a queue of 0
-Fri Jan 11 19:38:33 2013: Finished locally-encoded frame 21
-1357933113:380530 encoder thread 0x7fe3580a45f0 finishes local encode of 21
-1357933113:380913 encoder thread 0x7fe3580a45f0 sleeps
-1357933113:381429 decoder wakes with queue of 32
-1357933113:381828 writer wakes with a queue of 1
-1357933113:382145 decoder sleeps with queue of 32
-1357933113:382500 encoder thread 0x7fe3580a45f0 wakes with queue of 32
-Fri Jan 11 19:38:33 2013: Encoder thread 0x7fe3580a45f0 pops frame 24 from queue
-1357933113:383231 encoder thread 0x7fe3580a45f0 begins local encode of 24
-1357933113:385816 writer sleeps with a queue of 0
-Fri Jan 11 19:38:35 2013: Finished locally-encoded frame 16
-1357933115:890275 encoder thread 0x7fe3580a5330 finishes local encode of 16
-Fri Jan 11 19:38:35 2013: Finished locally-encoded frame 22
-1357933116:26523 encoder thread 0x7fe3580a5330 sleeps
-Fri Jan 11 19:38:36 2013: Finished locally-encoded frame 23
-1357933116:103278 writer wakes with a queue of 1
-Fri Jan 11 19:38:36 2013: Finished locally-encoded frame 18
-Fri Jan 11 19:38:36 2013: Finished locally-encoded frame 20
-1357933116:136045 encoder thread 0x7fe3580a4fe0 finishes local encode of 22
-Fri Jan 11 19:38:36 2013: Finished locally-encoded frame 19
-1357933116:136315 decoder wakes with queue of 31
-Fri Jan 11 19:38:36 2013: Finished locally-encoded frame 17
-1357933116:136580 encoder thread 0x7fe3580a4940 finishes local encode of 23
-Fri Jan 11 19:38:36 2013: Finished locally-encoded frame 24
-1357933116:136865 adding to queue of 31
-1357933116:136974 encoder thread 0x7fe3580a4c90 finishes local encode of 20
-1357933116:137088 encoder thread 0x7fe3580a59d0 finishes local encode of 18
-1357933116:137241 encoder thread 0x7fe3580a5330 wakes with queue of 32
-Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a5330 pops frame 25 from queue
-1357933116:137426 encoder thread 0x7fe358024570 finishes local encode of 19
-1357933116:137535 encoder thread 0x7fe3580a5330 begins local encode of 25
-1357933116:151350 encoder thread 0x7fe3580a45f0 finishes local encode of 24
-1357933116:164741 encoder thread 0x7fe3580a4940 sleeps
-1357933116:164866 writer sleeps with a queue of 5
-Fri Jan 11 19:38:36 2013: Source video frame ready; source at 1.902, output at 1.9019
-1357933116:165039 encoder thread 0x7fe3580a4fe0 sleeps
-1357933116:165109 encoder thread 0x7fe3580a4c90 sleeps
-1357933116:165172 encoder thread 0x7fe3580a5680 finishes local encode of 17
-1357933116:165240 encoder thread 0x7fe3580a59d0 sleeps
-1357933116:165297 encoder thread 0x7fe358024570 sleeps
-1357933116:165361 encoder thread 0x7fe3580a4940 wakes with queue of 31
-1357933116:165433 writer wakes with a queue of 5
-1357933116:165505 Decoder emits 57
-Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a4940 pops frame 26 from queue
-1357933116:165660 encoder thread 0x7fe3580a4940 begins local encode of 26
-1357933116:165704 encoder thread 0x7fe3580a4fe0 wakes with queue of 30
-Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a4fe0 pops frame 27 from queue
-1357933116:165850 encoder thread 0x7fe3580a4fe0 begins local encode of 27
-1357933116:165901 encoder thread 0x7fe3580a4c90 wakes with queue of 29
-Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a4c90 pops frame 28 from queue
-1357933116:166098 encoder thread 0x7fe3580a4c90 begins local encode of 28
-1357933116:166146 encoder thread 0x7fe3580a59d0 wakes with queue of 28
-Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a59d0 pops frame 29 from queue
-1357933116:166347 encoder thread 0x7fe3580a59d0 begins local encode of 29
-1357933116:166441 encoder thread 0x7fe358024570 wakes with queue of 27
-Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe358024570 pops frame 30 from queue
-1357933116:166582 encoder thread 0x7fe358024570 begins local encode of 30
-1357933116:176479 adding to queue of 26
-Fri Jan 11 19:38:36 2013: Source video frame ready; source at 1.935, output at 1.93527
-1357933116:178524 writer sleeps with a queue of 6
-1357933116:178587 writer wakes with a queue of 6
-1357933116:178665 Decoder emits 58
-1357933116:178906 adding to queue of 27
-Fri Jan 11 19:38:36 2013: Source video frame ready; source at 1.969, output at 1.96863
-1357933116:181499 Decoder emits 59
-1357933116:181653 adding to queue of 28
-1357933116:182321 writer sleeps with a queue of 5
-1357933116:182450 encoder thread 0x7fe3580a45f0 sleeps
-1357933116:182595 encoder thread 0x7fe3580a45f0 wakes with queue of 29
-Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a45f0 pops frame 31 from queue
-1357933116:182755 encoder thread 0x7fe3580a45f0 begins local encode of 31
-Fri Jan 11 19:38:36 2013: Source video frame ready; source at 2.002, output at 2.002
-1357933116:183530 Decoder emits 60
-1357933116:183657 adding to queue of 28
-Fri Jan 11 19:38:36 2013: Source video frame ready; source at 2.035, output at 2.03537
-1357933116:186741 Decoder emits 61
-1357933116:187020 adding to queue of 29
-1357933116:188457 encoder thread 0x7fe3580a5680 sleeps
-1357933116:189864 writer wakes with a queue of 5
-Fri Jan 11 19:38:36 2013: Source video frame ready; source at 2.069, output at 2.06873
-1357933116:192759 encoder thread 0x7fe3580a5680 wakes with queue of 30
-Fri Jan 11 19:38:36 2013: Encoder thread 0x7fe3580a5680 pops frame 32 from queue
-1357933116:193167 encoder thread 0x7fe3580a5680 begins local encode of 32
-1357933116:193338 Decoder emits 62
-1357933116:193598 adding to queue of 29
-1357933116:194932 writer sleeps with a queue of 4
-1357933116:195245 writer wakes with a queue of 4
-Fri Jan 11 19:38:36 2013: Source video frame ready; source at 2.102, output at 2.1021
-1357933116:196145 Decoder emits 63
-1357933116:196502 adding to queue of 30
-Fri Jan 11 19:38:36 2013: Source video frame ready; source at 2.135, output at 2.13547
-1357933116:198708 Decoder emits 64
-1357933116:198947 adding to queue of 31
-Fri Jan 11 19:38:36 2013: Source video frame ready; source at 2.169, output at 2.16883
-1357933116:200702 writer sleeps with a queue of 3
-1357933116:200906 writer wakes with a queue of 3
-1357933116:201299 Decoder emits 65
-1357933116:201648 decoder sleeps with queue of 32
-1357933116:204559 writer sleeps with a queue of 2
-1357933116:204726 writer wakes with a queue of 2
-1357933116:208531 writer sleeps with a queue of 1
-1357933116:208702 writer wakes with a queue of 1
-1357933116:212392 writer sleeps with a queue of 0
-Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 31
-1357933123:218645 encoder thread 0x7fe3580a45f0 finishes local encode of 31
-Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 30
-Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 28
-Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 26
-1357933123:407719 writer wakes with a queue of 1
-Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 27
-1357933123:407921 encoder thread 0x7fe3580a45f0 sleeps
-1357933123:408055 decoder wakes with queue of 32
-Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 29
-1357933123:408313 encoder thread 0x7fe358024570 finishes local encode of 30
-Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 25
-Fri Jan 11 19:38:43 2013: Finished locally-encoded frame 32
-1357933123:408728 encoder thread 0x7fe3580a4c90 finishes local encode of 28
-1357933123:408877 decoder sleeps with queue of 32
-1357933123:409054 encoder thread 0x7fe3580a45f0 wakes with queue of 32
-Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a45f0 pops frame 33 from queue
-1357933123:409261 encoder thread 0x7fe3580a45f0 begins local encode of 33
-1357933123:409461 encoder thread 0x7fe3580a4940 finishes local encode of 26
-1357933123:423780 encoder thread 0x7fe3580a4fe0 finishes local encode of 27
-1357933123:438064 encoder thread 0x7fe3580a5680 finishes local encode of 32
-1357933123:438162 decoder wakes with queue of 31
-1357933123:438320 encoder thread 0x7fe3580a5330 finishes local encode of 25
-1357933123:438404 encoder thread 0x7fe358024570 sleeps
-1357933123:438491 writer sleeps with a queue of 3
-1357933123:438571 encoder thread 0x7fe3580a4940 sleeps
-1357933123:438643 encoder thread 0x7fe3580a59d0 finishes local encode of 29
-1357933123:438723 adding to queue of 31
-1357933123:438820 writer wakes with a queue of 3
-Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.202, output at 2.2022
-1357933123:452255 encoder thread 0x7fe3580a4c90 sleeps
-1357933123:452389 encoder thread 0x7fe358024570 wakes with queue of 32
-1357933123:452482 Decoder emits 66
-Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe358024570 pops frame 34 from queue
-1357933123:452712 encoder thread 0x7fe358024570 begins local encode of 34
-1357933123:452767 encoder thread 0x7fe3580a4940 wakes with queue of 31
-Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a4940 pops frame 35 from queue
-1357933123:452989 encoder thread 0x7fe3580a4940 begins local encode of 35
-1357933123:466394 writer sleeps with a queue of 6
-1357933123:476131 encoder thread 0x7fe3580a4fe0 sleeps
-1357933123:486712 writer wakes with a queue of 6
-1357933123:486858 encoder thread 0x7fe3580a5680 sleeps
-1357933123:487013 encoder thread 0x7fe3580a4fe0 wakes with queue of 30
-1357933123:487118 encoder thread 0x7fe3580a5330 sleeps
-Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a4fe0 pops frame 36 from queue
-1357933123:487345 encoder thread 0x7fe3580a4fe0 begins local encode of 36
-1357933123:487428 encoder thread 0x7fe3580a4c90 wakes with queue of 29
-Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a4c90 pops frame 37 from queue
-1357933123:487643 encoder thread 0x7fe3580a4c90 begins local encode of 37
-1357933123:487765 adding to queue of 28
-Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.236, output at 2.23557
-1357933123:498635 encoder thread 0x7fe3580a59d0 sleeps
-1357933123:498886 Decoder emits 67
-1357933123:499017 encoder thread 0x7fe3580a5680 wakes with queue of 29
-Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a5680 pops frame 38 from queue
-1357933123:499271 encoder thread 0x7fe3580a5680 begins local encode of 38
-1357933123:499364 encoder thread 0x7fe3580a5330 wakes with queue of 28
-Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a5330 pops frame 39 from queue
-1357933123:499644 encoder thread 0x7fe3580a5330 begins local encode of 39
-1357933123:500692 writer sleeps with a queue of 5
-1357933123:500910 writer wakes with a queue of 5
-1357933123:501054 encoder thread 0x7fe3580a59d0 wakes with queue of 27
-Fri Jan 11 19:38:43 2013: Encoder thread 0x7fe3580a59d0 pops frame 40 from queue
-1357933123:501365 encoder thread 0x7fe3580a59d0 begins local encode of 40
-1357933123:501474 adding to queue of 26
-Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.269, output at 2.26893
-1357933123:503979 Decoder emits 68
-1357933123:504243 adding to queue of 27
-1357933123:504600 writer sleeps with a queue of 4
-1357933123:504728 writer wakes with a queue of 4
-Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.302, output at 2.3023
-1357933123:507138 Decoder emits 69
-1357933123:507367 adding to queue of 28
-1357933123:509074 writer sleeps with a queue of 3
-1357933123:509376 writer wakes with a queue of 3
-Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.336, output at 2.33567
-1357933123:510128 Decoder emits 70
-1357933123:510398 adding to queue of 29
-Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.369, output at 2.36903
-1357933123:512603 Decoder emits 71
-1357933123:512861 adding to queue of 30
-Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.402, output at 2.4024
-1357933123:515123 Decoder emits 72
-1357933123:515389 adding to queue of 31
-Fri Jan 11 19:38:43 2013: Source video frame ready; source at 2.436, output at 2.43577
-1357933123:517884 Decoder emits 73
-1357933123:518124 decoder sleeps with queue of 32
-1357933123:524203 writer sleeps with a queue of 2
-1357933123:524484 writer wakes with a queue of 2
-1357933123:529054 writer sleeps with a queue of 1
-1357933123:529365 writer wakes with a queue of 1
-1357933123:533776 writer sleeps with a queue of 0
-1357933124:395531 decoder wakes with queue of 32
-1357933124:395971 encoder thread 0x7fe358031550 sleeps
-1357933124:396417 decoder sleeps with queue of 32
-1357933124:396762 encoder thread 0x7fe3580a6a80 sleeps
-1357933124:397154 decoder wakes with queue of 32
-1357933124:397593 decoder sleeps with queue of 32
-1357933124:397957 encoder thread 0x7fe358031550 wakes with queue of 32
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe358031550 pops frame 41 from queue
-1357933124:398719 encoder thread 0x7fe3580a6a80 wakes with queue of 31
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a6a80 pops frame 42 from queue
-Fri Jan 11 19:38:44 2013: Remote encode of 41 on shankly failed (Host not found (authoritative)); thread sleeping for 30s
-Fri Jan 11 19:38:44 2013: Remote encode of 42 on shankly failed (Host not found (authoritative)); thread sleeping for 30s
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe358031550 pushes frame 41 back onto queue after failure
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a6a80 pushes frame 42 back onto queue after failure
-1357933124:403437 encoder thread 0x7fe3580a6090 sleeps
-1357933124:403804 decoder wakes with queue of 32
-1357933124:404217 decoder sleeps with queue of 32
-1357933124:404693 encoder thread 0x7fe3580a6090 wakes with queue of 32
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a6090 pops frame 42 from queue
-1357933124:405429 encoder thread 0x7fe358030eb0 sleeps
-1357933124:405849 decoder wakes with queue of 31
-1357933124:406590 adding to queue of 31
-Fri Jan 11 19:38:44 2013: Remote encode of 42 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 30s
-1357933124:410919 encoder thread 0x7fe3580a5d40 sleeps
-1357933124:411494 encoder thread 0x7fe358030eb0 wakes with queue of 32
-Fri Jan 11 19:38:44 2013: Source video frame ready; source at 2.469, output at 2.46913
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe358030eb0 pops frame 41 from queue
-1357933124:413762 Decoder emits 74
-1357933124:414500 encoder thread 0x7fe3580a6730 sleeps
-1357933124:427061 encoder thread 0x7fe358031200 sleeps
-Fri Jan 11 19:38:44 2013: Remote encode of 41 on shankly failed (Host not found (authoritative)); thread sleeping for 30s
-1357933124:428843 encoder thread 0x7fe3580a63e0 sleeps
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a6090 pushes frame 42 back onto queue after failure
-1357933124:429727 encoder thread 0x7fe3580a5d40 wakes with queue of 32
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a5d40 pops frame 42 from queue
-1357933124:430875 adding to queue of 31
-1357933124:431481 encoder thread 0x7fe3580a6730 wakes with queue of 32
-Fri Jan 11 19:38:44 2013: Remote encode of 42 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 30s
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a6730 pops frame 43 from queue
-1357933124:436047 encoder thread 0x7fe358031200 wakes with queue of 31
-Fri Jan 11 19:38:44 2013: Source video frame ready; source at 2.503, output at 2.5025
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe358031200 pops frame 44 from queue
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe358030eb0 pushes frame 41 back onto queue after failure
-1357933124:439474 Decoder emits 75
-Fri Jan 11 19:38:44 2013: Remote encode of 43 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 30s
-1357933124:440343 encoder thread 0x7fe3580a63e0 wakes with queue of 31
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a63e0 pops frame 41 from queue
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a5d40 pushes frame 42 back onto queue after failure
-Fri Jan 11 19:38:44 2013: Remote encode of 44 on shankly failed (Host not found (authoritative)); thread sleeping for 30s
-1357933124:442532 adding to queue of 31
-Fri Jan 11 19:38:44 2013: Remote encode of 41 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 30s
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a6730 pushes frame 43 back onto queue after failure
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe358031200 pushes frame 44 back onto queue after failure
-Fri Jan 11 19:38:44 2013: Encoder thread 0x7fe3580a63e0 pushes frame 41 back onto queue after failure
-Fri Jan 11 19:38:44 2013: Source video frame ready; source at 2.536, output at 2.53587
-1357933124:447003 Decoder emits 76
-1357933124:447526 decoder sleeps with queue of 35
-Fri Jan 11 19:38:48 2013: Finished locally-encoded frame 33
-1357933128:522199 encoder thread 0x7fe3580a45f0 finishes local encode of 33
-1357933128:522522 writer wakes with a queue of 1
-1357933128:570490 decoder wakes with queue of 35
-1357933128:572143 decoder sleeps with queue of 35
-1357933128:572227 encoder thread 0x7fe3580a45f0 sleeps
-1357933128:572348 encoder thread 0x7fe3580a45f0 wakes with queue of 35
-Fri Jan 11 19:38:48 2013: Encoder thread 0x7fe3580a45f0 pops frame 41 from queue
-1357933128:572508 encoder thread 0x7fe3580a45f0 begins local encode of 41
-1357933128:574402 writer sleeps with a queue of 0
-Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 34
-1357933129:154610 encoder thread 0x7fe358024570 finishes local encode of 34
-1357933129:155254 writer wakes with a queue of 1
-1357933129:167760 encoder thread 0x7fe358024570 sleeps
-1357933129:169522 decoder wakes with queue of 34
-1357933129:172190 decoder sleeps with queue of 34
-1357933129:172446 writer sleeps with a queue of 0
-1357933129:172631 encoder thread 0x7fe358024570 wakes with queue of 34
-Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe358024570 pops frame 44 from queue
-1357933129:172910 encoder thread 0x7fe358024570 begins local encode of 44
-Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 39
-1357933129:223266 encoder thread 0x7fe3580a5330 finishes local encode of 39
-1357933129:223556 writer wakes with a queue of 1
-1357933129:294239 encoder thread 0x7fe3580a5330 sleeps
-1357933129:294364 decoder wakes with queue of 33
-1357933129:294441 decoder sleeps with queue of 33
-1357933129:294532 encoder thread 0x7fe3580a5330 wakes with queue of 33
-Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe3580a5330 pops frame 43 from queue
-1357933129:294693 encoder thread 0x7fe3580a5330 begins local encode of 43
-1357933129:297852 writer sleeps with a queue of 0
-Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 38
-Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 40
-1357933129:466490 encoder thread 0x7fe3580a5680 finishes local encode of 38
-Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 36
-1357933129:522282 encoder thread 0x7fe3580a59d0 finishes local encode of 40
-Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 35
-Fri Jan 11 19:38:49 2013: Finished locally-encoded frame 37
-1357933129:523059 writer wakes with a queue of 1
-1357933129:523326 encoder thread 0x7fe3580a5680 sleeps
-1357933129:523649 decoder wakes with queue of 32
-1357933129:523876 decoder sleeps with queue of 32
-1357933129:523999 encoder thread 0x7fe3580a4940 finishes local encode of 35
-1357933129:528241 encoder thread 0x7fe3580a4fe0 finishes local encode of 36
-1357933129:549790 encoder thread 0x7fe3580a4c90 finishes local encode of 37
-1357933129:549940 encoder thread 0x7fe3580a59d0 sleeps
-1357933129:550044 encoder thread 0x7fe3580a5680 wakes with queue of 32
-Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe3580a5680 pops frame 42 from queue
-1357933129:550316 encoder thread 0x7fe3580a5680 begins local encode of 42
-1357933129:550420 decoder wakes with queue of 31
-1357933129:550552 adding to queue of 31
-1357933129:551794 writer sleeps with a queue of 4
-Fri Jan 11 19:38:49 2013: Source video frame ready; source at 2.569, output at 2.56923
-1357933129:592770 encoder thread 0x7fe3580a4940 sleeps
-1357933129:592953 Decoder emits 77
-1357933129:593076 writer wakes with a queue of 4
-1357933129:593222 encoder thread 0x7fe3580a4c90 sleeps
-1357933129:593369 encoder thread 0x7fe3580a59d0 wakes with queue of 32
-1357933129:593512 encoder thread 0x7fe3580a4fe0 sleeps
-Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe3580a59d0 pops frame 45 from queue
-1357933129:593796 encoder thread 0x7fe3580a59d0 begins local encode of 45
-1357933129:593909 encoder thread 0x7fe3580a4940 wakes with queue of 31
-Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe3580a4940 pops frame 46 from queue
-1357933129:594182 encoder thread 0x7fe3580a4940 begins local encode of 46
-1357933129:594319 adding to queue of 30
-Fri Jan 11 19:38:49 2013: Source video frame ready; source at 2.603, output at 2.6026
-1357933129:596326 encoder thread 0x7fe3580a4c90 wakes with queue of 31
-1357933129:598370 writer sleeps with a queue of 3
-1357933129:598521 Decoder emits 78
-Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe3580a4c90 pops frame 47 from queue
-1357933129:598799 writer wakes with a queue of 3
-1357933129:599014 encoder thread 0x7fe3580a4c90 begins local encode of 47
-1357933129:600090 encoder thread 0x7fe3580a4fe0 wakes with queue of 30
-Fri Jan 11 19:38:49 2013: Encoder thread 0x7fe3580a4fe0 pops frame 48 from queue
-1357933129:600552 adding to queue of 29
-1357933129:601030 encoder thread 0x7fe3580a4fe0 begins local encode of 48
-1357933129:602301 writer sleeps with a queue of 2
-1357933129:604481 writer wakes with a queue of 2
-Fri Jan 11 19:38:49 2013: Source video frame ready; source at 2.636, output at 2.63597
-1357933129:605223 Decoder emits 79
-1357933129:605653 adding to queue of 30
-Fri Jan 11 19:38:49 2013: Source video frame ready; source at 2.669, output at 2.66933
-1357933129:608174 Decoder emits 80
-1357933129:608533 adding to queue of 31
-1357933129:608773 writer sleeps with a queue of 1
-1357933129:609027 writer wakes with a queue of 1
-Fri Jan 11 19:38:49 2013: Source video frame ready; source at 2.703, output at 2.7027
-1357933129:611833 Decoder emits 81
-1357933129:612122 decoder sleeps with queue of 32
-1357933129:613890 writer sleeps with a queue of 0
-Fri Jan 11 19:38:51 2013: Finished locally-encoded frame 41
-1357933131:643683 encoder thread 0x7fe3580a45f0 finishes local encode of 41
-1357933131:644053 encoder thread 0x7fe3580a45f0 sleeps
-1357933131:644379 writer wakes with a queue of 1
-1357933131:644845 decoder wakes with queue of 32
-1357933131:645233 decoder sleeps with queue of 32
-1357933131:645607 encoder thread 0x7fe3580a45f0 wakes with queue of 32
-Fri Jan 11 19:38:51 2013: Encoder thread 0x7fe3580a45f0 pops frame 49 from queue
-1357933131:646289 encoder thread 0x7fe3580a45f0 begins local encode of 49
-1357933131:648638 writer sleeps with a queue of 0
-Fri Jan 11 19:38:54 2013: Finished locally-encoded frame 43
-1357933134:553870 encoder thread 0x7fe3580a5330 finishes local encode of 43
-1357933134:553990 writer wakes with a queue of 1
-1357933134:556931 writer sleeps with a queue of 0
-1357933134:557016 encoder thread 0x7fe3580a5330 sleeps
-1357933134:557111 decoder wakes with queue of 31
-1357933134:557261 adding to queue of 31
-1357933134:557388 encoder thread 0x7fe3580a5330 wakes with queue of 32
-Fri Jan 11 19:38:54 2013: Encoder thread 0x7fe3580a5330 pops frame 50 from queue
-1357933134:557536 encoder thread 0x7fe3580a5330 begins local encode of 50
-Fri Jan 11 19:38:54 2013: Source video frame ready; source at 2.736, output at 2.73607
-1357933134:558867 Decoder emits 82
-1357933134:558981 adding to queue of 31
-Fri Jan 11 19:38:54 2013: Source video frame ready; source at 2.769, output at 2.76943
-1357933134:560985 Decoder emits 83
-1357933134:561079 decoder sleeps with queue of 32
-Fri Jan 11 19:38:54 2013: Finished locally-encoded frame 44
-1357933134:782210 encoder thread 0x7fe358024570 finishes local encode of 44
-1357933134:782324 writer wakes with a queue of 1
-1357933134:782389 decoder wakes with queue of 32
-1357933134:782452 decoder sleeps with queue of 32
-1357933134:782494 encoder thread 0x7fe358024570 sleeps
-1357933134:782545 encoder thread 0x7fe358024570 wakes with queue of 32
-Fri Jan 11 19:38:54 2013: Encoder thread 0x7fe358024570 pops frame 51 from queue
-1357933134:782649 encoder thread 0x7fe358024570 begins local encode of 51
-1357933134:785342 writer sleeps with a queue of 0
-Fri Jan 11 19:38:55 2013: Finished locally-encoded frame 47
-1357933136:19792 encoder thread 0x7fe3580a4c90 finishes local encode of 47
-Fri Jan 11 19:38:56 2013: Finished locally-encoded frame 48
-Fri Jan 11 19:38:56 2013: Finished locally-encoded frame 46
-1357933136:121135 writer wakes with a queue of 1
-Fri Jan 11 19:38:56 2013: Finished locally-encoded frame 45
-1357933136:121397 encoder thread 0x7fe3580a4c90 sleeps
-1357933136:121508 decoder wakes with queue of 31
-Fri Jan 11 19:38:56 2013: Finished locally-encoded frame 42
-1357933136:121681 adding to queue of 31
-1357933136:121738 encoder thread 0x7fe3580a4fe0 finishes local encode of 48
-1357933136:122320 encoder thread 0x7fe3580a59d0 finishes local encode of 45
-1357933136:136126 encoder thread 0x7fe3580a5680 finishes local encode of 42
-Fri Jan 11 19:38:56 2013: Source video frame ready; source at 2.803, output at 2.8028
-1357933136:136343 encoder thread 0x7fe3580a4940 finishes local encode of 46
-1357933136:136434 encoder thread 0x7fe3580a4fe0 sleeps
-1357933136:136495 encoder thread 0x7fe3580a4c90 wakes with queue of 32
-1357933136:136551 Decoder emits 84
-Fri Jan 11 19:38:56 2013: Encoder thread 0x7fe3580a4c90 pops frame 52 from queue
-1357933136:136700 encoder thread 0x7fe3580a4c90 begins local encode of 52
-1357933136:166270 encoder thread 0x7fe3580a59d0 sleeps
-1357933136:187294 encoder thread 0x7fe3580a5680 sleeps
-1357933136:198771 writer sleeps with a queue of 4
-1357933136:198855 encoder thread 0x7fe3580a4940 sleeps
-1357933136:198942 encoder thread 0x7fe3580a4fe0 wakes with queue of 31
-Fri Jan 11 19:38:56 2013: Finished locally-encoded frame 49
-1357933136:199171 writer wakes with a queue of 4
-Fri Jan 11 19:38:56 2013: Encoder thread 0x7fe3580a4fe0 pops frame 53 from queue
-1357933136:199328 encoder thread 0x7fe3580a4fe0 begins local encode of 53
-1357933136:199413 adding to queue of 30
-1357933136:199592 encoder thread 0x7fe3580a59d0 wakes with queue of 31
-Fri Jan 11 19:38:56 2013: Encoder thread 0x7fe3580a59d0 pops frame 54 from queue
-1357933136:199726 encoder thread 0x7fe3580a45f0 finishes local encode of 49
-1357933136:199812 encoder thread 0x7fe3580a5680 wakes with queue of 30
-Fri Jan 11 19:38:56 2013: Encoder thread 0x7fe3580a5680 pops frame 55 from queue
-1357933136:199929 encoder thread 0x7fe3580a5680 begins local encode of 55
-1357933136:200006 encoder thread 0x7fe3580a4940 wakes with queue of 29
-Fri Jan 11 19:38:56 2013: Encoder thread 0x7fe3580a4940 pops frame 56 from queue
-1357933136:200167 encoder thread 0x7fe3580a4940 begins local encode of 56
-Fri Jan 11 19:38:56 2013: Source video frame ready; source at 2.836, output at 2.83617
-1357933136:211315 encoder thread 0x7fe3580a45f0 sleeps
-1357933136:211455 encoder thread 0x7fe3580a45f0 wakes with queue of 28
-1357933136:211508 writer sleeps with a queue of 4
-1357933136:211626 Decoder emits 85
-1357933136:211714 encoder thread 0x7fe3580a59d0 begins local encode of 54
-Fri Jan 11 19:38:56 2013: Encoder thread 0x7fe3580a45f0 pops frame 57 from queue
-1357933136:211907 writer wakes with a queue of 4
-1357933136:211990 adding to queue of 27
-Fri Jan 11 19:38:56 2013: Source video frame ready; source at 2.87, output at 2.86953
-1357933136:213971 Decoder emits 86
-1357933136:214237 adding to queue of 28
-1357933136:215566 writer sleeps with a queue of 3
-1357933136:216640 writer wakes with a queue of 3
-Fri Jan 11 19:38:56 2013: Source video frame ready; source at 2.903, output at 2.9029
-1357933136:217159 Decoder emits 87
-1357933136:217403 adding to queue of 29
-1357933136:218028 encoder thread 0x7fe3580a45f0 begins local encode of 57
-Fri Jan 11 19:38:56 2013: Source video frame ready; source at 2.936, output at 2.93627
-1357933136:219867 Decoder emits 88
-1357933136:220093 adding to queue of 30
-Fri Jan 11 19:38:56 2013: Source video frame ready; source at 2.97, output at 2.96963
-1357933136:222272 Decoder emits 89
-1357933136:222510 adding to queue of 31
-Fri Jan 11 19:38:56 2013: Source video frame ready; source at 3.003, output at 3.003
-1357933136:224745 Decoder emits 90
-1357933136:224989 decoder sleeps with queue of 32
-1357933136:233519 writer sleeps with a queue of 2
-1357933136:233767 writer wakes with a queue of 2
-1357933136:237516 writer sleeps with a queue of 1
-1357933136:237868 writer wakes with a queue of 1
-1357933136:242268 writer sleeps with a queue of 0
-Fri Jan 11 19:38:57 2013: Finished locally-encoded frame 50
-1357933137:284424 encoder thread 0x7fe3580a5330 finishes local encode of 50
-1357933137:284730 writer wakes with a queue of 1
-1357933137:292610 encoder thread 0x7fe3580a5330 sleeps
-1357933137:292956 decoder wakes with queue of 32
-1357933137:293271 decoder sleeps with queue of 32
-1357933137:293578 encoder thread 0x7fe3580a5330 wakes with queue of 32
-Fri Jan 11 19:38:57 2013: Encoder thread 0x7fe3580a5330 pops frame 58 from queue
-1357933137:294222 encoder thread 0x7fe3580a5330 begins local encode of 58
-1357933137:296253 writer sleeps with a queue of 0
-Fri Jan 11 19:38:57 2013: Finished locally-encoded frame 51
-1357933137:372268 encoder thread 0x7fe358024570 finishes local encode of 51
-1357933137:372706 writer wakes with a queue of 1
-1357933137:386056 encoder thread 0x7fe358024570 sleeps
-1357933137:386379 decoder wakes with queue of 31
-1357933137:386777 adding to queue of 31
-1357933137:387177 encoder thread 0x7fe358024570 wakes with queue of 32
-Fri Jan 11 19:38:57 2013: Encoder thread 0x7fe358024570 pops frame 59 from queue
-1357933137:387861 encoder thread 0x7fe358024570 begins local encode of 59
-Fri Jan 11 19:38:57 2013: Source video frame ready; source at 3.036, output at 3.03637
-1357933137:389941 writer sleeps with a queue of 0
-1357933137:390254 Decoder emits 91
-1357933137:390637 adding to queue of 31
-Fri Jan 11 19:38:57 2013: Source video frame ready; source at 3.07, output at 3.06973
-1357933137:392303 Decoder emits 92
-1357933137:392631 decoder sleeps with queue of 32
-Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 55
-1357933142:115338 encoder thread 0x7fe3580a5680 finishes local encode of 55
-1357933142:115697 writer wakes with a queue of 1
-1357933142:131598 writer sleeps with a queue of 0
-1357933142:131861 encoder thread 0x7fe3580a5680 sleeps
-1357933142:131999 decoder wakes with queue of 32
-1357933142:132227 decoder sleeps with queue of 32
-1357933142:132353 encoder thread 0x7fe3580a5680 wakes with queue of 32
-Fri Jan 11 19:39:02 2013: Encoder thread 0x7fe3580a5680 pops frame 60 from queue
-1357933142:132509 encoder thread 0x7fe3580a5680 begins local encode of 60
-Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 57
-1357933142:652200 encoder thread 0x7fe3580a45f0 finishes local encode of 57
-1357933142:652478 writer wakes with a queue of 1
-1357933142:670430 encoder thread 0x7fe3580a45f0 sleeps
-1357933142:672161 decoder wakes with queue of 31
-1357933142:672536 adding to queue of 31
-1357933142:674747 writer sleeps with a queue of 0
-Fri Jan 11 19:39:02 2013: Source video frame ready; source at 3.103, output at 3.1031
-1357933142:674991 encoder thread 0x7fe3580a45f0 wakes with queue of 32
-Fri Jan 11 19:39:02 2013: Encoder thread 0x7fe3580a45f0 pops frame 61 from queue
-1357933142:675160 encoder thread 0x7fe3580a45f0 begins local encode of 61
-1357933142:675251 Decoder emits 93
-1357933142:675422 adding to queue of 31
-Fri Jan 11 19:39:02 2013: Source video frame ready; source at 3.136, output at 3.13647
-1357933142:678756 Decoder emits 94
-1357933142:678875 decoder sleeps with queue of 32
-Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 56
-Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 52
-Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 53
-1357933142:884663 encoder thread 0x7fe3580a4940 finishes local encode of 56
-Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 54
-1357933142:899652 encoder thread 0x7fe3580a4c90 finishes local encode of 52
-1357933142:899866 writer wakes with a queue of 1
-1357933142:900134 encoder thread 0x7fe3580a4940 sleeps
-1357933142:900310 encoder thread 0x7fe3580a59d0 finishes local encode of 54
-1357933142:900505 decoder wakes with queue of 32
-1357933142:900675 decoder sleeps with queue of 32
-1357933142:914829 encoder thread 0x7fe3580a4c90 sleeps
-1357933142:915010 encoder thread 0x7fe3580a4940 wakes with queue of 32
-Fri Jan 11 19:39:02 2013: Encoder thread 0x7fe3580a4940 pops frame 62 from queue
-1357933142:915345 encoder thread 0x7fe3580a4940 begins local encode of 62
-1357933142:936039 encoder thread 0x7fe3580a59d0 sleeps
-1357933142:974305 decoder wakes with queue of 31
-1357933142:987770 writer sleeps with a queue of 2
-Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 58
-Fri Jan 11 19:39:02 2013: Finished locally-encoded frame 59
-1357933142:988424 adding to queue of 31
-1357933142:988714 writer wakes with a queue of 2
-1357933142:989115 encoder thread 0x7fe3580a4c90 wakes with queue of 32
-Fri Jan 11 19:39:02 2013: Encoder thread 0x7fe3580a4c90 pops frame 63 from queue
-1357933142:989600 encoder thread 0x7fe3580a4c90 begins local encode of 63
-1357933142:989810 encoder thread 0x7fe3580a5330 finishes local encode of 58
-1357933142:990161 encoder thread 0x7fe3580a59d0 wakes with queue of 31
-1357933142:990494 encoder thread 0x7fe358024570 finishes local encode of 59
-Fri Jan 11 19:39:02 2013: Source video frame ready; source at 3.17, output at 3.16983
-1357933142:991069 Decoder emits 95
-Fri Jan 11 19:39:02 2013: Encoder thread 0x7fe3580a59d0 pops frame 64 from queue
-1357933142:991534 encoder thread 0x7fe3580a59d0 begins local encode of 64
-1357933143:2971 writer sleeps with a queue of 3
-1357933143:9064 encoder thread 0x7fe3580a5330 sleeps
-1357933143:9313 encoder thread 0x7fe358024570 sleeps
-1357933143:9452 adding to queue of 30
-1357933143:9608 writer wakes with a queue of 3
-1357933143:9804 encoder thread 0x7fe3580a4fe0 finishes local encode of 53
-1357933143:9990 encoder thread 0x7fe3580a5330 wakes with queue of 31
-Fri Jan 11 19:39:03 2013: Encoder thread 0x7fe3580a5330 pops frame 65 from queue
-1357933143:10511 encoder thread 0x7fe3580a5330 begins local encode of 65
-1357933143:10803 encoder thread 0x7fe358024570 wakes with queue of 30
-Fri Jan 11 19:39:03 2013: Encoder thread 0x7fe358024570 pops frame 66 from queue
-Fri Jan 11 19:39:03 2013: Source video frame ready; source at 3.203, output at 3.2032
-1357933143:11848 Decoder emits 96
-1357933143:12061 encoder thread 0x7fe3580a4fe0 sleeps
-1357933143:12370 adding to queue of 29
-1357933143:12447 encoder thread 0x7fe358024570 begins local encode of 66
-1357933143:13252 writer sleeps with a queue of 3
-1357933143:13540 encoder thread 0x7fe3580a4fe0 wakes with queue of 30
-1357933143:13753 writer wakes with a queue of 3
-Fri Jan 11 19:39:03 2013: Source video frame ready; source at 3.237, output at 3.23657
-1357933143:14476 Decoder emits 97
-Fri Jan 11 19:39:03 2013: Encoder thread 0x7fe3580a4fe0 pops frame 67 from queue
-1357933143:15875 encoder thread 0x7fe3580a4fe0 begins local encode of 67
-1357933143:16079 adding to queue of 29
-Fri Jan 11 19:39:03 2013: Source video frame ready; source at 3.27, output at 3.26993
-1357933143:18286 Decoder emits 98
-1357933143:18493 writer sleeps with a queue of 2
-1357933143:18734 adding to queue of 30
-Fri Jan 11 19:39:03 2013: Source video frame ready; source at 3.303, output at 3.3033
-1357933143:21059 Decoder emits 99
-1357933143:21320 adding to queue of 31
-Fri Jan 11 19:39:03 2013: Source video frame ready; source at 3.337, output at 3.33667
-1357933143:27968 writer wakes with a queue of 2
-1357933143:28422 Decoder emits 100
-1357933143:29904 decoder sleeps with queue of 32
-1357933143:32086 writer sleeps with a queue of 1
-1357933143:32329 writer wakes with a queue of 1
-1357933143:46423 writer sleeps with a queue of 0
-Fri Jan 11 19:39:05 2013: Finished locally-encoded frame 60
-1357933145:40526 encoder thread 0x7fe3580a5680 finishes local encode of 60
-1357933145:40958 writer wakes with a queue of 1
-1357933145:54534 encoder thread 0x7fe3580a5680 sleeps
-1357933145:54905 decoder wakes with queue of 32
-1357933145:55214 decoder sleeps with queue of 32
-1357933145:55589 encoder thread 0x7fe3580a5680 wakes with queue of 32
-Fri Jan 11 19:39:05 2013: Encoder thread 0x7fe3580a5680 pops frame 68 from queue
-1357933145:56270 encoder thread 0x7fe3580a5680 begins local encode of 68
-1357933145:58332 writer sleeps with a queue of 0
-Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 65
-1357933149:516036 encoder thread 0x7fe3580a5330 finishes local encode of 65
-Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 61
-1357933149:544443 decoder wakes with queue of 31
-1357933149:544635 encoder thread 0x7fe3580a5330 sleeps
-1357933149:544726 adding to queue of 31
-1357933149:544802 writer wakes with a queue of 1
-1357933149:545457 encoder thread 0x7fe3580a45f0 finishes local encode of 61
-Fri Jan 11 19:39:09 2013: Source video frame ready; source at 3.37, output at 3.37003
-1357933149:563307 encoder thread 0x7fe3580a5330 wakes with queue of 32
-Fri Jan 11 19:39:09 2013: Encoder thread 0x7fe3580a5330 pops frame 69 from queue
-1357933149:563572 encoder thread 0x7fe3580a5330 begins local encode of 69
-1357933149:563682 Decoder emits 101
-1357933149:563935 adding to queue of 31
-1357933149:565648 writer sleeps with a queue of 1
-1357933149:565771 writer wakes with a queue of 1
-Fri Jan 11 19:39:09 2013: Source video frame ready; source at 3.403, output at 3.4034
-1357933149:567160 Decoder emits 102
-1357933149:567387 decoder sleeps with queue of 32
-1357933149:567530 encoder thread 0x7fe3580a45f0 sleeps
-1357933149:567617 encoder thread 0x7fe3580a45f0 wakes with queue of 32
-Fri Jan 11 19:39:09 2013: Encoder thread 0x7fe3580a45f0 pops frame 70 from queue
-1357933149:567830 encoder thread 0x7fe3580a45f0 begins local encode of 70
-1357933149:581549 writer sleeps with a queue of 0
-Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 63
-Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 62
-1357933149:935739 encoder thread 0x7fe3580a4c90 finishes local encode of 63
-Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 68
-Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 64
-1357933149:995417 encoder thread 0x7fe3580a4940 finishes local encode of 62
-Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 67
-1357933149:995680 writer wakes with a queue of 1
-Fri Jan 11 19:39:09 2013: Finished locally-encoded frame 66
-1357933149:995936 encoder thread 0x7fe3580a5680 finishes local encode of 68
-1357933149:996084 decoder wakes with queue of 31
-1357933149:996281 encoder thread 0x7fe3580a4c90 sleeps
-1357933149:996491 adding to queue of 31
-1357933149:997365 encoder thread 0x7fe358024570 finishes local encode of 66
-1357933150:10655 encoder thread 0x7fe3580a59d0 finishes local encode of 64
-1357933150:23649 encoder thread 0x7fe3580a4fe0 finishes local encode of 67
-Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.437, output at 3.43677
-1357933150:23841 encoder thread 0x7fe3580a4940 sleeps
-1357933150:23931 writer sleeps with a queue of 3
-1357933150:23995 encoder thread 0x7fe3580a5680 sleeps
-1357933150:24056 encoder thread 0x7fe3580a4c90 wakes with queue of 32
-1357933150:24114 Decoder emits 103
-1357933150:24194 writer wakes with a queue of 3
-Fri Jan 11 19:39:10 2013: Encoder thread 0x7fe3580a4c90 pops frame 71 from queue
-1357933150:24342 encoder thread 0x7fe3580a4c90 begins local encode of 71
-1357933150:27909 encoder thread 0x7fe358024570 sleeps
-1357933150:28026 encoder thread 0x7fe3580a4940 wakes with queue of 31
-Fri Jan 11 19:39:10 2013: Encoder thread 0x7fe3580a4940 pops frame 72 from queue
-1357933150:28216 encoder thread 0x7fe3580a4940 begins local encode of 72
-1357933150:28275 encoder thread 0x7fe3580a5680 wakes with queue of 30
-Fri Jan 11 19:39:10 2013: Encoder thread 0x7fe3580a5680 pops frame 73 from queue
-1357933150:28418 encoder thread 0x7fe3580a5680 begins local encode of 73
-1357933150:28475 adding to queue of 29
-Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.47, output at 3.47013
-1357933150:53366 writer sleeps with a queue of 4
-1357933150:53618 encoder thread 0x7fe3580a59d0 sleeps
-1357933150:53732 encoder thread 0x7fe3580a4fe0 sleeps
-1357933150:53830 encoder thread 0x7fe358024570 wakes with queue of 30
-1357933150:53957 writer wakes with a queue of 4
-1357933150:54040 Decoder emits 104
-Fri Jan 11 19:39:10 2013: Encoder thread 0x7fe358024570 pops frame 74 from queue
-1357933150:54310 encoder thread 0x7fe358024570 begins local encode of 74
-1357933150:54398 encoder thread 0x7fe3580a59d0 wakes with queue of 29
-Fri Jan 11 19:39:10 2013: Encoder thread 0x7fe3580a59d0 pops frame 75 from queue
-1357933150:54635 encoder thread 0x7fe3580a59d0 begins local encode of 75
-1357933150:54754 encoder thread 0x7fe3580a4fe0 wakes with queue of 28
-Fri Jan 11 19:39:10 2013: Encoder thread 0x7fe3580a4fe0 pops frame 76 from queue
-1357933150:55191 encoder thread 0x7fe3580a4fe0 begins local encode of 76
-1357933150:55375 adding to queue of 27
-Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.504, output at 3.5035
-1357933150:58456 Decoder emits 105
-1357933150:58844 adding to queue of 28
-Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.537, output at 3.53687
-1357933150:60887 Decoder emits 106
-1357933150:61233 adding to queue of 29
-Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.57, output at 3.57023
-1357933150:64285 Decoder emits 107
-1357933150:64633 adding to queue of 30
-Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.604, output at 3.6036
-1357933150:66537 Decoder emits 108
-1357933150:66676 writer sleeps with a queue of 3
-1357933150:67009 writer wakes with a queue of 3
-1357933150:68451 adding to queue of 31
-Fri Jan 11 19:39:10 2013: Source video frame ready; source at 3.637, output at 3.63697
-1357933150:72083 Decoder emits 109
-1357933150:72341 decoder sleeps with queue of 32
-1357933150:81537 writer sleeps with a queue of 2
-1357933150:81991 writer wakes with a queue of 2
-1357933150:87900 writer sleeps with a queue of 1
-1357933150:90060 writer wakes with a queue of 1
-1357933150:94749 writer sleeps with a queue of 0
-1357933154:401667 encoder thread 0x7fe358031550 sleeps
-1357933154:402110 decoder wakes with queue of 32
-1357933154:402594 decoder sleeps with queue of 32
-1357933154:403034 encoder thread 0x7fe3580a6a80 sleeps
-1357933154:403384 encoder thread 0x7fe358031550 wakes with queue of 32
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe358031550 pops frame 77 from queue
-1357933154:404149 decoder wakes with queue of 31
-1357933154:404586 adding to queue of 31
-1357933154:405021 encoder thread 0x7fe3580a6a80 wakes with queue of 32
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a6a80 pops frame 78 from queue
-Fri Jan 11 19:39:14 2013: Remote encode of 77 on shankly failed (Host not found (authoritative)); thread sleeping for 40s
-Fri Jan 11 19:39:14 2013: Source video frame ready; source at 3.67, output at 3.67033
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe358031550 pushes frame 77 back onto queue after failure
-1357933154:407061 Decoder emits 110
-Fri Jan 11 19:39:14 2013: Remote encode of 78 on shankly failed (Host not found (authoritative)); thread sleeping for 40s
-1357933154:407920 decoder sleeps with queue of 32
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a6a80 pushes frame 78 back onto queue after failure
-1357933154:429739 encoder thread 0x7fe3580a6090 sleeps
-1357933154:430165 decoder wakes with queue of 33
-1357933154:430503 decoder sleeps with queue of 33
-1357933154:430863 encoder thread 0x7fe3580a6090 wakes with queue of 33
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a6090 pops frame 78 from queue
-Fri Jan 11 19:39:14 2013: Remote encode of 78 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 40s
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a6090 pushes frame 78 back onto queue after failure
-1357933154:439557 encoder thread 0x7fe358030eb0 sleeps
-1357933154:439866 decoder wakes with queue of 33
-1357933154:440151 decoder sleeps with queue of 33
-1357933154:440511 encoder thread 0x7fe358030eb0 wakes with queue of 33
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe358030eb0 pops frame 78 from queue
-1357933154:441997 encoder thread 0x7fe3580a5d40 sleeps
-1357933154:442300 decoder wakes with queue of 32
-Fri Jan 11 19:39:14 2013: Remote encode of 78 on shankly failed (Host not found (authoritative)); thread sleeping for 40s
-1357933154:443063 decoder sleeps with queue of 32
-1357933154:443393 encoder thread 0x7fe3580a5d40 wakes with queue of 32
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a5d40 pops frame 77 from queue
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe358030eb0 pushes frame 78 back onto queue after failure
-1357933154:444645 decoder wakes with queue of 32
-1357933154:445028 encoder thread 0x7fe3580a6730 sleeps
-1357933154:445343 decoder sleeps with queue of 32
-Fri Jan 11 19:39:14 2013: Remote encode of 77 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 40s
-1357933154:445962 encoder thread 0x7fe358031200 sleeps
-1357933154:446264 encoder thread 0x7fe3580a6730 wakes with queue of 32
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a6730 pops frame 78 from queue
-1357933154:446887 decoder wakes with queue of 31
-1357933154:447235 adding to queue of 31
-1357933154:447642 encoder thread 0x7fe3580a63e0 sleeps
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a5d40 pushes frame 77 back onto queue after failure
-Fri Jan 11 19:39:14 2013: Remote encode of 78 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 40s
-1357933154:448719 encoder thread 0x7fe358031200 wakes with queue of 33
-Fri Jan 11 19:39:14 2013: Source video frame ready; source at 3.704, output at 3.7037
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe358031200 pops frame 77 from queue
-1357933154:449617 Decoder emits 111
-1357933154:449964 encoder thread 0x7fe3580a63e0 wakes with queue of 32
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a63e0 pops frame 79 from queue
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a6730 pushes frame 78 back onto queue after failure
-Fri Jan 11 19:39:14 2013: Remote encode of 77 on shankly failed (Host not found (authoritative)); thread sleeping for 40s
-1357933154:451408 decoder sleeps with queue of 32
-Fri Jan 11 19:39:14 2013: Remote encode of 79 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 40s
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe358031200 pushes frame 77 back onto queue after failure
-Fri Jan 11 19:39:14 2013: Encoder thread 0x7fe3580a63e0 pushes frame 79 back onto queue after failure
-Fri Jan 11 19:39:16 2013: Finished locally-encoded frame 69
-1357933156:856781 encoder thread 0x7fe3580a5330 finishes local encode of 69
-1357933156:866630 encoder thread 0x7fe3580a5330 sleeps
-1357933156:866855 decoder wakes with queue of 34
-1357933156:866944 decoder sleeps with queue of 34
-1357933156:867031 encoder thread 0x7fe3580a5330 wakes with queue of 34
-Fri Jan 11 19:39:16 2013: Encoder thread 0x7fe3580a5330 pops frame 79 from queue
-1357933156:867173 encoder thread 0x7fe3580a5330 begins local encode of 79
-1357933156:904295 writer wakes with a queue of 1
-1357933156:909270 writer sleeps with a queue of 0
-Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 70
-Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 74
-1357933157:96701 encoder thread 0x7fe3580a45f0 finishes local encode of 70
-Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 73
-1357933157:188013 encoder thread 0x7fe358024570 finishes local encode of 74
-1357933157:223228 writer wakes with a queue of 1
-Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 76
-Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 75
-1357933157:269634 encoder thread 0x7fe3580a5680 finishes local encode of 73
-1357933157:269830 decoder wakes with queue of 33
-1357933157:270010 encoder thread 0x7fe3580a45f0 sleeps
-Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 72
-Fri Jan 11 19:39:17 2013: Finished locally-encoded frame 71
-1357933157:270414 encoder thread 0x7fe3580a4fe0 finishes local encode of 76
-1357933157:270539 decoder sleeps with queue of 33
-1357933157:270694 encoder thread 0x7fe3580a59d0 finishes local encode of 75
-1357933157:274635 encoder thread 0x7fe3580a4c90 finishes local encode of 71
-1357933157:301852 encoder thread 0x7fe3580a45f0 wakes with queue of 33
-1357933157:301968 encoder thread 0x7fe358024570 sleeps
-1357933157:302077 encoder thread 0x7fe3580a5680 sleeps
-1357933157:302195 writer sleeps with a queue of 3
-Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe3580a45f0 pops frame 77 from queue
-1357933157:302423 writer wakes with a queue of 3
-1357933157:316504 encoder thread 0x7fe3580a45f0 begins local encode of 77
-1357933157:316677 encoder thread 0x7fe3580a4fe0 sleeps
-1357933157:316810 decoder wakes with queue of 32
-1357933157:316930 decoder sleeps with queue of 32
-1357933157:317071 encoder thread 0x7fe358024570 wakes with queue of 32
-Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe358024570 pops frame 78 from queue
-1357933157:317338 encoder thread 0x7fe358024570 begins local encode of 78
-1357933157:317453 encoder thread 0x7fe3580a5680 wakes with queue of 31
-Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe3580a5680 pops frame 80 from queue
-1357933157:317752 encoder thread 0x7fe3580a5680 begins local encode of 80
-1357933157:344719 encoder thread 0x7fe3580a59d0 sleeps
-1357933157:344933 writer sleeps with a queue of 4
-1357933157:345033 encoder thread 0x7fe3580a4c90 sleeps
-1357933157:345123 encoder thread 0x7fe3580a4fe0 wakes with queue of 30
-1357933157:345226 encoder thread 0x7fe3580a4940 finishes local encode of 72
-1357933157:345377 writer wakes with a queue of 4
-Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe3580a4fe0 pops frame 81 from queue
-1357933157:345640 encoder thread 0x7fe3580a4fe0 begins local encode of 81
-1357933157:345736 decoder wakes with queue of 29
-1357933157:345883 adding to queue of 29
-1357933157:346083 encoder thread 0x7fe3580a59d0 wakes with queue of 30
-Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe3580a59d0 pops frame 82 from queue
-1357933157:346331 encoder thread 0x7fe3580a59d0 begins local encode of 82
-1357933157:346915 encoder thread 0x7fe3580a4c90 wakes with queue of 29
-Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe3580a4c90 pops frame 83 from queue
-1357933157:347248 encoder thread 0x7fe3580a4c90 begins local encode of 83
-1357933157:347463 encoder thread 0x7fe3580a4940 sleeps
-Fri Jan 11 19:39:17 2013: Source video frame ready; source at 3.737, output at 3.73707
-1357933157:349555 Decoder emits 112
-1357933157:349668 encoder thread 0x7fe3580a4940 wakes with queue of 28
-Fri Jan 11 19:39:17 2013: Encoder thread 0x7fe3580a4940 pops frame 84 from queue
-1357933157:349977 encoder thread 0x7fe3580a4940 begins local encode of 84
-1357933157:350093 writer sleeps with a queue of 4
-1357933157:350265 adding to queue of 27
-Fri Jan 11 19:39:17 2013: Source video frame ready; source at 3.77, output at 3.77043
-1357933157:352268 Decoder emits 113
-1357933157:352530 adding to queue of 28
-Fri Jan 11 19:39:17 2013: Source video frame ready; source at 3.804, output at 3.8038
-1357933157:363455 Decoder emits 114
-1357933157:363872 adding to queue of 29
-Fri Jan 11 19:39:17 2013: Source video frame ready; source at 3.837, output at 3.83717
-1357933157:366499 Decoder emits 115
-1357933157:366809 adding to queue of 30
-Fri Jan 11 19:39:17 2013: Source video frame ready; source at 3.871, output at 3.87053
-1357933157:369799 Decoder emits 116
-1357933157:370025 writer wakes with a queue of 4
-1357933157:370281 adding to queue of 31
-Fri Jan 11 19:39:17 2013: Source video frame ready; source at 3.904, output at 3.9039
-1357933157:372780 Decoder emits 117
-1357933157:373003 decoder sleeps with queue of 32
-1357933157:374537 writer sleeps with a queue of 3
-1357933157:374715 writer wakes with a queue of 3
-1357933157:378775 writer sleeps with a queue of 2
-1357933157:379025 writer wakes with a queue of 2
-1357933157:398922 writer sleeps with a queue of 1
-1357933157:399123 writer wakes with a queue of 1
-1357933157:403505 writer sleeps with a queue of 0
-Fri Jan 11 19:39:22 2013: Finished locally-encoded frame 79
-1357933162:300558 encoder thread 0x7fe3580a5330 finishes local encode of 79
-1357933162:300646 encoder thread 0x7fe3580a5330 sleeps
-1357933162:300695 decoder wakes with queue of 32
-1357933162:300770 writer wakes with a queue of 1
-1357933162:300888 decoder sleeps with queue of 32
-1357933162:300962 encoder thread 0x7fe3580a5330 wakes with queue of 32
-Fri Jan 11 19:39:22 2013: Encoder thread 0x7fe3580a5330 pops frame 85 from queue
-1357933162:301073 encoder thread 0x7fe3580a5330 begins local encode of 85
-1357933162:304126 writer sleeps with a queue of 0
-Fri Jan 11 19:39:23 2013: Finished locally-encoded frame 84
-1357933163:759332 encoder thread 0x7fe3580a4940 finishes local encode of 84
-1357933163:759615 writer wakes with a queue of 1
-1357933163:778025 encoder thread 0x7fe3580a4940 sleeps
-1357933163:778119 decoder wakes with queue of 31
-1357933163:778328 adding to queue of 31
-1357933163:778494 encoder thread 0x7fe3580a4940 wakes with queue of 32
-Fri Jan 11 19:39:23 2013: Encoder thread 0x7fe3580a4940 pops frame 86 from queue
-1357933163:778654 encoder thread 0x7fe3580a4940 begins local encode of 86
-Fri Jan 11 19:39:23 2013: Source video frame ready; source at 3.937, output at 3.93727
-1357933163:781912 writer sleeps with a queue of 0
-1357933163:782018 Decoder emits 118
-1357933163:782253 adding to queue of 31
-Fri Jan 11 19:39:23 2013: Source video frame ready; source at 3.971, output at 3.97063
-1357933163:784596 Decoder emits 119
-1357933163:784843 decoder sleeps with queue of 32
-Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 82
-Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 80
-1357933164:244969 encoder thread 0x7fe3580a59d0 finishes local encode of 82
-Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 78
-Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 81
-Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 83
-Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 77
-1357933164:264693 writer wakes with a queue of 1
-1357933164:264886 encoder thread 0x7fe3580a59d0 sleeps
-1357933164:265091 decoder wakes with queue of 32
-1357933164:265315 encoder thread 0x7fe358024570 finishes local encode of 78
-1357933164:265582 encoder thread 0x7fe3580a4fe0 finishes local encode of 81
-1357933164:265740 decoder sleeps with queue of 32
-1357933164:265936 encoder thread 0x7fe3580a59d0 wakes with queue of 32
-Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe3580a59d0 pops frame 87 from queue
-1357933164:266248 encoder thread 0x7fe3580a59d0 begins local encode of 87
-1357933164:266399 encoder thread 0x7fe3580a4c90 finishes local encode of 83
-1357933164:278864 encoder thread 0x7fe3580a45f0 finishes local encode of 77
-1357933164:279034 encoder thread 0x7fe358024570 sleeps
-1357933164:279158 encoder thread 0x7fe3580a4fe0 sleeps
-1357933164:279258 decoder wakes with queue of 31
-1357933164:279402 adding to queue of 31
-1357933164:279905 encoder thread 0x7fe3580a5680 finishes local encode of 80
-1357933164:294035 writer sleeps with a queue of 4
-Fri Jan 11 19:39:24 2013: Source video frame ready; source at 4.004, output at 4.004
-1357933164:294328 encoder thread 0x7fe3580a45f0 sleeps
-1357933164:294452 encoder thread 0x7fe3580a4c90 sleeps
-1357933164:294537 encoder thread 0x7fe358024570 wakes with queue of 32
-1357933164:294620 writer wakes with a queue of 4
-1357933164:294704 Decoder emits 120
-Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe358024570 pops frame 88 from queue
-1357933164:294928 encoder thread 0x7fe358024570 begins local encode of 88
-1357933164:295016 encoder thread 0x7fe3580a4fe0 wakes with queue of 31
-Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe3580a4fe0 pops frame 89 from queue
-1357933164:295295 encoder thread 0x7fe3580a4fe0 begins local encode of 89
-1357933164:295415 encoder thread 0x7fe3580a45f0 wakes with queue of 30
-Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe3580a45f0 pops frame 90 from queue
-1357933164:295651 encoder thread 0x7fe3580a45f0 begins local encode of 90
-1357933164:295748 encoder thread 0x7fe3580a4c90 wakes with queue of 29
-Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe3580a4c90 pops frame 91 from queue
-1357933164:296038 encoder thread 0x7fe3580a4c90 begins local encode of 91
-1357933164:298408 writer sleeps with a queue of 4
-1357933164:298530 adding to queue of 28
-Fri Jan 11 19:39:24 2013: Source video frame ready; source at 4.037, output at 4.03737
-1357933164:301288 Decoder emits 121
-1357933164:301533 adding to queue of 29
-1357933164:301787 writer wakes with a queue of 4
-1357933164:301913 encoder thread 0x7fe3580a5680 sleeps
-1357933164:302318 encoder thread 0x7fe3580a5680 wakes with queue of 30
-Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe3580a5680 pops frame 92 from queue
-1357933164:302822 encoder thread 0x7fe3580a5680 begins local encode of 92
-Fri Jan 11 19:39:24 2013: Source video frame ready; source at 4.071, output at 4.07073
-1357933164:304770 Decoder emits 122
-1357933164:304991 adding to queue of 29
-1357933164:306671 writer sleeps with a queue of 3
-Fri Jan 11 19:39:24 2013: Source video frame ready; source at 4.104, output at 4.1041
-1357933164:307658 Decoder emits 123
-1357933164:307843 writer wakes with a queue of 3
-1357933164:308164 adding to queue of 30
-Fri Jan 11 19:39:24 2013: Source video frame ready; source at 4.137, output at 4.13747
-1357933164:311097 Decoder emits 124
-1357933164:311403 adding to queue of 31
-1357933164:313011 writer sleeps with a queue of 2
-1357933164:314222 writer wakes with a queue of 2
-Fri Jan 11 19:39:24 2013: Source video frame ready; source at 4.171, output at 4.17083
-1357933164:315088 Decoder emits 125
-1357933164:315362 decoder sleeps with queue of 32
-1357933164:318075 writer sleeps with a queue of 1
-1357933164:318317 writer wakes with a queue of 1
-1357933164:333907 writer sleeps with a queue of 0
-Fri Jan 11 19:39:24 2013: Finished locally-encoded frame 85
-1357933164:943495 encoder thread 0x7fe3580a5330 finishes local encode of 85
-1357933164:943772 writer wakes with a queue of 1
-1357933164:951312 encoder thread 0x7fe3580a5330 sleeps
-1357933164:951576 decoder wakes with queue of 32
-1357933164:951869 decoder sleeps with queue of 32
-1357933164:952173 encoder thread 0x7fe3580a5330 wakes with queue of 32
-Fri Jan 11 19:39:24 2013: Encoder thread 0x7fe3580a5330 pops frame 93 from queue
-1357933164:952848 encoder thread 0x7fe3580a5330 begins local encode of 93
-1357933164:954847 writer sleeps with a queue of 0
-Fri Jan 11 19:39:28 2013: Finished locally-encoded frame 86
-1357933168:218472 encoder thread 0x7fe3580a4940 finishes local encode of 86
-1357933168:218894 writer wakes with a queue of 1
-1357933168:234453 encoder thread 0x7fe3580a4940 sleeps
-1357933168:234819 decoder wakes with queue of 31
-1357933168:235189 adding to queue of 31
-1357933168:235585 encoder thread 0x7fe3580a4940 wakes with queue of 32
-Fri Jan 11 19:39:28 2013: Encoder thread 0x7fe3580a4940 pops frame 94 from queue
-Fri Jan 11 19:39:28 2013: Source video frame ready; source at 4.204, output at 4.2042
-1357933168:236976 encoder thread 0x7fe3580a4940 begins local encode of 94
-1357933168:237346 Decoder emits 126
-1357933168:237769 adding to queue of 31
-1357933168:238332 writer sleeps with a queue of 0
-Fri Jan 11 19:39:28 2013: Source video frame ready; source at 4.238, output at 4.23757
-1357933168:239315 Decoder emits 127
-1357933168:239673 decoder sleeps with queue of 32
-Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 88
-1357933171:278675 encoder thread 0x7fe358024570 finishes local encode of 88
-Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 89
-1357933171:389144 encoder thread 0x7fe358024570 sleeps
-Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 91
-1357933171:486733 decoder wakes with queue of 32
-1357933171:500331 writer wakes with a queue of 1
-1357933171:511296 encoder thread 0x7fe3580a4fe0 finishes local encode of 89
-Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 87
-Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 90
-1357933171:536686 encoder thread 0x7fe3580a4c90 finishes local encode of 91
-Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 92
-Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 93
-1357933171:536987 decoder sleeps with queue of 32
-Fri Jan 11 19:39:31 2013: Finished locally-encoded frame 94
-1357933171:537223 encoder thread 0x7fe3580a59d0 finishes local encode of 87
-1357933171:537285 encoder thread 0x7fe358024570 wakes with queue of 32
-Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe358024570 pops frame 95 from queue
-1357933171:537438 encoder thread 0x7fe358024570 begins local encode of 95
-1357933171:550605 encoder thread 0x7fe3580a45f0 finishes local encode of 90
-1357933171:566467 encoder thread 0x7fe3580a5680 finishes local encode of 92
-1357933171:579791 encoder thread 0x7fe3580a5330 finishes local encode of 93
-1357933171:579924 decoder wakes with queue of 31
-1357933171:580091 writer sleeps with a queue of 3
-1357933171:580171 adding to queue of 31
-1357933171:580224 encoder thread 0x7fe3580a4fe0 sleeps
-1357933171:580318 encoder thread 0x7fe3580a59d0 sleeps
-1357933171:580411 encoder thread 0x7fe3580a4940 finishes local encode of 94
-1357933171:580486 writer wakes with a queue of 3
-1357933171:580539 encoder thread 0x7fe3580a4c90 sleeps
-1357933171:580609 encoder thread 0x7fe3580a4fe0 wakes with queue of 32
-Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a4fe0 pops frame 96 from queue
-1357933171:580805 encoder thread 0x7fe3580a4fe0 begins local encode of 96
-1357933171:580865 encoder thread 0x7fe3580a59d0 wakes with queue of 31
-Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a59d0 pops frame 97 from queue
-1357933171:581232 encoder thread 0x7fe3580a59d0 begins local encode of 97
-1357933171:589731 encoder thread 0x7fe3580a45f0 sleeps
-1357933171:589881 encoder thread 0x7fe3580a4c90 wakes with queue of 30
-Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.271, output at 4.27093
-Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a4c90 pops frame 98 from queue
-1357933171:590335 encoder thread 0x7fe3580a4c90 begins local encode of 98
-1357933171:605346 Decoder emits 128
-1357933171:627210 writer sleeps with a queue of 6
-1357933171:627432 encoder thread 0x7fe3580a5680 sleeps
-1357933171:627569 encoder thread 0x7fe3580a5330 sleeps
-1357933171:627650 encoder thread 0x7fe3580a45f0 wakes with queue of 29
-1357933171:627736 encoder thread 0x7fe3580a4940 sleeps
-1357933171:627844 writer wakes with a queue of 6
-Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a45f0 pops frame 99 from queue
-1357933171:628058 encoder thread 0x7fe3580a45f0 begins local encode of 99
-1357933171:628124 adding to queue of 28
-1357933171:628508 encoder thread 0x7fe3580a5680 wakes with queue of 29
-Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a5680 pops frame 100 from queue
-1357933171:628769 encoder thread 0x7fe3580a5680 begins local encode of 100
-Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.304, output at 4.3043
-1357933171:630460 Decoder emits 129
-1357933171:632234 writer sleeps with a queue of 5
-1357933171:632351 encoder thread 0x7fe3580a5330 wakes with queue of 28
-1357933171:632446 writer wakes with a queue of 5
-Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a5330 pops frame 101 from queue
-1357933171:632713 encoder thread 0x7fe3580a5330 begins local encode of 101
-1357933171:633893 encoder thread 0x7fe3580a4940 wakes with queue of 27
-Fri Jan 11 19:39:31 2013: Encoder thread 0x7fe3580a4940 pops frame 102 from queue
-1357933171:634248 encoder thread 0x7fe3580a4940 begins local encode of 102
-1357933171:634389 adding to queue of 26
-1357933171:636023 writer sleeps with a queue of 4
-Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.338, output at 4.33767
-1357933171:637681 Decoder emits 130
-1357933171:637913 adding to queue of 27
-Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.371, output at 4.37103
-1357933171:639817 Decoder emits 131
-1357933171:639950 writer wakes with a queue of 4
-1357933171:640304 adding to queue of 28
-Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.404, output at 4.4044
-1357933171:642416 Decoder emits 132
-1357933171:642667 adding to queue of 29
-Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.438, output at 4.43777
-1357933171:644252 Decoder emits 133
-1357933171:644505 adding to queue of 30
-Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.471, output at 4.47113
-1357933171:646288 Decoder emits 134
-1357933171:646421 writer sleeps with a queue of 3
-1357933171:646627 writer wakes with a queue of 3
-1357933171:646831 adding to queue of 31
-Fri Jan 11 19:39:31 2013: Source video frame ready; source at 4.505, output at 4.5045
-1357933171:648695 Decoder emits 135
-1357933171:649000 decoder sleeps with queue of 32
-1357933171:651037 writer sleeps with a queue of 2
-1357933171:651208 writer wakes with a queue of 2
-1357933171:655192 writer sleeps with a queue of 1
-1357933171:655373 writer wakes with a queue of 1
-1357933171:659901 writer sleeps with a queue of 0
-Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 100
-1357933178:754229 encoder thread 0x7fe3580a5680 finishes local encode of 100
-Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 98
-Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 96
-Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 102
-1357933178:982783 encoder thread 0x7fe3580a5680 sleeps
-1357933178:982892 decoder wakes with queue of 32
-1357933178:982989 writer wakes with a queue of 1
-1357933178:983117 encoder thread 0x7fe3580a4c90 finishes local encode of 98
-1357933178:983215 encoder thread 0x7fe3580a4940 finishes local encode of 102
-Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 95
-Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 97
-1357933178:983485 encoder thread 0x7fe3580a4fe0 finishes local encode of 96
-Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 101
-1357933178:983766 decoder sleeps with queue of 32
-Fri Jan 11 19:39:38 2013: Finished locally-encoded frame 99
-1357933178:984038 encoder thread 0x7fe358024570 finishes local encode of 95
-1357933178:984101 encoder thread 0x7fe3580a5680 wakes with queue of 32
-Fri Jan 11 19:39:38 2013: Encoder thread 0x7fe3580a5680 pops frame 103 from queue
-1357933178:984251 encoder thread 0x7fe3580a5680 begins local encode of 103
-1357933178:998083 encoder thread 0x7fe3580a4c90 sleeps
-1357933179:25471 encoder thread 0x7fe3580a59d0 finishes local encode of 97
-1357933179:25602 encoder thread 0x7fe3580a45f0 finishes local encode of 99
-1357933179:25679 encoder thread 0x7fe3580a5330 finishes local encode of 101
-1357933179:25761 encoder thread 0x7fe3580a4940 sleeps
-1357933179:25826 writer sleeps with a queue of 4
-1357933179:25893 encoder thread 0x7fe3580a4fe0 sleeps
-1357933179:25957 encoder thread 0x7fe3580a4c90 wakes with queue of 31
-1357933179:26019 writer wakes with a queue of 4
-Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe3580a4c90 pops frame 104 from queue
-1357933179:26182 encoder thread 0x7fe3580a4c90 begins local encode of 104
-1357933179:36490 encoder thread 0x7fe3580a5330 sleeps
-1357933179:47899 writer sleeps with a queue of 6
-1357933179:47981 encoder thread 0x7fe3580a5330 wakes with queue of 30
-1357933179:48052 encoder thread 0x7fe358024570 sleeps
-1357933179:48130 writer wakes with a queue of 6
-Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe3580a5330 pops frame 105 from queue
-1357933179:48294 encoder thread 0x7fe3580a5330 begins local encode of 105
-1357933179:48351 encoder thread 0x7fe3580a4fe0 wakes with queue of 29
-Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe3580a4fe0 pops frame 106 from queue
-1357933179:48520 encoder thread 0x7fe3580a4fe0 begins local encode of 106
-1357933179:60923 encoder thread 0x7fe3580a59d0 sleeps
-1357933179:68886 writer sleeps with a queue of 5
-1357933179:68981 encoder thread 0x7fe3580a45f0 sleeps
-1357933179:69115 decoder wakes with queue of 28
-1357933179:69186 writer wakes with a queue of 5
-1357933179:69249 adding to queue of 28
-1357933179:69426 encoder thread 0x7fe3580a4940 wakes with queue of 29
-Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe3580a4940 pops frame 107 from queue
-1357933179:69682 encoder thread 0x7fe3580a4940 begins local encode of 107
-1357933179:69790 encoder thread 0x7fe358024570 wakes with queue of 28
-Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe358024570 pops frame 108 from queue
-1357933179:70118 encoder thread 0x7fe358024570 begins local encode of 108
-1357933179:70215 encoder thread 0x7fe3580a59d0 wakes with queue of 27
-Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe3580a59d0 pops frame 109 from queue
-Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.538, output at 4.53787
-1357933179:71090 Decoder emits 136
-1357933179:71356 adding to queue of 26
-1357933179:71570 encoder thread 0x7fe3580a45f0 wakes with queue of 27
-Fri Jan 11 19:39:39 2013: Encoder thread 0x7fe3580a45f0 pops frame 110 from queue
-1357933179:73111 encoder thread 0x7fe3580a45f0 begins local encode of 110
-Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.571, output at 4.57123
-1357933179:73729 Decoder emits 137
-1357933179:73966 adding to queue of 26
-Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.605, output at 4.6046
-1357933179:75609 Decoder emits 138
-1357933179:75774 adding to queue of 27
-Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.638, output at 4.63797
-1357933179:78084 encoder thread 0x7fe3580a59d0 begins local encode of 109
-1357933179:78312 writer sleeps with a queue of 4
-1357933179:78504 writer wakes with a queue of 4
-1357933179:78753 Decoder emits 139
-1357933179:79174 adding to queue of 28
-Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.671, output at 4.67133
-1357933179:83761 writer sleeps with a queue of 3
-1357933179:83985 writer wakes with a queue of 3
-1357933179:84375 Decoder emits 140
-1357933179:84843 adding to queue of 29
-Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.705, output at 4.7047
-1357933179:87382 Decoder emits 141
-1357933179:87678 adding to queue of 30
-1357933179:89018 writer sleeps with a queue of 2
-1357933179:89318 writer wakes with a queue of 2
-Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.738, output at 4.73807
-1357933179:90013 Decoder emits 142
-1357933179:90337 adding to queue of 31
-Fri Jan 11 19:39:39 2013: Source video frame ready; source at 4.771, output at 4.77143
-1357933179:92900 Decoder emits 143
-1357933179:93086 writer sleeps with a queue of 1
-1357933179:93279 writer wakes with a queue of 1
-1357933179:93506 decoder sleeps with queue of 32
-1357933179:96759 writer sleeps with a queue of 0
-Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 104
-Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 105
-Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 108
-1357933186:274915 encoder thread 0x7fe3580a4c90 finishes local encode of 104
-Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 110
-1357933186:288567 decoder wakes with queue of 32
-1357933186:288641 encoder thread 0x7fe3580a5330 finishes local encode of 105
-Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 106
-Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 103
-Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 107
-1357933186:289078 writer wakes with a queue of 1
-1357933186:289183 encoder thread 0x7fe358024570 finishes local encode of 108
-1357933186:289272 encoder thread 0x7fe3580a4c90 sleeps
-Fri Jan 11 19:39:46 2013: Finished locally-encoded frame 109
-1357933186:289514 decoder sleeps with queue of 32
-1357933186:289653 encoder thread 0x7fe3580a45f0 finishes local encode of 110
-1357933186:289938 encoder thread 0x7fe3580a4fe0 finishes local encode of 106
-1357933186:300780 encoder thread 0x7fe3580a5680 finishes local encode of 103
-1357933186:308513 writer sleeps with a queue of 4
-1357933186:308578 writer wakes with a queue of 4
-1357933186:308620 encoder thread 0x7fe3580a5330 sleeps
-1357933186:308690 encoder thread 0x7fe358024570 sleeps
-1357933186:308767 encoder thread 0x7fe3580a4c90 wakes with queue of 32
-1357933186:308828 encoder thread 0x7fe3580a4940 finishes local encode of 107
-1357933186:308906 encoder thread 0x7fe3580a59d0 finishes local encode of 109
-Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a4c90 pops frame 111 from queue
-1357933186:309072 encoder thread 0x7fe3580a4c90 begins local encode of 111
-1357933186:316420 encoder thread 0x7fe3580a45f0 sleeps
-1357933186:316521 decoder wakes with queue of 31
-1357933186:316643 adding to queue of 31
-Fri Jan 11 19:39:46 2013: Source video frame ready; source at 4.805, output at 4.8048
-1357933186:330616 writer sleeps with a queue of 6
-1357933186:344739 encoder thread 0x7fe3580a5680 sleeps
-1357933186:344810 encoder thread 0x7fe3580a4fe0 sleeps
-1357933186:344879 writer wakes with a queue of 6
-1357933186:344962 encoder thread 0x7fe3580a5330 wakes with queue of 32
-1357933186:345032 Decoder emits 144
-Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a5330 pops frame 112 from queue
-1357933186:345232 encoder thread 0x7fe3580a5330 begins local encode of 112
-1357933186:345303 encoder thread 0x7fe358024570 wakes with queue of 31
-Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe358024570 pops frame 113 from queue
-1357933186:345461 encoder thread 0x7fe358024570 begins local encode of 113
-1357933186:368710 encoder thread 0x7fe3580a4940 sleeps
-1357933186:368835 encoder thread 0x7fe3580a59d0 sleeps
-1357933186:368898 encoder thread 0x7fe3580a45f0 wakes with queue of 30
-Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a45f0 pops frame 114 from queue
-1357933186:369117 encoder thread 0x7fe3580a45f0 begins local encode of 114
-1357933186:369170 encoder thread 0x7fe3580a5680 wakes with queue of 29
-Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a5680 pops frame 115 from queue
-1357933186:369342 encoder thread 0x7fe3580a5680 begins local encode of 115
-1357933186:375527 encoder thread 0x7fe3580a4fe0 wakes with queue of 28
-1357933186:375745 writer sleeps with a queue of 5
-1357933186:375813 writer wakes with a queue of 5
-Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a4fe0 pops frame 116 from queue
-1357933186:376196 encoder thread 0x7fe3580a4fe0 begins local encode of 116
-1357933186:376453 adding to queue of 27
-1357933186:377212 encoder thread 0x7fe3580a4940 wakes with queue of 28
-Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a4940 pops frame 117 from queue
-1357933186:377543 encoder thread 0x7fe3580a4940 begins local encode of 117
-Fri Jan 11 19:39:46 2013: Source video frame ready; source at 4.838, output at 4.83817
-1357933186:379255 writer sleeps with a queue of 4
-1357933186:379395 encoder thread 0x7fe3580a59d0 wakes with queue of 27
-1357933186:379501 Decoder emits 145
-1357933186:379616 writer wakes with a queue of 4
-Fri Jan 11 19:39:46 2013: Encoder thread 0x7fe3580a59d0 pops frame 118 from queue
-1357933186:381383 encoder thread 0x7fe3580a59d0 begins local encode of 118
-1357933186:381547 adding to queue of 26
-1357933186:383259 writer sleeps with a queue of 3
-Fri Jan 11 19:39:46 2013: Source video frame ready; source at 4.872, output at 4.87153
-1357933186:384579 Decoder emits 146
-1357933186:384821 adding to queue of 27
-Fri Jan 11 19:39:46 2013: Source video frame ready; source at 4.905, output at 4.9049
-1357933186:386708 Decoder emits 147
-1357933186:386964 adding to queue of 28
-Fri Jan 11 19:39:46 2013: Source video frame ready; source at 4.938, output at 4.93827
-1357933186:389785 Decoder emits 148
-1357933186:390035 adding to queue of 29
-Fri Jan 11 19:39:46 2013: Source video frame ready; source at 4.972, output at 4.97163
-1357933186:392171 writer wakes with a queue of 3
-1357933186:392523 Decoder emits 149
-1357933186:392861 adding to queue of 30
-Fri Jan 11 19:39:46 2013: Source video frame ready; source at 5.005, output at 5.005
-1357933186:395163 Decoder emits 150
-1357933186:395456 adding to queue of 31
-1357933186:396558 writer sleeps with a queue of 2
-1357933186:396964 writer wakes with a queue of 2
-Fri Jan 11 19:39:46 2013: Source video frame ready; source at 5.038, output at 5.03837
-1357933186:397779 Decoder emits 151
-1357933186:398028 decoder sleeps with queue of 32
-1357933186:401782 writer sleeps with a queue of 1
-1357933186:402018 writer wakes with a queue of 1
-1357933186:409277 writer sleeps with a queue of 0
-Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 116
-1357933193:349837 encoder thread 0x7fe3580a4fe0 finishes local encode of 116
-1357933193:350054 writer wakes with a queue of 1
-1357933193:354234 writer sleeps with a queue of 0
-1357933193:374690 encoder thread 0x7fe3580a4fe0 sleeps
-1357933193:374857 decoder wakes with queue of 32
-1357933193:374940 decoder sleeps with queue of 32
-1357933193:375099 encoder thread 0x7fe3580a4fe0 wakes with queue of 32
-Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a4fe0 pops frame 119 from queue
-1357933193:375279 encoder thread 0x7fe3580a4fe0 begins local encode of 119
-Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 118
-Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 117
-Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 111
-Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 115
-1357933193:678184 encoder thread 0x7fe3580a59d0 finishes local encode of 118
-Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 112
-Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 114
-Fri Jan 11 19:39:53 2013: Finished locally-encoded frame 113
-1357933193:692105 writer wakes with a queue of 1
-1357933193:692327 encoder thread 0x7fe3580a4c90 finishes local encode of 111
-1357933193:692453 encoder thread 0x7fe3580a5680 finishes local encode of 115
-1357933193:692702 encoder thread 0x7fe3580a59d0 sleeps
-1357933193:692856 decoder wakes with queue of 31
-1357933193:692940 encoder thread 0x7fe3580a5330 finishes local encode of 112
-1357933193:693030 encoder thread 0x7fe358024570 finishes local encode of 113
-1357933193:693139 encoder thread 0x7fe3580a45f0 finishes local encode of 114
-1357933193:693272 adding to queue of 31
-1357933193:693783 encoder thread 0x7fe3580a4940 finishes local encode of 117
-Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.072, output at 5.07173
-1357933193:720807 writer sleeps with a queue of 6
-1357933193:720912 writer wakes with a queue of 6
-1357933193:721000 encoder thread 0x7fe3580a59d0 wakes with queue of 32
-1357933193:721076 encoder thread 0x7fe3580a4c90 sleeps
-1357933193:721173 encoder thread 0x7fe3580a5680 sleeps
-1357933193:721247 Decoder emits 152
-Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a59d0 pops frame 120 from queue
-1357933193:721409 encoder thread 0x7fe3580a59d0 begins local encode of 120
-1357933193:735893 encoder thread 0x7fe3580a5330 sleeps
-1357933193:743408 writer sleeps with a queue of 5
-1357933193:743490 writer wakes with a queue of 5
-1357933193:743572 encoder thread 0x7fe3580a4940 sleeps
-1357933193:743668 encoder thread 0x7fe3580a4c90 wakes with queue of 31
-1357933193:743742 encoder thread 0x7fe358024570 sleeps
-1357933193:743820 encoder thread 0x7fe3580a45f0 sleeps
-Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a4c90 pops frame 121 from queue
-1357933193:743992 encoder thread 0x7fe3580a4c90 begins local encode of 121
-1357933193:744106 encoder thread 0x7fe3580a5680 wakes with queue of 30
-Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a5680 pops frame 122 from queue
-1357933193:744319 encoder thread 0x7fe3580a5680 begins local encode of 122
-1357933193:744418 adding to queue of 29
-1357933193:744568 encoder thread 0x7fe3580a5330 wakes with queue of 30
-Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a5330 pops frame 123 from queue
-1357933193:744742 encoder thread 0x7fe3580a5330 begins local encode of 123
-1357933193:744818 encoder thread 0x7fe3580a4940 wakes with queue of 29
-Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a4940 pops frame 124 from queue
-1357933193:744997 encoder thread 0x7fe3580a4940 begins local encode of 124
-Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.105, output at 5.1051
-1357933193:746131 Decoder emits 153
-1357933193:746595 writer sleeps with a queue of 4
-1357933193:746652 writer wakes with a queue of 4
-1357933193:749908 writer sleeps with a queue of 3
-1357933193:750034 encoder thread 0x7fe358024570 wakes with queue of 28
-1357933193:750133 writer wakes with a queue of 3
-Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe358024570 pops frame 125 from queue
-1357933193:750396 encoder thread 0x7fe358024570 begins local encode of 125
-1357933193:750519 encoder thread 0x7fe3580a45f0 wakes with queue of 27
-Fri Jan 11 19:39:53 2013: Encoder thread 0x7fe3580a45f0 pops frame 126 from queue
-1357933193:750936 encoder thread 0x7fe3580a45f0 begins local encode of 126
-1357933193:751094 adding to queue of 26
-Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.138, output at 5.13847
-1357933193:758910 Decoder emits 154
-1357933193:759158 adding to queue of 27
-1357933193:760609 writer sleeps with a queue of 2
-1357933193:760878 writer wakes with a queue of 2
-Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.172, output at 5.17183
-1357933193:761568 Decoder emits 155
-1357933193:761830 adding to queue of 28
-Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.205, output at 5.2052
-1357933193:763729 Decoder emits 156
-1357933193:763941 adding to queue of 29
-Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.239, output at 5.23857
-1357933193:765932 Decoder emits 157
-1357933193:766178 adding to queue of 30
-Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.272, output at 5.27193
-1357933193:767794 Decoder emits 158
-1357933193:768019 adding to queue of 31
-Fri Jan 11 19:39:53 2013: Source video frame ready; source at 5.305, output at 5.3053
-1357933193:769709 Decoder emits 159
-1357933193:769913 decoder sleeps with queue of 32
-1357933193:781529 writer sleeps with a queue of 1
-1357933193:782078 writer wakes with a queue of 1
-1357933193:792101 writer sleeps with a queue of 0
-1357933194:407280 encoder thread 0x7fe358031550 sleeps
-1357933194:407760 decoder wakes with queue of 32
-1357933194:408135 decoder sleeps with queue of 32
-1357933194:408503 encoder thread 0x7fe358031550 wakes with queue of 32
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe358031550 pops frame 127 from queue
-1357933194:409338 encoder thread 0x7fe3580a6a80 sleeps
-1357933194:409728 decoder wakes with queue of 31
-1357933194:410215 adding to queue of 31
-1357933194:410644 encoder thread 0x7fe3580a6a80 wakes with queue of 32
-Fri Jan 11 19:39:54 2013: Remote encode of 127 on shankly failed (Host not found (authoritative)); thread sleeping for 50s
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a6a80 pops frame 128 from queue
-Fri Jan 11 19:39:54 2013: Source video frame ready; source at 5.339, output at 5.33867
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe358031550 pushes frame 127 back onto queue after failure
-1357933194:412750 Decoder emits 160
-Fri Jan 11 19:39:54 2013: Remote encode of 128 on shankly failed (Host not found (authoritative)); thread sleeping for 50s
-1357933194:413532 decoder sleeps with queue of 32
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a6a80 pushes frame 128 back onto queue after failure
-1357933194:433562 encoder thread 0x7fe3580a6090 sleeps
-1357933194:434003 decoder wakes with queue of 33
-1357933194:434361 decoder sleeps with queue of 33
-1357933194:434694 encoder thread 0x7fe3580a6090 wakes with queue of 33
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a6090 pops frame 128 from queue
-Fri Jan 11 19:39:54 2013: Remote encode of 128 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 50s
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a6090 pushes frame 128 back onto queue after failure
-1357933194:444618 encoder thread 0x7fe358030eb0 sleeps
-1357933194:444958 decoder wakes with queue of 33
-1357933194:445318 decoder sleeps with queue of 33
-1357933194:445690 encoder thread 0x7fe358030eb0 wakes with queue of 33
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe358030eb0 pops frame 128 from queue
-Fri Jan 11 19:39:54 2013: Remote encode of 128 on shankly failed (Host not found (authoritative)); thread sleeping for 50s
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe358030eb0 pushes frame 128 back onto queue after failure
-1357933194:448439 encoder thread 0x7fe3580a5d40 sleeps
-1357933194:448778 decoder wakes with queue of 33
-1357933194:449180 decoder sleeps with queue of 33
-1357933194:449576 encoder thread 0x7fe3580a5d40 wakes with queue of 33
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a5d40 pops frame 128 from queue
-Fri Jan 11 19:39:54 2013: Remote encode of 128 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 50s
-1357933194:451904 encoder thread 0x7fe3580a6730 sleeps
-1357933194:452236 decoder wakes with queue of 32
-1357933194:452577 decoder sleeps with queue of 32
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a5d40 pushes frame 128 back onto queue after failure
-1357933194:453428 encoder thread 0x7fe3580a6730 wakes with queue of 33
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a6730 pops frame 128 from queue
-1357933194:454393 encoder thread 0x7fe358031200 sleeps
-1357933194:454941 encoder thread 0x7fe3580a63e0 sleeps
-1357933194:455524 decoder wakes with queue of 32
-1357933194:456348 decoder sleeps with queue of 32
-Fri Jan 11 19:39:54 2013: Remote encode of 128 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 50s
-1357933194:457470 encoder thread 0x7fe358031200 wakes with queue of 32
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe358031200 pops frame 127 from queue
-1357933194:458437 encoder thread 0x7fe3580a63e0 wakes with queue of 31
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a63e0 pops frame 129 from queue
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a6730 pushes frame 128 back onto queue after failure
-Fri Jan 11 19:39:54 2013: Remote encode of 127 on shankly failed (Host not found (authoritative)); thread sleeping for 50s
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe358031200 pushes frame 127 back onto queue after failure
-Fri Jan 11 19:39:54 2013: Remote encode of 129 on shankly-gbit failed (Host not found (authoritative)); thread sleeping for 50s
-Fri Jan 11 19:39:54 2013: Encoder thread 0x7fe3580a63e0 pushes frame 129 back onto queue after failure
-Fri Jan 11 19:39:58 2013: Finished locally-encoded frame 123
-1357933198:967025 encoder thread 0x7fe3580a5330 finishes local encode of 123
-1357933198:967382 writer wakes with a queue of 1
-1357933198:982912 writer sleeps with a queue of 0
-1357933198:983079 encoder thread 0x7fe3580a5330 sleeps
-1357933198:983205 decoder wakes with queue of 33
-1357933198:983404 decoder sleeps with queue of 33
-1357933198:983524 encoder thread 0x7fe3580a5330 wakes with queue of 33
-Fri Jan 11 19:39:58 2013: Encoder thread 0x7fe3580a5330 pops frame 129 from queue
-1357933198:983688 encoder thread 0x7fe3580a5330 begins local encode of 129
-Fri Jan 11 19:39:59 2013: Finished locally-encoded frame 126
-Fri Jan 11 19:39:59 2013: Finished locally-encoded frame 125
-1357933199:496055 encoder thread 0x7fe3580a45f0 finishes local encode of 126
-1357933199:496328 encoder thread 0x7fe358024570 finishes local encode of 125
-1357933199:506491 writer wakes with a queue of 1
-1357933199:506810 encoder thread 0x7fe3580a45f0 sleeps
-1357933199:507491 decoder wakes with queue of 32
-1357933199:507687 decoder sleeps with queue of 32
-1357933199:517739 encoder thread 0x7fe358024570 sleeps
-1357933199:520670 writer sleeps with a queue of 1
-1357933199:520966 encoder thread 0x7fe3580a45f0 wakes with queue of 32
-1357933199:551489 writer wakes with a queue of 1
-Fri Jan 11 19:39:59 2013: Encoder thread 0x7fe3580a45f0 pops frame 127 from queue
-1357933199:551904 encoder thread 0x7fe3580a45f0 begins local encode of 127
-1357933199:552048 decoder wakes with queue of 31
-1357933199:552437 adding to queue of 31
-Fri Jan 11 19:39:59 2013: Source video frame ready; source at 5.372, output at 5.37203
-1357933199:555815 Decoder emits 161
-1357933199:585975 writer sleeps with a queue of 0
-1357933199:586174 encoder thread 0x7fe358024570 wakes with queue of 32
-Fri Jan 11 19:39:59 2013: Encoder thread 0x7fe358024570 pops frame 128 from queue
-1357933199:589103 encoder thread 0x7fe358024570 begins local encode of 128
-1357933199:589197 adding to queue of 31
-Fri Jan 11 19:39:59 2013: Source video frame ready; source at 5.405, output at 5.4054
-1357933199:592859 Decoder emits 162
-1357933199:593024 decoder sleeps with queue of 32
-Fri Jan 11 19:40:00 2013: Finished locally-encoded frame 122
-Fri Jan 11 19:40:00 2013: Finished locally-encoded frame 121
-Fri Jan 11 19:40:00 2013: Finished locally-encoded frame 120
-Fri Jan 11 19:40:00 2013: Finished locally-encoded frame 124
-Fri Jan 11 19:40:00 2013: Finished locally-encoded frame 119
-1357933200:131149 encoder thread 0x7fe3580a5680 finishes local encode of 122
-1357933200:131381 encoder thread 0x7fe3580a4c90 finishes local encode of 121
-1357933200:145988 encoder thread 0x7fe3580a59d0 finishes local encode of 120
-1357933200:146160 writer wakes with a queue of 1
-1357933200:146319 encoder thread 0x7fe3580a4940 finishes local encode of 124
-1357933200:146479 encoder thread 0x7fe3580a4fe0 finishes local encode of 119
-1357933200:146602 decoder wakes with queue of 32
-1357933200:146736 encoder thread 0x7fe3580a5680 sleeps
-1357933200:146838 decoder sleeps with queue of 32
-1357933200:161572 encoder thread 0x7fe3580a4c90 sleeps
-1357933200:172938 writer sleeps with a queue of 4
-1357933200:200744 encoder thread 0x7fe3580a4fe0 sleeps
-1357933200:200941 encoder thread 0x7fe3580a4940 sleeps
-1357933200:201094 encoder thread 0x7fe3580a5680 wakes with queue of 32
-1357933200:201215 writer wakes with a queue of 4
-1357933200:201334 encoder thread 0x7fe3580a59d0 sleeps
-Fri Jan 11 19:40:00 2013: Encoder thread 0x7fe3580a5680 pops frame 130 from queue
-1357933200:201644 encoder thread 0x7fe3580a5680 begins local encode of 130
-1357933200:201754 decoder wakes with queue of 31
-1357933200:201936 adding to queue of 31
-1357933200:202122 encoder thread 0x7fe3580a4c90 wakes with queue of 32
-Fri Jan 11 19:40:00 2013: Encoder thread 0x7fe3580a4c90 pops frame 131 from queue
-1357933200:202401 encoder thread 0x7fe3580a4c90 begins local encode of 131
-1357933200:202557 encoder thread 0x7fe3580a4fe0 wakes with queue of 31
-Fri Jan 11 19:40:00 2013: Encoder thread 0x7fe3580a4fe0 pops frame 132 from queue
-1357933200:202851 encoder thread 0x7fe3580a4fe0 begins local encode of 132
-1357933200:203068 encoder thread 0x7fe3580a4940 wakes with queue of 30
-Fri Jan 11 19:40:00 2013: Encoder thread 0x7fe3580a4940 pops frame 133 from queue
-Fri Jan 11 19:40:00 2013: Source video frame ready; source at 5.439, output at 5.43877
-1357933200:203879 Decoder emits 163
-1357933200:205621 encoder thread 0x7fe3580a59d0 wakes with queue of 29
-1357933200:205741 encoder thread 0x7fe3580a4940 begins local encode of 133
-1357933200:205843 writer sleeps with a queue of 3
-Fri Jan 11 19:40:00 2013: Encoder thread 0x7fe3580a59d0 pops frame 134 from queue
-1357933200:206112 writer wakes with a queue of 3
-1357933200:206218 adding to queue of 28
-Fri Jan 11 19:40:00 2013: Source video frame ready; source at 5.472, output at 5.47213
-1357933200:208814 Decoder emits 164
-1357933200:209172 adding to queue of 29
-1357933200:209351 encoder thread 0x7fe3580a59d0 begins local encode of 134
-1357933200:210101 writer sleeps with a queue of 2
-1357933200:210371 writer wakes with a queue of 2
-Fri Jan 11 19:40:00 2013: Source video frame ready; source at 5.506, output at 5.5055
-1357933200:211088 Decoder emits 165
-1357933200:211382 adding to queue of 30
-Fri Jan 11 19:40:00 2013: Source video frame ready; source at 5.539, output at 5.53887
-1357933200:214538 Decoder emits 166
-1357933200:214791 adding to queue of 31
-Fri Jan 11 19:40:00 2013: Source video frame ready; source at 5.572, output at 5.57223
-1357933200:216922 Decoder emits 167
-1357933200:217157 decoder sleeps with queue of 32
-1357933200:220148 writer sleeps with a queue of 1
-1357933200:220554 writer wakes with a queue of 1
-1357933200:224544 writer sleeps with a queue of 0
-Fri Jan 11 19:40:02 2013: Finished locally-encoded frame 129
-1357933202:83650 encoder thread 0x7fe3580a5330 finishes local encode of 129
-1357933202:83981 encoder thread 0x7fe3580a5330 sleeps
-1357933202:84243 writer wakes with a queue of 1
-1357933202:84556 decoder wakes with queue of 32
-1357933202:84913 decoder sleeps with queue of 32
-1357933202:85326 encoder thread 0x7fe3580a5330 wakes with queue of 32
-Fri Jan 11 19:40:02 2013: Encoder thread 0x7fe3580a5330 pops frame 135 from queue
-1357933202:86077 encoder thread 0x7fe3580a5330 begins local encode of 135
-1357933202:88210 writer sleeps with a queue of 0
-Fri Jan 11 19:40:05 2013: Finished locally-encoded frame 127
-1357933205:288446 encoder thread 0x7fe3580a45f0 finishes local encode of 127
-1357933205:288531 encoder thread 0x7fe3580a45f0 sleeps
-1357933205:288582 decoder wakes with queue of 31
-1357933205:288689 adding to queue of 31
-1357933205:288792 encoder thread 0x7fe3580a45f0 wakes with queue of 32
-Fri Jan 11 19:40:05 2013: Source video frame ready; source at 5.606, output at 5.6056
-1357933205:364162 Decoder emits 168
-1357933205:394427 writer wakes with a queue of 1
-Fri Jan 11 19:40:05 2013: Encoder thread 0x7fe3580a45f0 pops frame 136 from queue
-Fri Jan 11 19:40:05 2013: Finished locally-encoded frame 128
-1357933205:394722 adding to queue of 31
-1357933205:395192 encoder thread 0x7fe358024570 finishes local encode of 128
-1357933205:395260 encoder thread 0x7fe358024570 sleeps
-1357933205:395316 encoder thread 0x7fe358024570 wakes with queue of 32
-Fri Jan 11 19:40:05 2013: Encoder thread 0x7fe358024570 pops frame 137 from queue
-1357933205:395421 encoder thread 0x7fe358024570 begins local encode of 137
-Fri Jan 11 19:40:05 2013: Source video frame ready; source at 5.639, output at 5.63897
-1357933205:396291 Decoder emits 169
-1357933205:396402 adding to queue of 31
-Fri Jan 11 19:40:05 2013: Source video frame ready; source at 5.672, output at 5.67233
-1357933205:397317 Decoder emits 170
-1357933205:397391 decoder sleeps with queue of 32
-1357933205:397443 encoder thread 0x7fe3580a45f0 begins local encode of 136
-1357933205:397742 writer sleeps with a queue of 1
-1357933205:397790 writer wakes with a queue of 1
-1357933205:401013 writer sleeps with a queue of 0
-Fri Jan 11 19:40:06 2013: Finished locally-encoded frame 130
-1357933206:832217 encoder thread 0x7fe3580a5680 finishes local encode of 130
-Fri Jan 11 19:40:06 2013: Finished locally-encoded frame 134
-1357933206:952915 writer wakes with a queue of 1
-Fri Jan 11 19:40:07 2013: Finished locally-encoded frame 131
-1357933207:11661 encoder thread 0x7fe3580a5680 sleeps
-1357933207:11768 decoder wakes with queue of 32
-Fri Jan 11 19:40:07 2013: Finished locally-encoded frame 133
-Fri Jan 11 19:40:07 2013: Finished locally-encoded frame 132
-Fri Jan 11 19:40:07 2013: Finished locally-encoded frame 135
-1357933207:12501 decoder sleeps with queue of 32
-1357933207:12770 encoder thread 0x7fe3580a4c90 finishes local encode of 131
-1357933207:13108 encoder thread 0x7fe3580a5680 wakes with queue of 32
-Fri Jan 11 19:40:07 2013: Encoder thread 0x7fe3580a5680 pops frame 138 from queue
-1357933207:13585 encoder thread 0x7fe3580a4fe0 finishes local encode of 132
-1357933207:26871 encoder thread 0x7fe3580a4940 finishes local encode of 133
-1357933207:27003 encoder thread 0x7fe3580a5680 begins local encode of 138
-1357933207:27106 encoder thread 0x7fe3580a5330 finishes local encode of 135
-1357933207:27221 encoder thread 0x7fe3580a4c90 sleeps
-1357933207:27313 decoder wakes with queue of 31
-1357933207:27478 adding to queue of 31
-1357933207:27762 encoder thread 0x7fe3580a59d0 finishes local encode of 134
-Fri Jan 11 19:40:07 2013: Source video frame ready; source at 5.706, output at 5.7057
-1357933207:55750 writer sleeps with a queue of 4
-1357933207:68468 encoder thread 0x7fe3580a4fe0 sleeps
-1357933207:68618 encoder thread 0x7fe3580a4940 sleeps
-1357933207:68697 Decoder emits 171
-1357933207:68779 writer wakes with a queue of 4
-1357933207:68845 encoder thread 0x7fe3580a5330 sleeps
-1357933207:68933 encoder thread 0x7fe3580a4c90 wakes with queue of 32
-Fri Jan 11 19:40:07 2013: Encoder thread 0x7fe3580a4c90 pops frame 139 from queue
-1357933207:69084 encoder thread 0x7fe3580a4c90 begins local encode of 139
-1357933207:69127 encoder thread 0x7fe3580a4fe0 wakes with queue of 31
-Fri Jan 11 19:40:07 2013: Encoder thread 0x7fe3580a4fe0 pops frame 140 from queue
-1357933207:69280 encoder thread 0x7fe3580a4fe0 begins local encode of 140
-1357933207:69324 encoder thread 0x7fe3580a4940 wakes with queue of 30
-Fri Jan 11 19:40:07 2013: Encoder thread 0x7fe3580a4940 pops frame 141 from queue
-1357933207:69519 encoder thread 0x7fe3580a4940 begins local encode of 141
-1357933207:69582 adding to queue of 29
-1357933207:69977 encoder thread 0x7fe3580a59d0 sleeps
-Fri Jan 11 19:40:07 2013: Source video frame ready; source at 5.739, output at 5.73907
-1357933207:72464 writer sleeps with a queue of 4
-1357933207:72574 writer wakes with a queue of 4
-1357933207:72665 Decoder emits 172
-1357933207:73511 encoder thread 0x7fe3580a5330 wakes with queue of 30
-Fri Jan 11 19:40:07 2013: Encoder thread 0x7fe3580a5330 pops frame 142 from queue
-1357933207:73795 encoder thread 0x7fe3580a5330 begins local encode of 142
-1357933207:75724 writer sleeps with a queue of 3
-1357933207:75872 writer wakes with a queue of 3
-1357933207:77044 encoder thread 0x7fe3580a59d0 wakes with queue of 29
-Fri Jan 11 19:40:07 2013: Encoder thread 0x7fe3580a59d0 pops frame 143 from queue
-1357933207:77457 encoder thread 0x7fe3580a59d0 begins local encode of 143
-1357933207:77615 adding to queue of 28
-1357933207:91384 writer sleeps with a queue of 2
-Fri Jan 11 19:40:07 2013: Source video frame ready; source at 5.772, output at 5.77243
-1357933207:91734 Decoder emits 173
-1357933207:91867 adding to queue of 29
-Fri Jan 11 19:40:07 2013: Source video frame ready; source at 5.806, output at 5.8058
-1357933207:93716 Decoder emits 174
-1357933207:93887 adding to queue of 30
-Fri Jan 11 19:40:07 2013: Source video frame ready; source at 5.839, output at 5.83917
-1357933207:95435 Decoder emits 175
-1357933207:95568 adding to queue of 31
-Fri Jan 11 19:40:07 2013: Source video frame ready; source at 5.873, output at 5.87253
-1357933207:97226 writer wakes with a queue of 2
-1357933207:97514 Decoder emits 176
-1357933207:98891 decoder sleeps with queue of 32
-1357933207:101158 writer sleeps with a queue of 1
-1357933207:101363 writer wakes with a queue of 1
-1357933207:116791 writer sleeps with a queue of 0
-Fri Jan 11 19:40:08 2013: Finished locally-encoded frame 137
-1357933208:437367 encoder thread 0x7fe358024570 finishes local encode of 137
-1357933208:437839 writer wakes with a queue of 1
-1357933208:451320 encoder thread 0x7fe358024570 sleeps
-1357933208:451732 decoder wakes with queue of 32
-1357933208:452078 decoder sleeps with queue of 32
-1357933208:452430 encoder thread 0x7fe358024570 wakes with queue of 32
-Fri Jan 11 19:40:08 2013: Encoder thread 0x7fe358024570 pops frame 144 from queue
-1357933208:453028 encoder thread 0x7fe358024570 begins local encode of 144
-1357933208:454944 writer sleeps with a queue of 0
-Fri Jan 11 19:40:08 2013: Finished locally-encoded frame 136
-1357933208:509695 encoder thread 0x7fe3580a45f0 finishes local encode of 136
-1357933208:510026 writer wakes with a queue of 1
-1357933208:516892 encoder thread 0x7fe3580a45f0 sleeps
-1357933208:517230 decoder wakes with queue of 31
-1357933208:517631 adding to queue of 31
-1357933208:518098 encoder thread 0x7fe3580a45f0 wakes with queue of 32
-Fri Jan 11 19:40:08 2013: Encoder thread 0x7fe3580a45f0 pops frame 145 from queue
-1357933208:518943 encoder thread 0x7fe3580a45f0 begins local encode of 145
-Fri Jan 11 19:40:08 2013: Source video frame ready; source at 5.906, output at 5.9059
-1357933208:519693 Decoder emits 177
-1357933208:520028 adding to queue of 31
-1357933208:520685 writer sleeps with a queue of 0
-Fri Jan 11 19:40:08 2013: Source video frame ready; source at 5.939, output at 5.93927
-1357933208:527563 Decoder emits 178
-1357933208:527928 decoder sleeps with queue of 32
-Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 141
-1357933213:290129 encoder thread 0x7fe3580a4940 finishes local encode of 141
-1357933213:290422 writer wakes with a queue of 1
-1357933213:308220 encoder thread 0x7fe3580a4940 sleeps
-1357933213:308315 decoder wakes with queue of 32
-1357933213:308388 decoder sleeps with queue of 32
-1357933213:308467 encoder thread 0x7fe3580a4940 wakes with queue of 32
-Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a4940 pops frame 146 from queue
-1357933213:308630 encoder thread 0x7fe3580a4940 begins local encode of 146
-1357933213:312482 writer sleeps with a queue of 0
-Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 143
-Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 139
-1357933213:773670 encoder thread 0x7fe3580a59d0 finishes local encode of 143
-Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 138
-Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 140
-Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 142
-1357933213:790346 writer wakes with a queue of 1
-1357933213:790508 encoder thread 0x7fe3580a4c90 finishes local encode of 139
-1357933213:790799 encoder thread 0x7fe3580a59d0 sleeps
-1357933213:791023 decoder wakes with queue of 31
-1357933213:791375 encoder thread 0x7fe3580a5680 finishes local encode of 138
-1357933213:791614 encoder thread 0x7fe3580a5330 finishes local encode of 142
-1357933213:791759 adding to queue of 31
-1357933213:791896 encoder thread 0x7fe3580a4fe0 finishes local encode of 140
-Fri Jan 11 19:40:13 2013: Source video frame ready; source at 5.973, output at 5.97263
-1357933213:808217 encoder thread 0x7fe3580a4c90 sleeps
-1357933213:808325 encoder thread 0x7fe3580a59d0 wakes with queue of 32
-Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a59d0 pops frame 147 from queue
-1357933213:808557 Decoder emits 179
-1357933213:825154 encoder thread 0x7fe3580a59d0 begins local encode of 147
-1357933213:839728 encoder thread 0x7fe3580a5330 sleeps
-1357933213:855320 writer sleeps with a queue of 4
-1357933213:863567 encoder thread 0x7fe3580a5680 sleeps
-1357933213:892870 encoder thread 0x7fe3580a4fe0 sleeps
-1357933213:900683 encoder thread 0x7fe3580a4c90 wakes with queue of 31
-Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 144
-Fri Jan 11 19:40:13 2013: Finished locally-encoded frame 145
-1357933213:927093 writer wakes with a queue of 4
-Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a4c90 pops frame 148 from queue
-1357933213:927469 encoder thread 0x7fe3580a4c90 begins local encode of 148
-1357933213:927654 encoder thread 0x7fe358024570 finishes local encode of 144
-1357933213:927782 adding to queue of 30
-1357933213:928043 encoder thread 0x7fe3580a5330 wakes with queue of 31
-1357933213:928216 encoder thread 0x7fe3580a45f0 finishes local encode of 145
-Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a5330 pops frame 149 from queue
-1357933213:928494 encoder thread 0x7fe3580a5330 begins local encode of 149
-1357933213:928602 encoder thread 0x7fe3580a5680 wakes with queue of 30
-Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a5680 pops frame 150 from queue
-1357933213:928910 encoder thread 0x7fe3580a5680 begins local encode of 150
-1357933213:929087 encoder thread 0x7fe3580a4fe0 wakes with queue of 29
-Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a4fe0 pops frame 151 from queue
-Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.006, output at 6.006
-1357933213:937591 encoder thread 0x7fe358024570 sleeps
-1357933213:948264 encoder thread 0x7fe3580a4fe0 begins local encode of 151
-1357933213:948443 writer sleeps with a queue of 5
-1357933213:948574 Decoder emits 180
-1357933213:948709 encoder thread 0x7fe358024570 wakes with queue of 28
-1357933213:948812 encoder thread 0x7fe3580a45f0 sleeps
-1357933213:948916 writer wakes with a queue of 5
-Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe358024570 pops frame 152 from queue
-1357933213:949248 adding to queue of 27
-1357933213:950629 encoder thread 0x7fe3580a45f0 wakes with queue of 28
-Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.039, output at 6.03937
-1357933213:951268 Decoder emits 181
-1357933213:952749 writer sleeps with a queue of 4
-1357933213:952928 writer wakes with a queue of 4
-1357933213:953026 encoder thread 0x7fe358024570 begins local encode of 152
-1357933213:961791 writer sleeps with a queue of 3
-1357933213:961983 writer wakes with a queue of 3
-Fri Jan 11 19:40:13 2013: Encoder thread 0x7fe3580a45f0 pops frame 153 from queue
-1357933213:962553 encoder thread 0x7fe3580a45f0 begins local encode of 153
-1357933213:962748 adding to queue of 27
-Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.073, output at 6.07273
-1357933213:965536 Decoder emits 182
-1357933213:965806 adding to queue of 28
-Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.106, output at 6.1061
-1357933213:967723 Decoder emits 183
-1357933213:967969 adding to queue of 29
-1357933213:968815 writer sleeps with a queue of 2
-1357933213:969070 writer wakes with a queue of 2
-Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.139, output at 6.13947
-1357933213:970704 Decoder emits 184
-1357933213:971035 adding to queue of 30
-1357933213:972881 writer sleeps with a queue of 1
-1357933213:973063 writer wakes with a queue of 1
-Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.173, output at 6.17283
-1357933213:974744 Decoder emits 185
-1357933213:975182 adding to queue of 31
-Fri Jan 11 19:40:13 2013: Source video frame ready; source at 6.206, output at 6.2062
-1357933213:978576 writer sleeps with a queue of 0
-1357933213:978832 Decoder emits 186
-1357933213:979201 decoder sleeps with queue of 32
-Fri Jan 11 19:40:18 2013: Finished locally-encoded frame 146
-1357933218:137842 encoder thread 0x7fe3580a4940 finishes local encode of 146
-1357933218:138235 writer wakes with a queue of 1
-1357933218:151181 encoder thread 0x7fe3580a4940 sleeps
-1357933218:151542 decoder wakes with queue of 32
-1357933218:151859 decoder sleeps with queue of 32
-1357933218:152173 encoder thread 0x7fe3580a4940 wakes with queue of 32
-Fri Jan 11 19:40:18 2013: Encoder thread 0x7fe3580a4940 pops frame 154 from queue
-1357933218:152898 encoder thread 0x7fe3580a4940 begins local encode of 154
-1357933218:154726 writer sleeps with a queue of 0
-Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 149
-1357933220:603254 encoder thread 0x7fe3580a5330 finishes local encode of 149
-1357933220:603651 writer wakes with a queue of 1
-1357933220:620330 writer sleeps with a queue of 0
-1357933220:629730 encoder thread 0x7fe3580a5330 sleeps
-1357933220:629935 decoder wakes with queue of 31
-1357933220:630139 adding to queue of 31
-1357933220:630289 encoder thread 0x7fe3580a5330 wakes with queue of 32
-Fri Jan 11 19:40:20 2013: Encoder thread 0x7fe3580a5330 pops frame 155 from queue
-1357933220:630509 encoder thread 0x7fe3580a5330 begins local encode of 155
-Fri Jan 11 19:40:20 2013: Source video frame ready; source at 6.24, output at 6.23957
-1357933220:633105 Decoder emits 187
-1357933220:633352 adding to queue of 31
-Fri Jan 11 19:40:20 2013: Source video frame ready; source at 6.273, output at 6.27293
-1357933220:636497 Decoder emits 188
-1357933220:636642 decoder sleeps with queue of 32
-Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 148
-Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 152
-Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 150
-1357933220:910037 encoder thread 0x7fe3580a4c90 finishes local encode of 148
-Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 147
-Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 151
-1357933220:954556 encoder thread 0x7fe358024570 finishes local encode of 152
-Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 153
-1357933220:960042 writer wakes with a queue of 1
-1357933220:960114 encoder thread 0x7fe3580a5680 finishes local encode of 150
-1357933220:960268 encoder thread 0x7fe3580a4c90 sleeps
-1357933220:960449 decoder wakes with queue of 32
-1357933220:960604 encoder thread 0x7fe3580a59d0 finishes local encode of 147
-1357933220:960684 encoder thread 0x7fe3580a4fe0 finishes local encode of 151
-Fri Jan 11 19:40:20 2013: Finished locally-encoded frame 154
-1357933220:960878 encoder thread 0x7fe3580a45f0 finishes local encode of 153
-1357933220:960988 decoder sleeps with queue of 32
-1357933220:961542 encoder thread 0x7fe3580a4940 finishes local encode of 154
-1357933220:984624 encoder thread 0x7fe358024570 sleeps
-1357933220:984728 encoder thread 0x7fe3580a5680 sleeps
-1357933220:984842 encoder thread 0x7fe3580a4c90 wakes with queue of 32
-Fri Jan 11 19:40:20 2013: Encoder thread 0x7fe3580a4c90 pops frame 156 from queue
-1357933220:985115 encoder thread 0x7fe3580a4c90 begins local encode of 156
-1357933221:10659 encoder thread 0x7fe3580a59d0 sleeps
-1357933221:12559 encoder thread 0x7fe3580a4fe0 sleeps
-1357933221:20062 writer sleeps with a queue of 6
-1357933221:20161 encoder thread 0x7fe3580a45f0 sleeps
-1357933221:20265 decoder wakes with queue of 31
-1357933221:20375 writer wakes with a queue of 6
-1357933221:20470 adding to queue of 31
-Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.306, output at 6.3063
-1357933221:32732 encoder thread 0x7fe3580a4940 sleeps
-1357933221:32845 encoder thread 0x7fe358024570 wakes with queue of 32
-1357933221:32957 Decoder emits 189
-Fri Jan 11 19:40:21 2013: Encoder thread 0x7fe358024570 pops frame 157 from queue
-1357933221:33133 encoder thread 0x7fe358024570 begins local encode of 157
-1357933221:33238 encoder thread 0x7fe3580a5680 wakes with queue of 31
-Fri Jan 11 19:40:21 2013: Encoder thread 0x7fe3580a5680 pops frame 158 from queue
-1357933221:33440 encoder thread 0x7fe3580a5680 begins local encode of 158
-1357933221:33542 encoder thread 0x7fe3580a59d0 wakes with queue of 30
-Fri Jan 11 19:40:21 2013: Encoder thread 0x7fe3580a59d0 pops frame 159 from queue
-1357933221:33735 encoder thread 0x7fe3580a59d0 begins local encode of 159
-1357933221:33816 encoder thread 0x7fe3580a4fe0 wakes with queue of 29
-Fri Jan 11 19:40:21 2013: Encoder thread 0x7fe3580a4fe0 pops frame 160 from queue
-1357933221:34033 encoder thread 0x7fe3580a4fe0 begins local encode of 160
-1357933221:34104 encoder thread 0x7fe3580a45f0 wakes with queue of 28
-Fri Jan 11 19:40:21 2013: Encoder thread 0x7fe3580a45f0 pops frame 161 from queue
-1357933221:34361 encoder thread 0x7fe3580a45f0 begins local encode of 161
-1357933221:34708 writer sleeps with a queue of 5
-1357933221:34872 writer wakes with a queue of 5
-1357933221:35022 encoder thread 0x7fe3580a4940 wakes with queue of 27
-Fri Jan 11 19:40:21 2013: Encoder thread 0x7fe3580a4940 pops frame 162 from queue
-1357933221:40856 encoder thread 0x7fe3580a4940 begins local encode of 162
-1357933221:40985 adding to queue of 26
-Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.34, output at 6.33967
-1357933221:43235 Decoder emits 190
-1357933221:43454 adding to queue of 27
-Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.373, output at 6.37303
-1357933221:45029 Decoder emits 191
-1357933221:45259 adding to queue of 28
-Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.406, output at 6.4064
-1357933221:46908 writer sleeps with a queue of 4
-1357933221:47159 Decoder emits 192
-1357933221:47486 adding to queue of 29
-1357933221:47643 writer wakes with a queue of 4
-Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.44, output at 6.43977
-1357933221:49982 Decoder emits 193
-1357933221:50252 adding to queue of 30
-1357933221:51546 writer sleeps with a queue of 3
-1357933221:51717 writer wakes with a queue of 3
-Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.473, output at 6.47313
-1357933221:53353 Decoder emits 194
-1357933221:53691 adding to queue of 31
-Fri Jan 11 19:40:21 2013: Source video frame ready; source at 6.507, output at 6.5065
-1357933221:55670 Decoder emits 195
-1357933221:55911 decoder sleeps with queue of 32
-1357933221:56324 writer sleeps with a queue of 2
-1357933221:56496 writer wakes with a queue of 2
-1357933221:59946 writer sleeps with a queue of 1
-1357933221:60134 writer wakes with a queue of 1
-1357933221:64145 writer sleeps with a queue of 0
-Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 156
-1357933228:335212 encoder thread 0x7fe3580a4c90 finishes local encode of 156
-Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 155
-Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 162
-Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 160
-Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 158
-Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 157
-Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 161
-Fri Jan 11 19:40:28 2013: Finished locally-encoded frame 159
-1357933228:431256 encoder thread 0x7fe3580a5330 finishes local encode of 155
-1357933228:431360 encoder thread 0x7fe3580a4c90 sleeps
-1357933228:431509 decoder wakes with queue of 32
-1357933228:431607 writer wakes with a queue of 1
-1357933228:431694 decoder sleeps with queue of 32
-1357933228:432503 encoder thread 0x7fe3580a4fe0 finishes local encode of 160
-1357933228:439720 encoder thread 0x7fe3580a5680 finishes local encode of 158
-1357933228:439794 encoder thread 0x7fe3580a59d0 finishes local encode of 159
-1357933228:439898 encoder thread 0x7fe358024570 finishes local encode of 157
-1357933228:439985 encoder thread 0x7fe3580a45f0 finishes local encode of 161
-1357933228:440058 encoder thread 0x7fe3580a4940 finishes local encode of 162
-1357933228:440122 encoder thread 0x7fe3580a5330 sleeps
-1357933228:440182 decoder wakes with queue of 32
-1357933228:440252 decoder sleeps with queue of 32
-1357933228:440345 encoder thread 0x7fe3580a4c90 wakes with queue of 32
-Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a4c90 pops frame 163 from queue
-1357933228:440487 encoder thread 0x7fe3580a4c90 begins local encode of 163
-1357933228:454795 encoder thread 0x7fe3580a4fe0 sleeps
-1357933228:484602 encoder thread 0x7fe3580a5680 sleeps
-1357933228:498923 writer sleeps with a queue of 7
-1357933228:510758 writer wakes with a queue of 7
-1357933228:510821 encoder thread 0x7fe358024570 sleeps
-1357933228:510982 encoder thread 0x7fe3580a45f0 sleeps
-1357933228:511047 encoder thread 0x7fe3580a59d0 sleeps
-1357933228:511116 encoder thread 0x7fe3580a4940 sleeps
-1357933228:511197 encoder thread 0x7fe3580a5330 wakes with queue of 31
-Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a5330 pops frame 164 from queue
-1357933228:511389 encoder thread 0x7fe3580a5330 begins local encode of 164
-1357933228:511460 decoder wakes with queue of 30
-1357933228:511586 adding to queue of 30
-1357933228:511706 encoder thread 0x7fe3580a4fe0 wakes with queue of 31
-Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a4fe0 pops frame 165 from queue
-1357933228:511927 encoder thread 0x7fe3580a4fe0 begins local encode of 165
-1357933228:511981 encoder thread 0x7fe3580a5680 wakes with queue of 30
-Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a5680 pops frame 166 from queue
-1357933228:512240 encoder thread 0x7fe3580a5680 begins local encode of 166
-1357933228:512316 encoder thread 0x7fe358024570 wakes with queue of 29
-Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe358024570 pops frame 167 from queue
-1357933228:512498 encoder thread 0x7fe358024570 begins local encode of 167
-1357933228:512587 encoder thread 0x7fe3580a45f0 wakes with queue of 28
-Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a45f0 pops frame 168 from queue
-1357933228:512815 encoder thread 0x7fe3580a45f0 begins local encode of 168
-1357933228:513053 encoder thread 0x7fe3580a59d0 wakes with queue of 27
-Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.54, output at 6.53987
-1357933228:513470 Decoder emits 196
-Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a59d0 pops frame 169 from queue
-1357933228:513710 encoder thread 0x7fe3580a59d0 begins local encode of 169
-1357933228:513885 writer sleeps with a queue of 6
-1357933228:514017 writer wakes with a queue of 6
-1357933228:517170 writer sleeps with a queue of 5
-1357933228:517363 writer wakes with a queue of 5
-1357933228:517495 encoder thread 0x7fe3580a4940 wakes with queue of 26
-Fri Jan 11 19:40:28 2013: Encoder thread 0x7fe3580a4940 pops frame 170 from queue
-1357933228:517910 encoder thread 0x7fe3580a4940 begins local encode of 170
-1357933228:518072 adding to queue of 25
-Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.573, output at 6.57323
-1357933228:520309 Decoder emits 197
-1357933228:520653 adding to queue of 26
-1357933228:520887 writer sleeps with a queue of 4
-1357933228:521063 writer wakes with a queue of 4
-Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.607, output at 6.6066
-1357933228:522919 Decoder emits 198
-1357933228:523164 adding to queue of 27
-Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.64, output at 6.63997
-1357933228:525230 Decoder emits 199
-1357933228:525450 adding to queue of 28
-Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.673, output at 6.67333
-1357933228:527251 Decoder emits 200
-1357933228:527472 adding to queue of 29
-1357933228:528187 writer sleeps with a queue of 3
-1357933228:528611 writer wakes with a queue of 3
-Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.707, output at 6.7067
-1357933228:529589 Decoder emits 201
-1357933228:529794 adding to queue of 30
-Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.74, output at 6.74007
-1357933228:531614 Decoder emits 202
-1357933228:531836 adding to queue of 31
-Fri Jan 11 19:40:28 2013: Source video frame ready; source at 6.773, output at 6.77343
-1357933228:534921 Decoder emits 203
-1357933228:535135 decoder sleeps with queue of 32
-1357933228:541575 writer sleeps with a queue of 2
-1357933228:541984 writer wakes with a queue of 2
-1357933228:549575 writer sleeps with a queue of 1
-1357933228:549917 writer wakes with a queue of 1
-1357933228:553777 writer sleeps with a queue of 0
-Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 167
-1357933235:537904 encoder thread 0x7fe358024570 finishes local encode of 167
-Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 170
-1357933235:584070 writer wakes with a queue of 1
-1357933235:680813 encoder thread 0x7fe358024570 sleeps
-Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 168
-1357933235:783272 encoder thread 0x7fe3580a4940 finishes local encode of 170
-Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 164
-Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 169
-Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 163
-Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 165
-1357933235:783772 decoder wakes with queue of 32
-1357933235:783892 decoder sleeps with queue of 32
-Fri Jan 11 19:40:35 2013: Finished locally-encoded frame 166
-1357933235:784139 encoder thread 0x7fe358024570 wakes with queue of 32
-Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe358024570 pops frame 171 from queue
-1357933235:784307 encoder thread 0x7fe3580a45f0 finishes local encode of 168
-1357933235:784377 encoder thread 0x7fe358024570 begins local encode of 171
-1357933235:802457 writer sleeps with a queue of 2
-1357933235:809820 decoder wakes with queue of 31
-1357933235:809914 encoder thread 0x7fe3580a59d0 finishes local encode of 169
-1357933235:809998 encoder thread 0x7fe3580a5330 finishes local encode of 164
-1357933235:810063 encoder thread 0x7fe3580a5680 finishes local encode of 166
-1357933235:810131 encoder thread 0x7fe3580a4940 sleeps
-1357933235:810192 encoder thread 0x7fe3580a4c90 finishes local encode of 163
-1357933235:810263 encoder thread 0x7fe3580a45f0 sleeps
-1357933235:810336 encoder thread 0x7fe3580a4fe0 finishes local encode of 165
-1357933235:810408 writer wakes with a queue of 2
-1357933235:810469 adding to queue of 31
-Fri Jan 11 19:40:35 2013: Source video frame ready; source at 6.807, output at 6.8068
-1357933235:825103 encoder thread 0x7fe3580a45f0 wakes with queue of 32
-1357933235:825196 encoder thread 0x7fe3580a5680 sleeps
-1357933235:825248 Decoder emits 204
-Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a45f0 pops frame 172 from queue
-1357933235:825406 encoder thread 0x7fe3580a45f0 begins local encode of 172
-1357933235:840303 writer sleeps with a queue of 6
-1357933235:854047 encoder thread 0x7fe3580a59d0 sleeps
-1357933235:854156 writer wakes with a queue of 6
-1357933235:854248 encoder thread 0x7fe3580a5330 sleeps
-1357933235:854396 encoder thread 0x7fe3580a4940 wakes with queue of 31
-Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a4940 pops frame 173 from queue
-1357933235:854638 encoder thread 0x7fe3580a4940 begins local encode of 173
-1357933235:863490 encoder thread 0x7fe3580a4c90 sleeps
-1357933235:877655 encoder thread 0x7fe3580a4fe0 sleeps
-1357933235:877791 encoder thread 0x7fe3580a5680 wakes with queue of 30
-Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a5680 pops frame 174 from queue
-1357933235:878054 encoder thread 0x7fe3580a5680 begins local encode of 174
-1357933235:878150 adding to queue of 29
-1357933235:878340 encoder thread 0x7fe3580a59d0 wakes with queue of 30
-Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a59d0 pops frame 175 from queue
-1357933235:878548 encoder thread 0x7fe3580a59d0 begins local encode of 175
-1357933235:878678 encoder thread 0x7fe3580a5330 wakes with queue of 29
-Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a5330 pops frame 176 from queue
-1357933235:879013 encoder thread 0x7fe3580a5330 begins local encode of 176
-1357933235:879172 encoder thread 0x7fe3580a4c90 wakes with queue of 28
-Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a4c90 pops frame 177 from queue
-1357933235:879618 encoder thread 0x7fe3580a4c90 begins local encode of 177
-1357933235:879797 writer sleeps with a queue of 5
-1357933235:880021 writer wakes with a queue of 5
-Fri Jan 11 19:40:35 2013: Source video frame ready; source at 6.84, output at 6.84017
-1357933235:881619 Decoder emits 205
-1357933235:882570 encoder thread 0x7fe3580a4fe0 wakes with queue of 27
-Fri Jan 11 19:40:35 2013: Encoder thread 0x7fe3580a4fe0 pops frame 178 from queue
-1357933235:883015 adding to queue of 26
-Fri Jan 11 19:40:35 2013: Source video frame ready; source at 6.874, output at 6.87353
-1357933235:885209 Decoder emits 206
-1357933235:885316 encoder thread 0x7fe3580a4fe0 begins local encode of 178
-1357933235:896831 writer sleeps with a queue of 4
-1357933235:897273 adding to queue of 27
-Fri Jan 11 19:40:35 2013: Source video frame ready; source at 6.907, output at 6.9069
-1357933235:901894 Decoder emits 207
-1357933235:902468 adding to queue of 28
-Fri Jan 11 19:40:35 2013: Source video frame ready; source at 6.94, output at 6.94027
-1357933235:906674 Decoder emits 208
-1357933235:907081 adding to queue of 29
-Fri Jan 11 19:40:35 2013: Source video frame ready; source at 6.974, output at 6.97363
-1357933235:909295 writer wakes with a queue of 4
-1357933235:909557 Decoder emits 209
-1357933235:909873 adding to queue of 30
-Fri Jan 11 19:40:35 2013: Source video frame ready; source at 7.007, output at 7.007
-1357933235:912676 Decoder emits 210
-1357933235:912943 adding to queue of 31
-Fri Jan 11 19:40:35 2013: Source video frame ready; source at 7.04, output at 7.04037
-1357933235:914787 writer sleeps with a queue of 3
-1357933235:915006 writer wakes with a queue of 3
-1357933235:915169 Decoder emits 211
-1357933235:915486 decoder sleeps with queue of 32
-1357933235:918704 writer sleeps with a queue of 2
-1357933235:918904 writer wakes with a queue of 2
-1357933235:922506 writer sleeps with a queue of 1
-1357933235:922648 writer wakes with a queue of 1
-1357933235:926325 writer sleeps with a queue of 0
diff --git a/optimise/analog b/optimise/analog
deleted file mode 100755 (executable)
index 1743008..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/python
-
-import sys
-
-class Encoder:
-      def __init__(self):
-            self.awake = 0
-            self.asleep = 0
-            self.last_event = 0
-            self.state = None
-
-encoders = dict()
-
-f = open(sys.argv[1], 'r')
-while 1:
-      l = f.readline()
-      if l == '':
-        break
-
-      s = l.split()
-      if len(s) == 0:
-            continue
-
-      t = s[0].split(':')
-      if len(t) != 2:
-            continue
-
-      secs = float(t[0]) + float(t[1]) / 1e6
-      if s[1] == 'encoder' and s[2] == 'thread' and s[4] == 'finishes':
-            tid = s[3]
-            if not tid in encoders:
-                  encoders[tid] = Encoder()
-
-            assert(encoders[tid].state == None or encoders[tid].state == 'awake')
-            if encoders[tid].state == 'awake':
-                  encoders[tid].awake += (secs - encoders[tid].last_event)
-
-            encoders[tid].state = 'asleep'
-            encoders[tid].last_event = secs
-
-      elif s[1] == 'encoder' and s[2] == 'thread' and s[4] == 'begins':
-            tid = s[3]
-            if not tid in encoders:
-                  encoders[tid] = Encoder()
-
-            if encoders[tid].state is not None:
-                  encoders[tid].asleep += (secs - encoders[tid].last_event)
-
-            encoders[tid].state = 'awake'
-            encoders[tid].last_event = secs
-
-for k, v in encoders.iteritems():
-      print '%s: awake %f asleep %f' % (k, v.awake, v.asleep)
diff --git a/optimise/plotlog b/optimise/plotlog
deleted file mode 100755 (executable)
index 55b6fb8..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/python
-
-from pylab import *
-import sys
-
-class Point:
-    def __init__(self, t, a):
-        self.time = t
-        self.awake = a
-
-decoder = []
-writer = []
-encoder = dict()
-
-f = open(sys.argv[1], 'r')
-for l in f.readlines():
-    l = l.strip()
-    s = l.split()
-    if len(s) == 0:
-        continue
-
-    t = s[0].split(':')
-    if len(t) != 2:
-        continue
-
-    secs = float(t[0]) + float(t[1]) / 1e6
-    if s[1] == 'decoder' and s[2] == 'sleeps':
-        decoder.append(Point(secs, False))
-    elif s[1] == 'decoder' and s[2] == 'wakes':
-        decoder.append(Point(secs, True))
-    elif s[1] == 'encoder' and s[2] == 'thread' and s[4] == 'finishes':
-        if s[3] not in encoder:
-            print 'new encoder %s' % s[3]
-            encoder[s[3]] = []
-        encoder[str(s[3])].append(Point(secs, False))
-    elif s[1] == 'encoder' and s[2] == 'thread' and s[4] == 'begins':
-        if s[3] not in encoder:
-            print 'new encoder %s' % s[3]
-            encoder[s[3]] = []
-        encoder[s[3]].append(Point(secs, True))
-    elif s[1] == 'writer' and s[2] == 'sleeps':
-        writer.append(Point(secs, False))
-    elif s[1] == 'writer' and s[2] == 'wakes':
-        writer.append(Point(secs, True))
-
-def do_a_plot(points, tit, pos):
-    x = []
-    y = []
-    awake = False
-    for p in points:
-        if p.awake != awake:
-            x.append(p.time)
-            y.append(int(awake) + pos)
-            x.append(p.time)
-            y.append(int(p.awake) + pos)
-            awake = p.awake
-
-    plot(x, y)
-#    fill_between(x, y, 0, color='0.8')
-    title(tit)
-
-figure()
-
-N = len(encoder) + 2
-
-do_a_plot(decoder, 'dec', 0)
-do_a_plot(writer, 'wri', 1)
-
-encoder_list = []
-for k, v in encoder.iteritems():
-    encoder_list.append(v)
-
-print len(encoder_list)
-
-y = 2
-for e in encoder_list:
-    do_a_plot(e, 'enc', y)
-    y += 1
-
-show()
diff --git a/platform/linux/control-12.04-32 b/platform/linux/control-12.04-32
new file mode 100644 (file)
index 0000000..1d1e75b
--- /dev/null
@@ -0,0 +1,24 @@
+Source: dcpomatic
+Section: video
+Priority: extra
+Maintainer: Carl Hetherington <carl@dcpomatic.com>
+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://dcpomatic.com/
+
+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), libxmlsec1 (>= 1.2.14-1.2build1), libboost-datetime1.46.1 (>= 1.46.1)
+Description: Generator of Digital Cinema Packages (DCPs)
+  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: dcpomatic-dbg
+Architecture: i386
+Section: debug
+Priority: extra
+Depends: ${dcpomatic:Depends}, ${misc:Depends}
+Description: debugging symbols for dcpomatic
+  This package contains the debugging symbols for dcpomatic.
+
diff --git a/platform/linux/control-12.04-64 b/platform/linux/control-12.04-64
new file mode 100644 (file)
index 0000000..33bd398
--- /dev/null
@@ -0,0 +1,24 @@
+Source: dcpomatic
+Section: video
+Priority: extra
+Maintainer: Carl Hetherington <carl@dcpomatic.com>
+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://dcpomatic.com/
+
+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), libxmlsec1 (>= 1.2.14-1.2build1), libboost-datetime1.46.1 (>= 1.46.1)
+Description: Generator of Digital Cinema Packages (DCPs)
+  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: dcpomatic-dbg
+Architecture: amd64
+Section: debug
+Priority: extra
+Depends: ${dcpomatic:Depends}, ${misc:Depends}
+Description: debugging symbols for dcpomatic
+  This package contains the debugging symbols for dcpomatic.
+
diff --git a/platform/linux/control-12.10-32 b/platform/linux/control-12.10-32
new file mode 100644 (file)
index 0000000..784a8f1
--- /dev/null
@@ -0,0 +1,23 @@
+Source: dcpomatic
+Section: video
+Priority: extra
+Maintainer: Carl Hetherington <carl@dcpomatic.com>
+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://dcpomatic.com/
+
+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), libxmlsec1 (>= 1.2.18-2), libboost-datetime1.49.0 (>= 1.49.0)
+Description: Generator of Digital Cinema Packages (DCPs)
+  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: dcpomatic-dbg
+Architecture: i386
+Section: debug
+Priority: extra
+Depends: ${dcpomatic:Depends}, ${misc:Depends}
+Description: debugging symbols for dcpomatic
+  This package contains the debugging symbols for dcpomatic.
diff --git a/platform/linux/control-12.10-64 b/platform/linux/control-12.10-64
new file mode 100644 (file)
index 0000000..6d69d51
--- /dev/null
@@ -0,0 +1,24 @@
+Source: dcpomatic
+Section: video
+Priority: extra
+Maintainer: Carl Hetherington <carl@dcpomatic.com>
+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://dcpomatic.com/
+
+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), libxmlsec1 (>= 1.2.18-2), libboost-datetime1.49.0 (>= 1.49.0)
+Description: Generator of Digital Cinema Packages (DCPs)
+  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: dcpomatic-dbg
+Architecture: amd64
+Section: debug
+Priority: extra
+Depends: ${dcpomatic:Depends}, ${misc:Depends}
+Description: debugging symbols for dcpomatic
+  This package contains the debugging symbols for dcpomatic.
+
diff --git a/platform/linux/control-13.04-32 b/platform/linux/control-13.04-32
new file mode 100644 (file)
index 0000000..784a8f1
--- /dev/null
@@ -0,0 +1,23 @@
+Source: dcpomatic
+Section: video
+Priority: extra
+Maintainer: Carl Hetherington <carl@dcpomatic.com>
+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://dcpomatic.com/
+
+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), libxmlsec1 (>= 1.2.18-2), libboost-datetime1.49.0 (>= 1.49.0)
+Description: Generator of Digital Cinema Packages (DCPs)
+  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: dcpomatic-dbg
+Architecture: i386
+Section: debug
+Priority: extra
+Depends: ${dcpomatic:Depends}, ${misc:Depends}
+Description: debugging symbols for dcpomatic
+  This package contains the debugging symbols for dcpomatic.
diff --git a/platform/linux/control-13.04-64 b/platform/linux/control-13.04-64
new file mode 100644 (file)
index 0000000..6d69d51
--- /dev/null
@@ -0,0 +1,24 @@
+Source: dcpomatic
+Section: video
+Priority: extra
+Maintainer: Carl Hetherington <carl@dcpomatic.com>
+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://dcpomatic.com/
+
+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), libxmlsec1 (>= 1.2.18-2), libboost-datetime1.49.0 (>= 1.49.0)
+Description: Generator of Digital Cinema Packages (DCPs)
+  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: dcpomatic-dbg
+Architecture: amd64
+Section: debug
+Priority: extra
+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/wscript b/platform/linux/wscript
new file mode 100644 (file)
index 0000000..53a6efe
--- /dev/null
@@ -0,0 +1,19 @@
+def build(bld):
+    d = { 'PREFIX' : '${PREFIX' }
+
+    obj = bld(features = 'subst')
+    obj.source = 'dcpomatic.desktop.in'
+    obj.target = 'dcpomatic.desktop'
+    obj.dict = d
+
+    obj = bld(features = 'subst')
+    obj.source = 'dcpomatic_batch.desktop.in'
+    obj.target = 'dcpomatic_batch.desktop'
+    obj.dict = d
+
+    obj = bld(features = 'subst')
+    obj.source = 'dcpomatic_server.desktop.in'
+    obj.target = 'dcpomatic_server.desktop'
+    obj.dict = d
+
+    bld.install_files('${PREFIX}/share/applications', ['dcpomatic.desktop', 'dcpomatic_batch.desktop', 'dcpomatic_server.desktop'])
diff --git a/platform/osx/Info.plist.in b/platform/osx/Info.plist.in
new file mode 100644 (file)
index 0000000..0f67741
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>English</string>
+       <key>CFBundleExecutable</key>
+       <string>dcpomatic</string>
+       <key>CFBundleGetInfoString</key>
+       <string>DCP generator</string>
+       <key>CFBundleIconFile</key>
+       <string>DCP-o-matic.icns</string>
+       <key>CFBundleIdentifier</key>
+       <string>net.carlh.dcpomatic</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundleName</key>
+       <string>DCP-o-matic</string>
+       <key>CFBundlePackageType</key>
+       <string>APPL</string>
+       <key>CFBundleShortVersions</key>
+       <string>@VERSION@</string>
+       <key>CFBundleSignature</key>
+       <string>DOMC</string>
+       <key>CFBundleVersion</key>
+       <string>@VERSION@</string>
+       <key>LSUIElement</key>
+       <string>0</string>
+       <key>NSMainNibFile</key>
+       <string>MainMenu</string>
+       <key>NSPrincipalClass</key>
+       <string>NSApplication</string>
+       @ENV@
+</dict>
+</plist>
diff --git a/platform/osx/make_dmg.sh b/platform/osx/make_dmg.sh
new file mode 100644 (file)
index 0000000..19f4281
--- /dev/null
@@ -0,0 +1,141 @@
+#!/bin/bash
+
+set -e
+
+version=`cat wscript | egrep ^VERSION | awk '{print $3}' | sed -e "s/'//g"`
+
+# DMG size in megabytes
+DMG_SIZE=256
+WORK=build/platform/osx
+ENV=/Users/carl/Environments/osx
+ROOT=/Users/carl/cdist
+
+appdir="DCP-o-matic.app"
+approot=$appdir/Contents
+libs=$approot/lib
+macos=$approot/MacOS
+resources=$approot/Resources
+
+rm -rf $WORK/$appdir
+mkdir -p $WORK/$macos
+mkdir -p $WORK/$libs
+mkdir -p $WORK/$resources
+
+function universal_copy {
+    echo $2
+    for f in $1/32/$2; do
+        if [ -h $f ]; then
+           ln -s $(readlink $f) $3/`basename $f`
+        else
+          g=`echo $f | sed -e "s/\/32\//\/64\//g"`
+         mkdir -p $3
+          lipo -create $f $g -output $3/`basename $f`
+        fi
+    done
+}
+
+universal_copy $ROOT src/dvdomatic/build/src/tools/dcpomatic $WORK/$macos
+universal_copy $ROOT src/dvdomatic/build/src/tools/dcpomatic_cli $WORK/$macos
+universal_copy $ROOT src/dvdomatic/build/src/tools/dcpomatic_server_cli $WORK/$macos
+universal_copy $ROOT src/dvdomatic/build/src/lib/libdcpomatic.dylib $WORK/$libs
+universal_copy $ROOT src/dvdomatic/build/src/wx/libdcpomatic-wx.dylib $WORK/$libs
+universal_copy $ROOT lib/libcxml.dylib $WORK/$libs
+universal_copy $ROOT lib/libdcp.dylib $WORK/$libs
+universal_copy $ROOT lib/libasdcp-libdcp.dylib $WORK/$libs
+universal_copy $ROOT lib/libkumu-libdcp.dylib $WORK/$libs
+universal_copy $ROOT lib/libopenjpeg*.dylib $WORK/$libs
+universal_copy $ROOT lib/libavdevice*.dylib $WORK/$libs
+universal_copy $ROOT lib/libavformat*.dylib $WORK/$libs
+universal_copy $ROOT lib/libavfilter*.dylib $WORK/$libs
+universal_copy $ROOT lib/libavutil*.dylib $WORK/$libs
+universal_copy $ROOT lib/libavcodec*.dylib $WORK/$libs
+universal_copy $ROOT lib/libswscale*.dylib $WORK/$libs
+universal_copy $ROOT lib/libpostproc*.dylib $WORK/$libs
+universal_copy $ROOT lib/libswresample*.dylib $WORK/$libs
+universal_copy $ROOT bin/ffprobe $WORK/$macos
+universal_copy $ENV lib/libboost_system.dylib $WORK/$libs
+universal_copy $ENV lib/libboost_filesystem.dylib $WORK/$libs
+universal_copy $ENV lib/libboost_thread.dylib $WORK/$libs
+universal_copy $ENV lib/libboost_date_time.dylib $WORK/$libs
+universal_copy $ENV lib/libxml++-2.6*.dylib $WORK/$libs
+universal_copy $ENV lib/libxml2*.dylib $WORK/$libs
+universal_copy $ENV lib/libglibmm-2.4*.dylib $WORK/$libs
+universal_copy $ENV lib/libgobject*.dylib $WORK/$libs
+universal_copy $ENV lib/libgthread*.dylib $WORK/$libs
+universal_copy $ENV lib/libgmodule*.dylib $WORK/$libs
+universal_copy $ENV lib/libsigc*.dylib $WORK/$libs
+universal_copy $ENV lib/libglib-2*.dylib $WORK/$libs
+universal_copy $ENV lib/libintl*.dylib $WORK/$libs
+universal_copy $ENV lib/libsndfile*.dylib $WORK/$libs
+universal_copy $ENV lib/libMagick++*.dylib $WORK/$libs
+universal_copy $ENV lib/libMagickCore*.dylib $WORK/$libs
+universal_copy $ENV lib/libMagickWand*.dylib $WORK/$libs
+universal_copy $ENV lib/libssh*.dylib $WORK/$libs
+universal_copy $ENV lib/libwx*.dylib $WORK/$libs
+universal_copy $ENV lib/libfontconfig*.dylib $WORK/$libs
+universal_copy $ENV lib/libfreetype*.dylib $WORK/$libs
+universal_copy $ENV lib/libexpat*.dylib $WORK/$libs
+universal_copy $ENV lib/libltdl*.dylib $WORK/$libs
+universal_copy $ENV lib/libxmlsec1*.dylib $WORK/$libs
+
+for obj in $WORK/$macos/dcpomatic $WORK/$macos/ffprobe $WORK/$libs/*.dylib; do
+  deps=`otool -L $obj | awk '{print $1}' | egrep "(/Users/carl|libboost|libssh|libltdl)"`
+  changes=""
+  for dep in $deps; do
+    base=`basename $dep`
+    changes="$changes -change $dep @executable_path/../lib/$base"
+  done
+  if test "x$changes" != "x"; then
+    install_name_tool $changes $obj
+  fi
+done
+
+cp build/platform/osx/Info.plist $WORK/$approot
+cp icons/dcpomatic.icns $WORK/$resources/DCP-o-matic.icns
+
+tmp_dmg=$WORK/dcpomatic_tmp.dmg
+dmg="$WORK/DCP-o-matic $version.dmg"
+vol_name=DCP-o-matic-$version
+
+mkdir -p $WORK/$vol_name
+cp -r $WORK/$appdir $WORK/$vol_name
+ln -s /Applications $WORK/$vol_name/Applications
+
+rm -f $tmp_dmg "$dmg"
+hdiutil create -srcfolder $WORK/$vol_name -volname $vol_name -fs HFS+ -fsargs "-c c=64,a=16,e=16" -format UDRW -size $DMG_SIZE $tmp_dmg
+attach=$(hdiutil attach -readwrite -noverify -noautoopen $tmp_dmg)
+device=`echo $attach | egrep '^/dev/' | sed 1q | awk '{print $5}'`
+sleep 5
+
+echo '
+  tell application "Finder"
+    tell disk "'$vol_name'"
+           open
+           set current view of container window to icon view
+           set toolbar visible of container window to false
+           set statusbar visible of container window to false
+           set the bounds of container window to {400, 200, 790, 410}
+           set theViewOptions to the icon view options of container window
+           set arrangement of theViewOptions to not arranged
+           set icon size of theViewOptions to 64
+           set position of item "DCP-o-matic.app" of container window to {90, 80}
+           set position of item "Applications" of container window to {310, 80}
+           close
+           open
+           update without registering applications
+           delay 5
+     end tell
+   end tell
+' | osascript
+
+chmod -Rf go-w /Volumes/"$vol_name"/$appdir
+sync
+
+umount -f $device
+hdiutil eject $device
+hdiutil convert -format UDZO $tmp_dmg -imagekey zlib-level=9 -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/osx/waf b/platform/osx/waf
new file mode 100755 (executable)
index 0000000..dda6785
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+set -e
+
+ENV=/Users/carl/Environments/osx/64
+DEPS=/Users/carl/cdist/64
+
+export PKG_CONFIG_PATH=$DEPS/lib/pkgconfig:$ENV/lib/pkgconfig:/usr/lib/pkgconfig
+export LINKFLAGS="-L$ENV/lib"
+export CXXFLAGS="-I$ENV/include"
+export PATH=$PATH:$ENV/bin
+./waf $*
+
diff --git a/platform/osx/wscript b/platform/osx/wscript
new file mode 100644 (file)
index 0000000..d79c95b
--- /dev/null
@@ -0,0 +1,2 @@
+def build(bld):
+    bld.new_task_gen(features='subst', source='Info.plist.in', target='Info.plist', version=bld.env.VERSION)
diff --git a/platform/windows/.gtkrc-2.0 b/platform/windows/.gtkrc-2.0
new file mode 100755 (executable)
index 0000000..0ea1d69
--- /dev/null
@@ -0,0 +1,6 @@
+gtk-theme-name = "MS-Windows"
+style "user-font"
+{
+       font_name="Tahoma 8"
+}
+widget_class "*" style "user-font"
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..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/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/installer.nsi.32.in b/platform/windows/installer.nsi.32.in
new file mode 100644 (file)
index 0000000..536038f
--- /dev/null
@@ -0,0 +1,154 @@
+!include "MUI2.nsh"
+Name "DCP-o-matic"
+
+RequestExecutionLevel admin
+
+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\DCP-o-matic"
+
+!insertmacro MUI_PAGE_WELCOME
+!insertmacro MUI_PAGE_LICENSE "../../../COPYING"
+!insertmacro MUI_PAGE_DIRECTORY
+!insertmacro MUI_PAGE_INSTFILES
+!insertmacro MUI_PAGE_FINISH
+
+!insertmacro MUI_UNPAGE_WELCOME
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+!insertmacro MUI_UNPAGE_FINISH
+!insertmacro MUI_LANGUAGE "English"
+
+Section "install" "Installation info"
+SetOutPath "$INSTDIR\bin"
+
+File "%static_deps%/bin/libintl-8.dll"
+File "%static_deps%/bin/libboost_chrono-mt.dll"
+File "%static_deps%/bin/libboost_filesystem-mt.dll"
+File "%static_deps%/bin/libboost_system-mt.dll"
+File "%static_deps%/bin/libboost_thread_win32-mt.dll"
+File "%static_deps%/bin/libboost_date_time-mt.dll"
+File "%static_deps%/bin/libeay32.dll"
+File "%static_deps%/bin/libgcc_s_sjlj-1.dll"
+File "%static_deps%/bin/libgio-2.0-0.dll"
+File "%static_deps%/bin/libglib-2.0-0.dll"
+File "%static_deps%/bin/libgobject-2.0-0.dll"
+File "%static_deps%/bin/libiconv-2.dll"
+File "%static_deps%/bin/libjpeg-8.dll"
+File "%static_deps%/bin/libMagick++-6.Q16-2.dll"
+File "%static_deps%/bin/libMagickCore-6.Q16-1.dll"
+File "%static_deps%/bin/libMagickWand-6.Q16-1.dll"
+File "%static_deps%/bin/libpng15-15.dll"
+File "%static_deps%/bin/libsigc-2.0-0.dll"
+File "%static_deps%/bin/libsndfile-1.dll"
+File "%static_deps%/bin/libssh.dll"
+File "%static_deps%/bin/libstdc++-6.dll"
+File "%static_deps%/bin/zlib1.dll"
+File "%static_deps%/bin/libjpeg-8.dll"
+File "%static_deps%/bin/wxbase294u_gcc_custom.dll"
+File "%static_deps%/bin/wxmsw294u_core_gcc_custom.dll"
+File "%static_deps%/bin/wxmsw294u_adv_gcc_custom.dll"
+File "%static_deps%/bin/libcairo-2.dll"
+File "%static_deps%/bin/libfreetype-6.dll"
+File "%static_deps%/bin/libgthread-2.0-0.dll"
+File "%static_deps%/bin/libpango-1.0-0.dll"
+File "%static_deps%/bin/libgmodule-2.0-0.dll"
+File "%static_deps%/bin/libpangocairo-1.0-0.dll"
+File "%static_deps%/bin/libpangowin32-1.0-0.dll"
+File "%static_deps%/bin/libtiff-5.dll"
+File "%static_deps%/bin/libglibmm-2.4-1.dll"
+File "%static_deps%/bin/libxml++-2.6-2.dll"
+File "%static_deps%/bin/libxml2-2.dll"
+File "%static_deps%/bin/libpixman-1-0.dll"
+File "%static_deps%/bin/libfontconfig-1.dll"
+File "%static_deps%/bin/libexpat-1.dll"
+File "%static_deps%/bin/libbz2.dll"
+File "%static_deps%/bin/libxmlsec1.dll"
+File "%static_deps%/bin/libxmlsec1-openssl.dll"
+File "%static_deps%/bin/libexslt-0.dll"
+File "%static_deps%/bin/libxslt-1.dll"
+File "%static_deps%/bin/libffi-6.dll"
+
+File "%cdist_deps%/bin/asdcp-libdcp.dll"
+File "%cdist_deps%/bin/kumu-libdcp.dll"
+File "%cdist_deps%/bin/avcodec-55.dll"
+File "%cdist_deps%/bin/avfilter-3.dll"
+File "%cdist_deps%/bin/avformat-55.dll"
+File "%cdist_deps%/bin/avutil-52.dll"
+File "%cdist_deps%/bin/avdevice-55.dll"
+File "%cdist_deps%/bin/dcp.dll"
+File "%cdist_deps%/bin/libopenjpeg-1.dll"
+File "%cdist_deps%/bin/postproc-52.dll"
+File "%cdist_deps%/bin/swresample-0.dll"
+File "%cdist_deps%/bin/swscale-2.dll"
+File "%cdist_deps%/bin/cxml.dll"
+File "%cdist_deps%/bin/ffprobe.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
+# sometimes in the $PROFILE.  Meh.
+File "%static_deps%/etc/ImageMagick-6/delegates.xml"
+SetOutPath "$PROFILE\.magick"
+File "%static_deps%/etc/ImageMagick-6/delegates.xml"
+
+SetOutPath "$INSTDIR\locale\fr\LC_MESSAGES"
+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/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/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/libdcpomatic.mo"
+File "%binaries%/src/wx/mo/sv_SE/libdcpomatic-wx.mo"
+File "%binaries%/src/tools/mo/sv_SE/dcpomatic.mo"
+
+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\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\DCP-o-matic" "DisplayName" "DCP-o-matic (remove only)"
+WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
+WriteUninstaller "$INSTDIR\Uninstall.exe"
+SectionEnd
+Section "Uninstall"
+RMDir /r "$INSTDIR\*.*"    
+RMDir "$INSTDIR"
+Delete "$DESKTOP\DCP-o-matic.lnk"
+Delete "$DESKTOP\DCP-o-matic batch converter.lnk"
+Delete "$DESKTOP\DCP-o-matic encode server.lnk"
+Delete "$SMPROGRAMS\DCP-o-matic\*.*"
+RmDir  "$SMPROGRAMS\DCP-o-matic"
+DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DCP-o-matic"
+DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic"
+SectionEnd
diff --git a/platform/windows/installer.nsi.64.in b/platform/windows/installer.nsi.64.in
new file mode 100644 (file)
index 0000000..1bedd3f
--- /dev/null
@@ -0,0 +1,164 @@
+!include "MUI2.nsh"
+!include "x64.nsh"
+
+Name "DCP-o-matic"
+
+RequestExecutionLevel admin
+
+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\DCP-o-matic"
+
+!insertmacro MUI_PAGE_WELCOME
+!insertmacro MUI_PAGE_LICENSE "../../../COPYING"
+!insertmacro MUI_PAGE_DIRECTORY
+!insertmacro MUI_PAGE_INSTFILES
+!insertmacro MUI_PAGE_FINISH
+
+!insertmacro MUI_UNPAGE_WELCOME
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+!insertmacro MUI_UNPAGE_FINISH
+!insertmacro MUI_LANGUAGE "English"
+
+Section "install" "Installation info"
+
+${If} ${RunningX64}
+   DetailPrint "Installer running on 64-bit host"
+   ; disable registry redirection (enable access to 64-bit portion of registry)
+   SetRegView 64
+   ; change install dir
+   StrCpy $INSTDIR "$PROGRAMFILES64\DCP-o-matic"
+${EndIf}
+
+SetOutPath "$INSTDIR\bin"
+
+File "%static_deps%/bin/libintl-8.dll"
+File "%static_deps%/bin/libboost_chrono-mt.dll"
+File "%static_deps%/bin/libboost_filesystem-mt.dll"
+File "%static_deps%/bin/libboost_system-mt.dll"
+File "%static_deps%/bin/libboost_thread_win32-mt.dll"
+File "%static_deps%/bin/libboost_date_time-mt.dll"
+File "%static_deps%/bin/libeay32.dll"
+File "%static_deps%/bin/libgcc_s_sjlj-1.dll"
+File "%static_deps%/bin/libgio-2.0-0.dll"
+File "%static_deps%/bin/libglib-2.0-0.dll"
+File "%static_deps%/bin/libgobject-2.0-0.dll"
+File "%static_deps%/bin/libiconv-2.dll"
+File "%static_deps%/bin/libjpeg-8.dll"
+File "%static_deps%/bin/libMagick++-6.Q16-2.dll"
+File "%static_deps%/bin/libMagickCore-6.Q16-1.dll"
+File "%static_deps%/bin/libMagickWand-6.Q16-1.dll"
+File "%static_deps%/bin/libpng15-15.dll"
+File "%static_deps%/bin/libsigc-2.0-0.dll"
+File "%static_deps%/bin/libsndfile-1.dll"
+File "%static_deps%/bin/libssh.dll"
+File "%static_deps%/bin/libstdc++-6.dll"
+File "%static_deps%/bin/zlib1.dll"
+File "%static_deps%/bin/libjpeg-8.dll"
+File "%static_deps%/bin/wxbase294u_gcc_custom.dll"
+File "%static_deps%/bin/wxmsw294u_core_gcc_custom.dll"
+File "%static_deps%/bin/wxmsw294u_adv_gcc_custom.dll"
+File "%static_deps%/bin/libcairo-2.dll"
+File "%static_deps%/bin/libfreetype-6.dll"
+File "%static_deps%/bin/libgthread-2.0-0.dll"
+File "%static_deps%/bin/libpango-1.0-0.dll"
+File "%static_deps%/bin/libgmodule-2.0-0.dll"
+File "%static_deps%/bin/libpangocairo-1.0-0.dll"
+File "%static_deps%/bin/libpangowin32-1.0-0.dll"
+File "%static_deps%/bin/libtiff-5.dll"
+File "%static_deps%/bin/libglibmm-2.4-1.dll"
+File "%static_deps%/bin/libxml++-2.6-2.dll"
+File "%static_deps%/bin/libxml2-2.dll"
+File "%static_deps%/bin/libpixman-1-0.dll"
+File "%static_deps%/bin/libfontconfig-1.dll"
+File "%static_deps%/bin/libexpat-1.dll"
+File "%static_deps%/bin/libbz2.dll"
+File "%static_deps%/bin/libxmlsec1.dll"
+File "%static_deps%/bin/libxmlsec1-openssl.dll"
+File "%static_deps%/bin/libexslt-0.dll"
+File "%static_deps%/bin/libxslt-1.dll"
+File "%static_deps%/bin/libffi-6.dll"
+
+File "%cdist_deps%/bin/asdcp-libdcp.dll"
+File "%cdist_deps%/bin/kumu-libdcp.dll"
+File "%cdist_deps%/bin/avcodec-55.dll"
+File "%cdist_deps%/bin/avfilter-3.dll"
+File "%cdist_deps%/bin/avformat-55.dll"
+File "%cdist_deps%/bin/avutil-52.dll"
+File "%cdist_deps%/bin/avdevice-55.dll"
+File "%cdist_deps%/bin/dcp.dll"
+File "%cdist_deps%/bin/libopenjpeg-1.dll"
+File "%cdist_deps%/bin/postproc-52.dll"
+File "%cdist_deps%/bin/swresample-0.dll"
+File "%cdist_deps%/bin/swscale-2.dll"
+File "%cdist_deps%/bin/cxml.dll"
+File "%cdist_deps%/bin/ffprobe.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
+# sometimes in the $PROFILE.  Meh.
+File "%static_deps%/etc/ImageMagick-6/delegates.xml"
+SetOutPath "$PROFILE\.magick"
+File "%static_deps%/etc/ImageMagick-6/delegates.xml"
+
+SetOutPath "$INSTDIR\locale\fr\LC_MESSAGES"
+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/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/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/libdcpomatic.mo"
+File "%binaries%/src/wx/mo/sv_SE/libdcpomatic-wx.mo"
+File "%binaries%/src/tools/mo/sv_SE/dcpomatic.mo"
+
+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\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\DCP-o-matic" "DisplayName" "DCP-o-matic (remove only)"
+WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
+WriteUninstaller "$INSTDIR\Uninstall.exe"
+SectionEnd
+Section "Uninstall"
+RMDir /r "$INSTDIR\*.*"    
+RMDir "$INSTDIR"
+Delete "$DESKTOP\DCP-o-matic.lnk"
+Delete "$DESKTOP\DCP-o-matic batch converter.lnk"
+Delete "$DESKTOP\DCP-o-matic encode server.lnk"
+Delete "$SMPROGRAMS\DCP-o-matic\*.*"
+RmDir  "$SMPROGRAMS\DCP-o-matic"
+DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DCP-o-matic"
+DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DCP-o-matic"
+SectionEnd
diff --git a/platform/windows/wscript b/platform/windows/wscript
new file mode 100644 (file)
index 0000000..585cebe
--- /dev/null
@@ -0,0 +1,4 @@
+def build(bld):
+    bld.new_task_gen(features = 'subst', source = 'installer.nsi.32.in', target = 'installer.32.nsi', version = bld.env.VERSION)
+    bld.new_task_gen(features = 'subst', source = 'installer.nsi.64.in', target = 'installer.64.nsi', version = bld.env.VERSION)
+    
diff --git a/pre b/pre
deleted file mode 100755 (executable)
index 8a7025a..0000000
--- a/pre
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/python
-
-import os
-import shutil
-
-def command(c):
-    os.system(c)
-    print c
-
-def current_version():
-    f = open('wscript', 'rw')
-    while 1:
-        l = f.readline()
-        if l == '':
-            break
-
-        s = l.split()
-        if len(s) == 3 and s[0] == "VERSION":
-            return s[2][1:-1]
-
-    assert(false)
-
-v = current_version()
-
-command("./builds/windows-32")
-shutil.copy(os.path.join('build', 'windows', 'DVD-o-matic %s 32-bit Installer.exe' % v), '.')
-command("./builds/windows-64")
-shutil.copy(os.path.join('build', 'windows', 'DVD-o-matic %s 64-bit Installer.exe' % v), '.')
-command("./waf dist")
diff --git a/release b/release
deleted file mode 100755 (executable)
index e9a29ff..0000000
--- a/release
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/python
-
-import os
-import sys
-import datetime
-import shutil
-import version
-import argparse
-
-parser = argparse.ArgumentParser(description = "Build and tag a release.")
-parser.add_argument('-f', '--full', dest='full', action='store_const', const=True, help="full release", default=False)
-parser.add_argument('-b', '--beta', dest='beta', action='store_const', const=True, help="beta release", default=False)
-parser.add_argument('-d', '--debug', dest='debug', action='store_const', const=True, help="show commands but don't do anything", default=False)
-args = parser.parse_args()
-
-if not args.full and not args.beta:
-    print "%s: must specify --full or --beta" % sys.argv[0]
-    sys.exit(1)
-
-def command(c):
-    if not args.debug:
-        os.system(c)
-    print c
-
-def check_diff_with_user():
-    print "Planning to commit... ok? [y/n]"
-    command("git diff")
-    if (raw_input() != "y"):
-        command("git reset --hard")
-        print 'Aborted'
-        sys.exit(1)
-
-if not args.debug and os.popen('git status -s').read() != '':
-    print '%s: uncommitted changes exist.' % sys.argv[0]
-    sys.exit(1)
-
-m = version.Version.to_release
-if args.beta:
-    m = version.Version.bump_beta
-
-new_version = version.rewrite_wscript(m)
-version.append_to_changelog(new_version)
-command("dch -b -v %s-1 \"New upstream release.\"" % new_version)
-
-command("./waf clean")
-command("./waf dist")
-
-check_diff_with_user()
-
-command("git commit -a -m \"Bump version\"")
-command("git tag -m \"v%s\" v%s" % (new_version, new_version))
-
-if args.full:
-    version.rewrite_wscript(version.Version.bump_and_to_pre)
-    check_diff_with_user()
-    command("git commit -a -m \"Bump version\"")
diff --git a/run/alignomatic b/run/alignomatic
deleted file mode 100755 (executable)
index 9cc8c22..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-export LD_LIBRARY_PATH=build/src/lib:build/src/gtk:$LD_LIBRARY_PATH
-if [ "$1" == "--debug" ]; then
-    gdb --args build/src/tools/alignomatic $2
-elif [ "$1" == "--valgrind" ]; then
-    valgrind --tool="memcheck" build/src/tools/alignomatic $2
-else
-    build/src/tools/alignomatic "$1"
-fi
diff --git a/run/dcpomatic b/run/dcpomatic
new file mode 100755 (executable)
index 0000000..cf3de16
--- /dev/null
@@ -0,0 +1,18 @@
+#!/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" == "--callgrind" ]; then
+    shift
+    valgrind --tool="callgrind" 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/dcpomatic_server_cli b/run/dcpomatic_server_cli
new file mode 100755 (executable)
index 0000000..bc5790a
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
+if [ "$1" == "--debug" ]; then
+    gdb --args build/src/tools/dcpomatic_server_cli
+elif [ "$1" == "--valgrind" ]; then
+    valgrind --tool="memcheck" build/src/tools/dcpomatic_server_cli
+else
+    build/src/tools/dcpomatic_server_cli
+fi
diff --git a/run/dcpomatic_server_gui b/run/dcpomatic_server_gui
new file mode 100755 (executable)
index 0000000..b7b122e
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
+if [ "$1" == "--debug" ]; then
+    gdb --args build/src/tools/dcpomatic_server_gui
+elif [ "$1" == "--valgrind" ]; then
+    valgrind --tool="memcheck" build/src/tools/dcpomatic_server_gui
+else
+    build/src/tools/dcpomatic_server_gui
+fi
diff --git a/run/dvdomatic b/run/dvdomatic
deleted file mode 100755 (executable)
index ff38970..0000000
+++ /dev/null
@@ -1,12 +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 $*
-else
-    build/src/tools/dvdomatic "$*"
-fi
diff --git a/run/fixlengths b/run/fixlengths
deleted file mode 100755 (executable)
index 1a6cc0a..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH:build/src
-if [ "$1" == "--debug" ]; then
-    gdb --args build/src/tools/fixlengths "$@"
-elif [ "$1" == "--valgrind" ]; then
-    valgrind --tool="memcheck" --leak-check=full --show-reachable=yes build/src/tools/fixlengths "$@"
-else
-    build/src/tools/fixlengths "$@"
-fi
diff --git a/run/makedcp b/run/makedcp
deleted file mode 100755 (executable)
index f71345b..0000000
+++ /dev/null
@@ -1,12 +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" == "--valgrind" ]; then
-    shift
-    valgrind --tool="memcheck" --leak-check=full --show-reachable=yes build/src/tools/makedcp "$@"
-else
-    build/src/tools/makedcp "$@"
-fi
diff --git a/run/playomatic b/run/playomatic
deleted file mode 100755 (executable)
index 9fe191a..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-export LD_LIBRARY_PATH=build/src/lib:build/src/gtk:$LD_LIBRARY_PATH
-if [ "$1" == "--debug" ]; then
-    gdb --args build/src/tools/playomatic $2
-elif [ "$1" == "--valgrind" ]; then
-    valgrind --tool="memcheck" build/src/tools/playomatic $2
-else
-    build/src/tools/playomatic "$1"
-fi
diff --git a/run/server_test b/run/server_test
new file mode 100755 (executable)
index 0000000..20e9f48
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
+if [ "$1" == "--debug" ]; then
+    shift
+    gdb --args build/src/tools/server_test $*
+elif [ "$1" == "--valgrind" ]; then
+    shift
+    valgrind --tool="memcheck" build/src/tools/server_test $*
+else
+    build/src/tools/server_test $*
+fi
diff --git a/run/servomatic_cli b/run/servomatic_cli
deleted file mode 100755 (executable)
index 3dd67d2..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
-if [ "$1" == "--debug" ]; then
-    gdb --args build/src/tools/servomatic_cli
-elif [ "$1" == "--valgrind" ]; then
-    valgrind --tool="memcheck" build/src/tools/servomatic_cli
-else
-    build/src/tools/servomatic_cli
-fi
diff --git a/run/servomatic_gui b/run/servomatic_gui
deleted file mode 100755 (executable)
index 4f1c617..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
-if [ "$1" == "--debug" ]; then
-    gdb --args build/src/tools/servomatic_gui
-elif [ "$1" == "--valgrind" ]; then
-    valgrind --tool="memcheck" build/src/tools/servomatic_gui
-else
-    build/src/tools/servomatic_gui
-fi
diff --git a/run/servomatictest b/run/servomatictest
deleted file mode 100755 (executable)
index 58cea88..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
-if [ "$1" == "--debug" ]; then
-    shift
-    gdb --args build/src/tools/servomatictest $*
-elif [ "$1" == "--valgrind" ]; then
-    shift
-    valgrind --tool="memcheck" build/src/tools/servomatictest $*
-else
-    build/src/tools/servomatictest $*
-fi
index e1686a55cac84eff6af6f3f06ed2b321ace805d7..e6cdf4ba84656f4b739b0c54bd3e4d5ff16a6652 100755 (executable)
--- a/run/tests
+++ b/run/tests
@@ -2,10 +2,10 @@
 
 export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
 if [ "$1" == "--debug" ]; then
-    gdb --args build/test/unit-tests
+    gdb --args build/test/unit-tests --catch_system_errors=no
 elif [ "$1" == "--valgrind" ]; then
     valgrind --tool="memcheck" --leak-check=full build/test/unit-tests
 else
-    build/test/unit-tests
+    build/test/unit-tests --catch_system_errors=no
 fi
 
diff --git a/splitchapters b/splitchapters
deleted file mode 100755 (executable)
index 1e5cff0..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/python
-
-import os
-import sys
-
-if len(sys.argv) < 2:
-    print 'Syntax: %s <DVD-image>' % sys.argv[0]
-    sys.exit(1)
-
-lsdvd = os.popen('lsdvd -c "%s"' % sys.argv[1])
-lines = lsdvd.readlines()
-
-N = None
-
-for l in lines:
-    w = l.split()
-    if len(w) > 5 and w[4] == 'Chapters:':
-        N = int(w[5][:-1])
-
-if N == None:
-    print 'Could not get chapter count.'
-    sys.exit(1)
-
-for i in range(1, N + 1):
-    os.mkdir('%d' % i)
-    c = 'mplayer dvd:// -chapter %d-%d -dvd-device "%s" -dumpstream -dumpfile %d/%d.vob' % (i, i, sys.argv[1], i, i)
-    print c
-    os.system(c)
-
-
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc
deleted file mode 100644 (file)
index a6233c1..0000000
+++ /dev/null
@@ -1,66 +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"
-
-using std::string;
-using boost::shared_ptr;
-
-/** @param f Film to compare.
- *  @param o Options.
- */
-ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
-       : Job (f, req)
-       , _decode_opt (od)
-       , _encode_opt (oe)
-{
-       _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, _encode_opt)));
-               w.go ();
-               set_progress (1);
-               set_state (FINISHED_OK);
-
-       } catch (std::exception& e) {
-
-               set_state (FINISHED_ERROR);
-
-       }
-}
diff --git a/src/lib/ab_transcode_job.h b/src/lib/ab_transcode_job.h
deleted file mode 100644 (file)
index 86a2a81..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.
-
-*/
-
-/** @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"
-
-class Film;
-class DecodeOptions;
-class EncodeOptions;
-
-/** @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,
-               boost::shared_ptr<const DecodeOptions> od,
-               boost::shared_ptr<const EncodeOptions> oe,
-               boost::shared_ptr<Job> req
-               );
-
-       std::string name () const;
-       void run ();
-
-private:
-       boost::shared_ptr<const DecodeOptions> _decode_opt;
-       boost::shared_ptr<const EncodeOptions> _encode_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 53af43b..0000000
+++ /dev/null
@@ -1,119 +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"
-
-/** @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;
-
-/** @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, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e)
-       : _film_a (a)
-       , _film_b (b)
-       , _job (j)
-       , _encoder (e)
-{
-       _da = decoder_factory (_film_a, o, j);
-       _db = decoder_factory (_film_b, o, j);
-
-       if (_film_a->audio_stream()) {
-               shared_ptr<AudioStream> st = _film_a->audio_stream();
-               _matcher.reset (new Matcher (_film_a->log(), st->sample_rate(), _film_a->frames_per_second()));
-               _delay_line.reset (new DelayLine (_film_a->log(), st->channels(), _film_a->audio_delay() * st->sample_rate() / 1000));
-               _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain()));
-       }
-
-       /* 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 ());
-       _da.audio->set_audio_stream (_film_a->audio_stream ());
-
-       _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3));
-       _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3));
-
-       if (_matcher) {
-               _combiner->connect_video (_matcher);
-               _matcher->connect_video (_encoder);
-       } else {
-               _combiner->connect_video (_encoder);
-       }
-       
-       if (_matcher && _delay_line) {
-               _da.audio->connect_audio (_delay_line);
-               _delay_line->connect_audio (_matcher);
-               _matcher->connect_audio (_gain);
-               _gain->connect_audio (_encoder);
-       }
-}
-
-void
-ABTranscoder::go ()
-{
-       _encoder->process_begin ();
-       
-       while (1) {
-               bool const va = _da.video->pass ();
-               bool const vb = _db.video->pass ();
-               bool const a = _da.audio->pass ();
-
-               _da.video->set_progress ();
-
-               if (va && vb && a) {
-                       break;
-               }
-       }
-
-       if (_delay_line) {
-               _delay_line->process_end ();
-       }
-       if (_matcher) {
-               _matcher->process_end ();
-       }
-       if (_gain) {
-               _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 7bfcb39..0000000
+++ /dev/null
@@ -1,73 +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 DecodeOptions;
-class Image;
-class Log;
-class Subtitle;
-class Film;
-class Matcher;
-class DelayLine;
-class Gain;
-class Combiner;
-
-/** @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,
-               boost::shared_ptr<const 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<Image> _image;
-};
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc
new file mode 100644 (file)
index 0000000..8186f9d
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+    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_analysis.h"
+#include "analyse_audio_job.h"
+#include "compose.hpp"
+#include "film.h"
+#include "player.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::max;
+using std::min;
+using std::cout;
+using boost::shared_ptr;
+
+int const AnalyseAudioJob::_num_points = 1024;
+
+AnalyseAudioJob::AnalyseAudioJob (shared_ptr<const Film> f, shared_ptr<AudioContent> c)
+       : Job (f)
+       , _content (c)
+       , _done (0)
+       , _samples_per_point (1)
+{
+
+}
+
+string
+AnalyseAudioJob::name () const
+{
+       return _("Analyse audio");
+}
+
+void
+AnalyseAudioJob::run ()
+{
+       shared_ptr<AudioContent> content = _content.lock ();
+       if (!content) {
+               return;
+       }
+
+       shared_ptr<Playlist> playlist (new Playlist);
+       playlist->add (content);
+       shared_ptr<Player> player (new Player (_film, playlist));
+       player->disable_video ();
+       
+       player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1, _2));
+
+       _samples_per_point = max (int64_t (1), _film->time_to_audio_frames (_film->length()) / _num_points);
+
+       _current.resize (_film->audio_channels ());
+       _analysis.reset (new AudioAnalysis (_film->audio_channels ()));
+
+       _done = 0;
+       OutputAudioFrame const len = _film->time_to_audio_frames (_film->length ());
+       while (!player->pass ()) {
+               set_progress (double (_done) / len);
+       }
+
+       _analysis->write (content->audio_analysis_path ());
+       
+       set_progress (1);
+       set_state (FINISHED_OK);
+}
+
+void
+AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time)
+{
+       for (int i = 0; i < b->frames(); ++i) {
+               for (int j = 0; j < b->channels(); ++j) {
+                       float s = b->data(j)[i];
+                       if (fabsf (s) < 10e-7) {
+                               /* stringstream can't serialise and recover inf or -inf, so prevent such
+                                  values by replacing with this (140dB down) */
+                               s = 10e-7;
+                       }
+                       _current[j][AudioPoint::RMS] += pow (s, 2);
+                       _current[j][AudioPoint::PEAK] = max (_current[j][AudioPoint::PEAK], fabsf (s));
+
+                       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 ();
+                       }
+               }
+
+               ++_done;
+       }
+}
+
diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h
new file mode 100644 (file)
index 0000000..3d48819
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+    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 "job.h"
+#include "audio_analysis.h"
+#include "types.h"
+
+class AudioBuffers;
+class AudioContent;
+
+class AnalyseAudioJob : public Job
+{
+public:
+       AnalyseAudioJob (boost::shared_ptr<const Film>, boost::shared_ptr<AudioContent>);
+
+       std::string name () const;
+       void run ();
+
+private:
+       void audio (boost::shared_ptr<const AudioBuffers>, Time);
+
+       boost::weak_ptr<AudioContent> _content;
+       OutputAudioFrame _done;
+       int64_t _samples_per_point;
+       std::vector<AudioPoint> _current;
+
+       boost::shared_ptr<AudioAnalysis> _analysis;
+
+       static const int _num_points;
+};
+
diff --git a/src/lib/audio_analysis.cc b/src/lib/audio_analysis.cc
new file mode 100644 (file)
index 0000000..bc59bcc
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+    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 <cmath>
+#include <cassert>
+#include <fstream>
+#include <boost/filesystem.hpp>
+#include "audio_analysis.h"
+
+using std::ostream;
+using std::istream;
+using std::string;
+using std::ofstream;
+using std::ifstream;
+using std::vector;
+using std::cout;
+using std::max;
+using std::list;
+
+AudioPoint::AudioPoint ()
+{
+       for (int i = 0; i < COUNT; ++i) {
+               _data[i] = 0;
+       }
+}
+
+AudioPoint::AudioPoint (istream& s)
+{
+       for (int i = 0; i < COUNT; ++i) {
+               s >> _data[i];
+       }
+}
+
+AudioPoint::AudioPoint (AudioPoint const & other)
+{
+       for (int i = 0; i < COUNT; ++i) {
+               _data[i] = other._data[i];
+       }
+}
+
+AudioPoint &
+AudioPoint::operator= (AudioPoint const & other)
+{
+       if (this == &other) {
+               return *this;
+       }
+       
+       for (int i = 0; i < COUNT; ++i) {
+               _data[i] = other._data[i];
+       }
+
+       return *this;
+}
+
+void
+AudioPoint::write (ostream& s) const
+{
+       for (int i = 0; i < COUNT; ++i) {
+               s << _data[i] << "\n";
+       }
+}
+       
+
+AudioAnalysis::AudioAnalysis (int channels)
+{
+       _data.resize (channels);
+}
+
+AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
+{
+       ifstream f (filename.string().c_str ());
+
+       int channels;
+       f >> channels;
+       _data.resize (channels);
+
+       for (int i = 0; i < channels; ++i) {
+               int points;
+               f >> points;
+               for (int j = 0; j < points; ++j) {
+                       _data[i].push_back (AudioPoint (f));
+               }
+       }
+}
+
+void
+AudioAnalysis::add_point (int c, AudioPoint const & p)
+{
+       assert (c < channels ());
+       _data[c].push_back (p);
+}
+
+AudioPoint
+AudioAnalysis::get_point (int c, int p) const
+{
+       assert (p < points (c));
+       return _data[c][p];
+}
+
+int
+AudioAnalysis::channels () const
+{
+       return _data.size ();
+}
+
+int
+AudioAnalysis::points (int c) const
+{
+       assert (c < channels ());
+       return _data[c].size ();
+}
+
+void
+AudioAnalysis::write (boost::filesystem::path filename)
+{
+       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) {
+               f << i->size () << "\n";
+               for (vector<AudioPoint>::iterator j = i->begin(); j != i->end(); ++j) {
+                       j->write (f);
+               }
+       }
+
+       f.close ();
+       boost::filesystem::rename (tmp, filename);
+}
diff --git a/src/lib/audio_analysis.h b/src/lib/audio_analysis.h
new file mode 100644 (file)
index 0000000..cfc170c
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+    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 DCPOMATIC_AUDIO_ANALYSIS_H
+#define DCPOMATIC_AUDIO_ANALYSIS_H
+
+#include <iostream>
+#include <vector>
+#include <list>
+#include <boost/filesystem.hpp>
+
+class AudioPoint
+{
+public:
+       enum Type {
+               PEAK,
+               RMS,
+               COUNT
+       };
+
+       AudioPoint ();
+       AudioPoint (std::istream &);
+       AudioPoint (AudioPoint const &);
+       AudioPoint& operator= (AudioPoint const &);
+
+       void write (std::ostream &) const;
+       
+       float& operator[] (int t) {
+               return _data[t];
+       }
+
+private:
+       float _data[COUNT];
+};
+
+class AudioAnalysis : public boost::noncopyable
+{
+public:
+       AudioAnalysis (int c);
+       AudioAnalysis (boost::filesystem::path);
+
+       void add_point (int c, AudioPoint const & p);
+       
+       AudioPoint get_point (int c, int p) const;
+       int points (int c) const;
+       int channels () const;
+
+       void write (boost::filesystem::path);
+
+private:
+       std::vector<std::vector<AudioPoint> > _data;
+};
+
+#endif
diff --git a/src/lib/audio_buffers.cc b/src/lib/audio_buffers.cc
new file mode 100644 (file)
index 0000000..e80142b
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+    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 <cmath>
+#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)
+{
+       allocate (channels, frames);
+}
+
+/** Copy constructor.
+ *  @param other Other AudioBuffers; data is copied.
+ */
+AudioBuffers::AudioBuffers (AudioBuffers const & other)
+{
+       allocate (other._channels, other._frames);
+       copy_from (&other, other._frames, 0, 0);
+}
+
+AudioBuffers::AudioBuffers (boost::shared_ptr<const AudioBuffers> other)
+{
+       allocate (other->_channels, other->_frames);
+       copy_from (other.get(), other->_frames, 0, 0);
+}
+
+AudioBuffers &
+AudioBuffers::operator= (AudioBuffers const & other)
+{
+       if (this == &other) {
+               return *this;
+       }
+               
+       deallocate ();
+       allocate (other._channels, other._frames);
+       copy_from (&other, other._frames, 0, 0);
+
+       return *this;
+}
+
+/** AudioBuffers destructor */
+AudioBuffers::~AudioBuffers ()
+{
+       deallocate ();
+}
+
+void
+AudioBuffers::allocate (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 ();
+               }
+       }
+}
+
+void
+AudioBuffers::deallocate ()
+{
+       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.  If we reduce the number of frames, the `lost' frames will
+ *  be silenced.
+ *  @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);
+
+       for (int c = 0; c < _channels; ++c) {
+               for (int i = f; i < _frames; ++i) {
+                       _data[c][i] = 0;
+               }
+       }
+       
+       _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;
+       }
+}
+
+void
+AudioBuffers::make_silent (int from, int frames)
+{
+       assert ((from + frames) <= _allocated_frames);
+
+       for (int c = 0; c < _channels; ++c) {
+               for (int i = from; i < (from + 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) <= _allocated_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);
+       assert (to_channel <= _channels);
+
+       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];
+               }
+       }
+}
+
+/** @param dB gain in dB */
+void
+AudioBuffers::apply_gain (float dB)
+{
+       float const linear = pow (10, dB / 20);
+       
+       for (int i = 0; i < _channels; ++i) {
+               for (int j = 0; j < _frames; ++j) {
+                       _data[i][j] *= linear;
+               }
+       }
+}
diff --git a/src/lib/audio_buffers.h b/src/lib/audio_buffers.h
new file mode 100644 (file)
index 0000000..75bc686
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+    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.
+
+*/
+
+#ifndef DVDOMATIC_AUDIO_BUFFERS_H
+#define DVDOMATIC_AUDIO_BUFFERS_H
+
+#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 ();
+
+       AudioBuffers & operator= (AudioBuffers const &);
+
+       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 make_silent (int from, int frames);
+
+       void apply_gain (float);
+
+       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:
+       void allocate (int, int);
+       void deallocate ();
+       
+       /** 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;
+};
+
+#endif
diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc
new file mode 100644 (file)
index 0000000..100264d
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+    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");
+}
+
+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 ()));
+}
+
+string
+AudioContent::technical_summary () const
+{
+       return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate());
+}
diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h
new file mode 100644 (file)
index 0000000..7391910
--- /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>);
+
+       void as_xml (xmlpp::Node *) const;
+       std::string technical_summary () 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 9d8de971c654356a753f85fc48b8618d49568fc9..1f5868583675314df9158517d6159c2c13bce7ef 100644 (file)
 */
 
 #include "audio_decoder.h"
-#include "stream.h"
+#include "audio_buffers.h"
+#include "exceptions.h"
+#include "log.h"
+#include "resampler.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, shared_ptr<const DecodeOptions> o, Job* j)
-       : Decoder (f, o, j)
+AudioDecoder::AudioDecoder (shared_ptr<const Film> film)
+       : Decoder (film)
+       , _audio_position (0)
 {
 
 }
 
 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 013a6327f1d8587b3e9d4991f3603f618e995da5..2ad53da8bf42684e544e753bc6b0ca4708c72d19 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"
+#include "audio_content.h"
+
+class AudioBuffers;
 
 /** @class AudioDecoder.
  *  @brief Parent class for audio decoders.
  */
-class AudioDecoder : public AudioSource, public virtual Decoder
+class AudioDecoder : public virtual Decoder
 {
 public:
-       AudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
-
-       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..7a5da7d
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+    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 ()
+       : _content_channels (0)
+{
+
+}
+
+/** Create a default AudioMapping for a given channel count.
+ *  @param c Number of channels.
+ */
+AudioMapping::AudioMapping (int c)
+       : _content_channels (c)
+{
+
+}
+
+void
+AudioMapping::make_default ()
+{
+       if (_content_channels == 1) {
+               /* Mono -> Centre */
+               add (0, libdcp::CENTRE);
+       } else {
+               /* 1:1 mapping */
+               for (int i = 0; i < _content_channels; ++i) {
+                       add (i, static_cast<libdcp::Channel> (i));
+               }
+       }
+}
+
+AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node)
+{
+       _content_channels = node->number_child<int> ("ContentChannels");
+       
+       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<libdcp::Channel>
+AudioMapping::content_to_dcp (int c) const
+{
+       assert (c < _content_channels);
+       
+       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
+{
+       node->add_child ("ContentChannels")->add_child_text (lexical_cast<string> (_content_channels));
+       
+       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..9a507b5
--- /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.
+
+*/
+
+#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;
+}
+
+/** A many-to-many mapping from some content channels to DCP channels.
+ *  The number of content channels is set on construction and fixed,
+ *  and then each of those content channels can be mapped to zero or
+ *  more DCP channels.
+ */
+class AudioMapping
+{
+public:
+       AudioMapping ();
+       AudioMapping (int);
+       AudioMapping (boost::shared_ptr<const cxml::Node>);
+
+       /* Default copy constructor is fine */
+       
+       void as_xml (xmlpp::Node *) const;
+
+       void add (int, libdcp::Channel);
+       void make_default ();
+
+       std::list<int> dcp_to_content (libdcp::Channel) const;
+       std::list<std::pair<int, libdcp::Channel> > content_to_dcp () const {
+               return _content_to_dcp;
+       }
+
+       int content_channels () const {
+               return _content_channels;
+       }
+       
+       std::list<libdcp::Channel> content_to_dcp (int) const;
+
+private:
+       int _content_channels;
+       std::list<std::pair<int, libdcp::Channel> > _content_to_dcp;
+};
+
+#endif
diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h
new file mode 100644 (file)
index 0000000..226601e
--- /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 "audio_buffers.h"
+#include "util.h"
+
+template <class T, class F>
+class AudioMerger
+{
+public:
+       AudioMerger (int channels, boost::function<F (T)> t_to_f, boost::function<T (F)> f_to_t)
+               : _buffers (new AudioBuffers (channels, 0))
+               , _last_pull (0)
+               , _t_to_f (t_to_f)
+               , _f_to_t (f_to_t)
+       {}
+
+       /** Pull audio up to a given time; after this call, no more data can be pushed
+        *  before the specified time.
+        */
+       TimedAudioBuffers<T>
+       pull (T time)
+       {
+               TimedAudioBuffers<T> out;
+               
+               F const to_return = _t_to_f (time - _last_pull);
+               out.audio.reset (new AudioBuffers (_buffers->channels(), to_return));
+               /* And this is how many we will get from our buffer */
+               F const to_return_from_buffers = min (to_return, _buffers->frames ());
+               
+               /* Copy the data that we have to the back end of the return buffer */
+               out.audio->copy_from (_buffers.get(), to_return_from_buffers, 0, to_return - to_return_from_buffers);
+               /* Silence any gap at the start */
+               out.audio->make_silent (0, to_return - to_return_from_buffers);
+               
+               out.time = _last_pull;
+               _last_pull = time;
+               
+               /* And remove the data we're returning from our buffers */
+               if (_buffers->frames() > to_return_from_buffers) {
+                       _buffers->move (to_return_from_buffers, 0, _buffers->frames() - to_return_from_buffers);
+               }
+               _buffers->set_frames (_buffers->frames() - to_return_from_buffers);
+
+               return out;
+       }
+
+       void
+       push (boost::shared_ptr<const AudioBuffers> audio, T time)
+       {
+               assert (time >= _last_pull);
+
+               F frame = _t_to_f (time);
+               F after = max (_buffers->frames(), frame + audio->frames() - _t_to_f (_last_pull));
+               _buffers->ensure_size (after);
+               _buffers->accumulate_frames (audio.get(), 0, frame - _t_to_f (_last_pull), audio->frames ());
+               _buffers->set_frames (after);
+       }
+
+       F min (F a, int b)
+       {
+               if (a < b) {
+                       return a;
+               }
+
+               return b;
+       }
+
+       F max (int a, F b)
+       {
+               if (a > b) {
+                       return a;
+               }
+
+               return b;
+       }
+               
+       TimedAudioBuffers<T>
+       flush ()
+       {
+               if (_buffers->frames() == 0) {
+                       return TimedAudioBuffers<T> ();
+               }
+               
+               return TimedAudioBuffers<T> (_buffers, _last_pull);
+       }
+       
+private:
+       boost::shared_ptr<AudioBuffers> _buffers;
+       T _last_pull;
+       boost::function<F (T)> _t_to_f;
+       boost::function<T (F)> _f_to_t;
+};
diff --git a/src/lib/audio_sink.h b/src/lib/audio_sink.h
deleted file mode 100644 (file)
index 11d578a..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.
-
-*/
-
-#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<AudioBuffers>) = 0;
-};
-
-#endif
diff --git a/src/lib/audio_source.cc b/src/lib/audio_source.cc
deleted file mode 100644 (file)
index 53b0dda..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.
-
-*/
-
-#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));
-}
diff --git a/src/lib/audio_source.h b/src/lib/audio_source.h
deleted file mode 100644 (file)
index 5a1510d..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/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;
-
-/** A class that emits audio data */
-class AudioSource
-{
-public:
-       /** Emitted when some audio data is ready */
-       boost::signals2::signal<void (boost::shared_ptr<AudioBuffers>)> Audio;
-
-       void connect_audio (boost::shared_ptr<AudioSink>);
-};
-
-#endif
diff --git a/src/lib/check_hashes_job.cc b/src/lib/check_hashes_job.cc
deleted file mode 100644 (file)
index 701584c..0000000
+++ /dev/null
@@ -1,123 +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 <fstream>
-#include <boost/lexical_cast.hpp>
-#include <boost/filesystem.hpp>
-#include "check_hashes_job.h"
-#include "options.h"
-#include "log.h"
-#include "job_manager.h"
-#include "ab_transcode_job.h"
-#include "transcode_job.h"
-#include "film.h"
-#include "exceptions.h"
-
-using std::string;
-using std::stringstream;
-using std::ifstream;
-using boost::shared_ptr;
-
-CheckHashesJob::CheckHashesJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
-       : Job (f, req)
-       , _decode_opt (od)
-       , _encode_opt (oe)
-       , _bad (0)
-{
-
-}
-
-string
-CheckHashesJob::name () const
-{
-       return String::compose ("Check hashes of %1", _film->name());
-}
-
-void
-CheckHashesJob::run ()
-{
-       _bad = 0;
-
-       if (!_film->dcp_length()) {
-               throw EncodeError ("cannot check hashes of a DCP with unknown length");
-       }
-       
-       SourceFrame const N = _film->dcp_trim_start() + _film->dcp_length().get();
-       DCPFrameRate const dfr = dcp_frame_rate (_film->frames_per_second ());
-       
-       for (SourceFrame i = _film->dcp_trim_start(); i < N; i += dfr.skip) {
-               string const j2k_file = _encode_opt->frame_out_path (i, false);
-               string const hash_file = _encode_opt->hash_out_path (i, false);
-
-               if (!boost::filesystem::exists (j2k_file)) {
-                       _film->log()->log (String::compose ("Frame %1 has a missing J2K file.", i));
-                       boost::filesystem::remove (hash_file);
-                       ++_bad;
-               } else if (!boost::filesystem::exists (hash_file)) {
-                       _film->log()->log (String::compose ("Frame %1 has a missing hash file.", i));
-                       boost::filesystem::remove (j2k_file);
-                       ++_bad;
-               } else {
-                       ifstream ref (hash_file.c_str ());
-                       string hash;
-                       ref >> hash;
-                       if (hash != md5_digest (j2k_file)) {
-                               _film->log()->log (String::compose ("Frame %1 has wrong hash; deleting.", i));
-                               boost::filesystem::remove (j2k_file);
-                               boost::filesystem::remove (hash_file);
-                               ++_bad;
-                       }
-               }
-
-               set_progress (float (i) / N);
-       }
-
-       if (_bad) {
-               shared_ptr<Job> tc;
-
-               if (_film->dcp_ab()) {
-                       tc.reset (new ABTranscodeJob (_film, _decode_opt, _encode_opt, shared_from_this()));
-               } else {
-                       tc.reset (new TranscodeJob (_film, _decode_opt, _encode_opt, shared_from_this()));
-               }
-               
-               JobManager::instance()->add_after (shared_from_this(), tc);
-               JobManager::instance()->add_after (tc, shared_ptr<Job> (new CheckHashesJob (_film, _decode_opt, _encode_opt, tc)));
-       }
-               
-       set_progress (1);
-       set_state (FINISHED_OK);
-}
-
-string
-CheckHashesJob::status () const
-{
-       stringstream s;
-       s << Job::status ();
-       if (overall_progress() > 0) {
-               if (_bad == 0) {
-                       s << "; no bad frames found";
-               } else if (_bad == 1) {
-                       s << "; 1 bad frame found";
-               } else {
-                       s << "; " << _bad << " bad frames found";
-               }
-       }
-       return s.str ();
-}
diff --git a/src/lib/check_hashes_job.h b/src/lib/check_hashes_job.h
deleted file mode 100644 (file)
index c41af9d..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.
-
-*/
-
-#include "job.h"
-
-class DecodeOptions;
-class EncodeOptions;
-
-class CheckHashesJob : public Job
-{
-public:
-       CheckHashesJob (
-               boost::shared_ptr<Film> f,
-               boost::shared_ptr<const DecodeOptions> od,
-               boost::shared_ptr<const EncodeOptions> oe,
-               boost::shared_ptr<Job> req
-               );
-
-       std::string name () const;
-       void run ();
-       std::string status () const;
-
-private:
-       boost::shared_ptr<const DecodeOptions> _decode_opt;
-       boost::shared_ptr<const EncodeOptions> _encode_opt;
-       int _bad;
-};
diff --git a/src/lib/colour_conversion.cc b/src/lib/colour_conversion.cc
new file mode 100644 (file)
index 0000000..ceb3029
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+    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 <libdcp/colour_matrix.h>
+#include <libcxml/cxml.h>
+#include "config.h"
+#include "colour_conversion.h"
+#include "util.h"
+
+#include "i18n.h"
+
+using std::list;
+using std::string;
+using std::cout;
+using std::vector;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::optional;
+
+ColourConversion::ColourConversion ()
+       : input_gamma (2.4)
+       , input_gamma_linearised (true)
+       , matrix (3, 3)
+       , output_gamma (2.6)
+{
+       for (int i = 0; i < 3; ++i) {
+               for (int j = 0; j < 3; ++j) {
+                       matrix (i, j) = libdcp::colour_matrix::srgb_to_xyz[i][j];
+               }
+       }
+}
+
+ColourConversion::ColourConversion (double i, bool il, double const m[3][3], double o)
+       : input_gamma (i)
+       , input_gamma_linearised (il)
+       , matrix (3, 3)
+       , output_gamma (o)
+{
+       for (int i = 0; i < 3; ++i) {
+               for (int j = 0; j < 3; ++j) {
+                       matrix (i, j) = m[i][j];
+               }
+       }
+}
+
+ColourConversion::ColourConversion (shared_ptr<cxml::Node> node)
+       : matrix (3, 3)
+{
+       input_gamma = node->number_child<double> ("InputGamma");
+       input_gamma_linearised = node->bool_child ("InputGammaLinearised");
+
+       for (int i = 0; i < 3; ++i) {
+               for (int j = 0; j < 3; ++j) {
+                       matrix (i, j) = 0;
+               }
+       }
+
+       list<shared_ptr<cxml::Node> > m = node->node_children ("Matrix");
+       for (list<shared_ptr<cxml::Node> >::iterator i = m.begin(); i != m.end(); ++i) {
+               int const ti = (*i)->number_attribute<int> ("i");
+               int const tj = (*i)->number_attribute<int> ("j");
+               matrix(ti, tj) = lexical_cast<double> ((*i)->content ());
+       }
+
+       output_gamma = node->number_child<double> ("OutputGamma");
+}
+
+void
+ColourConversion::as_xml (xmlpp::Node* node) const
+{
+       node->add_child("InputGamma")->add_child_text (lexical_cast<string> (input_gamma));
+       node->add_child("InputGammaLinearised")->add_child_text (input_gamma_linearised ? "1" : "0");
+
+       for (int i = 0; i < 3; ++i) {
+               for (int j = 0; j < 3; ++j) {
+                       xmlpp::Element* m = node->add_child("Matrix");
+                       m->set_attribute ("i", lexical_cast<string> (i));
+                       m->set_attribute ("j", lexical_cast<string> (j));
+                       m->add_child_text (lexical_cast<string> (matrix (i, j)));
+               }
+       }
+
+       node->add_child("OutputGamma")->add_child_text (lexical_cast<string> (output_gamma));
+}
+
+optional<size_t>
+ColourConversion::preset () const
+{
+       vector<PresetColourConversion> presets = Config::instance()->colour_conversions ();
+       size_t i = 0;
+       while (i < presets.size() && (presets[i].conversion != *this)) {
+               ++i;
+       }
+
+       if (i >= presets.size ()) {
+               return optional<size_t> ();
+       }
+
+       return i;
+}
+
+string
+ColourConversion::identifier () const
+{
+       double numbers[12];
+
+       int n = 0;
+       numbers[n++] = input_gamma;
+       numbers[n++] = input_gamma_linearised;
+       for (int i = 0; i < 3; ++i) {
+               for (int j = 0; j < 3; ++j) {
+                       numbers[n++] = matrix (i, j);
+               }
+       }
+       numbers[n++] = output_gamma;
+
+       assert (n == 12);
+
+       return md5_digest (numbers, 12 * sizeof (double));
+}
+
+PresetColourConversion::PresetColourConversion ()
+       : name (_("Untitled"))
+{
+
+}
+
+PresetColourConversion::PresetColourConversion (string n, double i, bool il, double const m[3][3], double o)
+       : name (n)
+       , conversion (i, il, m, o)
+{
+
+}
+
+PresetColourConversion::PresetColourConversion (shared_ptr<cxml::Node> node)
+       : conversion (node)
+{
+       name = node->string_child ("Name");
+}
+
+void
+PresetColourConversion::as_xml (xmlpp::Node* node) const
+{
+       conversion.as_xml (node);
+       node->add_child("Name")->add_child_text (name);
+}
+
+static bool
+about_equal (double a, double b)
+{
+       static const double eps = 1e-6;
+       return fabs (a - b) < eps;
+}
+
+bool
+operator== (ColourConversion const & a, ColourConversion const & b)
+{
+       if (
+               !about_equal (a.input_gamma, b.input_gamma) ||
+               a.input_gamma_linearised != b.input_gamma_linearised ||
+               !about_equal (a.output_gamma, b.output_gamma)) {
+               return false;
+       }
+
+       for (int i = 0; i < 3; ++i) {
+               for (int j = 0; j < 3; ++j) {
+                       if (!about_equal (a.matrix (i, j), b.matrix (i, j))) {
+                               return false;
+                       }
+               }
+       }
+
+       return true;
+}
+
+bool
+operator!= (ColourConversion const & a, ColourConversion const & b)
+{
+       return !(a == b);
+}
diff --git a/src/lib/colour_conversion.h b/src/lib/colour_conversion.h
new file mode 100644 (file)
index 0000000..8931484
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+    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_COLOUR_CONVERSION_H
+#define DCPOMATIC_COLOUR_CONVERSION_H
+
+/* Hack for OS X compile failure; see https://bugs.launchpad.net/hugin/+bug/910160 */
+#ifdef check
+#undef check
+#endif
+
+#include <boost/utility.hpp>
+#include <boost/optional.hpp>
+#include <boost/numeric/ublas/matrix.hpp>
+
+namespace cxml {
+       class Node;
+}
+
+namespace xmlpp {
+       class Node;
+}
+
+class ColourConversion
+{
+public:
+       ColourConversion ();
+       ColourConversion (double, bool, double const matrix[3][3], double);
+       ColourConversion (boost::shared_ptr<cxml::Node>);
+
+       virtual void as_xml (xmlpp::Node *) const;
+       std::string identifier () const;
+
+       boost::optional<size_t> preset () const;
+
+       double input_gamma;
+       bool input_gamma_linearised;
+       boost::numeric::ublas::matrix<double> matrix;
+       double output_gamma;
+};
+
+class PresetColourConversion
+{
+public:
+       PresetColourConversion ();
+       PresetColourConversion (std::string, double, bool, double const matrix[3][3], double);
+       PresetColourConversion (boost::shared_ptr<cxml::Node>);
+
+       void as_xml (xmlpp::Node *) const;
+
+       std::string name;
+       ColourConversion conversion;
+};
+
+bool operator== (ColourConversion const &, ColourConversion const &);
+bool operator!= (ColourConversion const &, ColourConversion const &);
+
+#endif
diff --git a/src/lib/combiner.cc b/src/lib/combiner.cc
deleted file mode 100644 (file)
index 68aafd2..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.
-
-*/
-
-#include "combiner.h"
-#include "image.h"
-
-using boost::shared_ptr;
-
-Combiner::Combiner (Log* log)
-       : VideoProcessor (log)
-{
-
-}
-
-/** Process video for the left half of the frame.
- *  Subtitle parameter will be ignored.
- *  @param image Frame image.
- */
-void
-Combiner::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle>)
-{
-       _image = image;
-}
-
-/** Process video for the right half of the frame.
- *  @param image Frame image.
- *  @param sub Subtitle (which will be put onto the whole frame)
- */
-void
-Combiner::process_video_b (shared_ptr<Image> image, bool, shared_ptr<Subtitle> sub)
-{
-       /* Copy the right half of this image into our _image */
-       /* XXX: this should probably be in the Image class */
-       for (int i = 0; i < image->components(); ++i) {
-               int const line_size = image->line_size()[i];
-               int const half_line_size = line_size / 2;
-               int const stride = image->stride()[i];
-
-               uint8_t* p = _image->data()[i];
-               uint8_t* q = image->data()[i];
-                       
-               for (int j = 0; j < image->lines (i); ++j) {
-                       memcpy (p + half_line_size, q + half_line_size, half_line_size);
-                       p += stride;
-                       q += stride;
-               }
-       }
-
-       Video (_image, false, sub);
-       _image.reset ();
-}
diff --git a/src/lib/combiner.h b/src/lib/combiner.h
deleted file mode 100644 (file)
index 7fad1ae..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 VideoProcessor
-{
-public:
-       Combiner (Log* log);
-
-       void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s);
-       void process_video_b (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s);
-
-private:
-       /** The image that we are currently working on */
-       boost::shared_ptr<Image> _image;
-};
index a74c36f73c66e1c0d42ab25e88ae0d6ced9c0cd0..5b96d108ccdfdd51a9d08595eb1e6c0c9b9d710b 100644 (file)
 #include <fstream>
 #include <glib.h>
 #include <boost/filesystem.hpp>
+#include <libdcp/colour_matrix.h>
+#include <libcxml/cxml.h>
 #include "config.h"
 #include "server.h"
 #include "scaler.h"
 #include "filter.h"
+#include "ratio.h"
+#include "dcp_content_type.h"
 #include "sound_processor.h"
-#include "cinema.h"
+#include "colour_conversion.h"
+
+#include "i18n.h"
 
 using std::vector;
 using std::ifstream;
 using std::string;
 using std::ofstream;
 using std::list;
+using std::max;
 using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::optional;
 
 Config* Config::_instance = 0;
 
 /** Construct default configuration */
 Config::Config ()
-       : _num_local_encoding_threads (2)
+       : _num_local_encoding_threads (max (2U, boost::thread::hardware_concurrency()))
        , _server_port (6192)
-       , _reference_scaler (Scaler::from_id ("bicubic"))
-       , _tms_path (".")
-       , _sound_processor (SoundProcessor::from_id ("dolby_cp750"))
+       , _tms_path (N_("."))
+       , _sound_processor (SoundProcessor::from_id (N_("dolby_cp750")))
+       , _default_still_length (10)
+       , _default_container (Ratio::from_id ("185"))
+       , _default_dcp_content_type (DCPContentType::from_dci_name ("TST"))
+       , _default_j2k_bandwidth (200000000)
 {
-       ifstream f (read_file().c_str ());
-       string line;
+       _allowed_dcp_frame_rates.push_back (24);
+       _allowed_dcp_frame_rates.push_back (25);
+       _allowed_dcp_frame_rates.push_back (30);
+       _allowed_dcp_frame_rates.push_back (48);
+       _allowed_dcp_frame_rates.push_back (50);
+       _allowed_dcp_frame_rates.push_back (60);
+
+       _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6));
+       _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6));
+}
+
+void
+Config::read ()
+{
+       if (!boost::filesystem::exists (file (false))) {
+               read_old_metadata ();
+               return;
+       }
 
-       shared_ptr<Cinema> cinema;
-       shared_ptr<Screen> screen;
+       cxml::Document f ("Config");
+       f.read_file (file (false));
+       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");
        
+       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 (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);
+       _default_j2k_bandwidth = f.optional_number_child<int>("DefaultJ2KBandwidth").get_value_or (200000000);
+
+       list<shared_ptr<cxml::Node> > cc = f.node_children ("ColourConversion");
+
+       if (!cc.empty ()) {
+               _colour_conversions.clear ();
+       }
+       
+       for (list<shared_ptr<cxml::Node> >::iterator i = cc.begin(); i != cc.end(); ++i) {
+               _colour_conversions.push_back (PresetColourConversion (*i));
+       }
+}
+
+void
+Config::read_old_metadata ()
+{
+       ifstream f (file(true).c_str ());
+       string line;
+
        while (getline (f, line)) {
                if (line.empty ()) {
                        continue;
@@ -69,76 +153,58 @@ Config::Config ()
                string const k = line.substr (0, s);
                string const v = line.substr (s + 1);
 
-               if (k == "num_local_encoding_threads") {
+               if (k == N_("num_local_encoding_threads")) {
                        _num_local_encoding_threads = atoi (v.c_str ());
-               } else if (k == "default_directory") {
+               } else if (k == N_("default_directory")) {
                        _default_directory = v;
-               } else if (k == "server_port") {
+               } else if (k == N_("server_port")) {
                        _server_port = atoi (v.c_str ());
-               } else if (k == "reference_scaler") {
-                       _reference_scaler = Scaler::from_id (v);
-               } else if (k == "reference_filter") {
-                       _reference_filters.push_back (Filter::from_id (v));
-               } else if (k == "server") {
-                       _servers.push_back (ServerDescription::create_from_metadata (v));
-               } else if (k == "tms_ip") {
+               } else if (k == N_("server")) {
+                       optional<ServerDescription> server = ServerDescription::create_from_metadata (v);
+                       if (server) {
+                               _servers.push_back (server.get ());
+                       }
+               } else if (k == N_("tms_ip")) {
                        _tms_ip = v;
-               } else if (k == "tms_path") {
+               } else if (k == N_("tms_path")) {
                        _tms_path = v;
-               } else if (k == "tms_user") {
+               } else if (k == N_("tms_user")) {
                        _tms_user = v;
-               } else if (k == "tms_password") {
+               } else if (k == N_("tms_password")) {
                        _tms_password = v;
-               } else if (k == "sound_processor") {
+               } else if (k == N_("sound_processor")) {
                        _sound_processor = SoundProcessor::from_id (v);
-               } else if (k == "cinema") {
-                       if (cinema) {
-                               _cinemas.push_back (cinema);
-                       }
-                       cinema.reset (new Cinema (v, ""));
-               } else if (k == "cinema_email") {
-                       assert (cinema);
-                       cinema->email = v;
-               } else if (k == "screen") {
-                       assert (cinema);
-                       if (screen) {
-                               cinema->screens.push_back (screen);
-                       }
-                       screen.reset (new Screen (v, shared_ptr<libdcp::Certificate> ()));
-               } else if (k == "screen_certificate") {
-                       assert (screen);
-                       shared_ptr<Certificate> c (new libdcp::Certificate);
-                       c->set_from_string (v);
+               } else if (k == "language") {
+                       _language = v;
+               } else if (k == "default_container") {
+                       _default_container = Ratio::from_id (v);
+               } else if (k == "default_dcp_content_type") {
+                       _default_dcp_content_type = DCPContentType::from_dci_name (v);
+               } else if (k == "dcp_metadata_issuer") {
+                       _dcp_metadata.issuer = v;
+               } else if (k == "dcp_metadata_creator") {
+                       _dcp_metadata.creator = v;
+               } else if (k == "dcp_metadata_issue_date") {
+                       _dcp_metadata.issue_date = v;
                }
-       }
 
-       if (cinema) {
-               _cinemas.push_back (cinema);
+               _default_dci_metadata.read_old_metadata (k, v);
        }
 }
 
 /** @return Filename to write configuration to */
 string
-Config::write_file () const
+Config::file (bool old) const
 {
        boost::filesystem::path p;
        p /= g_get_user_config_dir ();
-       p /= "dvdomatic";
-       boost::filesystem::create_directory (p);
-       p /= "config";
-       return p.string ();
-}
-
-string
-Config::read_file () const
-{
-       if (boost::filesystem::exists (write_file ())) {
-               return write_file ();
+       boost::system::error_code ec;
+       boost::filesystem::create_directory (p, ec);
+       if (old) {
+               p /= ".dvdomatic";
+       } else {
+               p /= "dcpomatic.xml";
        }
-       
-       boost::filesystem::path p;
-       p /= g_get_user_config_dir ();
-       p /= ".dvdomatic";
        return p.string ();
 }
 
@@ -159,6 +225,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;
@@ -168,33 +241,46 @@ Config::instance ()
 void
 Config::write () const
 {
-       ofstream f (write_file().c_str ());
-       f << "num_local_encoding_threads " << _num_local_encoding_threads << "\n"
-         << "default_directory " << _default_directory << "\n"
-         << "server_port " << _server_port << "\n"
-         << "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";
+       for (vector<ServerDescription>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) {
+               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";
-       f << "sound_processor " << _sound_processor->id () << "\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) {
+               root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ());
+       }
+       if (_language) {
+               root->add_child("Language")->add_child_text (_language.get());
+       }
+       if (_default_container) {
+               root->add_child("DefaultContainer")->add_child_text (_default_container->id ());
+       }
+       if (_default_dcp_content_type) {
+               root->add_child("DefaultDCPContentType")->add_child_text (_default_dcp_content_type->dci_name ());
+       }
+       root->add_child("DCPMetadataIssuer")->add_child_text (_dcp_metadata.issuer);
+       root->add_child("DCPMetadataCreator")->add_child_text (_dcp_metadata.creator);
 
-       for (list<shared_ptr<Cinema> >::const_iterator i = _cinemas.begin(); i != _cinemas.end(); ++i) {
-               f << "cinema " << (*i)->name << "\n";
-               f << "cinema_email " << (*i)->email << "\n";
-               for (list<shared_ptr<Screen> >::iterator j = (*i)->screens.begin(); j != (*i)->screens.end(); ++j) {
-                       f << "screen " << (*j)->name << "\n";
-               }
+       _default_dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+
+       root->add_child("DefaultStillLength")->add_child_text (lexical_cast<string> (_default_still_length));
+       root->add_child("DefaultJ2KBandwidth")->add_child_text (lexical_cast<string> (_default_j2k_bandwidth));
+
+       for (vector<PresetColourConversion>::const_iterator i = _colour_conversions.begin(); i != _colour_conversions.end(); ++i) {
+               i->as_xml (root->add_child ("ColourConversion"));
        }
+
+       doc.write_to_file_formatted (file (false));
 }
 
 string
@@ -206,3 +292,10 @@ Config::default_directory_or (string a) const
 
        return _default_directory;
 }
+
+void
+Config::drop ()
+{
+       delete _instance;
+       _instance = 0;
+}
index ee4e4eaec49cd2a098eb04b66a4b49b517d4cee4..48eabd54ca3dc33b6f395f34c8981256413ce9f0 100644 (file)
  *  @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>
 #include <boost/signals2.hpp>
+#include <libdcp/metadata.h>
+#include "dci_metadata.h"
+#include "colour_conversion.h"
+#include "server.h"
 
 class ServerDescription;
 class Scaler;
 class Filter;
 class SoundProcessor;
+class DCPContentType;
+class Ratio;
 class Cinema;
 
 /** @class Config
  *  @brief A singleton class holding configuration.
  */
-class Config
+class Config : public boost::noncopyable
 {
 public:
 
@@ -58,18 +64,10 @@ public:
        }
 
        /** @return J2K encoding servers to use */
-       std::vector<ServerDescription*> servers () const {
+       std::vector<ServerDescription> servers () const {
                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;
@@ -98,6 +96,42 @@ public:
        std::list<boost::shared_ptr<Cinema> > cinemas () const {
                return _cinemas;
        }
+       
+       std::list<int> allowed_dcp_frame_rates () const {
+               return _allowed_dcp_frame_rates;
+       }
+       
+       DCIMetadata default_dci_metadata () const {
+               return _default_dci_metadata;
+       }
+
+       boost::optional<std::string> language () const {
+               return _language;
+       }
+
+       int default_still_length () const {
+               return _default_still_length;
+       }
+
+       Ratio const * default_container () const {
+               return _default_container;
+       }
+
+       DCPContentType const * default_dcp_content_type () const {
+               return _default_dcp_content_type;
+       }
+
+       libdcp::XMLMetadata dcp_metadata () const {
+               return _dcp_metadata;
+       }
+
+       int default_j2k_bandwidth () const {
+               return _default_j2k_bandwidth;
+       }
+
+       std::vector<PresetColourConversion> colour_conversions () const {
+               return _colour_conversions;
+       }
 
        /** @param n New number of local encoding threads */
        void set_num_local_encoding_threads (int n) {
@@ -114,7 +148,7 @@ public:
        }
 
        /** @param s New list of servers */
-       void set_servers (std::vector<ServerDescription*> s) {
+       void set_servers (std::vector<ServerDescription> s) {
                _servers = s;
        }
 
@@ -153,17 +187,59 @@ public:
        void remove_cinema (boost::shared_ptr<Cinema> c) {
                _cinemas.remove (c);
        }
+
+       void set_allowed_dcp_frame_rates (std::list<int> const & r) {
+               _allowed_dcp_frame_rates = r;
+       }
+
+       void set_default_dci_metadata (DCIMetadata d) {
+               _default_dci_metadata = d;
+       }
+
+       void set_language (std::string l) {
+               _language = l;
+       }
+
+       void unset_language () {
+               _language = boost::none;
+       }
+
+       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) {
+               _default_dcp_content_type = t;
+       }
+
+       void set_dcp_metadata (libdcp::XMLMetadata m) {
+               _dcp_metadata = m;
+       }
+
+       void set_default_j2k_bandwidth (int b) {
+               _default_j2k_bandwidth = b;
+       }
+
+       void set_colour_conversions (std::vector<PresetColourConversion> const & c) {
+               _colour_conversions = c;
+       }
        
        void write () const;
 
        std::string crypt_chain_directory () const;
 
        static Config* instance ();
+       static void drop ();
 
 private:
        Config ();
-       std::string read_file () const;
-       std::string write_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;
@@ -173,7 +249,7 @@ private:
        int _server_port;
 
        /** J2K encoding servers to use */
-       std::vector<ServerDescription *> _servers;
+       std::vector<ServerDescription> _servers;
        /** Scaler to use for the "A" part of A/B comparisons */
        Scaler const * _reference_scaler;
        /** Filters to use for the "A" part of A/B comparisons */
@@ -188,6 +264,16 @@ private:
        std::string _tms_password;
        /** Our sound processor */
        SoundProcessor const * _sound_processor;
+       std::list<int> _allowed_dcp_frame_rates;
+       /** Default DCI metadata for newly-created Films */
+       DCIMetadata _default_dci_metadata;
+       boost::optional<std::string> _language;
+       int _default_still_length;
+       Ratio const * _default_container;
+       DCPContentType const * _default_dcp_content_type;
+       libdcp::XMLMetadata _dcp_metadata;
+       int _default_j2k_bandwidth;
+       std::vector<PresetColourConversion> _colour_conversions;
 
        std::list<boost::shared_ptr<Cinema> > _cinemas;
 
diff --git a/src/lib/content.cc b/src/lib/content.cc
new file mode 100644 (file)
index 0000000..d2a07f7
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+    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"
+#include "content_factory.h"
+#include "ui_signaller.h"
+
+using std::string;
+using std::set;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+int const ContentProperty::POSITION = 400;
+int const ContentProperty::LENGTH = 401;
+int const ContentProperty::TRIM_START = 402;
+int const ContentProperty::TRIM_END = 403;
+
+Content::Content (shared_ptr<const Film> f, Time p)
+       : _film (f)
+       , _position (p)
+       , _trim_start (0)
+       , _trim_end (0)
+       , _change_signals_frequent (false)
+{
+
+}
+
+Content::Content (shared_ptr<const Film> f, boost::filesystem::path p)
+       : _film (f)
+       , _path (p)
+       , _position (0)
+       , _trim_start (0)
+       , _trim_end (0)
+       , _change_signals_frequent (false)
+{
+
+}
+
+Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+       : _film (f)
+       , _change_signals_frequent (false)
+{
+       _path = node->string_child ("Path");
+       _digest = node->string_child ("Digest");
+       _position = node->number_child<Time> ("Position");
+       _trim_start = node->number_child<Time> ("TrimStart");
+       _trim_end = node->number_child<Time> ("TrimEnd");
+}
+
+void
+Content::as_xml (xmlpp::Node* node) const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       
+       node->add_child("Path")->add_child_text (_path.string());
+       node->add_child("Digest")->add_child_text (_digest);
+       node->add_child("Position")->add_child_text (lexical_cast<string> (_position));
+       node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start));
+       node->add_child("TrimEnd")->add_child_text (lexical_cast<string> (_trim_end));
+}
+
+void
+Content::examine (shared_ptr<Job> job)
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       boost::filesystem::path p = _path;
+       lm.unlock ();
+       
+       string d;
+       if (boost::filesystem::is_regular_file (p)) {
+               d = md5_digest (p);
+       } else {
+               d = md5_digest_directory (p, job);
+       }
+
+       lm.lock ();
+       _digest = d;
+}
+
+void
+Content::signal_changed (int p)
+{
+       if (ui_signaller) {
+               ui_signaller->emit (boost::bind (boost::ref (Changed), shared_from_this (), p, _change_signals_frequent));
+       }
+}
+
+void
+Content::set_position (Time p)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _position = p;
+       }
+
+       signal_changed (ContentProperty::POSITION);
+}
+
+void
+Content::set_trim_start (Time t)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _trim_start = t;
+       }
+
+       signal_changed (ContentProperty::TRIM_START);
+}
+
+void
+Content::set_trim_end (Time t)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _trim_end = t;
+       }
+
+       signal_changed (ContentProperty::TRIM_END);
+}
+
+
+shared_ptr<Content>
+Content::clone () const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       if (!film) {
+               return shared_ptr<Content> ();
+       }
+       
+       /* This is a bit naughty, but I can't think of a compelling reason not to do it ... */
+       xmlpp::Document doc;
+       xmlpp::Node* node = doc.create_root_node ("Content");
+       as_xml (node);
+       return content_factory (film, shared_ptr<cxml::Node> (new cxml::Node (node)));
+}
+
+string
+Content::technical_summary () const
+{
+       return String::compose ("%1 %2 %3", path(), digest(), position());
+}
+
+Time
+Content::length_after_trim () const
+{
+       return full_length() - trim_start() - trim_end();
+}
+
+/** @param t A time relative to the start of this content (not the position).
+ *  @return true if this time is trimmed by our trim settings.
+ */
+bool
+Content::trimmed (Time t) const
+{
+       return (t < trim_start() || t > (full_length() - trim_end ()));
+}
diff --git a/src/lib/content.h b/src/lib/content.h
new file mode 100644 (file)
index 0000000..3c57ddd
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+    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 <set>
+#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 POSITION;
+       static int const LENGTH;
+       static int const TRIM_START;
+       static int const TRIM_END;
+};
+
+class Content : public boost::enable_shared_from_this<Content>, public boost::noncopyable
+{
+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>);
+       virtual ~Content () {}
+       
+       virtual void examine (boost::shared_ptr<Job>);
+       virtual std::string summary () const = 0;
+       virtual std::string technical_summary () const;
+       virtual std::string information () const = 0;
+       virtual void as_xml (xmlpp::Node *) const;
+       virtual Time full_length () const = 0;
+
+       boost::shared_ptr<Content> clone () const;
+       
+       boost::filesystem::path path () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _path;
+       }
+
+       /** @return MD5 digest of the content's file(s) */
+       std::string digest () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _digest;
+       }
+
+       void set_position (Time);
+
+       /** Time that this content starts; i.e. the time that the first
+        *  bit of the content (trimmed or not) will happen.
+        */
+       Time position () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _position;
+       }
+
+       void set_trim_start (Time);
+
+       Time trim_start () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _trim_start;
+       }
+
+       void set_trim_end (Time);
+       
+       Time trim_end () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _trim_end;
+       }
+       
+       Time end () const {
+               return position() + length_after_trim();
+       }
+
+       Time length_after_trim () const;
+       
+       void set_change_signals_frequent (bool f) {
+               _change_signals_frequent = f;
+       }
+
+       bool trimmed (Time) const;
+
+       boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed;
+
+protected:
+       void signal_changed (int);
+
+       boost::weak_ptr<const Film> _film;
+
+       /** _mutex which should be used to protect accesses, as examine
+           jobs can update content state in threads other than the main one.
+       */
+       mutable boost::mutex _mutex;
+
+private:
+       /** Path of a file or a directory containing files */
+       boost::filesystem::path _path;
+       std::string _digest;
+       Time _position;
+       Time _trim_start;
+       Time _trim_end;
+       bool _change_signals_frequent;
+};
+
+#endif
diff --git a/src/lib/content_factory.cc b/src/lib/content_factory.cc
new file mode 100644 (file)
index 0000000..6ed01f1
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+    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 "still_image_content.h"
+#include "moving_image_content.h"
+#include "sndfile_content.h"
+
+using std::string;
+using boost::shared_ptr;
+
+shared_ptr<Content>
+content_factory (shared_ptr<const Film> film, shared_ptr<cxml::Node> node)
+{
+       string const type = node->string_child ("Type");
+
+       boost::shared_ptr<Content> content;
+       
+       if (type == "FFmpeg") {
+               content.reset (new FFmpegContent (film, node));
+       } else if (type == "StillImage") {
+               content.reset (new StillImageContent (film, node));
+       } else if (type == "MovingImage") {
+               content.reset (new MovingImageContent (film, node));
+       } else if (type == "Sndfile") {
+               content.reset (new SndfileContent (film, node));
+       }
+
+       return content;
+}
diff --git a/src/lib/content_factory.h b/src/lib/content_factory.h
new file mode 100644 (file)
index 0000000..27cd360
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+    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.
+
+*/
+
+class Film;
+
+extern boost::shared_ptr<Content> content_factory (boost::shared_ptr<const Film>, boost::shared_ptr<cxml::Node>);
index 2c66ab53a865725d63dbbd73d97e3ce1799ea19e..61ec8de5e07f3fae30830ab77d3fdf6b6585f19f 100644 (file)
 
 */
 
+#include <fstream>
+#include <boost/algorithm/string.hpp>
 #include "cross.h"
-#ifdef DVDOMATIC_POSIX
+#include "compose.hpp"
+#include "log.h"
+#ifdef DCPOMATIC_LINUX
 #include <unistd.h>
+#include <mntent.h>
 #endif
-#ifdef DVDOMATIC_WINDOWS
-#include "windows.h"
+#ifdef DCPOMATIC_WINDOWS
+#include <windows.h>
+#undef DATADIR
+#include <shlwapi.h>
 #endif
+#ifdef DCPOMATIC_OSX
+#include <sys/sysctl.h>
+#include <mach-o/dyld.h>
+#endif
+
+using std::pair;
+using std::list;
+using std::ifstream;
+using std::string;
+using std::wstring;
+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
 }
+
+/** @return A string of CPU information (model name etc.) */
+string
+cpu_info ()
+{
+       string info;
+       
+#ifdef DCPOMATIC_LINUX
+       ifstream f ("/proc/cpuinfo");
+       while (f.good ()) {
+               string l;
+               getline (f, l);
+               if (boost::algorithm::starts_with (l, "model name")) {
+                       string::size_type const c = l.find (':');
+                       if (c != string::npos) {
+                               info = l.substr (c + 2);
+                       }
+               }
+       }
+#endif
+
+#ifdef DCPOMATIC_OSX
+       char buffer[64];
+       size_t N = sizeof (buffer);
+       if (sysctlbyname ("machdep.cpu.brand_string", buffer, &N, 0, 0) == 0) {
+               info = buffer;
+       }
+#endif         
+
+#ifdef DCPOMATIC_WINDOWS
+       HKEY key;
+       if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key) != ERROR_SUCCESS) {
+               return info;
+       }
+
+       DWORD type;
+       DWORD data;
+       if (RegQueryValueEx (key, L"ProcessorNameString", 0, &type, 0, &data) != ERROR_SUCCESS) {
+               return info;
+       }
+
+       if (type != REG_SZ) {
+               return info;
+       }
+
+       wstring value (data / sizeof (wchar_t), L'\0');
+       if (RegQueryValueEx (key, L"ProcessorNameString", 0, 0, reinterpret_cast<LPBYTE> (&value[0]), &data) != ERROR_SUCCESS) {
+               RegCloseKey (key);
+               return info;
+       }
+
+       info = string (value.begin(), value.end());
+       
+       RegCloseKey (key);
+
+#endif 
+       
+       return info;
+}
+
+void
+run_ffprobe (boost::filesystem::path content, boost::filesystem::path out, shared_ptr<Log> log)
+{
+#ifdef DCPOMATIC_WINDOWS
+       SECURITY_ATTRIBUTES security;
+       security.nLength = sizeof (security);
+       security.bInheritHandle = TRUE;
+       security.lpSecurityDescriptor = 0;
+
+       HANDLE child_stderr_read;
+       HANDLE child_stderr_write;
+       if (!CreatePipe (&child_stderr_read, &child_stderr_write, &security, 0)) {
+               log->log ("ffprobe call failed (could not CreatePipe)");
+               return;
+       }
+
+       wchar_t dir[512];
+       GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
+       PathRemoveFileSpec (dir);
+       SetCurrentDirectory (dir);
+
+       STARTUPINFO startup_info;
+       ZeroMemory (&startup_info, sizeof (startup_info));
+       startup_info.cb = sizeof (startup_info);
+       startup_info.hStdError = child_stderr_write;
+       startup_info.dwFlags |= STARTF_USESTDHANDLES;
+
+       wchar_t command[512];
+       wcscpy (command, L"ffprobe.exe \"");
+
+       wchar_t file[512];
+       MultiByteToWideChar (CP_UTF8, 0, content.string().c_str(), -1, file, sizeof(file));
+       wcscat (command, file);
+
+       wcscat (command, L"\"");
+
+       PROCESS_INFORMATION process_info;
+       ZeroMemory (&process_info, sizeof (process_info));
+       if (!CreateProcess (0, command, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
+               log->log ("ffprobe call failed (could not CreateProcess)");
+               return;
+       }
+
+       FILE* o = fopen (out.string().c_str(), "w");
+       if (!o) {
+               log->log ("ffprobe call failed (could not create output file)");
+               return;
+       }
+
+       CloseHandle (child_stderr_write);
+
+       while (1) {
+               char buffer[512];
+               DWORD read;
+               if (!ReadFile(child_stderr_read, buffer, sizeof(buffer), &read, 0) || read == 0) {
+                       break;
+               }
+               fwrite (buffer, read, 1, o);
+       }
+
+       fclose (o);
+
+       WaitForSingleObject (process_info.hProcess, INFINITE);
+       CloseHandle (process_info.hProcess);
+       CloseHandle (process_info.hThread);
+       CloseHandle (child_stderr_read);
+#endif
+
+#ifdef DCPOMATIC_LINUX 
+       string ffprobe = "ffprobe \"" + content.string() + "\" 2> \"" + out.string() + "\"";
+       log->log (String::compose ("Probing with %1", ffprobe));
+        system (ffprobe.c_str ());
+#endif
+
+#ifdef DCPOMATIC_OSX
+       uint32_t size = 1024;
+       char buffer[size];
+       if (_NSGetExecutablePath (buffer, &size)) {
+               log->log ("_NSGetExecutablePath failed");
+               return;
+       }
+       
+       boost::filesystem::path path (buffer);
+       path.remove_filename ();
+       path /= "ffprobe";
+       
+       string ffprobe = path.string() + " \"" + content.string() + "\" 2> \"" + out.string() + "\"";
+       log->log (String::compose ("Probing with %1", ffprobe));
+       system (ffprobe.c_str ());
+#endif
+}
+
+list<pair<string, string> >
+mount_info ()
+{
+       list<pair<string, string> > m;
+       
+#ifdef DCPOMATIC_LINUX
+       FILE* f = setmntent ("/etc/mtab", "r");
+       if (!f) {
+               return m;
+       }
+       
+       while (1) {
+               struct mntent* mnt = getmntent (f);
+               if (!mnt) {
+                       break;
+               }
+
+               m.push_back (make_pair (mnt->mnt_dir, mnt->mnt_type));
+       }
+
+       endmntent (f);
+#endif
+
+       return m;
+}
index 110660b16ea1783fec6b625d8a6748e793974e00..58fa821c7276aba3a96fb0a8996b6f224ca3b2e2 100644 (file)
 
 */
 
-#ifdef DVDOMATIC_WINDOWS
+#include <boost/filesystem.hpp>
+
+#ifdef DCPOMATIC_WINDOWS
 #define WEXITSTATUS(w) (w)
 #endif
 
-void dvdomatic_sleep (int);
+class Log;
+
+void dcpomatic_sleep (int);
+extern std::string cpu_info ();
+extern void run_ffprobe (boost::filesystem::path, boost::filesystem::path, boost::shared_ptr<Log>);
+extern std::list<std::pair<std::string, std::string> > mount_info ();
diff --git a/src/lib/dci_metadata.cc b/src/lib/dci_metadata.cc
new file mode 100644 (file)
index 0000000..27306a1
--- /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.
+
+*/
+
+#include <iostream>
+#include <libcxml/cxml.h>
+#include "dci_metadata.h"
+
+#include "i18n.h"
+
+using std::string;
+using boost::lexical_cast;
+using boost::shared_ptr;
+
+DCIMetadata::DCIMetadata (shared_ptr<const cxml::Node> node)
+{
+       content_version = node->number_child<int> ("ContentVersion");
+       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::as_xml (xmlpp::Node* root) const
+{
+       root->add_child("ContentVersion")->add_child_text (lexical_cast<string> (content_version));
+       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_old_metadata (string k, string v)
+{
+       if (k == N_("audio_language")) {
+               audio_language = v;
+       } else if (k == N_("subtitle_language")) {
+               subtitle_language = v;
+       } else if (k == N_("territory")) {
+               territory = v;
+       } else if (k == N_("rating")) {
+               rating = v;
+       } else if (k == N_("studio")) {
+               studio = v;
+       } else if (k == N_("facility")) {
+               facility = v;
+       } else if (k == N_("package_type")) {
+               package_type = v;
+       }
+}      
diff --git a/src/lib/dci_metadata.h b/src/lib/dci_metadata.h
new file mode 100644 (file)
index 0000000..738e439
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+    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 DCPOMATIC_DCI_METADATA_H
+#define DCPOMATIC_DCI_METADATA_H
+
+#include <string>
+#include <libxml++/libxml++.h>
+
+namespace cxml {
+       class Node;
+}
+
+class DCIMetadata
+{
+public:
+       DCIMetadata ()
+               : content_version (1)
+       {}
+       
+       DCIMetadata (boost::shared_ptr<const cxml::Node>);
+
+       void as_xml (xmlpp::Node *) const;
+       void read_old_metadata (std::string, std::string);
+
+       int content_version;
+       std::string audio_language;
+       std::string subtitle_language;
+       std::string territory;
+       std::string rating;
+       std::string studio;
+       std::string facility;
+       std::string package_type;
+};
+
+#endif
index aae80530831228d7440e6b2f06dade0af5a81033..82bd5fa018429c6ceea666cc580204e58b5a735d 100644 (file)
@@ -24,6 +24,8 @@
 #include <cassert>
 #include "dcp_content_type.h"
 
+#include "i18n.h"
+
 using namespace std;
 
 vector<DCPContentType const *> DCPContentType::_dcp_content_types;
@@ -39,16 +41,16 @@ DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d)
 void
 DCPContentType::setup_dcp_content_types ()
 {
-       _dcp_content_types.push_back (new DCPContentType ("Feature", libdcp::FEATURE, "FTR"));
-       _dcp_content_types.push_back (new DCPContentType ("Short", libdcp::SHORT, "SHR"));
-       _dcp_content_types.push_back (new DCPContentType ("Trailer", libdcp::TRAILER, "TLR"));
-       _dcp_content_types.push_back (new DCPContentType ("Test", libdcp::TEST, "TST"));
-       _dcp_content_types.push_back (new DCPContentType ("Transitional", libdcp::TRANSITIONAL, "XSN"));
-       _dcp_content_types.push_back (new DCPContentType ("Rating", libdcp::RATING, "RTG"));
-       _dcp_content_types.push_back (new DCPContentType ("Teaser", libdcp::TEASER, "TSR"));
-       _dcp_content_types.push_back (new DCPContentType ("Policy", libdcp::POLICY, "POL"));
-       _dcp_content_types.push_back (new DCPContentType ("Public Service Announcement", libdcp::PUBLIC_SERVICE_ANNOUNCEMENT, "PSA"));
-       _dcp_content_types.push_back (new DCPContentType ("Advertisement", libdcp::ADVERTISEMENT, "ADV"));
+       _dcp_content_types.push_back (new DCPContentType (_("Feature"), libdcp::FEATURE, N_("FTR")));
+       _dcp_content_types.push_back (new DCPContentType (_("Short"), libdcp::SHORT, N_("SHR")));
+       _dcp_content_types.push_back (new DCPContentType (_("Trailer"), libdcp::TRAILER, N_("TLR")));
+       _dcp_content_types.push_back (new DCPContentType (_("Test"), libdcp::TEST, N_("TST")));
+       _dcp_content_types.push_back (new DCPContentType (_("Transitional"), libdcp::TRANSITIONAL, N_("XSN")));
+       _dcp_content_types.push_back (new DCPContentType (_("Rating"), libdcp::RATING, N_("RTG")));
+       _dcp_content_types.push_back (new DCPContentType (_("Teaser"), libdcp::TEASER, N_("TSR")));
+       _dcp_content_types.push_back (new DCPContentType (_("Policy"), libdcp::POLICY, N_("POL")));
+       _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), libdcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA")));
+       _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), libdcp::ADVERTISEMENT, N_("ADV")));
 }
 
 DCPContentType const *
@@ -63,6 +65,18 @@ DCPContentType::from_pretty_name (string n)
        return 0;
 }
 
+DCPContentType const *
+DCPContentType::from_dci_name (string n)
+{
+       for (vector<DCPContentType const *>::const_iterator i = _dcp_content_types.begin(); i != _dcp_content_types.end(); ++i) {
+               if ((*i)->dci_name() == n) {
+                       return *i;
+               }
+       }
+
+       return 0;
+}
+
 DCPContentType const *
 DCPContentType::from_index (int n)
 {
index 2b6e60a19cb902b701b9075c5797d34f28f575f5..965c163478af5fa30eeeefe3b36a1b0522a86d4d 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.)
@@ -31,7 +31,7 @@
 /** @class DCPContentType
  *  @brief A description of the type of content for a DCP (e.g. feature, trailer etc.)
  */
-class DCPContentType
+class DCPContentType : public boost::noncopyable
 {
 public:
        DCPContentType (std::string, libdcp::ContentKind, std::string);
@@ -50,6 +50,7 @@ public:
        }
 
        static DCPContentType const * from_pretty_name (std::string);
+       static DCPContentType const * from_dci_name (std::string);
        static DCPContentType const * from_index (int);
        static int as_index (DCPContentType const *);
        static std::vector<DCPContentType const *> all ();
index c6b29ba4169a62b8d61578caf1261c50262ad70a..1c812cbf25c90a2e31a4176f6b4a43aea719a594 100644 (file)
 #include <boost/asio.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/lexical_cast.hpp>
+#include <libdcp/rec709_linearised_gamma_lut.h>
+#include <libdcp/srgb_linearised_gamma_lut.h>
+#include <libdcp/gamma_lut.h>
+#include <libdcp/xyz_frame.h>
+#include <libdcp/rgb_xyz.h>
+#include <libdcp/colour_matrix.h>
+#include <libcxml/cxml.h>
 #include "film.h"
 #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"
 
 using std::string;
 using std::stringstream;
 using std::ofstream;
+using std::cout;
 using boost::shared_ptr;
+using boost::lexical_cast;
+using libdcp::Size;
+
+#define DCI_COEFFICENT (48.0 / 52.37)
 
 /** 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 Film.
- *  @param fps Frames per second of the Film.
- *  @param pp FFmpeg post-processing string to use.
- *  @param clut Colour look-up table to use (see Config::colour_lut_index ())
+ *  @param f Index of the frame within the DCP.
  *  @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, SourceFrame f, float fps, string pp, int clut, int bw, Log* l
+       shared_ptr<const Image> image, int f, Eyes eyes, ColourConversion c, int dcp_fps, 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_frame_rate(fps).frames_per_second)
-       , _post_process (pp)
-       , _colour_lut (clut)
+       , _eyes (eyes)
+       , _conversion (c)
+       , _frames_per_second (dcp_fps)
        , _j2k_bandwidth (bw)
        , _log (l)
-       , _image (0)
-       , _parameters (0)
-       , _cinfo (0)
-       , _cio (0)
 {
        
 }
 
-/** Create a libopenjpeg container suitable for our output image */
-void
-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].x0 = 0;
-               _cmptparm[i].y0 = 0;
-               _cmptparm[i].prec = 12;
-               _cmptparm[i].bpp = 12;
-               _cmptparm[i].sgnd = 0;
-       }
-
-       _image = opj_image_create (3, &_cmptparm[0], CLRSPC_SRGB);
-       if (_image == 0) {
-               throw EncodeError ("could not create libopenjpeg image");
-       }
-
-       _image->x0 = 0;
-       _image->y0 = 0;
-       _image->x1 = _out_size.width;
-       _image->y1 = _out_size.height;
-}
-
-DCPVideoFrame::~DCPVideoFrame ()
+DCPVideoFrame::DCPVideoFrame (shared_ptr<const Image> image, shared_ptr<const cxml::Node> node, shared_ptr<Log> log)
+       : _image (image)
+       , _log (log)
 {
-       if (_image) {
-               opj_image_destroy (_image);
-       }
-
-       if (_cio) {
-               opj_cio_close (_cio);
-       }
-
-       if (_cinfo) {
-               opj_destroy_compress (_cinfo);
+       _frame = node->number_child<int> ("Frame");
+       string const eyes = node->string_child ("Eyes");
+       if (eyes == "Both") {
+               _eyes = EYES_BOTH;
+       } else if (eyes == "Left") {
+               _eyes = EYES_LEFT;
+       } else if (eyes == "Right") {
+               _eyes = EYES_RIGHT;
+       } else {
+               assert (false);
        }
-
-       if (_parameters) {
-               free (_parameters->cp_comment);
-       }
-       
-       delete _parameters;
+       _conversion = ColourConversion (node->node_child ("ColourConversion"));
+       _frames_per_second = node->number_child<int> ("FramesPerSecond");
+       _j2k_bandwidth = node->number_child<int> ("J2KBandwidth");
 }
 
 /** J2K-encode this frame on the local host.
@@ -153,151 +118,133 @@ 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) {
-               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());
+       shared_ptr<libdcp::LUT> in_lut;
+       if (_conversion.input_gamma_linearised) {
+               in_lut = libdcp::SRGBLinearisedGammaLUT::cache.get (12, _conversion.input_gamma);
+       } else {
+               in_lut = libdcp::GammaLUT::cache.get (12, _conversion.input_gamma);
        }
 
-       create_openjpeg_container ();
-
-       struct {
-               double r, g, b;
-       } s;
-
-       struct {
-               double x, y, z;
-       } d;
-
-       /* 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) {
-
-                       /* In gamma LUT (converting 8-bit input to 12-bit) */
-                       s.r = lut_in[_colour_lut][*p++ << 4];
-                       s.g = lut_in[_colour_lut][*p++ << 4];
-                       s.b = lut_in[_colour_lut][*p++ << 4];
-                       
-                       /* RGB to XYZ Matrix */
-                       d.x = ((s.r * color_matrix[_colour_lut][0][0]) +
-                              (s.g * color_matrix[_colour_lut][0][1]) +
-                              (s.b * color_matrix[_colour_lut][0][2]));
-                       
-                       d.y = ((s.r * color_matrix[_colour_lut][1][0]) +
-                              (s.g * color_matrix[_colour_lut][1][1]) +
-                              (s.b * color_matrix[_colour_lut][1][2]));
-                       
-                       d.z = ((s.r * color_matrix[_colour_lut][2][0]) +
-                              (s.g * color_matrix[_colour_lut][2][1]) +
-                              (s.b * color_matrix[_colour_lut][2][2]));
-                       
-                       /* DCI companding */
-                       d.x = d.x * DCI_COEFFICENT * (DCI_LUT_SIZE - 1);
-                       d.y = d.y * DCI_COEFFICENT * (DCI_LUT_SIZE - 1);
-                       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];
-
-                       ++jn;
+       /* XXX: libdcp should probably use boost */
+       
+       double matrix[3][3];
+       for (int i = 0; i < 3; ++i) {
+               for (int j = 0; j < 3; ++j) {
+                       matrix[i][j] = _conversion.matrix (i, j);
                }
        }
-
+       
+       shared_ptr<libdcp::XYZFrame> xyz = libdcp::rgb_to_xyz (
+               _image,
+               in_lut,
+               libdcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma),
+               matrix
+               );
+               
        /* Set the max image and component sizes based on frame_rate */
-       int const max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second;
+       int max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second;
+       if (_eyes == EYES_LEFT || _eyes == EYES_RIGHT) {
+               /* In 3D we have only half the normal bandwidth per eye */
+               max_cs_len /= 2;
+       }
        int const max_comp_size = max_cs_len / 1.25;
 
+       /* get a J2K compressor handle */
+       opj_cinfo_t* cinfo = opj_create_compress (CODEC_J2K);
+       if (cinfo == 0) {
+               throw EncodeError (N_("could not create JPEG2000 encoder"));
+       }
+
        /* Set encoding parameters to default values */
-       _parameters = new opj_cparameters_t;
-       opj_set_default_encoder_parameters (_parameters);
+       opj_cparameters_t parameters;
+       opj_set_default_encoder_parameters (&parameters);
 
        /* Set default cinema parameters */
-       _parameters->tile_size_on = false;
-       _parameters->cp_tdx = 1;
-       _parameters->cp_tdy = 1;
+       parameters.tile_size_on = false;
+       parameters.cp_tdx = 1;
+       parameters.cp_tdy = 1;
        
        /* Tile part */
-       _parameters->tp_flag = 'C';
-       _parameters->tp_on = 1;
+       parameters.tp_flag = 'C';
+       parameters.tp_on = 1;
        
        /* Tile and Image shall be at (0,0) */
-       _parameters->cp_tx0 = 0;
-       _parameters->cp_ty0 = 0;
-       _parameters->image_offset_x0 = 0;
-       _parameters->image_offset_y0 = 0;
+       parameters.cp_tx0 = 0;
+       parameters.cp_ty0 = 0;
+       parameters.image_offset_x0 = 0;
+       parameters.image_offset_y0 = 0;
 
        /* Codeblock size = 32x32 */
-       _parameters->cblockw_init = 32;
-       _parameters->cblockh_init = 32;
-       _parameters->csty |= 0x01;
+       parameters.cblockw_init = 32;
+       parameters.cblockh_init = 32;
+       parameters.csty |= 0x01;
        
        /* The progression order shall be CPRL */
-       _parameters->prog_order = CPRL;
+       parameters.prog_order = CPRL;
        
        /* No ROI */
-       _parameters->roi_compno = -1;
+       parameters.roi_compno = -1;
        
-       _parameters->subsampling_dx = 1;
-       _parameters->subsampling_dy = 1;
+       parameters.subsampling_dx = 1;
+       parameters.subsampling_dy = 1;
        
        /* 9-7 transform */
-       _parameters->irreversible = 1;
+       parameters.irreversible = 1;
        
-       _parameters->tcp_rates[0] = 0;
-       _parameters->tcp_numlayers++;
-       _parameters->cp_disto_alloc = 1;
-       _parameters->cp_rsiz = CINEMA2K;
-       _parameters->cp_comment = strdup ("DVD-o-matic");
-       _parameters->cp_cinema = CINEMA2K_24;
+       parameters.tcp_rates[0] = 0;
+       parameters.tcp_numlayers++;
+       parameters.cp_disto_alloc = 1;
+       parameters.cp_rsiz = CINEMA2K;
+       parameters.cp_comment = strdup (N_("DCP-o-matic"));
+       parameters.cp_cinema = CINEMA2K_24;
 
        /* 3 components, so use MCT */
-       _parameters->tcp_mct = 1;
+       parameters.tcp_mct = 1;
        
        /* 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);
-
-       /* get a J2K compressor handle */
-       _cinfo = opj_create_compress (CODEC_J2K);
-       if (_cinfo == 0) {
-               throw EncodeError ("could not create JPEG2000 encoder");
-       }
+       parameters.max_comp_size = max_comp_size;
+       parameters.tcp_rates[0] = ((float) (3 * xyz->size().width * xyz->size().height * 12)) / (max_cs_len * 8);
 
        /* Set event manager to null (openjpeg 1.3 bug) */
-       _cinfo->event_mgr = 0;
+       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, xyz->opj_image ());
 
-       _cio = opj_cio_open ((opj_common_ptr) _cinfo, 0, 0);
-       if (_cio == 0) {
-               throw EncodeError ("could not open JPEG2000 stream");
+       opj_cio_t* cio = opj_cio_open ((opj_common_ptr) cinfo, 0, 0);
+       if (cio == 0) {
+               opj_destroy_compress (cinfo);
+               throw EncodeError (N_("could not open JPEG2000 stream"));
        }
 
-       int const r = opj_encode (_cinfo, _cio, _image, 0);
+       int const r = opj_encode (cinfo, cio, xyz->opj_image(), 0);
        if (r == 0) {
-               throw EncodeError ("JPEG2000 encoding failed");
+               opj_cio_close (cio);
+               opj_destroy_compress (cinfo);
+               throw EncodeError (N_("JPEG2000 encoding failed"));
        }
 
-       _log->log (String::compose ("Finished locally-encoded frame %1", _frame));
-       
-       return shared_ptr<EncodedData> (new LocallyEncodedData (_cio->buffer, cio_tell (_cio)));
+       switch (_eyes) {
+       case EYES_BOTH:
+               _log->log (String::compose (N_("Finished locally-encoded frame %1 for mono"), _frame));
+               break;
+       case EYES_LEFT:
+               _log->log (String::compose (N_("Finished locally-encoded frame %1 for L"), _frame));
+               break;
+       case EYES_RIGHT:
+               _log->log (String::compose (N_("Finished locally-encoded frame %1 for R"), _frame));
+               break;
+       default:
+               break;
+       }
+
+       shared_ptr<EncodedData> enc (new LocallyEncodedData (cio->buffer, cio_tell (cio)));
+
+       opj_cio_close (cio);
+       free (parameters.cp_comment);
+       opj_destroy_compress (cinfo);
+
+       return enc;
 }
 
 /** Send this frame to a remote server for J2K encoding, then read the result.
@@ -305,100 +252,135 @@ DCPVideoFrame::encode_locally ()
  *  @return Encoded data.
  */
 shared_ptr<EncodedData>
-DCPVideoFrame::encode_remotely (ServerDescription const * serv)
+DCPVideoFrame::encode_remotely (ServerDescription serv)
 {
        boost::asio::io_service io_service;
        boost::asio::ip::tcp::resolver resolver (io_service);
-       boost::asio::ip::tcp::resolver::query query (serv->host_name(), boost::lexical_cast<string> (Config::instance()->server_port ()));
+       boost::asio::ip::tcp::resolver::query query (serv.host_name(), boost::lexical_cast<string> (Config::instance()->server_port ()));
        boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve (query);
 
        shared_ptr<Socket> socket (new Socket);
 
-       socket->connect (*endpoint_iterator, 30);
-
-       stringstream s;
-       s << "encode please\n"
-         << "input_width " << _input->size().width << "\n"
-         << "input_height " << _input->size().height << "\n"
-         << "input_pixel_format " << _input->pixel_format() << "\n"
-         << "output_width " << _out_size.width << "\n"
-         << "output_height " << _out_size.height << "\n"
-         << "padding " <<  _padding << "\n"
-         << "subtitle_offset " << _subtitle_offset << "\n"
-         << "subtitle_scale " << _subtitle_scale << "\n"
-         << "scaler " << _scaler->id () << "\n"
-         << "frame " << _frame << "\n"
-         << "frames_per_second " << _frames_per_second << "\n";
-
-       if (!_post_process.empty()) {
-               s << "post_process " << _post_process << "\n";
-       }
-       
-       s << "colour_lut " << _colour_lut << "\n"
-         << "j2k_bandwidth " << _j2k_bandwidth << "\n";
-
-       if (_subtitle) {
-               s << "subtitle_x " << _subtitle->position().x << "\n"
-                 << "subtitle_y " << _subtitle->position().y << "\n"
-                 << "subtitle_width " << _subtitle->image()->size().width << "\n"
-                 << "subtitle_height " << _subtitle->image()->size().height << "\n";
-       }
+       socket->connect (*endpoint_iterator);
+
+       xmlpp::Document doc;
+       xmlpp::Element* root = doc.create_root_node ("EncodingRequest");
+
+       root->add_child("Version")->add_child_text (lexical_cast<string> (SERVER_LINK_VERSION));
+       root->add_child("Width")->add_child_text (lexical_cast<string> (_image->size().width));
+       root->add_child("Height")->add_child_text (lexical_cast<string> (_image->size().height));
+       add_metadata (root);
+
+       stringstream xml;
+       doc.write_to_stream (xml, "UTF-8");
 
        _log->log (String::compose (
-                          "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]
+                          N_("Sending to remote; pixel format %1, components %2, lines (%3,%4,%5), line sizes (%6,%7,%8)"),
+                          _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 (xml.str().length() + 1);
+       socket->write ((uint8_t *) xml.str().c_str(), xml.str().length() + 1);
+
+       _image->write_to_socket (socket);
+
+       shared_ptr<EncodedData> e (new RemotelyEncodedData (socket->read_uint32 ()));
+       socket->read (e->data(), e->size());
+
+       _log->log (String::compose (N_("Finished remotely-encoded frame %1"), _frame));
        
-       socket->write ((uint8_t *) s.str().c_str(), s.str().length() + 1, 30);
+       return e;
+}
 
-       _input->write_to_socket (socket);
-       if (_subtitle) {
-               _subtitle->image()->write_to_socket (socket);
+void
+DCPVideoFrame::add_metadata (xmlpp::Element* el) const
+{
+       el->add_child("Frame")->add_child_text (lexical_cast<string> (_frame));
+
+       switch (_eyes) {
+       case EYES_BOTH:
+               el->add_child("Eyes")->add_child_text ("Both");
+               break;
+       case EYES_LEFT:
+               el->add_child("Eyes")->add_child_text ("Left");
+               break;
+       case EYES_RIGHT:
+               el->add_child("Eyes")->add_child_text ("Right");
+               break;
+       default:
+               assert (false);
        }
+       
+       _conversion.as_xml (el->add_child("ColourConversion"));
+
+       el->add_child("FramesPerSecond")->add_child_text (lexical_cast<string> (_frames_per_second));
+       el->add_child("J2KBandwidth")->add_child_text (lexical_cast<string> (_j2k_bandwidth));
+}
 
-       char buffer[32];
-       socket->read_indefinite ((uint8_t *) buffer, sizeof (buffer), 30);
-       socket->consume (strlen (buffer) + 1);
-       shared_ptr<EncodedData> e (new RemotelyEncodedData (atoi (buffer)));
+EncodedData::EncodedData (int s)
+       : _data (new uint8_t[s])
+       , _size (s)
+{
 
-       /* now read the rest */
-       socket->read_definite_and_consume (e->data(), e->size(), 30);
+}
 
-       _log->log (String::compose ("Finished remotely-encoded frame %1", _frame));
+EncodedData::EncodedData (string file)
+{
+       _size = boost::filesystem::file_size (file);
+       _data = new uint8_t[_size];
+
+       FILE* f = fopen (file.c_str(), N_("rb"));
+       if (!f) {
+               throw FileError (_("could not open file for reading"), file);
+       }
        
-       return e;
+       size_t const r = fread (_data, 1, _size, f);
+       if (r != size_t (_size)) {
+               fclose (f);
+               throw FileError (_("could not read encoded data"), file);
+       }
+               
+       fclose (f);
+}
+
+
+EncodedData::~EncodedData ()
+{
+       delete[] _data;
 }
 
 /** Write this data to a J2K file.
- *  @param opt Options.
- *  @param frame Frame index.
+ *  @param Film Film.
+ *  @param frame DCP frame index.
  */
 void
-EncodedData::write (shared_ptr<const EncodeOptions> opt, SourceFrame frame)
+EncodedData::write (shared_ptr<const Film> film, int frame, Eyes eyes) const
 {
-       string const tmp_j2k = opt->frame_out_path (frame, true);
+       string const tmp_j2c = film->j2c_path (frame, eyes, true);
 
-       FILE* f = fopen (tmp_j2k.c_str (), "wb");
+       FILE* f = fopen (tmp_j2c.c_str (), N_("wb"));
        
        if (!f) {
-               throw WriteFileError (tmp_j2k, errno);
+               throw WriteFileError (tmp_j2c, errno);
        }
 
        fwrite (_data, 1, _size, f);
        fclose (f);
 
-       string const real_j2k = opt->frame_out_path (frame, false);
+       string const real_j2c = film->j2c_path (frame, eyes, false);
 
        /* Rename the file from foo.j2c.tmp to foo.j2c now that it is complete */
-       boost::filesystem::rename (tmp_j2k, real_j2k);
+       boost::filesystem::rename (tmp_j2c, real_j2c);
+}
 
-       /* Write a file containing the hash */
-       string const hash = opt->hash_out_path (frame, false);
-       ofstream h (hash.c_str());
-       h << md5_digest (_data, _size) << "\n";
-       h.close ();
+void
+EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, libdcp::FrameInfo fin) const
+{
+       string const info = film->info_path (frame, eyes);
+       ofstream h (info.c_str());
+       fin.write (h);
 }
 
 /** Send this data to a socket.
@@ -407,20 +389,19 @@ EncodedData::write (shared_ptr<const EncodeOptions> opt, SourceFrame frame)
 void
 EncodedData::send (shared_ptr<Socket> socket)
 {
-       stringstream s;
-       s << _size;
-       socket->write ((uint8_t *) s.str().c_str(), s.str().length() + 1, 30);
-       socket->write (_data, _size, 30);
+       socket->write (_size);
+       socket->write (_data, _size);
 }
 
-/** @param s Size of data in bytes */
-RemotelyEncodedData::RemotelyEncodedData (int s)
-       : EncodedData (new uint8_t[s], s)
+LocallyEncodedData::LocallyEncodedData (uint8_t* d, int s)
+       : EncodedData (s)
 {
-
+       memcpy (_data, d, s);
 }
 
-RemotelyEncodedData::~RemotelyEncodedData ()
+/** @param s Size of data in bytes */
+RemotelyEncodedData::RemotelyEncodedData (int s)
+       : EncodedData (s)
 {
-       delete[] _data;
+
 }
index 134720da8d5fdba78b80b9dd1749498bc51ebf6c..bf0b7f8c4614da1237f91adada7e30ba002a907f 100644 (file)
 */
 
 #include <openjpeg.h>
+#include <libdcp/picture_asset.h>
+#include <libdcp/picture_asset_writer.h>
 #include "util.h"
 
 /** @file  src/dcp_video_frame.h
  *  @brief A single frame of video destined for a DCP.
  */
 
-class FilmState;
-class EncodeOptions;
+class Film;
 class ServerDescription;
 class Scaler;
 class Image;
@@ -36,21 +37,19 @@ class Subtitle;
 /** @class EncodedData
  *  @brief Container for J2K-encoded data.
  */
-class EncodedData
+class EncodedData : public boost::noncopyable
 {
 public:
-       /** @param d Data (will not be freed by this class, but may be by subclasses)
-        *  @param s Size of data, in bytes.
-        */
-       EncodedData (uint8_t* d, int s)
-               : _data (d)
-               , _size (s)
-       {}
+       /** @param s Size of data, in bytes */
+       EncodedData (int s);
 
-       virtual ~EncodedData () {}
+       EncodedData (std::string f);
+
+       virtual ~EncodedData ();
 
        void send (boost::shared_ptr<Socket> socket);
-       void write (boost::shared_ptr<const EncodeOptions>, SourceFrame);
+       void write (boost::shared_ptr<const Film>, int, Eyes) const;
+       void write_info (boost::shared_ptr<const Film>, int, Eyes, libdcp::FrameInfo) const;
 
        /** @return data */
        uint8_t* data () const {
@@ -64,7 +63,7 @@ public:
 
 protected:
        uint8_t* _data; ///< data
-       int _size;      ///< data size in bytes
+       int _size;      ///< data size in bytes
 };
 
 /** @class LocallyEncodedData
@@ -75,12 +74,10 @@ protected:
 class LocallyEncodedData : public EncodedData
 {
 public:
-       /** @param d Data (which will not be freed by this class)
+       /** @param d Data (which will be copied by this class)
         *  @param s Size of data, in bytes.
         */
-       LocallyEncodedData (uint8_t* d, int s)
-               : EncodedData (d, s)
-       {}
+       LocallyEncodedData (uint8_t* d, int s);
 };
 
 /** @class RemotelyEncodedData
@@ -91,7 +88,6 @@ class RemotelyEncodedData : public EncodedData
 {
 public:
        RemotelyEncodedData (int s);
-       ~RemotelyEncodedData ();
 };
 
 /** @class DCPVideoFrame
@@ -103,44 +99,33 @@ public:
  *  Objects of this class are used for the queue that we keep
  *  of images that require encoding.
  */
-class DCPVideoFrame
+class DCPVideoFrame : public boost::noncopyable
 {
 public:
-       DCPVideoFrame (
-               boost::shared_ptr<const Image>, boost::shared_ptr<Subtitle>, Size,
-               int, int, float, Scaler const *, SourceFrame, float, std::string, int, int, Log *
-               );
-       
-       virtual ~DCPVideoFrame ();
+       DCPVideoFrame (boost::shared_ptr<const Image>, int, Eyes, ColourConversion, int, int, boost::shared_ptr<Log>);
+       DCPVideoFrame (boost::shared_ptr<const Image>, boost::shared_ptr<const cxml::Node>, boost::shared_ptr<Log>);
 
        boost::shared_ptr<EncodedData> encode_locally ();
-       boost::shared_ptr<EncodedData> encode_remotely (ServerDescription const *);
+       boost::shared_ptr<EncodedData> encode_remotely (ServerDescription);
 
-       SourceFrame frame () const {
+       Eyes eyes () const {
+               return _eyes;
+       }
+       
+       int frame () const {
                return _frame;
        }
        
 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
-       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
-       SourceFrame _frame;              ///< frame index within the Film's source
-       int _frames_per_second;          ///< Frames per second that we will use for the DCP (rounded)
-       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
-
-       Log* _log; ///< log
-
-       opj_image_cmptparm_t _cmptparm[3]; ///< libopenjpeg's opj_image_cmptparm_t
-       opj_image* _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
+
+       void add_metadata (xmlpp::Element *) const;
+       
+       boost::shared_ptr<const Image> _image;
+       int _frame;                      ///< frame index within the DCP's intrinsic duration
+       Eyes _eyes;
+       ColourConversion _conversion;
+       int _frames_per_second;          ///< Frames per second that we will use for the DCP
+       int _j2k_bandwidth;              ///< J2K bandwidth to use
+
+       boost::shared_ptr<Log> _log; ///< log
 };
index 7066b488e6507352320c06767caaa216218c0275..3f4cda6eb5a4345595410fe475314f3d24881bd7 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 "job.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"
 
-using std::string;
-using std::stringstream;
-using std::min;
-using std::pair;
-using std::list;
+#include "i18n.h"
+
 using boost::shared_ptr;
-using boost::optional;
 
 /** @param f Film.
- *  @param o Options.
- *  @param j Job that we are running within, or 0
+ *  @param o Decode options.
  */
-Decoder::Decoder (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j)
+Decoder::Decoder (shared_ptr<const Film> f)
        : _film (f)
-       , _opt (o)
-       , _job (j)
-{
-       _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 ("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 ("decoder does not support seek");
 }
index 3908afa2fbfed522b24266846925fad693ee374f..d67592ed812544c644b8766bcb1b1be1c03e84de 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 <boost/weak_ptr.hpp>
+#include <boost/utility.hpp>
 
-class Job;
-class DecodeOptions;
-class Image;
-class Log;
-class DelayLine;
-class TimedSubtitle;
-class Subtitle;
-class FilterGraph;
+class Film;
 
 /** @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
+class Decoder : public boost::noncopyable
 {
 public:
-       Decoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+       Decoder (boost::shared_ptr<const Film>);
        virtual ~Decoder () {}
 
-       virtual bool pass () = 0;
-       virtual bool seek (double);
-       virtual bool seek_to_last ();
-
-       boost::signals2::signal<void()> OutputChanged;
+       /** Perform one decode pass of the content, which may or may not
+        *  cause the object to emit some data.
+        */
+       virtual void pass () = 0;
+       virtual bool done () const = 0;
 
 protected:
-       /** our Film */
-       boost::shared_ptr<Film> _film;
-       /** our options */
-       boost::shared_ptr<const DecodeOptions> _opt;
-       /** associated Job, or 0 */
-       Job* _job;
 
-private:
-       virtual void film_changed (Film::Property) {}
+       virtual void flush () {};
        
-       boost::signals2::scoped_connection _film_connection;
+       /** The Film that we are decoding in */
+       boost::weak_ptr<const Film> _film;
 };
 
 #endif
diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc
deleted file mode 100644 (file)
index 2a0d828..0000000
+++ /dev/null
@@ -1,56 +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 "external_audio_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, shared_ptr<const DecodeOptions> o, Job* j
-       )
-{
-       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, j)),
-                       shared_ptr<AudioDecoder> (new ExternalAudioDecoder (f, o, j))
-                       );
-       }
-
-       shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (f, o, j));
-       if (f->use_content_audio()) {
-               return Decoders (fd, fd);
-       }
-
-       return Decoders (fd, shared_ptr<AudioDecoder> (new ExternalAudioDecoder (f, o, j)));
-}
diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h
deleted file mode 100644 (file)
index 47d977c..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.
- */
-
-class Film;
-class DecodeOptions;
-class Job;
-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>, boost::shared_ptr<const DecodeOptions>, Job *
-       );
-
-#endif
diff --git a/src/lib/delay_line.cc b/src/lib/delay_line.cc
deleted file mode 100644 (file)
index 45d8e9d..0000000
+++ /dev/null
@@ -1,102 +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 channels Number of channels of audio.
- *  @param frames Delay in frames, +ve to move audio later.
- */
-DelayLine::DelayLine (Log* log, int channels, int frames)
-       : AudioProcessor (log)
-       , _negative_delay_remaining (0)
-       , _frames (frames)
-{
-       if (_frames > 0) {
-               /* We need a buffer to keep some data in */
-               _buffers.reset (new AudioBuffers (channels, _frames));
-               _buffers->make_silent ();
-       } else if (_frames < 0) {
-               /* We can do -ve delays just by chopping off
-                  the start, so no buffer needed.
-               */
-               _negative_delay_remaining = -_frames;
-       }
-}
-
-void
-DelayLine::process_audio (shared_ptr<AudioBuffers> data)
-{
-       if (_buffers) {
-               /* We have some buffers, so we are moving the audio later */
-
-               /* Copy the input data */
-               AudioBuffers input (*data.get ());
-
-               int to_do = data->frames ();
-
-               /* Write some of our buffer to the output */
-               int const from_buffer = min (to_do, _buffers->frames());
-               data->copy_from (_buffers.get(), from_buffer, 0, 0);
-               to_do -= from_buffer;
-
-               /* Write some of the input to the output */
-               int const from_input = to_do;
-               data->copy_from (&input, from_input, 0, from_buffer);
-
-               int const left_in_buffer = _buffers->frames() - from_buffer;
-
-               /* Shuffle our buffer down */
-               _buffers->move (from_buffer, 0, left_in_buffer);
-
-               /* Copy remaining input data to our buffer */
-               _buffers->copy_from (&input, input.frames() - from_input, from_input, left_in_buffer);
-
-       } else {
-
-               /* Chop the initial data off until _negative_delay_remaining
-                  is zero, then just pass data.
-               */
-
-               int const to_do = min (data->frames(), _negative_delay_remaining);
-               if (to_do) {
-                       data->move (to_do, 0, data->frames() - to_do);
-                       data->set_frames (data->frames() - to_do);
-                       _negative_delay_remaining -= to_do;
-               }
-       }
-
-       Audio (data);
-}
-
-void
-DelayLine::process_end ()
-{
-       if (_frames < 0) {
-               _buffers->make_silent ();
-               Audio (_buffers);
-       }
-}
diff --git a/src/lib/delay_line.h b/src/lib/delay_line.h
deleted file mode 100644 (file)
index fa2870a..0000000
+++ /dev/null
@@ -1,38 +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"
-
-class AudioBuffers;
-
-/** A delay line for audio */
-class DelayLine : public AudioProcessor
-{
-public:
-       DelayLine (Log* log, int channels, int frames);
-       
-       void process_audio (boost::shared_ptr<AudioBuffers>);
-       void process_end ();
-
-private:
-       boost::shared_ptr<AudioBuffers> _buffers;
-       int _negative_delay_remaining; ///< number of frames of negative delay that remain to emit
-       int _frames;
-};
index 262e57bc75e6caa14c9b5e5cc1204e2e4200ae5a..162626ff06435c65998d284d6f9307aa63f15b21 100644 (file)
 
 #include "dolby_cp750.h"
 
+#include "i18n.h"
+
 using namespace std;
 
 DolbyCP750::DolbyCP750 ()
-       : SoundProcessor ("dolby_cp750", "Dolby CP750")
+       : SoundProcessor ("dolby_cp750", _("Dolby CP750"))
 {
 
 }
index efedfcfef76e18bd30923a3ca193ac25a19a3129..35ebfb52e1fc089d88383e5d0558b6719fa4f3c9 100644 (file)
  */
 
 #include <iostream>
-#include <boost/filesystem.hpp>
-#include <boost/lexical_cast.hpp>
 #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 "cross.h"
+#include "writer.h"
+
+#include "i18n.h"
 
 using std::pair;
 using std::string;
@@ -42,187 +40,108 @@ using std::stringstream;
 using std::vector;
 using std::list;
 using std::cout;
+using std::min;
 using std::make_pair;
-using namespace boost;
+using boost::shared_ptr;
+using boost::optional;
 
 int const Encoder::_history_size = 25;
 
-/** @param f Film that we are encoding.
- *  @param o Options.
- */
-Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o)
+/** @param f Film that we are encoding */
+Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<Job> j)
        : _film (f)
-       , _opt (o)
-       , _just_skipped (false)
-       , _video_frame (0)
-       , _audio_frame (0)
-#ifdef HAVE_SWRESAMPLE   
-       , _swr_context (0)
-#endif   
-       , _audio_frames_written (0)
-       , _process_end (false)
+       , _job (j)
+       , _video_frames_out (0)
+       , _state (TRANSCODING)
+       , _terminate (false)
 {
-       if (_film->audio_stream()) {
-               /* Create sound output files with .tmp suffixes; we will rename
-                  them if and when we complete.
-               */
-               for (int i = 0; i < dcp_audio_channels (_film->audio_channels()); ++i) {
-                       SF_INFO sf_info;
-                       sf_info.samplerate = dcp_audio_sample_rate (_film->audio_stream()->sample_rate());
-                       /* We write mono files */
-                       sf_info.channels = 1;
-                       sf_info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24;
-                       SNDFILE* f = sf_open (_opt->multichannel_audio_out_path (i, true).c_str (), SFM_WRITE, &sf_info);
-                       if (f == 0) {
-                               throw CreateFileError (_opt->multichannel_audio_out_path (i, true));
-                       }
-                       _sound_files.push_back (f);
-               }
-       }
+       _have_a_real_frame[EYES_BOTH] = false;
+       _have_a_real_frame[EYES_LEFT] = false;
+       _have_a_real_frame[EYES_RIGHT] = false;
 }
 
 Encoder::~Encoder ()
 {
-       close_sound_files ();
-       terminate_worker_threads ();
+       terminate_threads ();
+       if (_writer) {
+               _writer->finish ();
+       }
 }
 
 void
 Encoder::process_begin ()
 {
-       if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate()) {
-#ifdef HAVE_SWRESAMPLE
-
-               stringstream s;
-               s << "Will resample audio from " << _film->audio_stream()->sample_rate() << " to " << _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) {
-               _worker_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, (ServerDescription *) 0)));
+               _threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, optional<ServerDescription> ())));
        }
 
-       vector<ServerDescription*> servers = Config::instance()->servers ();
+       vector<ServerDescription> servers = Config::instance()->servers ();
 
-       for (vector<ServerDescription*>::iterator i = servers.begin(); i != servers.end(); ++i) {
-               for (int j = 0; j < (*i)->threads (); ++j) {
-                       _worker_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, *i)));
+       for (vector<ServerDescription>::iterator i = servers.begin(); i != servers.end(); ++i) {
+               for (int j = 0; j < i->threads (); ++j) {
+                       _threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, *i)));
                }
        }
+
+       _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
+       boost::mutex::scoped_lock lock (_mutex);
 
-       if (_film->audio_stream()) {
-               close_sound_files ();
-               
-               /* Rename .wav.tmp files to .wav */
-               for (int i = 0; i < dcp_audio_channels (_film->audio_channels()); ++i) {
-                       if (boost::filesystem::exists (_opt->multichannel_audio_out_path (i, false))) {
-                               boost::filesystem::remove (_opt->multichannel_audio_out_path (i, false));
-                       }
-                       boost::filesystem::rename (_opt->multichannel_audio_out_path (i, true), _opt->multichannel_audio_out_path (i, false));
-               }
-       }
-
-       boost::mutex::scoped_lock lock (_worker_mutex);
-
-       _film->log()->log ("Clearing queue of " + lexical_cast<string> (_queue.size ()));
+       _film->log()->log (String::compose (N_("Clearing queue of %1"), _queue.size ()));
 
        /* Keep waking workers until the queue is empty */
        while (!_queue.empty ()) {
-               _film->log()->log ("Waking with " + lexical_cast<string> (_queue.size ()), Log::VERBOSE);
-               _worker_condition.notify_all ();
-               _worker_condition.wait (lock);
+               _film->log()->log (String::compose (N_("Waking with %1"), _queue.size ()), Log::VERBOSE);
+               _condition.notify_all ();
+               _condition.wait (lock);
        }
 
        lock.unlock ();
        
-       terminate_worker_threads ();
+       terminate_threads ();
 
-       _film->log()->log ("Mopping up " + lexical_cast<string> (_queue.size()));
+       _film->log()->log (String::compose (N_("Mopping up %1"), _queue.size()));
 
        /* The following sequence of events can occur in the above code:
             1. a remote worker takes the last image off the queue
             2. the loop above terminates
             3. the remote worker fails to encode the image and puts it back on the queue
-            4. the remote worker is then terminated by terminate_worker_threads
+            4. the remote worker is then terminated by terminate_threads
 
             So just mop up anything left in the queue here.
        */
 
        for (list<shared_ptr<DCPVideoFrame> >::iterator i = _queue.begin(); i != _queue.end(); ++i) {
-               _film->log()->log (String::compose ("Encode left-over frame %1", (*i)->frame ()));
+               _film->log()->log (String::compose (N_("Encode left-over frame %1"), (*i)->frame ()));
                try {
-                       shared_ptr<EncodedData> e = (*i)->encode_locally ();
-                       e->write (_opt, (*i)->frame ());
+                       _writer->write ((*i)->encode_locally(), (*i)->frame (), (*i)->eyes ());
                        frame_done ();
                } catch (std::exception& e) {
-                       _film->log()->log (String::compose ("Local encode failed (%1)", e.what ()));
+                       _film->log()->log (String::compose (N_("Local encode failed (%1)"), e.what ()));
                }
        }
 
-       /* Now do links (or copies on windows) to duplicate frames */
-       for (list<pair<int, int> >::iterator i = _links_required.begin(); i != _links_required.end(); ++i) {
-               link (_opt->frame_out_path (i->first, false), _opt->frame_out_path (i->second, false));
-               link (_opt->hash_out_path (i->first, false), _opt->hash_out_path (i->second, false));
+       {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               _state = HASHING;
        }
+               
+       _writer->finish ();
+       _writer.reset ();
 }      
 
 /** @return an estimate of the current number of frames we are encoding per second,
  *  or 0 if not known.
  */
 float
-Encoder::current_frames_per_second () const
+Encoder::current_encoding_rate () const
 {
-       boost::mutex::scoped_lock lock (_history_mutex);
+       boost::mutex::scoped_lock lock (_state_mutex);
        if (int (_time_history.size()) < _history_size) {
                return 0;
        }
@@ -233,20 +152,12 @@ Encoder::current_frames_per_second () const
        return _history_size / (seconds (now) - seconds (_time_history.back ()));
 }
 
-/** @return true if the last frame to be processed was skipped as it already existed */
-bool
-Encoder::skipping () const
-{
-       boost::mutex::scoped_lock (_history_mutex);
-       return _just_skipped;
-}
-
-/** @return Number of video frames that have been received */
-SourceFrame
-Encoder::video_frame () const
+/** @return Number of video frames that have been sent out */
+int
+Encoder::video_frames_out () const
 {
-       boost::mutex::scoped_lock (_history_mutex);
-       return _video_frame;
+       boost::mutex::scoped_lock (_state_mutex);
+       return _video_frames_out;
 }
 
 /** Should be called when a frame has been encoded successfully.
@@ -255,8 +166,7 @@ Encoder::video_frame () const
 void
 Encoder::frame_done ()
 {
-       boost::mutex::scoped_lock lock (_history_mutex);
-       _just_skipped = false;
+       boost::mutex::scoped_lock lock (_state_mutex);
        
        struct timeval tv;
        gettimeofday (&tv, 0);
@@ -266,186 +176,81 @@ Encoder::frame_done ()
        }
 }
 
-/** Called by a subclass when it has just skipped the processing
-    of a frame because it has already been done.
-*/
-void
-Encoder::frame_skipped ()
-{
-       boost::mutex::scoped_lock lock (_history_mutex);
-       _just_skipped = true;
-}
-
 void
-Encoder::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub)
+Encoder::process_video (shared_ptr<const Image> image, Eyes eyes, ColourConversion conversion, bool same)
 {
-       if (_opt->video_skip != 0 && (_video_frame % _opt->video_skip) != 0) {
-               ++_video_frame;
-               return;
-       }
+       boost::mutex::scoped_lock lock (_mutex);
 
-       if (_opt->video_range) {
-               pair<SourceFrame, SourceFrame> const r = _opt->video_range.get();
-               if (_video_frame < r.first || _video_frame >= r.second) {
-                       ++_video_frame;
-                       return;
-               }
-       }
-
-       boost::mutex::scoped_lock lock (_worker_mutex);
+       /* XXX: discard 3D here if required */
 
        /* Wait until the queue has gone down a bit */
-       while (_queue.size() >= _worker_threads.size() * 2 && !_process_end) {
+       while (_queue.size() >= _threads.size() * 2 && !_terminate) {
                TIMING ("decoder sleeps with queue of %1", _queue.size());
-               _worker_condition.wait (lock);
+               _condition.wait (lock);
                TIMING ("decoder wakes with queue of %1", _queue.size());
        }
 
-       if (_process_end) {
+       if (_terminate) {
                return;
        }
 
-       /* Only do the processing if we don't already have a file for this frame */
-       if (boost::filesystem::exists (_opt->frame_out_path (_video_frame, false))) {
-               frame_skipped ();
-               return;
+       if (_writer->thrown ()) {
+               _writer->rethrow ();
        }
 
-       if (same && _last_real_frame) {
-               /* Use the last frame that we encoded.  We need to postpone doing the actual link,
-                  as on windows the link is really a copy and the reference frame might not have
-                  finished encoding yet.
-               */
-               _links_required.push_back (make_pair (_last_real_frame.get(), _video_frame));
+       if (_writer->can_fake_write (_video_frames_out)) {
+               _writer->fake_write (_video_frames_out, eyes);
+               _have_a_real_frame[eyes] = false;
+               frame_done ();
+       } else if (same && _have_a_real_frame[eyes]) {
+               /* Use the last frame that we encoded. */
+               _writer->repeat (_video_frames_out, eyes);
+               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> (
+               _queue.push_back (shared_ptr<DCPVideoFrame> (
                                          new DCPVideoFrame (
-                                                 image, sub, _opt->out_size, _opt->padding, _film->subtitle_offset(), _film->subtitle_scale(),
-                                                 _film->scaler(), _video_frame, _film->frames_per_second(), s.second,
-                                                 _film->colour_lut(), _film->j2k_bandwidth(),
-                                                 _film->log()
+                                                 image, _video_frames_out, eyes, conversion, _film->video_frame_rate(),
+                                                 _film->j2k_bandwidth(), _film->log()
                                                  )
                                          ));
                
-               _worker_condition.notify_all ();
-               _last_real_frame = _video_frame;
-       }
-
-       ++_video_frame;
-}
-
-void
-Encoder::process_audio (shared_ptr<AudioBuffers> data)
-{
-       if (_opt->audio_range) {
-               shared_ptr<AudioBuffers> trimmed (new AudioBuffers (*data.get ()));
-               
-               /* Range that we are encoding */
-               pair<int64_t, int64_t> required_range = _opt->audio_range.get();
-               /* Range of this block of data */
-               pair<int64_t, int64_t> this_range (_audio_frame, _audio_frame + trimmed->frames());
-
-               if (this_range.second < required_range.first || required_range.second < this_range.first) {
-                       /* No part of this audio is within the required range */
-                       return;
-               } else if (required_range.first >= this_range.first && required_range.first < this_range.second) {
-                       /* Trim start */
-                       int64_t const shift = required_range.first - this_range.first;
-                       trimmed->move (shift, 0, trimmed->frames() - shift);
-                       trimmed->set_frames (trimmed->frames() - shift);
-               } else if (required_range.second >= this_range.first && required_range.second < this_range.second) {
-                       /* Trim end */
-                       trimmed->set_frames (required_range.second - this_range.first);
-               }
-
-               data = trimmed;
+               _condition.notify_all ();
+               _have_a_real_frame[eyes] = true;
        }
 
-#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;
+       if (eyes != EYES_LEFT) {
+               ++_video_frames_out;
        }
-#endif
-
-       if (_film->audio_channels() == 1) {
-               /* We need to switch things around so that the mono channel is on
-                  the centre channel of a 5.1 set (with other channels silent).
-               */
-
-               shared_ptr<AudioBuffers> b (new AudioBuffers (6, data->frames ()));
-               b->make_silent (libdcp::LEFT);
-               b->make_silent (libdcp::RIGHT);
-               memcpy (b->data()[libdcp::CENTRE], data->data()[0], data->frames() * sizeof(float));
-               b->make_silent (libdcp::LFE);
-               b->make_silent (libdcp::LS);
-               b->make_silent (libdcp::RS);
-
-               data = b;
-       }
-
-       write_audio (data);
-       
-       _audio_frame += data->frames ();
 }
 
 void
-Encoder::write_audio (shared_ptr<const AudioBuffers> audio)
+Encoder::process_audio (shared_ptr<const AudioBuffers> data)
 {
-       for (int i = 0; i < audio->channels(); ++i) {
-               sf_write_float (_sound_files[i], audio->data(i), audio->frames());
-       }
-
-       _audio_frames_written += audio->frames ();
+       _writer->write (data);
 }
 
 void
-Encoder::close_sound_files ()
+Encoder::terminate_threads ()
 {
-       for (vector<SNDFILE*>::iterator i = _sound_files.begin(); i != _sound_files.end(); ++i) {
-               sf_close (*i);
-       }
-
-       _sound_files.clear ();
-}      
-
-void
-Encoder::terminate_worker_threads ()
-{
-       boost::mutex::scoped_lock lock (_worker_mutex);
-       _process_end = true;
-       _worker_condition.notify_all ();
+       boost::mutex::scoped_lock lock (_mutex);
+       _terminate = true;
+       _condition.notify_all ();
        lock.unlock ();
 
-       for (list<boost::thread *>::iterator i = _worker_threads.begin(); i != _worker_threads.end(); ++i) {
-               (*i)->join ();
+       for (list<boost::thread *>::iterator i = _threads.begin(); i != _threads.end(); ++i) {
+               if ((*i)->joinable ()) {
+                       (*i)->join ();
+               }
                delete *i;
        }
+
+       _threads.clear ();
 }
 
 void
-Encoder::encoder_thread (ServerDescription* server)
+Encoder::encoder_thread (optional<ServerDescription> server)
 {
        /* Number of seconds that we currently wait between attempts
           to connect to the server; not relevant for localhost
@@ -456,18 +261,18 @@ Encoder::encoder_thread (ServerDescription* server)
        while (1) {
 
                TIMING ("encoder thread %1 sleeps", boost::this_thread::get_id());
-               boost::mutex::scoped_lock lock (_worker_mutex);
-               while (_queue.empty () && !_process_end) {
-                       _worker_condition.wait (lock);
+               boost::mutex::scoped_lock lock (_mutex);
+               while (_queue.empty () && !_terminate) {
+                       _condition.wait (lock);
                }
 
-               if (_process_end) {
+               if (_terminate) {
                        return;
                }
 
                TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size());
-               boost::shared_ptr<DCPVideoFrame> vf = _queue.front ();
-               _film->log()->log (String::compose ("Encoder thread %1 pops frame %2 from queue", boost::this_thread::get_id(), vf->frame()), Log::VERBOSE);
+               shared_ptr<DCPVideoFrame> vf = _queue.front ();
+               _film->log()->log (String::compose (N_("Encoder thread %1 pops frame %2 (%3) from queue"), boost::this_thread::get_id(), vf->frame(), vf->eyes ()));
                _queue.pop_front ();
                
                lock.unlock ();
@@ -476,10 +281,10 @@ Encoder::encoder_thread (ServerDescription* server)
 
                if (server) {
                        try {
-                               encoded = vf->encode_remotely (server);
+                               encoded = vf->encode_remotely (server.get ());
 
                                if (remote_backoff > 0) {
-                                       _film->log()->log (String::compose ("%1 was lost, but now she is found; removing backoff", server->host_name ()));
+                                       _film->log()->log (String::compose (N_("%1 was lost, but now she is found; removing backoff"), server->host_name ()));
                                }
                                
                                /* This job succeeded, so remove any backoff */
@@ -492,7 +297,7 @@ Encoder::encoder_thread (ServerDescription* server)
                                }
                                _film->log()->log (
                                        String::compose (
-                                               "Remote encode of %1 on %2 failed (%3); thread sleeping for %4s",
+                                               N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"),
                                                vf->frame(), server->host_name(), e.what(), remote_backoff)
                                        );
                        }
@@ -503,42 +308,27 @@ Encoder::encoder_thread (ServerDescription* server)
                                encoded = vf->encode_locally ();
                                TIMING ("encoder thread %1 finishes local encode of %2", boost::this_thread::get_id(), vf->frame());
                        } catch (std::exception& e) {
-                               _film->log()->log (String::compose ("Local encode failed (%1)", e.what ()));
+                               _film->log()->log (String::compose (N_("Local encode failed (%1)"), e.what ()));
                        }
                }
 
                if (encoded) {
-                       encoded->write (_opt, vf->frame ());
+                       _writer->write (encoded, vf->frame (), vf->eyes ());
                        frame_done ();
                } else {
                        lock.lock ();
                        _film->log()->log (
-                               String::compose ("Encoder thread %1 pushes frame %2 back onto queue after failure", boost::this_thread::get_id(), vf->frame())
+                               String::compose (N_("Encoder thread %1 pushes frame %2 back onto queue after failure"), boost::this_thread::get_id(), vf->frame())
                                );
                        _queue.push_front (vf);
                        lock.unlock ();
                }
 
                if (remote_backoff > 0) {
-                       dvdomatic_sleep (remote_backoff);
+                       dcpomatic_sleep (remote_backoff);
                }
 
                lock.lock ();
-               _worker_condition.notify_all ();
+               _condition.notify_all ();
        }
 }
-
-void
-Encoder::link (string a, string b) const
-{
-#ifdef DVDOMATIC_POSIX                 
-       int const r = symlink (a.c_str(), b.c_str());
-       if (r) {
-               throw EncodeError (String::compose ("could not create symlink from %1 to %2", a, b));
-       }
-#endif
-       
-#ifdef DVDOMATIC_WINDOWS
-       boost::filesystem::copy_file (a, b);
-#endif                 
-}
index 52ccfc166833f60a2e0e9a6efe4583092f15ad54..e9b30df9e558cbf60c00f54e612869d7e2887b09 100644 (file)
 
 */
 
-#ifndef DVDOMATIC_ENCODER_H
-#define DVDOMATIC_ENCODER_H
+#ifndef DCPOMATIC_ENCODER_H
+#define DCPOMATIC_ENCODER_H
 
 /** @file src/encoder.h
- *  @brief Parent class for classes which can encode video and audio frames.
+ *  @brief Encoder to J2K and WAV for DCP.
  */
 
 #include <boost/shared_ptr.hpp>
 #include <stdint.h>
 extern "C" {
 #include <libavutil/samplefmt.h>
-}
-#ifdef HAVE_SWRESAMPLE
-extern "C" {
 #include <libswresample/swresample.h>
 }
-#endif
-#include <sndfile.h>
 #include "util.h"
-#include "video_sink.h"
-#include "audio_sink.h"
 
-class EncodeOptions;
 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 boost::noncopyable
 {
 public:
-       Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const EncodeOptions> o);
+       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<Image> i, bool same, boost::shared_ptr<Subtitle> s);
+       void process_video (boost::shared_ptr<const Image> i, Eyes eyes, ColourConversion, bool same);
 
        /** Call with some audio data */
-       void process_audio (boost::shared_ptr<AudioBuffers>);
+       void process_audio (boost::shared_ptr<const AudioBuffers>);
 
        /** Called when a processing run has finished */
-       virtual void process_end ();
+       void process_end ();
+
+       float current_encoding_rate () const;
+       int video_frames_out () const;
 
-       float current_frames_per_second () const;
-       bool skipping () const;
-       SourceFrame video_frame () const;
+       enum State {
+               TRANSCODING,
+               HASHING
+       };
 
-protected:
+       State state () const {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               return _state;
+       }
+
+private:
        
        void frame_done ();
-       void frame_skipped ();
        
+       void encoder_thread (boost::optional<ServerDescription>);
+       void terminate_threads ();
+
        /** Film that we are encoding */
        boost::shared_ptr<const Film> _film;
-       /** Options */
-       boost::shared_ptr<const EncodeOptions> _opt;
+       boost::shared_ptr<Job> _job;
 
-       /** Mutex for _time_history, _just_skipped and _last_frame */
-       mutable boost::mutex _history_mutex;
+       /** Mutex for _time_history, _last_frame and _state */
+       mutable boost::mutex _state_mutex;
        /** List of the times of completion of the last _history_size frames;
            first is the most recently completed.
        */
        std::list<struct timeval> _time_history;
        /** Number of frames that we should keep history for */
        static int const _history_size;
-       /** true if the last frame we processed was skipped (because it was already done) */
-       bool _just_skipped;
 
-       /** Number of video frames received so far */
-       SourceFrame _video_frame;
-       /** Number of audio frames received so far */
-       int64_t _audio_frame;
+       /** Number of video frames written for the DCP so far */
+       int _video_frames_out;
+       State _state;
 
-private:
-       void close_sound_files ();
-       void write_audio (boost::shared_ptr<const AudioBuffers> audio);
-
-       void encoder_thread (ServerDescription *);
-       void terminate_worker_threads ();
-       void link (std::string, std::string) const;
-
-#if HAVE_SWRESAMPLE    
-       SwrContext* _swr_context;
-#endif
-
-       /** List of links that we need to create when all frames have been processed;
-        *  such that we need to call link (first, second) for each member of this list.
-        *  In other words, `first' is a `real' frame and `second' should be a link to `first'.
-        */
-       std::list<std::pair<int, int> > _links_required;
-
-       std::vector<SNDFILE*> _sound_files;
-       int64_t _audio_frames_written;
-
-       boost::optional<int> _last_real_frame;
-       bool _process_end;
+       bool _have_a_real_frame[EYES_COUNT];
+       bool _terminate;
        std::list<boost::shared_ptr<DCPVideoFrame> > _queue;
-       std::list<boost::thread *> _worker_threads;
-       mutable boost::mutex _worker_mutex;
-       boost::condition _worker_condition;
+       std::list<boost::thread *> _threads;
+       mutable boost::mutex _mutex;
+       boost::condition _condition;
+
+       boost::shared_ptr<Writer> _writer;
 };
 
 #endif
index a783cde339f646a009d7b4c3da9eea617ace0b16..cbf180ffcb3bf451379e410039d5d28dfed75d80 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 std::cout;
 using boost::shared_ptr;
 
-ExamineContentJob::ExamineContentJob (shared_ptr<Film> f, shared_ptr<Job> req)
-       : Job (f, req)
+ExamineContentJob::ExamineContentJob (shared_ptr<const Film> f, shared_ptr<Content> c)
+       : Job (f)
+       , _content (c)
 {
 
 }
@@ -49,61 +43,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 ());
-               
-               shared_ptr<DecodeOptions> o (new DecodeOptions);
-               o->decode_audio = false;
-               
-               Decoders decoders = decoder_factory (_film, o, this);
-               
-               set_progress_unknown ();
-               while (!decoders.video->pass()) {
-                       /* keep going */
-               }
-               
-               _film->set_length (decoders.video->video_frame());
-               
-               _film->log()->log (String::compose ("Video length examined as %1 frames", _film->length().get()));
-               
-       } else {
-
-               /* Get a quick decoder to get the content's length from its header */
-               
-               shared_ptr<DecodeOptions> o (new DecodeOptions);
-               Decoders d = decoder_factory (_film, o, 0);
-               _film->set_length (d.video->length());
-       
-               _film->log()->log (String::compose ("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 729c287b58fe66488526f02eeff31708d2f7d307..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>, boost::shared_ptr<Job> req);
+       ExamineContentJob (boost::shared_ptr<const Film>, boost::shared_ptr<Content>);
        ~ExamineContentJob ();
 
        std::string name () const;
        void run ();
+
+private:
+       boost::shared_ptr<Content> _content;
 };
 
diff --git a/src/lib/exceptions.cc b/src/lib/exceptions.cc
new file mode 100644 (file)
index 0000000..8144f41
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+    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 "exceptions.h"
+#include "compose.hpp"
+
+#include "i18n.h"
+
+using std::string;
+
+/** @param f File that we were trying to open */
+OpenFileError::OpenFileError (boost::filesystem::path f)
+       : FileError (String::compose (_("could not open file %1"), f.string()), f)
+{
+
+}
+
+/** @param f File that we were trying to create */
+CreateFileError::CreateFileError (boost::filesystem::path f)
+       : FileError (String::compose (_("could not create file %1"), f.string()), f)
+{
+
+}
+
+ReadFileError::ReadFileError (boost::filesystem::path f, int e)
+       : FileError (String::compose (_("could not read from file %1 (%2)"), f.string(), strerror (e)), f)
+{
+
+}
+
+WriteFileError::WriteFileError (boost::filesystem::path f, int e)
+       : FileError (String::compose (_("could not write to file %1 (%2)"), f.string(), strerror (e)), f)
+{
+       
+}
+
+MissingSettingError::MissingSettingError (string s)
+       : SettingError (s, String::compose (_("missing required setting %1"), s))
+{
+
+}
+
+PixelFormatError::PixelFormatError (std::string o, AVPixelFormat f)
+       : StringError (String::compose (_("Cannot handle pixel format %1 during %2"), f, o))
+{
+
+}
index 06177863ab27e5720125c91b0320827c627251b7..b04d973dc7a62197571ce7d6697917f2023b23e0 100644 (file)
 
 */
 
+#ifndef DCPOMATIC_EXCEPTIONS_H
+#define DCPOMATIC_EXCEPTIONS_H
+
 /** @file  src/exceptions.h
  *  @brief Our exceptions.
  */
 
 #include <stdexcept>
-#include <sstream>
 #include <cstring>
+#include <boost/exception/all.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/thread.hpp>
+extern "C" {
+#include <libavutil/pixfmt.h>
+}
 
 /** @class StringError
  *  @brief A parent class for exceptions using messages held in a std::string
@@ -80,7 +88,7 @@ public:
        /** @param m Error message.
         *  @param f Name of the file that this exception concerns.
         */
-       FileError (std::string m, std::string f)
+       FileError (std::string m, boost::filesystem::path f)
                : StringError (m)
                , _file (f)
        {}
@@ -88,13 +96,13 @@ public:
        virtual ~FileError () throw () {}
 
        /** @return name of the file that this exception concerns */
-       std::string file () const {
+       boost::filesystem::path file () const {
                return _file;
        }
 
 private:
        /** name of the file that this exception concerns */
-       std::string _file;
+       boost::filesystem::path _file;
 };
        
 
@@ -105,9 +113,7 @@ class OpenFileError : public FileError
 {
 public:
        /** @param f File that we were trying to open */
-       OpenFileError (std::string f)
-               : FileError ("could not open file " + f, f)
-       {}
+       OpenFileError (boost::filesystem::path f);
 };
 
 /** @class CreateFileError.
@@ -117,9 +123,7 @@ class CreateFileError : public FileError
 {
 public:
        /** @param f File that we were trying to create */
-       CreateFileError (std::string f)
-               : FileError ("could not create file " + f, f)
-       {}
+       CreateFileError (boost::filesystem::path f);
 };
 
 
@@ -132,16 +136,7 @@ public:
        /** @param f File that we were trying to read from.
         *  @param e errno value, or 0.
         */
-       ReadFileError (std::string f, int e = 0)
-               : FileError ("", f)
-       {
-               std::stringstream s;
-               s << "could not read from file " << f;
-               if (e) {
-                       s << " (" << strerror (e) << ")";
-               }
-               _what = s.str ();
-       }
+       ReadFileError (boost::filesystem::path f, int e = 0);
 };
 
 /** @class WriteFileError.
@@ -153,16 +148,7 @@ public:
        /** @param f File that we were trying to write to.
         *  @param e errno value, or 0.
         */
-       WriteFileError (std::string f, int e)
-               : FileError ("", f)
-       {
-               std::stringstream s;
-               s << "could not write to file " << f;
-               if (e) {
-                       s << " (" << strerror (e) << ")";
-               }
-               _what = s.str ();
-       }
+       WriteFileError (boost::filesystem::path f, int e);
 };
 
 /** @class SettingError.
@@ -197,9 +183,7 @@ class MissingSettingError : public SettingError
 {
 public:
        /** @param s Name of setting that was required */
-       MissingSettingError (std::string s)
-               : SettingError (s, "missing required setting " + s)
-       {}
+       MissingSettingError (std::string s);
 };
 
 /** @class BadSettingError
@@ -232,3 +216,38 @@ public:
                : StringError (s)
        {}
 };
+
+class PixelFormatError : public StringError
+{
+public:
+       PixelFormatError (std::string o, AVPixelFormat f);
+};
+
+class ExceptionStore
+{
+public:
+       bool thrown () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _exception;
+       }
+       
+       void rethrow () {
+               boost::mutex::scoped_lock lm (_mutex);
+               boost::rethrow_exception (_exception);
+       }
+
+protected:     
+       
+       void store_current () {
+               boost::mutex::scoped_lock lm (_mutex);
+               _exception = boost::current_exception ();
+       }
+
+private:
+       boost::exception_ptr _exception;
+       mutable boost::mutex _mutex;
+};
+
+       
+
+#endif
diff --git a/src/lib/external_audio_decoder.cc b/src/lib/external_audio_decoder.cc
deleted file mode 100644 (file)
index 25c8068..0000000
+++ /dev/null
@@ -1,186 +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 <sndfile.h>
-#include "external_audio_decoder.h"
-#include "film.h"
-#include "exceptions.h"
-
-using std::vector;
-using std::string;
-using std::stringstream;
-using std::min;
-using std::cout;
-using boost::shared_ptr;
-using boost::optional;
-
-ExternalAudioDecoder::ExternalAudioDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
-       : Decoder (f, o, j)
-       , AudioDecoder (f, o, j)
-{
-       sf_count_t frames;
-       vector<SNDFILE*> sf = open_files (frames);
-       close_files (sf);
-}
-
-vector<SNDFILE*>
-ExternalAudioDecoder::open_files (sf_count_t & frames)
-{
-       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 vector<SNDFILE*> ();
-       }
-
-       bool first = true;
-       frames = 0;
-       
-       vector<SNDFILE*> sndfiles;
-       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");
-                       }
-
-                       if (info.channels != 1) {
-                               throw DecodeError ("external audio files must be mono");
-                       }
-                       
-                       sndfiles.push_back (s);
-
-                       if (first) {
-                               shared_ptr<ExternalAudioStream> st (
-                                       new ExternalAudioStream (
-                                               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");
-                               }
-                       }
-               }
-       }
-
-       return sndfiles;
-}
-
-bool
-ExternalAudioDecoder::pass ()
-{
-       sf_count_t frames;
-       vector<SNDFILE*> sndfiles = open_files (frames);
-       if (sndfiles.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));
-       while (frames > 0) {
-               sf_count_t const this_time = min (block, frames);
-               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), block);
-                       }
-               }
-
-               audio->set_frames (this_time);
-               Audio (audio);
-               frames -= this_time;
-       }
-
-       close_files (sndfiles);
-
-       return true;
-}
-
-void
-ExternalAudioDecoder::close_files (vector<SNDFILE*> const & sndfiles)
-{
-       for (size_t i = 0; i < sndfiles.size(); ++i) {
-               sf_close (sndfiles[i]);
-       }
-}
-
-shared_ptr<ExternalAudioStream>
-ExternalAudioStream::create ()
-{
-       return shared_ptr<ExternalAudioStream> (new ExternalAudioStream);
-}
-
-shared_ptr<ExternalAudioStream>
-ExternalAudioStream::create (string t, optional<int> v)
-{
-       if (!v) {
-               /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
-               return shared_ptr<ExternalAudioStream> ();
-       }
-
-       stringstream s (t);
-       string type;
-       s >> type;
-       if (type != "external") {
-               return shared_ptr<ExternalAudioStream> ();
-       }
-
-       return shared_ptr<ExternalAudioStream> (new ExternalAudioStream (t, v));
-}
-
-ExternalAudioStream::ExternalAudioStream (string t, optional<int> v)
-{
-       assert (v);
-
-       stringstream s (t);
-       string type;
-       s >> type >> _sample_rate >> _channel_layout;
-}
-
-ExternalAudioStream::ExternalAudioStream ()
-{
-
-}
-
-string
-ExternalAudioStream::to_string () const
-{
-       return String::compose ("external %1 %2", _sample_rate, _channel_layout);
-}
diff --git a/src/lib/external_audio_decoder.h b/src/lib/external_audio_decoder.h
deleted file mode 100644 (file)
index 2558955..0000000
+++ /dev/null
@@ -1,54 +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 <sndfile.h>
-#include "decoder.h"
-#include "audio_decoder.h"
-#include "stream.h"
-
-class ExternalAudioStream : public AudioStream
-{
-public:
-       ExternalAudioStream (int sample_rate, int64_t layout)
-               : AudioStream (sample_rate, layout)
-       {}
-                              
-       std::string to_string () const;
-
-       static boost::shared_ptr<ExternalAudioStream> create ();
-       static boost::shared_ptr<ExternalAudioStream> create (std::string t, boost::optional<int> v);
-
-private:
-       friend class stream_test;
-       
-       ExternalAudioStream ();
-       ExternalAudioStream (std::string t, boost::optional<int> v);
-};
-
-class ExternalAudioDecoder : public AudioDecoder
-{
-public:
-       ExternalAudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
-
-       bool pass ();
-
-private:
-       std::vector<SNDFILE*> open_files (sf_count_t &);
-       void close_files (std::vector<SNDFILE*> const &);
-};
diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc
new file mode 100644 (file)
index 0000000..c97b79a
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+    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::cout;
+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->path().string().c_str(), 0, 0) < 0) {
+               throw OpenFileError (_ffmpeg_content->path().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_compatibility.cc b/src/lib/ffmpeg_compatibility.cc
deleted file mode 100644 (file)
index 09f9276..0000000
+++ /dev/null
@@ -1,117 +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.
-
-*/
-
-extern "C" {
-#include <libavfilter/avfiltergraph.h>
-}
-#include "exceptions.h"
-
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-
-typedef struct {
-       enum PixelFormat pix_fmt;
-} AVSinkContext;
-
-static int
-avsink_init (AVFilterContext* ctx, const char* args, void* opaque)
-{
-       AVSinkContext* priv = (AVSinkContext *) ctx->priv;
-       if (!opaque) {
-               return AVERROR (EINVAL);
-       }
-
-       *priv = *(AVSinkContext *) opaque;
-       return 0;
-}
-
-static void
-null_end_frame (AVFilterLink *)
-{
-
-}
-
-static int
-avsink_query_formats (AVFilterContext* ctx)
-{
-       AVSinkContext* priv = (AVSinkContext *) ctx->priv;
-       enum PixelFormat pix_fmts[] = {
-               priv->pix_fmt,
-               PIX_FMT_NONE
-       };
-
-       avfilter_set_common_formats (ctx, avfilter_make_format_list ((int *) pix_fmts));
-       return 0;
-}
-
-#endif
-
-AVFilter*
-get_sink ()
-{
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-       /* XXX does this leak stuff? */
-       AVFilter* buffer_sink = new AVFilter;
-       buffer_sink->name = av_strdup ("avsink");
-       buffer_sink->priv_size = sizeof (AVSinkContext);
-       buffer_sink->init = avsink_init;
-       buffer_sink->query_formats = avsink_query_formats;
-       buffer_sink->inputs = new AVFilterPad[2];
-       AVFilterPad* i0 = const_cast<AVFilterPad*> (&buffer_sink->inputs[0]);
-       i0->name = "default";
-       i0->type = AVMEDIA_TYPE_VIDEO;
-       i0->min_perms = AV_PERM_READ;
-       i0->rej_perms = 0;
-       i0->start_frame = 0;
-       i0->get_video_buffer = 0;
-       i0->get_audio_buffer = 0;
-       i0->end_frame = null_end_frame;
-       i0->draw_slice = 0;
-       i0->filter_samples = 0;
-       i0->poll_frame = 0;
-       i0->request_frame = 0;
-       i0->config_props = 0;
-       const_cast<AVFilterPad*> (&buffer_sink->inputs[1])->name = 0;
-       buffer_sink->outputs = new AVFilterPad[1];
-       const_cast<AVFilterPad*> (&buffer_sink->outputs[0])->name = 0;
-       return buffer_sink;
-#else
-       AVFilter* buffer_sink = avfilter_get_by_name("buffersink");
-       if (buffer_sink == 0) {
-               throw DecodeError ("Could not create buffer sink filter");
-       }
-
-       return buffer_sink;
-#endif
-}
-
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-AVFilterInOut *
-avfilter_inout_alloc ()
-{
-       return (AVFilterInOut *) av_malloc (sizeof (AVFilterInOut));
-}
-#endif
-
-#ifndef HAVE_AV_FRAME_GET_BEST_EFFORT_TIMESTAMP
-int64_t av_frame_get_best_effort_timestamp (AVFrame const * f)
-{
-       return f->best_effort_timestamp;
-}
-
-#endif
diff --git a/src/lib/ffmpeg_compatibility.h b/src/lib/ffmpeg_compatibility.h
deleted file mode 100644 (file)
index 772d22c..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.
-
-*/
-
-struct AVFilterInOut;
-
-extern AVFilter* get_sink ();
-extern AVFilterInOut* avfilter_inout_alloc ();
-
-#ifndef HAVE_AV_PIXEL_FORMAT
-#define AVPixelFormat PixelFormat
-#endif
-
-#ifndef HAVE_AV_FRAME_GET_BEST_EFFORT_TIMESTAMP
-extern int64_t av_frame_get_best_effort_timestamp (AVFrame const *);
-#endif
diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc
new file mode 100644 (file)
index 0000000..de967c0
--- /dev/null
@@ -0,0 +1,407 @@
+/*
+    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 std::pair;
+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)
+       , SubtitleContent (f, p)
+{
+
+}
+
+FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+       : Content (f, node)
+       , VideoContent (f, node)
+       , AudioContent (f, node)
+       , SubtitleContent (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<double> ("FirstVideo");
+}
+
+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);
+       SubtitleContent::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
+{
+       /* Get the string() here so that the name does not have quotes around it */
+       return String::compose (_("%1 [movie]"), path().filename().string());
+}
+
+string
+FFmpegContent::technical_summary () const
+{
+       string as = "none";
+       if (_audio_stream) {
+               as = String::compose ("id %1", _audio_stream->id);
+       }
+
+       string ss = "none";
+       if (_subtitle_stream) {
+               ss = String::compose ("id %1", _subtitle_stream->id);
+       }
+
+       pair<string, string> filt = Filter::ffmpeg_strings (_filters);
+       
+       return Content::technical_summary() + " - "
+               + VideoContent::technical_summary() + " - "
+               + AudioContent::technical_summary() + " - "
+               + String::compose (
+                       "ffmpeg: audio %1, subtitle %2, filters %3 %4", as, ss, filt.first, filt.second
+                       );
+}
+
+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->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->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)
+       : mapping (node->node_child ("Mapping"))
+{
+       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");
+       first_audio = node->optional_number_child<double> ("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.get ()));
+       }
+       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));
+}
+
+Time
+FFmpegContent::full_length () const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ());
+       return video_length() * frc.factor() * TIME_HZ / film->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);
+}
+
+string
+FFmpegContent::identifier () const
+{
+       stringstream s;
+
+       s << VideoContent::identifier();
+
+       boost::mutex::scoped_lock lm (_mutex);
+
+       if (_subtitle_stream) {
+               s << "_" << _subtitle_stream->id;
+       }
+
+       for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
+               s << "_" << (*i)->id ();
+       }
+
+       return s.str ();
+}
+
diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h
new file mode 100644 (file)
index 0000000..775cb92
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+    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"
+#include "subtitle_content.h"
+#include "audio_mapping.h"
+
+class Filter;
+class ffmpeg_pts_offset_test;
+
+class FFmpegAudioStream
+{
+public:
+       FFmpegAudioStream (std::string n, int i, int f, int c)
+               : name (n)
+               , id (i)
+               , frame_rate (f)
+               , channels (c)
+               , mapping (c)
+       {
+               mapping.make_default ();
+       }
+
+       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;
+
+private:
+       friend class ffmpeg_pts_offset_test;
+
+       /* Constructor for tests */
+       FFmpegAudioStream ()
+               : mapping (1)
+       {}
+};
+
+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 SubtitleContent
+{
+public:
+       FFmpegContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+       FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+       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 technical_summary () const;
+       std::string information () const;
+       void as_xml (xmlpp::Node *) const;
+       Time full_length () const;
+
+       std::string identifier () 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<double> first_video () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _first_video;
+       }
+       
+private:
+       friend class ffmpeg_pts_offset_test;
+       
+       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<double> _first_video;
+       /** Video filters that should be used when generating DCPs */
+       std::vector<Filter const *> _filters;
+};
+
+#endif
index a19f26ad79bdfde7dc66e3d795d87254e5796d43..8da607e7eb62861812d035d88461f39fc83c1bcd 100644 (file)
 #include <iostream>
 #include <stdint.h>
 #include <boost/lexical_cast.hpp>
+#include <sndfile.h>
 extern "C" {
-#include <tiffio.h>
 #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"
 #include "log.h"
 #include "ffmpeg_decoder.h"
 #include "filter_graph.h"
-#include "subtitle.h"
+#include "audio_buffers.h"
+#include "ffmpeg_content.h"
+
+#include "i18n.h"
 
 using std::cout;
 using std::string;
 using std::vector;
 using std::stringstream;
 using std::list;
+using std::min;
+using std::pair;
 using boost::shared_ptr;
 using boost::optional;
 using boost::dynamic_pointer_cast;
-
-FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
-       : Decoder (f, o, j)
-       , VideoDecoder (f, o, j)
-       , AudioDecoder (f, o, j)
-       , _format_context (0)
-       , _video_stream (-1)
-       , _frame (0)
-       , _video_codec_context (0)
-       , _video_codec (0)
-       , _audio_codec_context (0)
-       , _audio_codec (0)
+using libdcp::Size;
+
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
+       : Decoder (f)
+       , VideoDecoder (f, c)
+       , AudioDecoder (f)
+       , SubtitleDecoder (f)
+       , FFmpeg (c)
        , _subtitle_codec_context (0)
        , _subtitle_codec (0)
+       , _decode_video (video)
+       , _decode_audio (audio)
+       , _video_pts_offset (0)
+       , _audio_pts_offset (0)
+       , _just_sought (false)
 {
-       setup_general ();
-       setup_video ();
-       setup_audio ();
        setup_subtitle ();
 
-       if (!o->video_sync) {
-               _first_video = 0;
-       }
-}
+       /* Audio and video frame PTS values may not start with 0.  We want
+          to fiddle them so that:
 
-FFmpegDecoder::~FFmpegDecoder ()
-{
-       if (_audio_codec_context) {
-               avcodec_close (_audio_codec_context);
-       }
-       
-       if (_video_codec_context) {
-               avcodec_close (_video_codec_context);
-       }
+          1.  One of them starts at time 0.
+          2.  The first video PTS value ends up on a frame boundary.
 
-       if (_subtitle_codec_context) {
-               avcodec_close (_subtitle_codec_context);
-       }
+          Then we remove big initial gaps in PTS and we allow our
+          insertion of black frames to work.
 
-       av_free (_frame);
-       
-       avformat_close_input (&_format_context);
-}      
+          We will do:
+            audio_pts_to_use = audio_pts_from_ffmpeg + audio_pts_offset;
+            video_pts_to_use = video_pts_from_ffmpeg + video_pts_offset;
+       */
 
-void
-FFmpegDecoder::setup_general ()
-{
-       av_register_all ();
+       bool const have_video = video && c->first_video();
+       bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio;
 
-       if (avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0) < 0) {
-               throw OpenFileError (_film->content_path ());
-       }
+       /* First, make one of them start at 0 */
 
-       if (avformat_find_stream_info (_format_context, 0) < 0) {
-               throw DecodeError ("could not find stream information");
+       if (have_audio && have_video) {
+               _video_pts_offset = _audio_pts_offset = - min (c->first_video().get(), c->audio_stream()->first_audio.get());
+       } else if (have_video) {
+               _video_pts_offset = - c->first_video().get();
+       } else if (have_audio) {
+               _audio_pts_offset = - c->audio_stream()->first_audio.get();
        }
 
-       /* 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)
-                                       )
-                               );
+       /* Now adjust both so that the video pts starts on a frame */
+       if (have_video && have_audio) {
+               double first_video = c->first_video().get() + _video_pts_offset;
+               double const old_first_video = first_video;
+               
+               /* Round the first video up to a frame boundary */
+               if (fabs (rint (first_video * c->video_frame_rate()) - first_video * c->video_frame_rate()) > 1e-6) {
+                       first_video = ceil (first_video * c->video_frame_rate()) / c->video_frame_rate ();
                }
-       }
-
-       if (_video_stream < 0) {
-               throw DecodeError ("could not find video stream");
-       }
 
-       _frame = avcodec_alloc_frame ();
-       if (_frame == 0) {
-               throw DecodeError ("could not allocate frame");
+               _video_pts_offset += first_video - old_first_video;
+               _audio_pts_offset += first_video - old_first_video;
        }
 }
 
-void
-FFmpegDecoder::setup_video ()
+FFmpegDecoder::~FFmpegDecoder ()
 {
-       _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");
-       }
+       boost::mutex::scoped_lock lm (_mutex);
 
-       if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
-               throw DecodeError ("could not open video decoder");
+       if (_subtitle_codec_context) {
+               avcodec_close (_subtitle_codec_context);
        }
 }
 
 void
-FFmpegDecoder::setup_audio ()
+FFmpegDecoder::flush ()
 {
-       if (!_audio_stream) {
-               return;
+       /* Get any remaining frames */
+       
+       _packet.data = 0;
+       _packet.size = 0;
+       
+       /* XXX: should we reset _packet.data and size after each *_decode_* call? */
+       
+       if (_decode_video) {
+               while (decode_video_packet ()) {}
        }
-
-       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 (_ffmpeg_content->audio_stream() && _decode_audio) {
+               decode_audio_packet ();
        }
 
-       if (avcodec_open2 (_audio_codec_context, _audio_codec, 0) < 0) {
-               throw DecodeError ("could not open audio decoder");
-       }
+       /* Stop us being asked for any more data */
+       _video_position = _ffmpeg_content->video_length ();
+       _audio_position = _ffmpeg_content->audio_length ();
 }
 
 void
-FFmpegDecoder::setup_subtitle ()
-{
-       if (!_subtitle_stream) {
-               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 ("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 ("error on av_read_frame (%1) (%2)", buf, r));
-               }
-               
-               /* Get any remaining frames */
-               
-               _packet.data = 0;
-               _packet.size = 0;
-
-               /* XXX: should we reset _packet.data and size after each *_decode_* call? */
-
-               int frame_finished;
-
-               while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
-                       filter_and_emit_video (_frame);
+                       shared_ptr<const Film> film = _film.lock ();
+                       assert (film);
+                       film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
                }
 
-               if (_audio_stream && _opt->decode_audio) {
-                       while (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
-                               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[0], data_size));
-                       }
-               }
-
-               return true;
+               flush ();
+               return;
        }
 
        avcodec_get_frame_defaults (_frame);
 
-       shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-
-       if (_packet.stream_index == _video_stream) {
-
-               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 ("Used only %1 bytes of %2 in packet", r, _packet.size));
-                       }
-
-                       if (_opt->video_sync) {
-                               out_with_sync ();
-                       } else {
-                               filter_and_emit_video (_frame);
-                       }
-               }
-
-       } else if (ffa && _packet.stream_index == ffa->id() && _opt->decode_audio) {
-
-               int frame_finished;
-               if (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
-
-                       /* Where we are in the source, in seconds */
-                       double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base)
-                               * av_frame_get_best_effort_timestamp(_frame);
-
-                       /* We only decode audio if we've had our first video packet through, and if it
-                          was before this packet.  Until then audio is thrown away.
-                       */
-                               
-                       if (_first_video && _first_video.get() <= source_pts_seconds) {
-
-                               if (!_first_audio) {
-                                       _first_audio = source_pts_seconds;
-                                       
-                                       /* This is our first audio frame, and if we've arrived here we must have had our
-                                          first video frame.  Push some silence to make up any gap between our first
-                                          video frame and our first audio.
-                                       */
-                       
-                                       /* frames of silence that we must push */
-                                       int const s = rint ((_first_audio.get() - _first_video.get()) * ffa->sample_rate ());
-                                       
-                                       _film->log()->log (
-                                               String::compose (
-                                                       "First video at %1, first audio at %2, pushing %3 audio frames of silence for %4 channels (%5 bytes per sample)",
-                                                       _first_video.get(), _first_audio.get(), s, ffa->channels(), bytes_per_audio_sample()
-                                                       )
-                                               );
-                                       
-                                       if (s) {
-                                               shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), s));
-                                               audio->make_silent ();
-                                               Audio (audio);
-                                       }
-                               }
-
-                               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[0], data_size));
-                       }
-               }
-                       
-       } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt->decode_subtitles && _first_video) {
-
-               int got_subtitle;
-               AVSubtitle sub;
-               if (avcodec_decode_subtitle2 (_subtitle_codec_context, &sub, &got_subtitle, &_packet) && got_subtitle) {
-                       /* Sometimes we get an empty AVSubtitle, which is used by some codecs to
-                          indicate that the previous subtitle should stop.
-                       */
-                       if (sub.num_rects > 0) {
-                               shared_ptr<TimedSubtitle> ts;
-                               try {
-                                       emit_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> ());
-                       }
-                       avsubtitle_free (&sub);
-               }
-       }
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
        
+       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 (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id && film->with_subtitles ()) {
+               decode_subtitle_packet ();
+       }
+
        av_free_packet (&_packet);
-       return false;
 }
 
+/** @param data pointer to array of pointers to buffers.
+ *  Only the first buffer will be used for non-planar data, otherwise there will be one per channel.
+ */
 shared_ptr<AudioBuffers>
-FFmpegDecoder::deinterleave_audio (uint8_t* data, int size)
+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:
        {
-               int16_t* p = reinterpret_cast<int16_t *> (data);
+               int16_t* p = reinterpret_cast<int16_t *> (data[0]);
                int sample = 0;
                int channel = 0;
                for (int i = 0; i < total_samples; ++i) {
                        audio->data(channel)[sample] = float(*p++) / (1 << 15);
 
                        ++channel;
-                       if (channel == _film->audio_channels()) {
+                       if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
@@ -386,10 +220,10 @@ 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) {
+               int16_t** p = reinterpret_cast<int16_t **> (data);
+               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++) / (1 << 15);
+                               audio->data(i)[j] = static_cast<float>(p[i][j]) / (1 << 15);
                        }
                }
        }
@@ -397,14 +231,14 @@ FFmpegDecoder::deinterleave_audio (uint8_t* data, int size)
        
        case AV_SAMPLE_FMT_S32:
        {
-               int32_t* p = reinterpret_cast<int32_t *> (data);
+               int32_t* p = reinterpret_cast<int32_t *> (data[0]);
                int sample = 0;
                int channel = 0;
                for (int i = 0; i < total_samples; ++i) {
                        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;
                        }
@@ -414,14 +248,14 @@ FFmpegDecoder::deinterleave_audio (uint8_t* data, int size)
 
        case AV_SAMPLE_FMT_FLT:
        {
-               float* p = reinterpret_cast<float*> (data);
+               float* p = reinterpret_cast<float*> (data[0]);
                int sample = 0;
                int channel = 0;
                for (int i = 0; i < total_samples; ++i) {
                        audio->data(channel)[sample] = *p++;
 
                        ++channel;
-                       if (channel == _film->audio_channels()) {
+                       if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
@@ -431,309 +265,342 @@ 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) {
-                       memcpy (audio->data(i), p, frames * sizeof(float));
-                       p += frames;
+               float** p = reinterpret_cast<float**> (data);
+               for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
+                       memcpy (audio->data(i), p[i], frames * sizeof(float));
                }
        }
        break;
 
        default:
-               throw DecodeError (String::compose ("Unrecognised audio sample format (%1)", static_cast<int> (audio_sample_format())));
+               throw DecodeError (String::compose (_("Unrecognised audio sample format (%1)"), static_cast<int> (audio_sample_format())));
        }
 
        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;
+       return audio_codec_context()->sample_fmt;
 }
 
-Size
-FFmpegDecoder::native_size () const
+int
+FFmpegDecoder::bytes_per_audio_sample () const
 {
-       return Size (_video_codec_context->width, _video_codec_context->height);
+       return av_get_bytes_per_sample (audio_sample_format ());
 }
 
-PixelFormat
-FFmpegDecoder::pixel_format () const
+void
+FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
 {
-       return _video_codec_context->pix_fmt;
-}
+       double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
 
-int
-FFmpegDecoder::time_base_numerator () const
-{
-       return _video_codec_context->time_base.num;
-}
+       /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being
+          a number plucked from the air) earlier than we want to end up.  The loop below
+          will hopefully then step through to where we want to be.
+       */
+       int initial = frame;
 
-int
-FFmpegDecoder::time_base_denominator () const
-{
-       return _video_codec_context->time_base.den;
-}
+       if (accurate) {
+               initial -= 5;
+       }
 
-int
-FFmpegDecoder::sample_aspect_ratio_numerator () const
-{
-       return _video_codec_context->sample_aspect_ratio.num;
-}
+       if (initial < 0) {
+               initial = 0;
+       }
 
-int
-FFmpegDecoder::sample_aspect_ratio_denominator () const
-{
-       return _video_codec_context->sample_aspect_ratio.den;
-}
+       /* Initial seek time in the stream's timebase */
+       int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _video_pts_offset) / time_base;
 
-string
-FFmpegDecoder::stream_name (AVStream* s) const
-{
-       stringstream n;
-       
-       AVDictionaryEntry const * lang = av_dict_get (s->metadata, "language", 0, 0);
-       if (lang) {
-               n << lang->value;
+       av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+
+       avcodec_flush_buffers (video_codec_context());
+       if (_subtitle_codec_context) {
+               avcodec_flush_buffers (_subtitle_codec_context);
        }
+
+       _just_sought = true;
+       _video_position = frame;
        
-       AVDictionaryEntry const * title = av_dict_get (s->metadata, "title", 0, 0);
-       if (title) {
-               if (!n.str().empty()) {
-                       n << " ";
-               }
-               n << title->value;
+       if (frame == 0 || !accurate) {
+               /* We're already there, or we're as close as we need to be */
+               return;
        }
 
-       if (n.str().empty()) {
-               n << "unknown";
-       }
+       while (1) {
+               int r = av_read_frame (_format_context, &_packet);
+               if (r < 0) {
+                       return;
+               }
 
-       return n.str ();
-}
+               if (_packet.stream_index != _video_stream) {
+                       continue;
+               }
+               
+               avcodec_get_frame_defaults (_frame);
+               
+               int finished = 0;
+               r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
+               if (r >= 0 && finished) {
+                       _video_position = rint (
+                               (av_frame_get_best_effort_timestamp (_frame) * time_base + _video_pts_offset) * _ffmpeg_content->video_frame_rate()
+                               );
 
-int
-FFmpegDecoder::bytes_per_audio_sample () const
-{
-       return av_get_bytes_per_sample (audio_sample_format ());
+                       if (_video_position >= (frame - 1)) {
+                               av_free_packet (&_packet);
+                               break;
+                       }
+               }
+               
+               av_free_packet (&_packet);
+       }
 }
 
 void
-FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s)
+FFmpegDecoder::decode_audio_packet ()
 {
-       AudioDecoder::set_audio_stream (s);
-       setup_audio ();
-}
+       /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
+          several times.
+       */
+       
+       AVPacket copy_packet = _packet;
+       
+       while (copy_packet.size > 0) {
 
-void
-FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
-       VideoDecoder::set_subtitle_stream (s);
-       setup_subtitle ();
-       OutputChanged ();
+               int frame_finished;
+               int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
+               if (decode_result < 0) {
+                       shared_ptr<const Film> film = _film.lock ();
+                       assert (film);
+                       film->log()->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
+                       return;
+               }
+
+               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) + _audio_pts_offset;
+
+                               if (pts > 0) {
+                                       /* Emit some silence */
+                                       shared_ptr<AudioBuffers> silence (
+                                               new AudioBuffers (
+                                                       _ffmpeg_content->audio_channels(),
+                                                       pts * _ffmpeg_content->content_audio_frame_rate()
+                                                       )
+                                               );
+                                       
+                                       silence->make_silent ();
+                                       audio (silence, _audio_position);
+                               }
+                       }
+                       
+                       int const data_size = av_samples_get_buffer_size (
+                               0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
+                               );
+                       
+                       audio (deinterleave_audio (_frame->data, data_size), _audio_position);
+               }
+                       
+               copy_packet.data += decode_result;
+               copy_packet.size -= decode_result;
+       }
 }
 
-void
-FFmpegDecoder::filter_and_emit_video (AVFrame* frame)
+bool
+FFmpegDecoder::decode_video_packet ()
 {
+       int frame_finished;
+       if (avcodec_decode_video2 (video_codec_context(), _frame, &frame_finished, &_packet) < 0 || !frame_finished) {
+               return false;
+       }
+
        boost::mutex::scoped_lock lm (_filter_graphs_mutex);
-       
-       shared_ptr<FilterGraph> graph;
 
+       shared_ptr<FilterGraph> graph;
+       
        list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
-       while (i != _filter_graphs.end() && !(*i)->can_process (Size (frame->width, frame->height), (AVPixelFormat) frame->format)) {
+       while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
                ++i;
        }
 
        if (i == _filter_graphs.end ()) {
-               graph.reset (new FilterGraph (_film, this, Size (frame->width, frame->height), (AVPixelFormat) frame->format));
+               shared_ptr<const Film> film = _film.lock ();
+               assert (film);
+
+               graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
                _filter_graphs.push_back (graph);
-               _film->log()->log (String::compose ("New graph for %1x%2, pixel format %3", frame->width, frame->height, frame->format));
+
+               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, frame_time ());
-       }
-}
-
-bool
-FFmpegDecoder::seek (double p)
-{
-       return do_seek (p, 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);
-}
+       list<pair<shared_ptr<Image>, int64_t> > images = graph->process (_frame);
 
-bool
-FFmpegDecoder::do_seek (double p, bool backwards)
-{
-       int64_t const vt = p / av_q2d (_format_context->streams[_video_stream]->time_base);
-
-       int const r = av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
+       string post_process = Filter::ffmpeg_strings (_ffmpeg_content->filters()).second;
        
-       avcodec_flush_buffers (_video_codec_context);
-       if (_subtitle_codec_context) {
-               avcodec_flush_buffers (_subtitle_codec_context);
-       }
-       
-       return r < 0;
-}
+       for (list<pair<shared_ptr<Image>, int64_t> >::iterator i = images.begin(); i != images.end(); ++i) {
 
-shared_ptr<FFmpegAudioStream>
-FFmpegAudioStream::create (string t, optional<int> v)
-{
-       if (!v) {
-               /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
-               return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
-       }
+               shared_ptr<Image> image = i->first;
+               if (!post_process.empty ()) {
+                       image = image->post_process (post_process, true);
+               }
+               
+               if (i->second != AV_NOPTS_VALUE) {
 
-       stringstream s (t);
-       string type;
-       s >> type;
-       if (type != "ffmpeg") {
-               return shared_ptr<FFmpegAudioStream> ();
-       }
+                       double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _video_pts_offset;
 
-       return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
-}
+                       if (_just_sought) {
+                               /* We just did a seek, so disable any attempts to correct for where we
+                                  are / should be.
+                               */
+                               _video_position = rint (pts * _ffmpeg_content->video_frame_rate ());
+                               _just_sought = false;
+                       }
 
-FFmpegAudioStream::FFmpegAudioStream (string t, optional<int> version)
-{
-       stringstream n (t);
-       
-       int name_index = 4;
-       if (!version) {
-               name_index = 2;
-               int channels;
-               n >> _id >> channels;
-               _channel_layout = av_get_default_channel_layout (channels);
-               _sample_rate = 0;
-       } else {
-               string type;
-               /* Current (marked version 1) */
-               n >> type >> _id >> _sample_rate >> _channel_layout;
-               assert (type == "ffmpeg");
-       }
+                       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.
+                               */
+
+                               /* XXX: I think this should be a copy of the last frame... */
+                               boost::shared_ptr<Image> black (
+                                       new Image (
+                                               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;
+                       }
 
-       for (int i = 0; i < name_index; ++i) {
-               size_t const s = t.find (' ');
-               if (s != string::npos) {
-                       t = t.substr (s + 1);
+                       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");
                }
        }
 
-       _name = t;
-}
-
-string
-FFmpegAudioStream::to_string () const
-{
-       return String::compose ("ffmpeg %1 %2 %3 %4", _id, _sample_rate, _channel_layout, _name);
+       return true;
 }
 
+       
 void
-FFmpegDecoder::out_with_sync ()
+FFmpegDecoder::setup_subtitle ()
 {
-       /* Where we are in the output, in seconds */
-       double const out_pts_seconds = video_frame() / frames_per_second();
-       
-       /* Where we are in the source, in seconds */
-       double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base)
-               * av_frame_get_best_effort_timestamp(_frame);
+       boost::mutex::scoped_lock lm (_mutex);
        
-       _film->log()->log (
-               String::compose ("Source video frame ready; source at %1, output at %2", source_pts_seconds, out_pts_seconds),
-               Log::VERBOSE
-               );
-       
-       if (!_first_video) {
-               _first_video = source_pts_seconds;
+       if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->id >= int (_format_context->nb_streams)) {
+               return;
        }
-       
-       /* Difference between where we are and where we should be */
-       double const delta = source_pts_seconds - _first_video.get() - out_pts_seconds;
-       double const one_frame = 1 / frames_per_second();
-       
-       /* Insert frames if required to get out_pts_seconds up to pts_seconds */
-       if (delta > one_frame) {
-               int const extra = rint (delta / one_frame);
-               for (int i = 0; i < extra; ++i) {
-                       repeat_last_video ();
-                       _film->log()->log (
-                               String::compose (
-                                       "Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)",
-                                       out_pts_seconds, video_frame(), source_pts_seconds, frames_per_second()
-                                       )
-                               );
-               }
+
+       _subtitle_codec_context = _format_context->streams[_ffmpeg_content->subtitle_stream()->id]->codec;
+       _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
+
+       if (_subtitle_codec == 0) {
+               throw DecodeError (_("could not find subtitle decoder"));
        }
        
-       if (delta > -one_frame) {
-               /* Process this frame */
-               filter_and_emit_video (_frame);
-       } else {
-               /* Otherwise we are omitting a frame to keep things right */
-               _film->log()->log (String::compose ("Frame removed at %1s", out_pts_seconds));
+       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;
+}
+       
 void
-FFmpegDecoder::film_changed (Film::Property p)
+FFmpegDecoder::decode_subtitle_packet ()
 {
-       switch (p) {
-       case Film::CROP:
-       case Film::FILTERS:
-       {
-               boost::mutex::scoped_lock lm (_filter_graphs_mutex);
-               _filter_graphs.clear ();
+       int got_subtitle;
+       AVSubtitle sub;
+       if (avcodec_decode_subtitle2 (_subtitle_codec_context, &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) {
+               return;
        }
-       OutputChanged ();
-       break;
 
-       default:
-               break;
+       /* Sometimes we get an empty AVSubtitle, which is used by some codecs to
+          indicate that the previous subtitle should stop.
+       */
+       if (sub.num_rects <= 0) {
+               subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
+               return;
+       } else if (sub.num_rects > 1) {
+               throw DecodeError (_("multi-part subtitles not yet supported"));
        }
-}
+               
+       /* Subtitle PTS in seconds (within the source, not taking into account any of the
+          source that we may have chopped off for the DCP)
+       */
+       double const packet_time = static_cast<double> (sub.pts) / AV_TIME_BASE;
+       
+       /* hence start time for this sub */
+       Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
+       Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
 
-/** @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();
-}
+       AVSubtitleRect const * rect = sub.rects[0];
 
-double
-FFmpegDecoder::frame_time () const
-{
-       return av_frame_get_best_effort_timestamp(_frame) * av_q2d (_format_context->streams[_video_stream]->time_base);
+       if (rect->type != SUBTITLE_BITMAP) {
+               throw DecodeError (_("non-bitmap subtitles not yet supported"));
+       }
+       
+       shared_ptr<Image> image (new Image (PIX_FMT_RGBA, libdcp::Size (rect->w, rect->h), true));
+
+       /* Start of the first line in the subtitle */
+       uint8_t* sub_p = rect->pict.data[0];
+       /* sub_p looks up into a RGB palette which is here */
+       uint32_t const * palette = (uint32_t *) rect->pict.data[1];
+       /* Start of the output data */
+       uint32_t* out_p = (uint32_t *) image->data()[0];
+       
+       for (int y = 0; y < rect->h; ++y) {
+               uint8_t* sub_line_p = sub_p;
+               uint32_t* out_line_p = out_p;
+               for (int x = 0; x < rect->w; ++x) {
+                       *out_line_p++ = palette[*sub_line_p++];
+               }
+               sub_p += rect->pict.linesize[0];
+               out_p += image->stride()[0] / sizeof (uint32_t);
+       }
+
+       libdcp::Size const vs = _ffmpeg_content->video_size ();
+
+       subtitle (
+               image,
+               dcpomatic::Rect<double> (
+                       static_cast<double> (rect->x) / vs.width,
+                       static_cast<double> (rect->y) / vs.height,
+                       static_cast<double> (rect->w) / vs.width,
+                       static_cast<double> (rect->h) / vs.height
+                       ),
+               from,
+               to
+               );
+                         
+       
+       avsubtitle_free (&sub);
 }
-
index 2fb8675f996b243e13b097b5feba65ef8d1c9727..11f83ed97c9808aeaff603f110814463b51234a6 100644 (file)
@@ -35,115 +35,55 @@ 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 "subtitle_decoder.h"
+#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 FilterGraph;
+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 SubtitleDecoder, public FFmpeg
 {
 public:
-       FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+       FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio);
        ~FFmpegDecoder ();
 
-       float frames_per_second () const;
-       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 pass ();
+       void seek (VideoContent::Frame, bool);
+       bool done () const;
 
 private:
+       friend class ::ffmpeg_pts_offset_test;
 
-       bool pass ();
-       bool do_seek (double p, bool);
-       PixelFormat pixel_format () const;
-       AVSampleFormat audio_sample_format () const;
-       int bytes_per_audio_sample () const;
+       static double compute_pts_offset (double, double, float);
 
-       void out_with_sync ();
-       void filter_and_emit_video (AVFrame *);
-       double frame_time () const;
+       void flush ();
 
-       void setup_general ();
-       void setup_video ();
-       void setup_audio ();
        void setup_subtitle ();
 
-       void maybe_add_subtitle ();
-       boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t* data, int size);
-
-       void film_changed (Film::Property);
+       AVSampleFormat audio_sample_format () const;
+       int bytes_per_audio_sample () const;
 
-       std::string stream_name (AVStream* s) const;
+       bool decode_video_packet ();
+       void decode_audio_packet ();
+       void decode_subtitle_packet ();
 
-       AVFormatContext* _format_context;
-       int _video_stream;
-       
-       AVFrame* _frame;
+       void maybe_add_subtitle ();
+       boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
 
-       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;
-
-       boost::optional<double> _first_video;
-       boost::optional<double> _first_audio;
-
+       AVCodec* _subtitle_codec;                ///< may be 0 if there is no subtitle
+       
        std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
        boost::mutex _filter_graphs_mutex;
+
+       bool _decode_video;
+       bool _decode_audio;
+
+       double _video_pts_offset;
+       double _audio_pts_offset;
+       bool _just_sought;
 };
diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc
new file mode 100644 (file)
index 0000000..ceeaee1
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+    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);
+
+               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;
+               }
+
+               av_free_packet (&_packet);
+               
+               if (_first_video && have_all_audio) {
+                       break;
+               }
+       }
+}
+
+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..4912d89
--- /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 fb3423bb42c1c35707798ca0c4f23c363e3e5c86..e885fe5fd350f804fb8e0e8ec489005da6b4501d 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 <boost/lexical_cast.hpp>
 #include <boost/date_time.hpp>
 #include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
 #include <libdcp/crypt_chain.h>
-#include <libdcp/certificates.h>
-#include "cinema.h"
+#include <libdcp/cpl.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 "make_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 "check_hashes_job.h"
 #include "version.h"
 #include "ui_signaller.h"
-#include "video_decoder.h"
-#include "audio_decoder.h"
-#include "external_audio_decoder.h"
+#include "playlist.h"
+#include "player.h"
+#include "dcp_content_type.h"
+#include "ratio.h"
+#include "cross.h"
+#include "cinema.h"
+
+#include "i18n.h"
 
 using std::string;
 using std::stringstream;
@@ -69,52 +66,54 @@ using std::ofstream;
 using std::setfill;
 using std::min;
 using std::make_pair;
-using std::list;
+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;
 using boost::optional;
+using libdcp::Size;
 
-int const Film::state_version = 1;
+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.
+ *  @param dir Film directory.
  */
 
-Film::Film (string d, bool must_exist)
-       : _use_dci_name (true)
-       , _trust_content_header (true)
-       , _dcp_content_type (0)
-       , _format (0)
+Film::Film (boost::filesystem::path dir)
+       : _playlist (new Playlist)
+       , _use_dci_name (true)
+       , _dcp_content_type (Config::instance()->default_dcp_content_type ())
+       , _container (Config::instance()->default_container ())
+       , _resolution (RESOLUTION_2K)
        , _scaler (Scaler::from_id ("bicubic"))
-       , _dcp_trim_start (0)
-       , _dcp_trim_end (0)
-       , _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)
        , _encrypted (false)
-       , _colour_lut (0)
-       , _j2k_bandwidth (200000000)
-       , _frames_per_second (0)
+       , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ())
+       , _dci_metadata (Config::instance()->default_dci_metadata ())
+       , _video_frame_rate (24)
+       , _audio_channels (MAX_AUDIO_CHANNELS)
+       , _three_d (false)
+       , _sequence_video (true)
+       , _interop (false)
        , _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)
        */
        
-       boost::filesystem::path p (boost::filesystem::system_complete (d));
+       boost::filesystem::path p (boost::filesystem::system_complete (dir));
        boost::filesystem::path result;
        for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
                if (*i == "..") {
@@ -129,239 +128,161 @@ 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());
-               }
-       }
+       _log.reset (new FileLog (file ("log")));
 
-       _external_audio_stream = ExternalAudioStream::create ();
-       
-       if (must_exist) {
-               read_metadata ();
-       }
-
-       _log = new FileLog (file ("log"));
-       set_dci_date_today ();
+       _playlist->set_sequence_video (_sequence_video);
 }
 
-Film::Film (Film const & o)
-       : boost::enable_shared_from_this<Film> (o)
-       , _log (0)
-       , _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)
-       , _scaler            (o._scaler)
-       , _dcp_trim_start    (o._dcp_trim_start)
-       , _dcp_trim_end      (o._dcp_trim_end)
-       , _reel_size         (o._reel_size)
-       , _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)
-       , _encrypted         (o._encrypted)
-       , _colour_lut        (o._colour_lut)
-       , _j2k_bandwidth     (o._j2k_bandwidth)
-       , _audio_language    (o._audio_language)
-       , _subtitle_language (o._subtitle_language)
-       , _territory         (o._territory)
-       , _rating            (o._rating)
-       , _studio            (o._studio)
-       , _facility          (o._facility)
-       , _package_type      (o._package_type)
-       , _size              (o._size)
-       , _length            (o._length)
-       , _content_digest    (o._content_digest)
-       , _content_audio_streams (o._content_audio_streams)
-       , _external_audio_stream (o._external_audio_stream)
-       , _subtitle_streams  (o._subtitle_streams)
-       , _frames_per_second (o._frames_per_second)
-       , _dirty             (o._dirty)
+string
+Film::video_identifier () const
 {
+       assert (container ());
+       LocaleGuard lg;
 
-}
+       stringstream s;
+       s << container()->id()
+         << "_" << resolution_to_string (_resolution)
+         << "_" << _playlist->video_identifier()
+         << "_" << _video_frame_rate
+         << "_" << scaler()->id()
+         << "_" << j2k_bandwidth();
 
-Film::~Film ()
-{
-       delete _log;
+       if (_interop) {
+               s << "_I";
+       } else {
+               s << "_S";
+       }
+
+       if (_three_d) {
+               s << "_3D";
+       }
+
+       return s.str ();
 }
          
-/** @return The path to the directory to write JPEG2000 files to */
+/** @return The path to the directory to write video frame info files to */
 string
-Film::j2k_dir () const
+Film::info_dir () const
 {
-       assert (format());
-
        boost::filesystem::path p;
+       p /= "info";
+       p /= video_identifier ();
+       return dir (p.string());
+}
 
-       /* Start with j2c */
-       p /= "j2c";
+string
+Film::internal_video_mxf_dir () const
+{
+       return dir ("video");
+}
 
-       pair<string, string> f = Filter::ffmpeg_strings (filters());
+string
+Film::internal_video_mxf_filename () const
+{
+       return video_identifier() + ".mxf";
+}
 
-       /* Write stuff to specify the filter / post-processing settings that are in use,
-          so that we don't get confused about J2K files generated using different
-          settings.
-       */
-       stringstream s;
-       s << format()->id()
-         << "_" << content_digest()
-         << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
-         << "_" << f.first << "_" << f.second
-         << "_" << scaler()->id()
-         << "_" << j2k_bandwidth()
-         << "_" << boost::lexical_cast<int> (colour_lut());
+string
+Film::video_mxf_filename () const
+{
+       return filename_safe_name() + "_video.mxf";
+}
 
-       p /= s.str ();
+string
+Film::audio_mxf_filename () const
+{
+       return filename_safe_name() + "_audio.mxf";
+}
 
-       /* Similarly for the A/B case */
-       if (dcp_ab()) {
-               stringstream s;
-               pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
-               s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
-               p /= s.str ();
+string
+Film::filename_safe_name () const
+{
+       string const n = name ();
+       string o;
+       for (size_t i = 0; i < n.length(); ++i) {
+               if (isalnum (n[i])) {
+                       o += n[i];
+               } else {
+                       o += "_";
+               }
        }
-       
-       return dir (p.string());
+
+       return o;
 }
 
-/** Add suitable Jobs to the JobManager to create a DCP for this Film.
- *  @param true to transcode, false to use the WAV and J2K files that are already there.
- */
+boost::filesystem::path
+Film::audio_analysis_path (shared_ptr<const AudioContent> c) const
+{
+       boost::filesystem::path p = dir ("analysis");
+       p /= c->digest();
+       return p;
+}
+
+/** Add suitable Jobs to the JobManager to create a DCP for this Film */
 void
-Film::make_dcp (bool transcode)
+Film::make_dcp ()
 {
        set_dci_date_today ();
        
        if (dcp_name().find ("/") != string::npos) {
-               throw BadSettingError ("name", "cannot contain slashes");
+               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];
                gethostname (buffer, sizeof (buffer));
                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")));
-       log()->log (String::compose ("Content length %1", length().get()));
-       log()->log (String::compose ("Content digest %1", content_digest()));
+
+       ContentList cl = content ();
+       for (ContentList::const_iterator i = cl.begin(); i != cl.end(); ++i) {
+               log()->log (String::compose ("Content: %1", (*i)->technical_summary()));
+       }
+       log()->log (String::compose ("DCP video rate %1 fps", video_frame_rate()));
        log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
        log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
-#ifdef DVDOMATIC_DEBUG
-       log()->log ("DVD-o-matic built in debug mode.");
+#ifdef DCPOMATIC_DEBUG
+       log()->log ("DCP-o-matic built in debug mode.");
 #else
-       log()->log ("DVD-o-matic built in optimised mode.");
+       log()->log ("DCP-o-matic built in optimised mode.");
 #endif
 #ifdef LIBDCP_DEBUG
        log()->log ("libdcp built in debug mode.");
 #else
        log()->log ("libdcp built in optimised mode.");
 #endif
-       pair<string, int> const c = cpu_info ();
-       log()->log (String::compose ("CPU: %1, %2 processors", c.first, c.second));
+       log()->log (String::compose ("CPU: %1, %2 processors", cpu_info(), boost::thread::hardware_concurrency ()));
+       list<pair<string, string> > const m = mount_info ();
+       for (list<pair<string, string> >::const_iterator i = m.begin(); i != m.end(); ++i) {
+               log()->log (String::compose ("Mount: %1 %2", i->first, i->second));
+       }
        
-       if (format() == 0) {
-               throw MissingSettingError ("format");
+       if (container() == 0) {
+               throw MissingSettingError (_("container"));
        }
 
-       if (content().empty ()) {
-               throw MissingSettingError ("content");
+       if (content().empty()) {
+               throw StringError (_("You must add some content to the DCP before creating it"));
        }
 
        if (dcp_content_type() == 0) {
-               throw MissingSettingError ("content type");
+               throw MissingSettingError (_("content type"));
        }
 
        if (name().empty()) {
-               throw MissingSettingError ("name");
-       }
-
-       shared_ptr<EncodeOptions> oe (new EncodeOptions (j2k_dir(), ".j2c", dir ("wavs")));
-       oe->out_size = format()->dcp_size ();
-       oe->padding = format()->dcp_padding (shared_from_this ());
-       if (dcp_length ()) {
-               oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
-               if (audio_stream()) {
-                       oe->audio_range = make_pair (
-
-                               video_frames_to_audio_frames (
-                                       oe->video_range.get().first,
-                                       dcp_audio_sample_rate (audio_stream()->sample_rate()),
-                                       dcp_frame_rate (frames_per_second()).frames_per_second
-                                       ),
-                               
-                               video_frames_to_audio_frames (
-                                       oe->video_range.get().second,
-                                       dcp_audio_sample_rate (audio_stream()->sample_rate()),
-                                       dcp_frame_rate (frames_per_second()).frames_per_second
-                                       )
-                               );
-               }
-                       
-       }
-       
-       oe->video_skip = dcp_frame_rate (frames_per_second()).skip;
-
-       shared_ptr<DecodeOptions> od (new DecodeOptions);
-       od->decode_subtitles = with_subtitles ();
-
-       shared_ptr<Job> r;
-
-       if (transcode) {
-               if (dcp_ab()) {
-                       r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
-               } else {
-                       r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
-               }
+               throw MissingSettingError (_("name"));
        }
 
-       r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), od, oe, r)));
-       JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), oe, r)));
-}
-
-/** 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(), shared_ptr<Job> ()));
-       _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this));
-       JobManager::instance()->add (_examine_content_job);
-}
-
-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 */
 void
 Film::send_dcp_to_tms ()
 {
-       shared_ptr<Job> j (new SCPDCPJob (shared_from_this(), shared_ptr<Job> ()));
+       shared_ptr<Job> j (new SCPDCPJob (shared_from_this()));
        JobManager::instance()->add (j);
 }
 
@@ -371,12 +292,12 @@ Film::send_dcp_to_tms ()
 int
 Film::encoded_frames () const
 {
-       if (format() == 0) {
+       if (container() == 0) {
                return 0;
        }
 
        int N = 0;
-       for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (j2k_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
+       for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (info_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
                ++N;
                boost::this_thread::interruption_point ();
        }
@@ -388,86 +309,44 @@ Film::encoded_frames () const
 void
 Film::write_metadata () const
 {
-       boost::mutex::scoped_lock lm (_state_mutex);
+       if (!boost::filesystem::exists (directory())) {
+               boost::filesystem::create_directory (directory());
+       }
+       
+       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 << "\n";
+       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 << "\n";
-       f << "use_dci_name " << _use_dci_name << "\n";
-       f << "content " << _content << "\n";
-       f << "trust_content_header " << (_trust_content_header ? "1" : "0") << "\n";
        if (_dcp_content_type) {
-               f << "dcp_content_type " << _dcp_content_type->pretty_name () << "\n";
-       }
-       if (_format) {
-               f << "format " << _format->as_metadata () << "\n";
-       }
-       f << "left_crop " << _crop.left << "\n";
-       f << "right_crop " << _crop.right << "\n";
-       f << "top_crop " << _crop.top << "\n";
-       f << "bottom_crop " << _crop.bottom << "\n";
-       for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
-               f << "filter " << (*i)->id () << "\n";
-       }
-       f << "scaler " << _scaler->id () << "\n";
-       f << "dcp_trim_start " << _dcp_trim_start << "\n";
-       f << "dcp_trim_end " << _dcp_trim_end << "\n";
-       if (_reel_size) {
-               f << "reel_size " << _reel_size.get() << "\n";
-       }
-       f << "dcp_ab " << (_dcp_ab ? "1" : "0") << "\n";
-       if (_content_audio_stream) {
-               f << "selected_content_audio_stream " << _content_audio_stream->to_string() << "\n";
-       }
-       for (vector<string>::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) {
-               f << "external_audio " << *i << "\n";
-       }
-       f << "use_content_audio " << (_use_content_audio ? "1" : "0") << "\n";
-       f << "audio_gain " << _audio_gain << "\n";
-       f << "audio_delay " << _audio_delay << "\n";
-       f << "still_duration " << _still_duration << "\n";
-       if (_subtitle_stream) {
-               f << "selected_subtitle_stream " << _subtitle_stream->to_string() << "\n";
+               root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
        }
-       f << "with_subtitles " << _with_subtitles << "\n";
-       f << "subtitle_offset " << _subtitle_offset << "\n";
-       f << "subtitle_scale " << _subtitle_scale << "\n";
-       f << "encrypted " << _encrypted << "\n";
-       f << "colour_lut " << _colour_lut << "\n";
-       f << "j2k_bandwidth " << _j2k_bandwidth << "\n";
-       f << "audio_language " << _audio_language << "\n";
-       f << "subtitle_language " << _subtitle_language << "\n";
-       f << "territory " << _territory << "\n";
-       f << "rating " << _rating << "\n";
-       f << "studio " << _studio << "\n";
-       f << "facility " << _facility << "\n";
-       f << "package_type " << _package_type << "\n";
-
-       f << "width " << _size.width << "\n";
-       f << "height " << _size.height << "\n";
-       f << "length " << _length.get_value_or(0) << "\n";
-       f << "content_digest " << _content_digest << "\n";
-
-       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 () << "\n";
-       }
-
-       f << "external_audio_stream " << _external_audio_stream->to_string() << "\n";
 
-       for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
-               f << "subtitle_stream " << (*i)->to_string () << "\n";
+       if (_container) {
+               root->add_child("Container")->add_child_text (_container->id ());
        }
 
-       f << "frames_per_second " << _frames_per_second << "\n";
+       root->add_child("Resolution")->add_child_text (resolution_to_string (_resolution));
+       root->add_child("Scaler")->add_child_text (_scaler->id ());
+       root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
+       root->add_child("J2KBandwidth")->add_child_text (lexical_cast<string> (_j2k_bandwidth));
+       _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+       root->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate));
+       root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
+       root->add_child("AudioChannels")->add_child_text (lexical_cast<string> (_audio_channels));
+       root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0");
+       root->add_child("SequenceVideo")->add_child_text (_sequence_video ? "1" : "0");
+       root->add_child("Interop")->add_child_text (_interop ? "1" : "0");
+       root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0");
+       _playlist->as_xml (root->add_child ("Playlist"));
+
+       doc.write_to_file_formatted (file ("metadata.xml"));
        
        _dirty = false;
 }
@@ -476,273 +355,82 @@ Film::write_metadata () const
 void
 Film::read_metadata ()
 {
-       boost::mutex::scoped_lock lm (_state_mutex);
-
-       _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;
+       LocaleGuard lg;
 
-       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::Document f ("Metadata");
+       f.read_file (file ("metadata.xml"));
        
-       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());
+       {
+               optional<string> c = f.optional_string_child ("DCPContentType");
+               if (c) {
+                       _dcp_content_type = DCPContentType::from_dci_name (c.get ());
                }
+       }
 
-               /* 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") {
-                       _dcp_content_type = DCPContentType::from_pretty_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 (k == "dcp_trim_start") {
-                       _dcp_trim_start = atoi (v.c_str ());
-               } else if (k == "dcp_trim_end") {
-                       _dcp_trim_end = atoi (v.c_str ());
-               } else if (k == "reel_size") {
-                       _reel_size = boost::lexical_cast<uint64_t> (v);
-               } 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 == "encrypted") {
-                       _encrypted = (v == "1");
-               } 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 == "audio_language") {
-                       _audio_language = v;
-               } else if (k == "subtitle_language") {
-                       _subtitle_language = v;
-               } else if (k == "territory") {
-                       _territory = v;
-               } else if (k == "rating") {
-                       _rating = v;
-               } else if (k == "studio") {
-                       _studio = v;
-               } else if (k == "facility") {
-                       _facility = v;
-               } else if (k == "package_type") {
-                       _package_type = 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") {
-                       _external_audio_stream = audio_stream_factory (v, version);
-               } else if (k == "subtitle_stream") {
-                       _subtitle_streams.push_back (subtitle_stream_factory (v, version));
-               } else if (k == "frames_per_second") {
-                       _frames_per_second = atof (v.c_str ());
+       {
+               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());
-                       }
-               }
+       _resolution = string_to_resolution (f.string_child ("Resolution"));
+       _scaler = Scaler::from_id (f.string_child ("Scaler"));
+       _with_subtitles = f.bool_child ("WithSubtitles");
+       _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
+       _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
+       _video_frame_rate = f.number_child<int> ("VideoFrameRate");
+       _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
+       _audio_channels = f.number_child<int> ("AudioChannels");
+       _sequence_video = f.bool_child ("SequenceVideo");
+       _three_d = f.bool_child ("ThreeD");
+       _interop = f.bool_child ("Interop");
 
-               /* 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;
 }
 
-Size
-Film::cropped_size (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.
  */
 string
 Film::dir (string d) const
 {
-       boost::mutex::scoped_lock lm (_directory_mutex);
        boost::filesystem::path p;
        p /= _directory;
        p /= d;
+       
        boost::filesystem::create_directories (p);
+       
        return p.string ();
 }
 
 /** Given a file or directory name, return its full path within the Film's directory.
- *  _directory_mutex must not be locked on entry.
+ *  Any required parent directories will be created.
  */
 string
 Film::file (string f) const
 {
-       boost::mutex::scoped_lock lm (_directory_mutex);
        boost::filesystem::path p;
        p /= _directory;
        p /= f;
-       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;
-       }
+       boost::filesystem::create_directories (p.parent_path ());
        
-       /* Resample to a DCI-approved sample rate */
-       double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
-
-       DCPFrameRate dfr = dcp_frame_rate (frames_per_second ());
-
-       /* Compensate for the fact that video will be rounded to the
-          nearest integer number of frames per second.
-       */
-       if (dfr.run_fast) {
-               t *= _frames_per_second * dfr.skip / dfr.frames_per_second;
-       }
-
-       return rint (t);
-}
-
-boost::optional<int>
-Film::dcp_length () const
-{
-       if (content_type() == STILL) {
-               return _still_duration * frames_per_second();
-       }
-       
-       if (!length()) {
-               return boost::optional<int> ();
-       }
-
-       return length().get() - dcp_trim_start() - dcp_trim_end();
+       return p.string ();
 }
 
 /** @return a DCI-compliant name for a DCP of this film */
 string
-Film::dci_name () const
+Film::dci_name (bool if_created_now) const
 {
        stringstream d;
 
@@ -758,64 +446,82 @@ Film::dci_name () const
                fixed_name = fixed_name.substr (0, 14);
        }
 
-       d << fixed_name << "_";
+       d << fixed_name;
 
        if (dcp_content_type()) {
-               d << dcp_content_type()->dci_name() << "_";
+               d << "_" << dcp_content_type()->dci_name();
+               d << "-" << dci_metadata().content_version;
+       }
+
+       if (three_d ()) {
+               d << "-3D";
+       }
+
+       if (video_frame_rate() != 24) {
+               d << "-" << video_frame_rate();
        }
 
-       if (format()) {
-               d << format()->dci_name() << "_";
+       if (container()) {
+               d << "_" << container()->dci_name();
        }
 
-       if (!audio_language().empty ()) {
-               d << audio_language();
-               if (!subtitle_language().empty() && with_subtitles()) {
-                       d << "-" << subtitle_language();
+       DCIMetadata const dm = dci_metadata ();
+
+       if (!dm.audio_language.empty ()) {
+               d << "_" << dm.audio_language;
+               if (!dm.subtitle_language.empty() && with_subtitles()) {
+                       d << "-" << dm.subtitle_language;
                } else {
                        d << "-XX";
                }
-                       
-               d << "_";
        }
 
-       if (!territory().empty ()) {
-               d << territory();
-               if (!rating().empty ()) {
-                       d << "-" << rating();
+       if (!dm.territory.empty ()) {
+               d << "_" << dm.territory;
+               if (!dm.rating.empty ()) {
+                       d << "-" << dm.rating;
                }
-               d << "_";
        }
 
-       switch (audio_channels()) {
+       switch (audio_channels ()) {
        case 1:
-               d << "10_";
+               d << "_10";
                break;
        case 2:
-               d << "20_";
+               d << "_20";
                break;
-       case 6:
-               d << "51_";
+       case 3:
+               d << "_30";
+               break;
+       case 4:
+               d << "_40";
                break;
-       case 8:
-               d << "71_";
+       case 5:
+               d << "_50";
+               break;
+       case 6:
+               d << "_51";
                break;
        }
 
-       d << "2K_";
+       d << "_" << resolution_to_string (_resolution);
 
-       if (!studio().empty ()) {
-               d << studio() << "_";
+       if (!dm.studio.empty ()) {
+               d << "_" << dm.studio;
        }
 
-       d << boost::gregorian::to_iso_string (_dci_date) << "_";
+       if (if_created_now) {
+               d << "_" << boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
+       } else {
+               d << "_" << boost::gregorian::to_iso_string (_dci_date);
+       }
 
-       if (!facility().empty ()) {
-               d << facility() << "_";
+       if (!dm.facility.empty ()) {
+               d << "_" << dm.facility;
        }
 
-       if (!package_type().empty ()) {
-               d << package_type();
+       if (!dm.package_type.empty ()) {
+               d << "_" << dm.package_type;
        }
 
        return d.str ();
@@ -823,10 +529,10 @@ Film::dci_name () const
 
 /** @return name to give the DCP */
 string
-Film::dcp_name () const
+Film::dcp_name (bool if_created_now) const
 {
        if (use_dci_name()) {
-               return dci_name ();
+               return dci_name (if_created_now);
        }
 
        return name();
@@ -836,7 +542,6 @@ Film::dcp_name () const
 void
 Film::set_directory (string d)
 {
-       boost::mutex::scoped_lock lm (_state_mutex);
        _directory = d;
        _dirty = true;
 }
@@ -844,612 +549,344 @@ Film::set_directory (string d)
 void
 Film::set_name (string n)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _name = n;
-       }
+       _name = n;
        signal_changed (NAME);
 }
 
 void
 Film::set_use_dci_name (bool u)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _use_dci_name = u;
-       }
+       _use_dci_name = u;
        signal_changed (USE_DCI_NAME);
 }
 
-void
-Film::set_content (string c)
-{
-       string check = directory ();
-
-#if BOOST_FILESYSTEM_VERSION == 3
-       boost::filesystem::path slash ("/");
-       string platform_slash = slash.make_preferred().string ();
-#else
-#ifdef DVDOMATIC_WINDOWS
-       string platform_slash = "\\";
-#else
-       string platform_slash = "/";
-#endif
-#endif 
-
-       if (!ends_with (check, platform_slash)) {
-               check += platform_slash;
-       }
-       
-       if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) {
-               c = c.substr (_directory.length() + 1);
-       }
-
-       string old_content;
-       
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               if (c == _content) {
-                       return;
-               }
-
-               old_content = _content;
-               _content = c;
-       }
-
-       /* Reset streams here in case the new content doesn't have one or the other */
-       _content_audio_stream = shared_ptr<AudioStream> ();
-       _subtitle_stream = shared_ptr<SubtitleStream> ();
-
-       /* Start off using content audio */
-       set_use_content_audio (true);
-
-       /* Create a temporary decoder so that we can get information
-          about the content.
-       */
-
-       try {
-               shared_ptr<DecodeOptions> o (new DecodeOptions);
-               Decoders d = decoder_factory (shared_from_this(), o, 0);
-               
-               set_size (d.video->native_size ());
-               set_frames_per_second (d.video->frames_per_second ());
-               set_subtitle_streams (d.video->subtitle_streams ());
-               if (d.audio) {
-                       set_content_audio_streams (d.audio->audio_streams ());
-               }
-
-               /* 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());
-               }
-               
-               {
-                       boost::mutex::scoped_lock lm (_state_mutex);
-                       _content = c;
-               }
-               
-               signal_changed (CONTENT);
-               
-               examine_content ();
-
-       } catch (...) {
-
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _content = old_content;
-               throw;
-
-       }
-
-       /* Default format */
-       switch (content_type()) {
-       case STILL:
-               set_format (Format::from_id ("var-185"));
-               break;
-       case VIDEO:
-               set_format (Format::from_id ("185"));
-               break;
-       }
-
-       /* 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)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_content_type = t;
-       }
+       _dcp_content_type = t;
        signal_changed (DCP_CONTENT_TYPE);
 }
 
 void
-Film::set_format (Format const * f)
+Film::set_container (Ratio const * c)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _format = f;
-       }
-       signal_changed (FORMAT);
+       _container = c;
+       signal_changed (CONTAINER);
 }
 
 void
-Film::set_crop (Crop c)
+Film::set_resolution (Resolution r)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _crop = c;
-       }
-       signal_changed (CROP);
+       _resolution = r;
+       signal_changed (RESOLUTION);
 }
 
 void
-Film::set_left_crop (int c)
+Film::set_scaler (Scaler const * s)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               
-               if (_crop.left == c) {
-                       return;
-               }
-               
-               _crop.left = c;
-       }
-       signal_changed (CROP);
+       _scaler = s;
+       signal_changed (SCALER);
 }
 
 void
-Film::set_right_crop (int c)
+Film::set_with_subtitles (bool w)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               if (_crop.right == c) {
-                       return;
-               }
-               
-               _crop.right = c;
-       }
-       signal_changed (CROP);
+       _with_subtitles = w;
+       signal_changed (WITH_SUBTITLES);
 }
 
 void
-Film::set_top_crop (int c)
+Film::set_j2k_bandwidth (int b)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               if (_crop.top == c) {
-                       return;
-               }
-               
-               _crop.top = c;
-       }
-       signal_changed (CROP);
+       _j2k_bandwidth = b;
+       signal_changed (J2K_BANDWIDTH);
 }
 
 void
-Film::set_bottom_crop (int c)
+Film::set_dci_metadata (DCIMetadata m)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               if (_crop.bottom == c) {
-                       return;
-               }
-               
-               _crop.bottom = c;
-       }
-       signal_changed (CROP);
+       _dci_metadata = m;
+       signal_changed (DCI_METADATA);
 }
 
 void
-Film::set_filters (vector<Filter const *> f)
+Film::set_video_frame_rate (int f)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _filters = f;
-       }
-       signal_changed (FILTERS);
+       _video_frame_rate = f;
+       signal_changed (VIDEO_FRAME_RATE);
 }
 
 void
-Film::set_scaler (Scaler const * s)
+Film::set_audio_channels (int c)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _scaler = s;
-       }
-       signal_changed (SCALER);
+       _audio_channels = c;
+       signal_changed (AUDIO_CHANNELS);
 }
 
 void
-Film::set_dcp_trim_start (int t)
+Film::set_three_d (bool t)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_trim_start = t;
-       }
-       signal_changed (DCP_TRIM_START);
+       _three_d = t;
+       signal_changed (THREE_D);
 }
 
 void
-Film::set_dcp_trim_end (int t)
+Film::set_interop (bool i)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_trim_end = t;
-       }
-       signal_changed (DCP_TRIM_END);
+       _interop = i;
+       signal_changed (INTEROP);
 }
 
 void
-Film::set_reel_size (uint64_t s)
+Film::signal_changed (Property p)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _reel_size = s;
-       }
-       signal_changed (REEL_SIZE);
-}
+       _dirty = true;
 
-void
-Film::unset_reel_size ()
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _reel_size = boost::optional<uint64_t> ();
+       switch (p) {
+       case Film::CONTENT:
+               set_video_frame_rate (_playlist->best_dcp_frame_rate ());
+               break;
+       case Film::VIDEO_FRAME_RATE:
+       case Film::SEQUENCE_VIDEO:
+               _playlist->maybe_sequence_video ();
+               break;
+       default:
+               break;
        }
-       signal_changed (REEL_SIZE);
-}
 
-void
-Film::set_dcp_ab (bool a)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_ab = a;
+       if (ui_signaller) {
+               ui_signaller->emit (boost::bind (boost::ref (Changed), p));
        }
-       signal_changed (DCP_AB);
 }
 
 void
-Film::set_content_audio_stream (shared_ptr<AudioStream> s)
+Film::set_dci_date_today ()
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _content_audio_stream = s;
-       }
-       signal_changed (CONTENT_AUDIO_STREAM);
+       _dci_date = boost::gregorian::day_clock::local_day ();
 }
 
-void
-Film::set_external_audio (vector<string> a)
+string
+Film::info_path (int f, Eyes e) const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _external_audio = a;
-       }
+       boost::filesystem::path p;
+       p /= info_dir ();
 
-       shared_ptr<DecodeOptions> o (new DecodeOptions);
-       shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), o, 0));
-       if (decoder->audio_stream()) {
-               _external_audio_stream = decoder->audio_stream ();
+       stringstream s;
+       s.width (8);
+       s << setfill('0') << f;
+
+       if (e == EYES_LEFT) {
+               s << ".L";
+       } else if (e == EYES_RIGHT) {
+               s << ".R";
        }
+
+       s << ".md5";
        
-       signal_changed (EXTERNAL_AUDIO);
+       p /= s.str();
+
+       /* info_dir() will already have added any initial bit of the path,
+          so don't call file() on this.
+       */
+       return p.string ();
 }
 
-void
-Film::set_use_content_audio (bool e)
+string
+Film::j2c_path (int f, Eyes e, bool t) const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _use_content_audio = e;
-       }
+       boost::filesystem::path p;
+       p /= "j2c";
+       p /= video_identifier ();
 
-       signal_changed (USE_CONTENT_AUDIO);
-}
+       stringstream s;
+       s.width (8);
+       s << setfill('0') << f;
 
-void
-Film::set_audio_gain (float g)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _audio_gain = g;
+       if (e == EYES_LEFT) {
+               s << ".L";
+       } else if (e == EYES_RIGHT) {
+               s << ".R";
        }
-       signal_changed (AUDIO_GAIN);
-}
+       
+       s << ".j2c";
 
-void
-Film::set_audio_delay (int d)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _audio_delay = d;
+       if (t) {
+               s << ".tmp";
        }
-       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);
+       p /= s.str();
+       return file (p.string ());
 }
 
-void
-Film::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _subtitle_stream = s;
-       }
-       signal_changed (SUBTITLE_STREAM);
-}
+/** Make an educated guess as to whether we have a complete DCP
+ *  or not.
+ *  @return true if we do.
+ */
 
-void
-Film::set_with_subtitles (bool w)
+bool
+Film::have_dcp () const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _with_subtitles = w;
+       try {
+               libdcp::DCP dcp (dir (dcp_name()));
+               dcp.read ();
+       } catch (...) {
+               return false;
        }
-       signal_changed (WITH_SUBTITLES);
-}
 
-void
-Film::set_subtitle_offset (int o)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _subtitle_offset = o;
-       }
-       signal_changed (SUBTITLE_OFFSET);
+       return true;
 }
 
-void
-Film::set_subtitle_scale (float s)
+shared_ptr<Player>
+Film::make_player () const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _subtitle_scale = s;
-       }
-       signal_changed (SUBTITLE_SCALE);
+       return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
 }
 
 void
 Film::set_encrypted (bool e)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _encrypted = e;
-       }
+       _encrypted = e;
        signal_changed (ENCRYPTED);
 }
 
-void
-Film::set_colour_lut (int i)
+shared_ptr<Playlist>
+Film::playlist () const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _colour_lut = i;
-       }
-       signal_changed (COLOUR_LUT);
+       return _playlist;
 }
 
-void
-Film::set_j2k_bandwidth (int b)
+ContentList
+Film::content () const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _j2k_bandwidth = b;
-       }
-       signal_changed (J2K_BANDWIDTH);
+       return _playlist->content ();
 }
 
 void
-Film::set_audio_language (string l)
+Film::examine_and_add_content (shared_ptr<Content> c)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _audio_language = l;
-       }
-       signal_changed (DCI_METADATA);
+       shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
+       j->Finished.connect (bind (&Film::maybe_add_content, this, boost::weak_ptr<Job> (j), boost::weak_ptr<Content> (c)));
+       JobManager::instance()->add (j);
 }
 
 void
-Film::set_subtitle_language (string l)
+Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _subtitle_language = l;
+       shared_ptr<Job> job = j.lock ();
+       if (!job || !job->finished_ok ()) {
+               return;
        }
-       signal_changed (DCI_METADATA);
-}
-
-void
-Film::set_territory (string t)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _territory = t;
+       
+       shared_ptr<Content> content = c.lock ();
+       if (content) {
+               add_content (content);
        }
-       signal_changed (DCI_METADATA);
 }
 
 void
-Film::set_rating (string r)
+Film::add_content (shared_ptr<Content> c)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _rating = r;
+       /* Add video content after any existing content */
+       if (dynamic_pointer_cast<VideoContent> (c)) {
+               c->set_position (_playlist->video_end ());
        }
-       signal_changed (DCI_METADATA);
+
+       _playlist->add (c);
 }
 
 void
-Film::set_studio (string s)
+Film::remove_content (shared_ptr<Content> c)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _studio = s;
-       }
-       signal_changed (DCI_METADATA);
+       _playlist->remove (c);
 }
 
-void
-Film::set_facility (string f)
+Time
+Film::length () const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _facility = f;
-       }
-       signal_changed (DCI_METADATA);
+       return _playlist->length ();
 }
 
-void
-Film::set_package_type (string p)
+bool
+Film::has_subtitles () const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _package_type = p;
-       }
-       signal_changed (DCI_METADATA);
+       return _playlist->has_subtitles ();
 }
 
-void
-Film::set_size (Size s)
+OutputVideoFrame
+Film::best_video_frame_rate () const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _size = s;
-       }
-       signal_changed (SIZE);
+       return _playlist->best_dcp_frame_rate ();
 }
 
 void
-Film::set_length (SourceFrame l)
+Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _length = l;
+       if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
+               set_video_frame_rate (_playlist->best_dcp_frame_rate ());
+       } 
+
+       if (ui_signaller) {
+               ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
        }
-       signal_changed (LENGTH);
 }
 
 void
-Film::unset_length ()
+Film::playlist_changed ()
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _length = boost::none;
-       }
-       signal_changed (LENGTH);
+       signal_changed (CONTENT);
 }      
 
-void
-Film::set_content_digest (string d)
+OutputAudioFrame
+Film::time_to_audio_frames (Time t) const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _content_digest = d;
-       }
-       _dirty = true;
+       return t * audio_frame_rate () / TIME_HZ;
 }
 
-void
-Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s)
+OutputVideoFrame
+Film::time_to_video_frames (Time t) const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _content_audio_streams = s;
-       }
-       signal_changed (CONTENT_AUDIO_STREAMS);
+       return t * video_frame_rate () / TIME_HZ;
 }
 
-void
-Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s)
+Time
+Film::audio_frames_to_time (OutputAudioFrame f) const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _subtitle_streams = s;
-       }
-       signal_changed (SUBTITLE_STREAMS);
+       return f * TIME_HZ / audio_frame_rate ();
 }
 
-void
-Film::set_frames_per_second (float f)
+Time
+Film::video_frames_to_time (OutputVideoFrame f) const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _frames_per_second = f;
-       }
-       signal_changed (FRAMES_PER_SECOND);
-}
-       
-void
-Film::signal_changed (Property p)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _dirty = true;
-       }
-
-       if (ui_signaller) {
-               ui_signaller->emit (boost::bind (boost::ref (Changed), p));
-       }
+       return f * TIME_HZ / video_frame_rate ();
 }
 
-int
-Film::audio_channels () const
+OutputAudioFrame
+Film::audio_frame_rate () const
 {
-       shared_ptr<AudioStream> s = audio_stream ();
-       if (!s) {
-               return 0;
-       }
-
-       return s->channels ();
+       /* XXX */
+       return 48000;
 }
 
 void
-Film::set_dci_date_today ()
+Film::set_sequence_video (bool s)
 {
-       _dci_date = boost::gregorian::day_clock::local_day ();
+       _sequence_video = s;
+       _playlist->set_sequence_video (s);
+       signal_changed (SEQUENCE_VIDEO);
 }
 
-boost::shared_ptr<AudioStream>
-Film::audio_stream () const
+libdcp::Size
+Film::full_frame () const
 {
-       if (use_content_audio()) {
-               return _content_audio_stream;
+       switch (_resolution) {
+       case RESOLUTION_2K:
+               return libdcp::Size (2048, 1080);
+       case RESOLUTION_4K:
+               return libdcp::Size (4096, 2160);
        }
 
-       return _external_audio_stream;
+       assert (false);
+       return libdcp::Size ();
 }
 
 void
@@ -1509,7 +946,9 @@ Film::make_kdms (
                dcp.read ();
                
                /* XXX: single CPL only */
-               shared_ptr<xmlpp::Document> kdm = dcp.cpls().front()->make_kdm (chain, signer_key.string(), (*i)->certificate, from, until);
+               shared_ptr<xmlpp::Document> kdm = dcp.cpls().front()->make_kdm (
+                       chain, signer_key.string(), (*i)->certificate, from, until, _interop, libdcp::MXFMetadata (), Config::instance()->dcp_metadata ()
+                       );
 
                boost::filesystem::path out = directory;
                out /= "kdm.xml";
index 1a78e9d34a606e62037c259316e01eeb47dac2b8..809eabdaa9a1257cae980be9ce68a2c0b78e8452 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 <inttypes.h>
-#include <boost/thread/mutex.hpp>
-#include <boost/thread.hpp>
 #include <boost/signals2.hpp>
 #include <boost/enable_shared_from_this.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
-extern "C" {
-#include <libavcodec/avcodec.h>
-}
-#include "dcp_content_type.h"
+#include <boost/filesystem.hpp>
 #include "util.h"
-#include "stream.h"
+#include "types.h"
+#include "dci_metadata.h"
 
-class Format;
-class Job;
-class Filter;
+class DCPContentType;
 class Log;
-class ExamineContentJob;
-class ExternalAudioStream;
+class Content;
+class Player;
+class Playlist;
+class AudioContent;
+class Scaler;
 class Screen;
 
 /** @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.
+ *
+ *  The content of a Film is held in a Playlist (created and managed by the Film).
  */
-class Film : public boost::enable_shared_from_this<Film>
+class Film : public boost::enable_shared_from_this<Film>, public boost::noncopyable
 {
 public:
-       Film (std::string d, bool must_exist = true);
-       Film (Film const &);
-       ~Film ();
+       Film (boost::filesystem::path);
 
-       std::string j2k_dir () const;
+       std::string info_dir () const;
+       std::string j2c_path (int, Eyes, bool) const;
+       std::string info_path (int, Eyes) const;
+       std::string internal_video_mxf_dir () const;
+       std::string internal_video_mxf_filename () const;
+       boost::filesystem::path audio_analysis_path (boost::shared_ptr<const AudioContent>) const;
 
-       void examine_content ();
-       void send_dcp_to_tms ();
+       std::string video_mxf_filename () const;
+       std::string audio_mxf_filename () const;
 
-       void make_dcp (bool);
+       void send_dcp_to_tms ();
+       void make_dcp ();
 
        /** @return Logger.
         *  It is safe to call this from any thread.
         */
-       Log* log () const {
+       boost::shared_ptr<Log> log () const {
                return _log;
        }
 
@@ -80,27 +81,38 @@ 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;
 
-       Size cropped_size (Size) const;
-       boost::optional<int> dcp_length () const;
-       std::string dci_name () const;
-       std::string dcp_name () const;
+       std::string dci_name (bool if_created_now) const;
+       std::string dcp_name (bool if_created_now = false) const;
 
        /** @return true if our state has changed since we last saved it */
        bool dirty () const {
                return _dirty;
        }
 
-       int audio_channels () const;
+       libdcp::Size full_frame () const;
 
-       void set_dci_date_today ();
+       bool have_dcp () const;
+
+       boost::shared_ptr<Player> make_player () const;
+       boost::shared_ptr<Playlist> playlist () const;
+
+       OutputAudioFrame 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 */
+
+       ContentList content () const;
+
+       Time length () const;
+       bool has_subtitles () const;
+       OutputVideoFrame best_video_frame_rate () const;
 
        void make_kdms (
                std::list<boost::shared_ptr<Screen> >,
@@ -116,424 +128,180 @@ 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,
                DCP_CONTENT_TYPE,
-               FORMAT,
-               CROP,
-               FILTERS,
+               CONTAINER,
+               RESOLUTION,
                SCALER,
-               DCP_TRIM_START,
-               DCP_TRIM_END,
-               REEL_SIZE,
-               DCP_AB,
-               CONTENT_AUDIO_STREAM,
-               EXTERNAL_AUDIO,
-               USE_CONTENT_AUDIO,
-               AUDIO_GAIN,
-               AUDIO_DELAY,
-               STILL_DURATION,
-               SUBTITLE_STREAM,
                WITH_SUBTITLES,
-               SUBTITLE_OFFSET,
-               SUBTITLE_SCALE,
                ENCRYPTED,
-               COLOUR_LUT,
                J2K_BANDWIDTH,
                DCI_METADATA,
-               SIZE,
-               LENGTH,
-               CONTENT_AUDIO_STREAMS,
-               SUBTITLE_STREAMS,
-               FRAMES_PER_SECOND,
+               VIDEO_FRAME_RATE,
+               AUDIO_CHANNELS,
+               /** The setting of _three_d has been changed */
+               THREE_D,
+               SEQUENCE_VIDEO,
+               INTEROP,
        };
 
 
        /* GET */
 
        std::string directory () const {
-               boost::mutex::scoped_lock lm (_directory_mutex);
                return _directory;
        }
 
        std::string name () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
                return _name;
        }
 
        bool use_dci_name () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
                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;
+       Ratio const * container () const {
+               return _container;
        }
 
-       Crop crop () 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;
+       Resolution resolution () const {
+               return _resolution;
        }
 
        Scaler const * scaler () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
                return _scaler;
        }
 
-       SourceFrame dcp_trim_start () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _dcp_trim_start;
-       }
-
-       SourceFrame dcp_trim_end () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _dcp_trim_end;
-       }
-
-       boost::optional<uint64_t> reel_size () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _reel_size;
-       }
-       
-       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;
-       }
-
-       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;
        }
 
-       int subtitle_offset () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _subtitle_offset;
-       }
-
-       float subtitle_scale () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _subtitle_scale;
-       }
-
        bool encrypted () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
                return _encrypted;
        }
 
-       int colour_lut () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _colour_lut;
-       }
-
        int j2k_bandwidth () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
                return _j2k_bandwidth;
        }
 
-       std::string audio_language () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _audio_language;
-       }
-       
-       std::string subtitle_language () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _subtitle_language;
-       }
-       
-       std::string territory () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _territory;
-       }
-       
-       std::string rating () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _rating;
-       }
-       
-       std::string studio () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _studio;
-       }
-       
-       std::string facility () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _facility;
-       }
-       
-       std::string package_type () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _package_type;
+       DCIMetadata dci_metadata () const {
+               return _dci_metadata;
        }
 
-       Size size () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _size;
+       /** @return The frame rate of the DCP */
+       int video_frame_rate () const {
+               return _video_frame_rate;
        }
 
-       boost::optional<SourceFrame> length () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _length;
+       int audio_channels () const {
+               return _audio_channels;
        }
-       
-       std::string content_digest () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _content_digest;
+
+       bool three_d () const {
+               return _three_d;
        }
-       
-       std::vector<boost::shared_ptr<AudioStream> > content_audio_streams () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _content_audio_streams;
+
+       bool sequence_video () const {
+               return _sequence_video;
        }
 
-       std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _subtitle_streams;
+       bool interop () const {
+               return _interop;
        }
        
-       float frames_per_second () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               if (content_type() == STILL) {
-                       return 24;
-               }
-               
-               return _frames_per_second;
-       }
-
-       boost::shared_ptr<AudioStream> audio_stream () 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_resolution (Resolution);
        void set_scaler (Scaler const *);
-       void set_dcp_trim_start (int);
-       void set_dcp_trim_end (int);
-       void set_reel_size (uint64_t);
-       void unset_reel_size ();
-       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_encrypted (bool);
-       void set_colour_lut (int);
        void set_j2k_bandwidth (int);
-       void set_audio_language (std::string);
-       void set_subtitle_language (std::string);
-       void set_territory (std::string);
-       void set_rating (std::string);
-       void set_studio (std::string);
-       void set_facility (std::string);
-       void set_package_type (std::string);
-       void set_size (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_frames_per_second (float);
-
-       /** Emitted when some property has changed */
+       void set_dci_metadata (DCIMetadata);
+       void set_video_frame_rate (int);
+       void set_audio_channels (int);
+       void set_three_d (bool);
+       void set_dci_date_today ();
+       void set_sequence_video (bool);
+       void set_interop (bool);
+
+       /** Emitted when some property has of the Film has changed */
        mutable boost::signals2::signal<void (Property)> Changed;
 
+       /** 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 */
-       Log* _log;
-
-       /** Any running ExamineContentJob, or 0 */
-       boost::shared_ptr<ExamineContentJob> _examine_content_job;
-
-       /** The date that we should use in a DCI name */
-       boost::gregorian::date _dci_date;
 
        void signal_changed (Property);
-       void examine_content_finished ();
+       std::string video_identifier () const;
+       void playlist_changed ();
+       void playlist_content_changed (boost::weak_ptr<Content>, int);
+       std::string filename_safe_name () const;
+       void maybe_add_content (boost::weak_ptr<Job>, 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.
         */
        std::string _directory;
-       /** 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;
+       /** DCP resolution (2K or 4K) */
+       Resolution _resolution;
        /** Scaler algorithm to use */
        Scaler const * _scaler;
-       /** Frames to trim off the start of the DCP */
-       int _dcp_trim_start;
-       /** Frames to trim off the end of the DCP */
-       int _dcp_trim_end;
-       /** Approximate target reel size in bytes; if not set, use a single reel */
-       boost::optional<uint64_t> _reel_size;
-       /** 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
-           the frame, -ve is further up.
-       */
-       int _subtitle_offset;
-       /** scale factor to apply to subtitles */
-       float _subtitle_scale;
        bool _encrypted;
-
-       /** index of colour LUT to use when converting RGB to XYZ.
-        *  0: sRGB
-        *  1: Rec 709
-        */
-       int _colour_lut;
        /** bandwidth for J2K files in bits per second */
        int _j2k_bandwidth;
-       
-       /* DCI naming stuff */
-       std::string _audio_language;
-       std::string _subtitle_language;
-       std::string _territory;
-       std::string _rating;
-       std::string _studio;
-       std::string _facility;
-       std::string _package_type;
-
-       /* Data which are cached to speed things up */
-
-       /** Size, in pixels, of the source (ignoring cropping) */
-       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> _external_audio_stream;
-       /** the subtitle streams that we can use */
-       std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams;
-       /** Frames per second of the source */
-       float _frames_per_second;
+       /** DCI naming stuff */
+       DCIMetadata _dci_metadata;
+       /** Frames per second to run our DCP at */
+       int _video_frame_rate;
+       /** The date that we should use in a DCI name */
+       boost::gregorian::date _dci_date;
+       /** Number of audio channels to put in the DCP */
+       int _audio_channels;
+       /** If true, the DCP will be written in 3D mode; otherwise in 2D.
+           This will be regardless of what content is on the playlist.
+       */
+       bool _three_d;
+       bool _sequence_video;
+       bool _interop;
 
        /** true if our state has changed since we last saved it */
        mutable bool _dirty;
 
-       /** Mutex for all state except _directory */
-       mutable boost::mutex _state_mutex;
-
        friend class paths_test;
+       friend class film_metadata_test;
 };
 
 #endif
index 446cc111dcdeeb7f0e22027969213aab6353a4fc..640a531e8aeb7185471c472542af2fed8e3fdbb0 100644 (file)
  */
 
 #include "filter.h"
+extern "C" {
+#include <libavfilter/avfilter.h>
+#include <libpostproc/postprocess.h>
+}
+
+#include "i18n.h"
 
 using namespace std;
 
@@ -29,12 +35,14 @@ vector<Filter const *> Filter::_filters;
 
 /** @param i Our id.
  *  @param n User-visible name.
+ *  @param c User-visible category.
  *  @param v String for a FFmpeg video filter descriptor, or "".
  *  @param p String for a FFmpeg post-processing descriptor, or "".
  */
-Filter::Filter (string i, string n, string v, string p)
+Filter::Filter (string i, string n, string c, string v, string p)
        : _id (i)
        , _name (n)
+       , _category (c)
        , _vf (v)
        , _pp (p)
 {
@@ -57,30 +65,46 @@ Filter::setup_filters ()
 {
        /* Note: "none" is a magic id name, so don't use it here */
           
-       _filters.push_back (new Filter ("pphb", "Horizontal deblocking filter", "", "hb"));
-       _filters.push_back (new Filter ("ppvb", "Vertical deblocking filter", "", "vb"));
-       _filters.push_back (new Filter ("ppha", "Horizontal deblocking filter A", "", "ha"));
-       _filters.push_back (new Filter ("ppva", "Vertical deblocking filter A", "", "va"));
-       _filters.push_back (new Filter ("pph1", "Experimental horizontal deblocking filter 1", "", "h1"));
-       _filters.push_back (new Filter ("pphv", "Experimental vertical deblocking filter 1", "", "v1"));
-       _filters.push_back (new Filter ("ppdr", "Deringing filter", "", "dr"));
-       _filters.push_back (new Filter ("pplb", "Linear blend deinterlacer", "", "lb"));
-       _filters.push_back (new Filter ("ppli", "Linear interpolating deinterlacer", "", "li"));
-       _filters.push_back (new Filter ("ppci", "Cubic interpolating deinterlacer", "", "ci"));
-       _filters.push_back (new Filter ("ppmd", "Median deinterlacer", "", "md"));
-       _filters.push_back (new Filter ("ppfd", "FFMPEG deinterlacer", "", "fd"));
-       _filters.push_back (new Filter ("ppl5", "FIR low-pass deinterlacer", "", "l5"));
-       _filters.push_back (new Filter ("mcdeint", "Motion compensating deinterlacer", "mcdeint", ""));
-       _filters.push_back (new Filter ("kerndeint", "Kernel deinterlacer", "kerndeint", ""));
-       _filters.push_back (new Filter ("yadif", "Yet Another Deinterlacing Filter", "yadif", ""));
-       _filters.push_back (new Filter ("pptn", "Temporal noise reducer", "", "tn"));
-       _filters.push_back (new Filter ("ppfq", "Force quantizer", "", "fq"));
-       _filters.push_back (new Filter ("gradfun", "Gradient debander", "gradfun", ""));
-       _filters.push_back (new Filter ("unsharp", "Unsharp mask and Gaussian blur", "unsharp", ""));
-       _filters.push_back (new Filter ("denoise3d", "3D denoiser", "denoise3d", ""));
-       _filters.push_back (new Filter ("hqdn3d", "High quality 3D denoiser", "hqdn3d", ""));
-       _filters.push_back (new Filter ("telecine", "Telecine filter", "telecine", ""));
-       _filters.push_back (new Filter ("ow", "Overcomplete wavelet denoiser", "mp=ow", ""));
+       maybe_add (N_("pphb"),      _("Horizontal deblocking filter"),                _("De-blocking"),     N_(""),          N_("hb"));
+       maybe_add (N_("ppvb"),      _("Vertical deblocking filter"),                  _("De-blocking"),     N_(""),          N_("vb"));
+       maybe_add (N_("ppha"),      _("Horizontal deblocking filter A"),              _("De-blocking"),     N_(""),          N_("ha"));
+       maybe_add (N_("ppva"),      _("Vertical deblocking filter A"),                _("De-blocking"),     N_(""),          N_("va"));
+       maybe_add (N_("pph1"),      _("Experimental horizontal deblocking filter 1"), _("De-blocking"),     N_(""),          N_("h1"));
+       maybe_add (N_("pphv"),      _("Experimental vertical deblocking filter 1"),   _("De-blocking"),     N_(""),          N_("v1"));
+       maybe_add (N_("ppdr"),      _("Deringing filter"),                            _("Misc"),            N_(""),          N_("dr"));
+       maybe_add (N_("pplb"),      _("Linear blend deinterlacer"),                   _("De-interlacing"),  N_(""),          N_("lb"));
+       maybe_add (N_("ppli"),      _("Linear interpolating deinterlacer"),           _("De-interlacing"),  N_(""),          N_("li"));
+       maybe_add (N_("ppci"),      _("Cubic interpolating deinterlacer"),            _("De-interlacing"),  N_(""),          N_("ci"));
+       maybe_add (N_("ppmd"),      _("Median deinterlacer"),                         _("De-interlacing"),  N_(""),          N_("md"));
+       maybe_add (N_("ppfd"),      _("FFMPEG deinterlacer"),                         _("De-interlacing"),  N_(""),          N_("fd"));
+       maybe_add (N_("ppl5"),      _("FIR low-pass deinterlacer"),                   _("De-interlacing"),  N_(""),          N_("l5"));
+       maybe_add (N_("mcdeint"),   _("Motion compensating deinterlacer"),            _("De-interlacing"),  N_("mcdeint"),   N_(""));
+       maybe_add (N_("kerndeint"), _("Kernel deinterlacer"),                         _("De-interlacing"),  N_("kerndeint"), N_(""));
+       maybe_add (N_("yadif"),     _("Yet Another Deinterlacing Filter"),            _("De-interlacing"),  N_("yadif"),     N_(""));
+       maybe_add (N_("pptn"),      _("Temporal noise reducer"),                      _("Noise reduction"), N_(""),          N_("tn"));
+       maybe_add (N_("ppfq"),      _("Force quantizer"),                             _("Misc"),            N_(""),          N_("fq"));
+       maybe_add (N_("gradfun"),   _("Gradient debander"),                           _("Misc"),            N_("gradfun"),   N_(""));
+       maybe_add (N_("unsharp"),   _("Unsharp mask and Gaussian blur"),              _("Misc"),            N_("unsharp"),   N_(""));
+       maybe_add (N_("denoise3d"), _("3D denoiser"),                                 _("Noise reduction"), N_("denoise3d"), N_(""));
+       maybe_add (N_("hqdn3d"),    _("High quality 3D denoiser"),                    _("Noise reduction"), N_("hqdn3d"),    N_(""));
+       maybe_add (N_("telecine"),  _("Telecine filter"),                             _("Misc"),            N_("telecine"),  N_(""));
+       maybe_add (N_("ow"),        _("Overcomplete wavelet denoiser"),               _("Noise reduction"), N_("mp=ow"),     N_(""));
+}
+
+void
+Filter::maybe_add (string i, string n, string c, string v, string p)
+{
+       if (!v.empty ()) {
+               if (avfilter_get_by_name (i.c_str())) {
+                       _filters.push_back (new Filter (i, n, c, v, p));
+               }
+       } else if (!p.empty ()) {
+               pp_mode* m = pp_get_mode_by_name_and_quality (p.c_str(), PP_QUALITY_MAX);
+               if (m) {
+                       _filters.push_back (new Filter (i, n, c, v, p));
+                       pp_free_mode (m);
+               }
+       }
 }
 
 /** @param filters Set of filters.
@@ -96,14 +120,14 @@ Filter::ffmpeg_strings (vector<Filter const *> const & filters)
        for (vector<Filter const *>::const_iterator i = filters.begin(); i != filters.end(); ++i) {
                if (!(*i)->vf().empty ()) {
                        if (!vf.empty ()) {
-                               vf += ",";
+                               vf += N_(",");
                        }
                        vf += (*i)->vf ();
                }
                
                if (!(*i)->pp().empty ()) {
                        if (!pp.empty()) {
-                               pp += ",";
+                               pp += N_(",");
                        }
                        pp += (*i)->pp ();
                }
index 20c55049c4367dad7fb8dae691e437078245fb34..5971cd5cf86e3ebb8da4ee1d8914300b64253e9d 100644 (file)
  *  @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>
+#include <boost/utility.hpp>
 
 /** @class Filter
  *  @brief A class to describe one of FFmpeg's video or post-processing filters.
  */
-class Filter
+class Filter : public boost::noncopyable
 {
 public:
-       Filter (std::string, std::string, std::string, std::string);
+       Filter (std::string, std::string, std::string, std::string, std::string);
 
        /** @return our id */
        std::string id () const {
@@ -54,6 +55,10 @@ public:
        std::string pp () const {
                return _pp;
        }
+
+       std::string category () const {
+               return _category;
+       }
        
        static std::vector<Filter const *> all ();
        static Filter const * from_id (std::string);
@@ -66,6 +71,7 @@ private:
        std::string _id;
        /** user-visible name */
        std::string _name;
+       std::string _category;
        /** string for a FFmpeg video filter descriptor */
        std::string _vf;
        /** string for a FFmpeg post-processing descriptor */
@@ -73,6 +79,7 @@ private:
 
        /** all available filters */
        static std::vector<Filter const *> _filters;
+       static void maybe_add (std::string, std::string, std::string, std::string, std::string);
 };
 
 #endif
index 376ab404fad8e23e42944c9504bb9fbbc5df5615..cd5d198079683f588c2a12c68a1648e4a5136e29 100644 (file)
 
 extern "C" {
 #include <libavfilter/avfiltergraph.h>
-#ifdef HAVE_BUFFERSRC_H        
 #include <libavfilter/buffersrc.h>
-#endif 
-#if (LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 53 && LIBAVFILTER_VERSION_MINOR <= 77) || LIBAVFILTER_VERSION_MAJOR == 3
 #include <libavfilter/avcodec.h>
 #include <libavfilter/buffersink.h>
-#elif LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-#include <libavfilter/vsrc_buffer.h>
-#endif
 #include <libavformat/avio.h>
 }
 #include "decoder.h"
 #include "filter_graph.h"
-#include "ffmpeg_compatibility.h"
 #include "filter.h"
 #include "exceptions.h"
 #include "image.h"
-#include "film.h"
-#include "ffmpeg_decoder.h"
+#include "ffmpeg_content.h"
+
+#include "i18n.h"
 
 using std::stringstream;
 using std::string;
 using std::list;
+using std::pair;
+using std::make_pair;
+using std::cout;
 using boost::shared_ptr;
+using boost::weak_ptr;
+using libdcp::Size;
 
-/** Construct a FilterGraph 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.
  */
-FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, 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)
        , _pixel_format (p)
 {
-       string filters = Filter::ffmpeg_strings (film->filters()).first;
-       if (!filters.empty ()) {
-               filters += ",";
+       _frame = av_frame_alloc ();
+       
+       string filters = Filter::ffmpeg_strings (content->filters()).first;
+       if (filters.empty ()) {
+               filters = "copy";
        }
 
-       filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size()));
-
-       avfilter_register_all ();
-       
        AVFilterGraph* graph = avfilter_graph_alloc();
        if (graph == 0) {
-               throw DecodeError ("Could not create filter graph.");
+               throw DecodeError (N_("could not create filter graph."));
        }
 
-       AVFilter* buffer_src = avfilter_get_by_name("buffer");
+       AVFilter* buffer_src = avfilter_get_by_name(N_("buffer"));
        if (buffer_src == 0) {
-               throw DecodeError ("Could not find buffer src filter");
+               throw DecodeError (N_("could not find buffer src filter"));
        }
 
-       AVFilter* buffer_sink = get_sink ();
+       AVFilter* buffer_sink = avfilter_get_by_name(N_("buffersink"));
+       if (buffer_sink == 0) {
+               throw DecodeError (N_("Could not create buffer sink filter"));
+       }
 
        stringstream a;
-       a << _size.width << ":"
-         << _size.height << ":"
-         << _pixel_format << ":"
-         << decoder->time_base_numerator() << ":"
-         << decoder->time_base_denominator() << ":"
-         << decoder->sample_aspect_ratio_numerator() << ":"
-         << decoder->sample_aspect_ratio_denominator();
-
-       int r;
-
-       if ((r = avfilter_graph_create_filter (&_buffer_src_context, buffer_src, "in", a.str().c_str(), 0, graph)) < 0) {
-               throw DecodeError ("could not create buffer source");
+       a << "video_size=" << _size.width << "x" << _size.height << ":"
+         << "pix_fmt=" << _pixel_format << ":"
+         << "time_base=1/1:"
+         << "pixel_aspect=1/1";
+
+       if (avfilter_graph_create_filter (&_buffer_src_context, buffer_src, "in", a.str().c_str(), 0, graph) < 0) {
+               throw DecodeError (N_("could not create buffer source"));
        }
 
        AVBufferSinkParams* sink_params = av_buffersink_params_alloc ();
@@ -102,99 +96,59 @@ FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, Size s,
        pixel_fmts[1] = PIX_FMT_NONE;
        sink_params->pixel_fmts = pixel_fmts;
        
-       if (avfilter_graph_create_filter (&_buffer_sink_context, buffer_sink, "out", 0, sink_params, graph) < 0) {
-               throw DecodeError ("could not create buffer sink.");
+       if (avfilter_graph_create_filter (&_buffer_sink_context, buffer_sink, N_("out"), 0, sink_params, graph) < 0) {
+               throw DecodeError (N_("could not create buffer sink."));
        }
 
+       av_free (sink_params);
+
        AVFilterInOut* outputs = avfilter_inout_alloc ();
-       outputs->name = av_strdup("in");
+       outputs->name = av_strdup(N_("in"));
        outputs->filter_ctx = _buffer_src_context;
        outputs->pad_idx = 0;
        outputs->next = 0;
 
        AVFilterInOut* inputs = avfilter_inout_alloc ();
-       inputs->name = av_strdup("out");
+       inputs->name = av_strdup(N_("out"));
        inputs->filter_ctx = _buffer_sink_context;
        inputs->pad_idx = 0;
        inputs->next = 0;
 
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-       if (avfilter_graph_parse (graph, filters.c_str(), inputs, outputs, 0) < 0) {
-               throw DecodeError ("could not set up filter graph.");
-       }
-#else  
        if (avfilter_graph_parse (graph, filters.c_str(), &inputs, &outputs, 0) < 0) {
-               throw DecodeError ("could not set up filter graph.");
+               throw DecodeError (N_("could not set up filter graph."));
        }
-#endif 
        
        if (avfilter_graph_config (graph, 0) < 0) {
-               throw DecodeError ("could not configure filter graph.");
+               throw DecodeError (N_("could not configure filter graph."));
        }
 
        /* XXX: leaking `inputs' / `outputs' ? */
 }
 
+FilterGraph::~FilterGraph ()
+{
+       av_frame_free (&_frame);
+}
+
 /** Take an AVFrame and process it using our configured filters, returning a
- *  set of Images.
+ *  set of Images.  Caller handles memory management of the input frame.
  */
-list<shared_ptr<Image> >
-FilterGraph::process (AVFrame const * frame)
+list<pair<shared_ptr<Image>, int64_t> >
+FilterGraph::process (AVFrame* frame)
 {
-       list<shared_ptr<Image> > images;
-       
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 53 && LIBAVFILTER_VERSION_MINOR <= 61
-
-       if (av_vsrc_buffer_add_frame (_buffer_src_context, frame, 0) < 0) {
-               throw DecodeError ("could not push buffer into filter chain.");
-       }
-
-#elif LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-
-       AVRational par;
-       par.num = sample_aspect_ratio_numerator ();
-       par.den = sample_aspect_ratio_denominator ();
-
-       if (av_vsrc_buffer_add_frame (_buffer_src_context, frame, 0, par) < 0) {
-               throw DecodeError ("could not push buffer into filter chain.");
-       }
-
-#else
+       list<pair<shared_ptr<Image>, int64_t> > images;
 
        if (av_buffersrc_write_frame (_buffer_src_context, frame) < 0) {
-               throw DecodeError ("could not push buffer into filter chain.");
+               throw DecodeError (N_("could not push buffer into filter chain."));
        }
 
-#endif 
-       
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 15 && LIBAVFILTER_VERSION_MINOR <= 61       
-       while (avfilter_poll_frame (_buffer_sink_context->inputs[0])) {
-#else
-       while (av_buffersink_read (_buffer_sink_context, 0)) {
-#endif         
-
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 15
-               
-               int r = avfilter_request_frame (_buffer_sink_context->inputs[0]);
-               if (r < 0) {
-                       throw DecodeError ("could not request filtered frame");
-               }
-               
-               AVFilterBufferRef* filter_buffer = _buffer_sink_context->inputs[0]->cur_buf;
-               
-#else
-
-               AVFilterBufferRef* filter_buffer;
-               if (av_buffersink_get_buffer_ref (_buffer_sink_context, &filter_buffer, 0) < 0) {
-                       filter_buffer = 0;
+       while (1) {
+               if (av_buffersink_get_frame (_buffer_sink_context, _frame) < 0) {
+                       break;
                }
 
-#endif         
-               
-               if (filter_buffer) {
-                       /* This takes ownership of filter_buffer */
-                       images.push_back (shared_ptr<Image> (new FilterBufferImage ((PixelFormat) frame->format, filter_buffer)));
-               }
+               images.push_back (make_pair (shared_ptr<Image> (new Image (_frame)), av_frame_get_best_effort_timestamp (_frame)));
+               av_frame_unref (_frame);
        }
        
        return images;
@@ -205,7 +159,7 @@ FilterGraph::process (AVFrame const * frame)
  *  @return true if this chain can process images with `s' and `p', otherwise false.
  */
 bool
-FilterGraph::can_process (Size s, AVPixelFormat p) const
+FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
 {
        return (_size == s && _pixel_format == p);
 }
index 9e6ac62528859591acaf0af5152a5bb3e53e947f..9b403c2bc65734cf03a9b8458180a0b8695a26fc 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"
-#include "ffmpeg_compatibility.h"
 
 class Image;
 class VideoFilter;
-class FFmpegDecoder;
+class FFmpegContent;
 
 /** @class FilterGraph
  *  @brief A graph of FFmpeg filters.
  */
-class FilterGraph
+class FilterGraph : public boost::noncopyable
 {
 public:
-       FilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, Size s, AVPixelFormat p);
+       FilterGraph (boost::shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p);
+       ~FilterGraph ();
 
-       bool can_process (Size s, AVPixelFormat p) const;
-       std::list<boost::shared_ptr<Image> > process (AVFrame const * frame);
+       bool can_process (libdcp::Size s, AVPixelFormat p) const;
+       std::list<std::pair<boost::shared_ptr<Image>, int64_t> > process (AVFrame * frame);
 
 private:
        AVFilterContext* _buffer_src_context;
        AVFilterContext* _buffer_sink_context;
-       Size _size; ///< size of the images that this chain can process
+       libdcp::Size _size; ///< size of the images that this chain can process
        AVPixelFormat _pixel_format; ///< pixel format of the images that this chain can process
+       AVFrame* _frame;
 };
 
 #endif
diff --git a/src/lib/format.cc b/src/lib/format.cc
deleted file mode 100644 (file)
index 9758624..0000000
+++ /dev/null
@@ -1,186 +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"
-
-using std::string;
-using std::setprecision;
-using std::stringstream;
-using std::vector;
-using boost::shared_ptr;
-
-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 << " (";
-       }
-
-       s << setprecision(3) << (_ratio / 100.0) << ":1";
-
-       if (!_nickname.empty ()) {
-               s << ")";
-       }
-
-       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 ()
-{
-       _formats.push_back (new FixedFormat (119, Size (1285, 1080), "119", "1.19", "F"));
-       _formats.push_back (new FixedFormat (133, Size (1436, 1080), "133", "1.33", "F"));
-       _formats.push_back (new FixedFormat (138, Size (1485, 1080), "138", "1.375", "F"));
-       _formats.push_back (new FixedFormat (133, Size (1998, 1080), "133-in-flat", "4:3 within Flat", "F"));
-       _formats.push_back (new FixedFormat (137, Size (1480, 1080), "137", "Academy", "F"));
-       _formats.push_back (new FixedFormat (166, Size (1793, 1080), "166", "1.66", "F"));
-       _formats.push_back (new FixedFormat (166, Size (1998, 1080), "166-in-flat", "1.66 within Flat", "F"));
-       _formats.push_back (new FixedFormat (178, Size (1998, 1080), "178-in-flat", "16:9 within Flat", "F"));
-       _formats.push_back (new FixedFormat (178, Size (1920, 1080), "178", "16:9", "F"));
-       _formats.push_back (new FixedFormat (185, Size (1998, 1080), "185", "Flat", "F"));
-       _formats.push_back (new FixedFormat (239, Size (2048, 858), "239", "Scope", "S"));
-       _formats.push_back (new VariableFormat (Size (1998, 1080), "var-185", "Flat", "F"));
-       _formats.push_back (new VariableFormat (Size (2048, 858), "var-239", "Scope", "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 multiplied by 100 (e.g. 185)
- *  @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 (int r, Size dcp, string id, string n, string d)
-       : Format (dcp, id, n, d)
-       , _ratio (r)
-{
-
-}
-
-int
-Format::dcp_padding (shared_ptr<const Film> f) const
-{
-       int p = rint ((_dcp_size.width - (_dcp_size.height * ratio_as_integer(f) / 100.0)) / 2.0);
-
-       /* This comes out -ve for Scope; bodge it */
-       if (p < 0) {
-               p = 0;
-       }
-       
-       return p;
-}
-
-VariableFormat::VariableFormat (Size dcp, string id, string n, string d)
-       : Format (dcp, id, n, d)
-{
-
-}
-
-int
-VariableFormat::ratio_as_integer (shared_ptr<const Film> f) const
-{
-       return rint (ratio_as_float (f) * 100);
-}
-
-float
-VariableFormat::ratio_as_float (shared_ptr<const Film> f) const
-{
-       return float (f->size().width) / f->size().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 2118237..0000000
+++ /dev/null
@@ -1,134 +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 (Size dcp, std::string id, std::string n, std::string d)
-               : _dcp_size (dcp)
-               , _id (id)
-               , _nickname (n)
-               , _dci_name (d)
-       {}
-
-       /** @return the aspect ratio multiplied by 100
-        *  (e.g. 239 for Cinemascope 2.39:1)
-        */
-       virtual int ratio_as_integer (boost::shared_ptr<const Film> f) const = 0;
-
-       /** @return the ratio as a floating point number */
-       virtual float ratio_as_float (boost::shared_ptr<const Film> f) const = 0;
-
-       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.
-        */
-       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:     
-       /** 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.
-        */
-       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 (int, Size, std::string, std::string, std::string);
-
-       int ratio_as_integer (boost::shared_ptr<const Film>) const {
-               return _ratio;
-       }
-
-       float ratio_as_float (boost::shared_ptr<const Film>) const {
-               return _ratio / 100.0;
-       }
-
-       std::string name () const;
-       
-private:
-
-       /** Ratio expressed as the actual ratio multiplied by 100 */
-       int _ratio;
-};
-
-class VariableFormat : public Format
-{
-public:
-       VariableFormat (Size, std::string, std::string, std::string);
-
-       int ratio_as_integer (boost::shared_ptr<const Film> f) const;
-       float ratio_as_float (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 cec3b3c..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 (Log* log, float gain)
-       : AudioProcessor (log)
-       , _gain (gain)
-{
-
-}
-
-void
-Gain::process_audio (shared_ptr<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 716ee9b..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 (Log* log, float gain);
-
-       void process_audio (boost::shared_ptr<AudioBuffers>);
-
-private:
-       float _gain;
-};
diff --git a/src/lib/i18n.h b/src/lib/i18n.h
new file mode 100644 (file)
index 0000000..890313b
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+    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 <libintl.h>
+
+#define _(x) dgettext ("libdcpomatic", x)
+#define N_(x) x
index f774f476fd707c7b02ddf00b6ca099b6c3a6ca85..dbea62d26b3b7363d7c33ce7df19dff170430a5f 100644 (file)
 */
 
 /** @file src/image.cc
- *  @brief A set of classes to describe video images.
+ *  @brief A class to describe a video image.
  */
 
-#include <sstream>
-#include <iomanip>
 #include <iostream>
-#include <sys/time.h>
-#include <boost/algorithm/string.hpp>
-#include <boost/bind.hpp>
-#include <openjpeg.h>
 extern "C" {
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
 #include <libswscale/swscale.h>
-#include <libavfilter/avfiltergraph.h>
-#include <libpostproc/postprocess.h>
 #include <libavutil/pixfmt.h>
+#include <libavutil/pixdesc.h>
+#include <libpostproc/postprocess.h>
 }
 #include "image.h"
 #include "exceptions.h"
 #include "scaler.h"
 
-using namespace std;
-using namespace boost;
+using std::string;
+using std::min;
+using std::cout;
+using boost::shared_ptr;
+using libdcp::Size;
 
-void
-Image::swap (Image& other)
+int
+Image::line_factor (int n) const
 {
-       std::swap (_pixel_format, other._pixel_format);
+       if (n == 0) {
+               return 1;
+       }
+
+       AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format);
+       if (!d) {
+               throw PixelFormatError ("lines()", _pixel_format);
+       }
+       
+       return pow (2.0f, d->log2_chroma_h);
 }
 
 /** @param n Component index.
@@ -55,55 +59,39 @@ Image::swap (Image& other)
 int
 Image::lines (int n) const
 {
-       switch (_pixel_format) {
-       case PIX_FMT_YUV420P:
-               if (n == 0) {
-                       return size().height;
-               } else {
-                       return size().height / 2;
-               }
-               break;
-       case PIX_FMT_RGB24:
-       case PIX_FMT_RGBA:
-       case PIX_FMT_YUV422P10LE:
-       case PIX_FMT_YUV422P:
-               return size().height;
-       default:
-               assert (false);
-       }
-
-       return 0;
+       return rint (ceil (static_cast<double>(size().height) / line_factor (n)));
 }
 
 /** @return Number of components */
 int
 Image::components () const
 {
-       switch (_pixel_format) {
-       case PIX_FMT_YUV420P:
-       case PIX_FMT_YUV422P10LE:
-       case PIX_FMT_YUV422P:
-               return 3;
-       case PIX_FMT_RGB24:
-       case PIX_FMT_RGBA:
-               return 1;
-       default:
-               assert (false);
+       AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format);
+       if (!d) {
+               throw PixelFormatError ("components()", _pixel_format);
        }
 
-       return 0;
+       if ((d->flags & PIX_FMT_PLANAR) == 0) {
+               return 1;
+       }
+       
+       return d->nb_components;
 }
 
 shared_ptr<Image>
-Image::scale (Size out_size, Scaler const * scaler, bool aligned) const
+Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat result_format, bool result_aligned) const
 {
        assert (scaler);
+       /* Empirical testing suggests that sws_scale() will crash if
+          the input image is not aligned.
+       */
+       assert (aligned ());
 
-       shared_ptr<Image> scaled (new SimpleImage (pixel_format(), out_size, aligned));
+       shared_ptr<Image> scaled (new Image (result_format, out_size, result_aligned));
 
        struct SwsContext* scale_context = sws_getContext (
                size().width, size().height, pixel_format(),
-               out_size.width, out_size.height, pixel_format(),
+               out_size.width, out_size.height, result_format,
                scaler->ffmpeg_id (), 0, 0, 0
                );
 
@@ -119,61 +107,6 @@ Image::scale (Size out_size, Scaler const * scaler, bool aligned) const
        return scaled;
 }
 
-/** Scale this image to a given size and convert it to RGB.
- *  @param out_size Output image size in pixels.
- *  @param scaler Scaler to use.
- */
-shared_ptr<Image>
-Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler, bool aligned) const
-{
-       assert (scaler);
-
-       Size content_size = out_size;
-       content_size.width -= (padding * 2);
-
-       shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, content_size, aligned));
-
-       struct SwsContext* scale_context = sws_getContext (
-               size().width, size().height, pixel_format(),
-               content_size.width, content_size.height, PIX_FMT_RGB24,
-               scaler->ffmpeg_id (), 0, 0, 0
-               );
-
-       /* Scale and convert to RGB from whatever its currently in (which may be RGB) */
-       sws_scale (
-               scale_context,
-               data(), stride(),
-               0, size().height,
-               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, 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;
-}
-
 /** Run a FFmpeg post-process on this image and return the processed version.
  *  @param pp Flags for the required set of post processes.
  *  @return Post-processed image.
@@ -181,7 +114,7 @@ Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scal
 shared_ptr<Image>
 Image::post_process (string pp, bool aligned) const
 {
-       shared_ptr<Image> out (new SimpleImage (pixel_format(), size (), aligned));
+       shared_ptr<Image> out (new Image (pixel_format(), size (), aligned));
 
        int pp_format = 0;
        switch (pixel_format()) {
@@ -190,10 +123,17 @@ Image::post_process (string pp, bool aligned) const
                break;
        case PIX_FMT_YUV422P10LE:
        case PIX_FMT_YUV422P:
+       case PIX_FMT_UYVY422:
                pp_format = PP_FORMAT_422;
                break;
+       case PIX_FMT_YUV444P:
+       case PIX_FMT_YUV444P9BE:
+       case PIX_FMT_YUV444P9LE:
+       case PIX_FMT_YUV444P10BE:
+       case PIX_FMT_YUV444P10LE:
+               pp_format = PP_FORMAT_444;
        default:
-               assert (false);
+               throw PixelFormatError ("post_process", pixel_format());
        }
                
        pp_mode* mode = pp_get_mode_by_name_and_quality (pp.c_str (), PP_QUALITY_MAX);
@@ -215,53 +155,137 @@ Image::post_process (string pp, bool aligned) const
 shared_ptr<Image>
 Image::crop (Crop crop, bool aligned) const
 {
-       Size cropped_size = size ();
+       libdcp::Size cropped_size = size ();
        cropped_size.width -= crop.left + crop.right;
        cropped_size.height -= crop.top + crop.bottom;
 
-       shared_ptr<Image> out (new SimpleImage (pixel_format(), cropped_size, aligned));
+       shared_ptr<Image> out (new Image (pixel_format(), cropped_size, aligned));
 
        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;
-                       
+               /* bytes_per_pixel() could be a fraction; in this case the stride will be rounded
+                  up, and we need to make sure that we copy over the width (up to the stride)
+                  rather than short of the width; hence the ceil() here.
+               */
+               int const cropped_width_in_bytes = ceil (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 += line_size()[c];
-                       out_p += out->line_size()[c];
+                       in_p += stride()[c];
+                       out_p += out->stride()[c];
                }
        }
 
        return out;
 }
 
+/** Blacken a YUV image whose bits per pixel is rounded up to 16 */
+void
+Image::yuv_16_black (uint16_t v)
+{
+       memset (data()[0], 0, lines(0) * stride()[0]);
+       for (int i = 1; i < 3; ++i) {
+               int16_t* p = reinterpret_cast<int16_t*> (data()[i]);
+               for (int y = 0; y < size().height; ++y) {
+                       for (int x = 0; x < line_size()[i] / 2; ++x) {
+                               p[x] = v;
+                       }
+                       p += stride()[i] / 2;
+               }
+       }
+}
+
+uint16_t
+Image::swap_16 (uint16_t v)
+{
+       return ((v >> 8) & 0xff) | ((v & 0xff) << 8);
+}
+
 void
 Image::make_black ()
 {
+       /* U/V black value for 8-bit colour */
+       static uint8_t const eight_bit_uv =     (1 << 7) - 1;
+       /* U/V black value for 9-bit colour */
+       static uint16_t const nine_bit_uv =     (1 << 8) - 1;
+       /* U/V black value for 10-bit colour */
+       static uint16_t const ten_bit_uv =      (1 << 9) - 1;
+       /* U/V black value for 16-bit colour */
+       static uint16_t const sixteen_bit_uv =  (1 << 15) - 1;
+       
        switch (_pixel_format) {
        case PIX_FMT_YUV420P:
-       case PIX_FMT_YUV422P10LE:
        case PIX_FMT_YUV422P:
+       case PIX_FMT_YUV444P:
                memset (data()[0], 0, lines(0) * stride()[0]);
-               memset (data()[1], 0x80, lines(1) * stride()[1]);
-               memset (data()[2], 0x80, lines(2) * stride()[2]);
+               memset (data()[1], eight_bit_uv, lines(1) * stride()[1]);
+               memset (data()[2], eight_bit_uv, lines(2) * stride()[2]);
+               break;
+
+       case PIX_FMT_YUVJ420P:
+       case PIX_FMT_YUVJ422P:
+       case PIX_FMT_YUVJ444P:
+               memset (data()[0], 0, lines(0) * stride()[0]);
+               memset (data()[1], eight_bit_uv + 1, lines(1) * stride()[1]);
+               memset (data()[2], eight_bit_uv + 1, lines(2) * stride()[2]);
+               break;
+
+       case PIX_FMT_YUV422P9LE:
+       case PIX_FMT_YUV444P9LE:
+               yuv_16_black (nine_bit_uv);
+               break;
+
+       case PIX_FMT_YUV422P9BE:
+       case PIX_FMT_YUV444P9BE:
+               yuv_16_black (swap_16 (nine_bit_uv));
+               break;
+               
+       case PIX_FMT_YUV422P10LE:
+       case PIX_FMT_YUV444P10LE:
+               yuv_16_black (ten_bit_uv);
+               break;
+
+       case PIX_FMT_YUV422P16LE:
+       case PIX_FMT_YUV444P16LE:
+               yuv_16_black (sixteen_bit_uv);
+               break;
+               
+       case PIX_FMT_YUV444P10BE:
+       case PIX_FMT_YUV422P10BE:
+               yuv_16_black (swap_16 (ten_bit_uv));
                break;
 
        case PIX_FMT_RGB24:             
                memset (data()[0], 0, lines(0) * stride()[0]);
                break;
 
+       case PIX_FMT_UYVY422:
+       {
+               int const Y = lines(0);
+               int const X = line_size()[0];
+               uint8_t* p = data()[0];
+               for (int y = 0; y < Y; ++y) {
+                       for (int x = 0; x < X / 4; ++x) {
+                               *p++ = eight_bit_uv; // Cb
+                               *p++ = 0;            // Y0
+                               *p++ = eight_bit_uv; // Cr
+                               *p++ = 0;            // Y1
+                       }
+               }
+               break;
+       }
+
        default:
-               assert (false);
+               throw PixelFormatError ("make_black()", _pixel_format);
        }
 }
 
 void
-Image::alpha_blend (shared_ptr<const Image> other, Position position)
+Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
 {
        /* Only implemented for RGBA onto RGB24 so far */
        assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
@@ -296,13 +320,28 @@ Image::alpha_blend (shared_ptr<const Image> other, Position position)
        }
 }
 
+void
+Image::copy (shared_ptr<const Image> other, Position<int> 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)
 {
        for (int i = 0; i < components(); ++i) {
                uint8_t* p = data()[i];
                for (int y = 0; y < lines(i); ++y) {
-                       socket->read_definite_and_consume (p, line_size()[i], 30);
+                       socket->read (p, line_size()[i]);
                        p += stride()[i];
                }
        }
@@ -314,7 +353,7 @@ Image::write_to_socket (shared_ptr<Socket> socket) const
        for (int i = 0; i < components(); ++i) {
                uint8_t* p = data()[i];
                for (int y = 0; y < lines(i); ++y) {
-                       socket->write (p, line_size()[i], 30);
+                       socket->write (p, line_size()[i]);
                        p += stride()[i];
                }
        }
@@ -324,60 +363,52 @@ Image::write_to_socket (shared_ptr<Socket> socket) const
 float
 Image::bytes_per_pixel (int c) const
 {
-       if (c == 3) {
+       AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format);
+       if (!d) {
+               throw PixelFormatError ("lines()", _pixel_format);
+       }
+
+       if (c >= components()) {
                return 0;
        }
+
+       float bpp[4] = { 0, 0, 0, 0 };
+
+       bpp[0] = floor ((d->comp[0].depth_minus1 + 1 + 7) / 8);
+       if (d->nb_components > 1) {
+               bpp[1] = floor ((d->comp[1].depth_minus1 + 1 + 7) / 8) / pow (2.0f, d->log2_chroma_w);
+       }
+       if (d->nb_components > 2) {
+               bpp[2] = floor ((d->comp[2].depth_minus1 + 1 + 7) / 8) / pow (2.0f, d->log2_chroma_w);
+       }
+       if (d->nb_components > 3) {
+               bpp[3] = floor ((d->comp[3].depth_minus1 + 1 + 7) / 8) / pow (2.0f, d->log2_chroma_w);
+       }
        
-       switch (_pixel_format) {
-       case PIX_FMT_RGB24:
-               if (c == 0) {
-                       return 3;
-               } else {
-                       return 0;
-               }
-       case PIX_FMT_RGBA:
-               if (c == 0) {
-                       return 4;
-               } else {
-                       return 0;
-               }
-       case PIX_FMT_YUV420P:
-       case PIX_FMT_YUV422P:
-               if (c == 0) {
-                       return 1;
-               } else {
-                       return 0.5;
-               }
-       case PIX_FMT_YUV422P10LE:
-               if (c == 1) {
-                       return 2;
-               } else {
-                       return 1;
-               }
-       default:
-               assert (false);
+       if ((d->flags & PIX_FMT_PLANAR) == 0) {
+               /* Not planar; sum them up */
+               return bpp[0] + bpp[1] + bpp[2] + bpp[3];
        }
 
-       return 0;
+       return bpp[c];
 }
 
-
-/** Construct a SimpleImage of a given size and format, allocating memory
+/** Construct a Image of a given size and format, allocating memory
  *  as required.
  *
  *  @param p Pixel format.
  *  @param s Size in pixels.
  */
-SimpleImage::SimpleImage (AVPixelFormat p, Size s, bool aligned)
-       : Image (p)
-       , _size (s)
+Image::Image (AVPixelFormat p, libdcp::Size s, bool aligned)
+       : libdcp::Image (s)
+       , _pixel_format (p)
        , _aligned (aligned)
 {
        allocate ();
 }
 
 void
-SimpleImage::allocate ()
+Image::allocate ()
 {
        _data = (uint8_t **) av_malloc (4 * sizeof (uint8_t *));
        _data[0] = _data[1] = _data[2] = _data[3] = 0;
@@ -389,43 +420,96 @@ SimpleImage::allocate ()
        _stride[0] = _stride[1] = _stride[2] = _stride[3] = 0;
 
        for (int i = 0; i < components(); ++i) {
-               _line_size[i] = _size.width * bytes_per_pixel(i);
+               _line_size[i] = ceil (_size.width * bytes_per_pixel(i));
                _stride[i] = stride_round_up (i, _line_size, _aligned ? 32 : 1);
-               _data[i] = (uint8_t *) av_malloc (_stride[i] * lines (i));
+
+               /* The assembler function ff_rgb24ToY_avx (in libswscale/x86/input.asm)
+                  uses a 16-byte fetch to read three bytes (R/G/B) of image data.
+                  Hence on the last pixel of the last line it reads over the end of
+                  the actual data by 1 byte.  If the width of an image is a multiple
+                  of the stride alignment there will be no padding at the end of image lines.
+                  OS X crashes on this illegal read, though other operating systems don't
+                  seem to mind.  The nasty + 1 in this malloc makes sure there is always a byte
+                  for that instruction to read safely.
+               */
+               _data[i] = (uint8_t *) av_malloc (_stride[i] * lines (i) + 1);
        }
 }
 
-SimpleImage::SimpleImage (SimpleImage const & other)
-       : Image (other)
+Image::Image (Image const & other)
+       : libdcp::Image (other)
+       ,  _pixel_format (other._pixel_format)
+       , _aligned (other._aligned)
+{
+       allocate ();
+
+       for (int i = 0; i < components(); ++i) {
+               uint8_t* p = _data[i];
+               uint8_t* q = other._data[i];
+               for (int j = 0; j < lines(i); ++j) {
+                       memcpy (p, q, _line_size[i]);
+                       p += stride()[i];
+                       q += other.stride()[i];
+               }
+       }
+}
+
+Image::Image (AVFrame* frame)
+       : libdcp::Image (libdcp::Size (frame->width, frame->height))
+       , _pixel_format (static_cast<AVPixelFormat> (frame->format))
+       , _aligned (true)
 {
-       _size = other._size;
-       _aligned = other._aligned;
-       
        allocate ();
 
        for (int i = 0; i < components(); ++i) {
-               memcpy (_data[i], other._data[i], _line_size[i] * lines(i));
+               uint8_t* p = _data[i];
+               uint8_t* q = frame->data[i];
+               for (int j = 0; j < lines(i); ++j) {
+                       memcpy (p, q, _line_size[i]);
+                       p += stride()[i];
+                       /* AVFrame's linesize is what we call `stride' */
+                       q += frame->linesize[i];
+               }
        }
 }
 
-SimpleImage&
-SimpleImage::operator= (SimpleImage const & other)
+Image::Image (shared_ptr<const Image> other, bool aligned)
+       : libdcp::Image (other)
+       , _pixel_format (other->_pixel_format)
+       , _aligned (aligned)
+{
+       allocate ();
+
+       for (int i = 0; i < components(); ++i) {
+               assert(line_size()[i] == other->line_size()[i]);
+               uint8_t* p = _data[i];
+               uint8_t* q = other->data()[i];
+               for (int j = 0; j < lines(i); ++j) {
+                       memcpy (p, q, line_size()[i]);
+                       p += stride()[i];
+                       q += other->stride()[i];
+               }
+       }
+}
+
+Image&
+Image::operator= (Image const & other)
 {
        if (this == &other) {
                return *this;
        }
 
-       SimpleImage tmp (other);
+       Image tmp (other);
        swap (tmp);
        return *this;
 }
 
 void
-SimpleImage::swap (SimpleImage & other)
+Image::swap (Image & other)
 {
-       Image::swap (other);
+       libdcp::Image::swap (other);
        
-       std::swap (_size, other._size);
+       std::swap (_pixel_format, other._pixel_format);
 
        for (int i = 0; i < 4; ++i) {
                std::swap (_data[i], other._data[i]);
@@ -436,8 +520,8 @@ SimpleImage::swap (SimpleImage & other)
        std::swap (_aligned, other._aligned);
 }
 
-/** Destroy a SimpleImage */
-SimpleImage::~SimpleImage ()
+/** Destroy a Image */
+Image::~Image ()
 {
        for (int i = 0; i < components(); ++i) {
                av_free (_data[i]);
@@ -449,91 +533,32 @@ SimpleImage::~SimpleImage ()
 }
 
 uint8_t **
-SimpleImage::data () const
+Image::data () const
 {
        return _data;
 }
 
 int *
-SimpleImage::line_size () const
+Image::line_size () const
 {
        return _line_size;
 }
 
 int *
-SimpleImage::stride () const
+Image::stride () const
 {
        return _stride;
 }
 
-Size
-SimpleImage::size () const
+libdcp::Size
+Image::size () const
 {
        return _size;
 }
 
-FilterBufferImage::FilterBufferImage (AVPixelFormat p, AVFilterBufferRef* b)
-       : Image (p)
-       , _buffer (b)
-{
-
-}
-
-FilterBufferImage::~FilterBufferImage ()
-{
-       avfilter_unref_buffer (_buffer);
-}
-
-uint8_t **
-FilterBufferImage::data () const
-{
-       return _buffer->data;
-}
-
-int *
-FilterBufferImage::line_size () const
-{
-       return _buffer->linesize;
-}
-
-int *
-FilterBufferImage::stride () const
-{
-       /* XXX? */
-       return _buffer->linesize;
-}
-
-Size
-FilterBufferImage::size () const
-{
-       return Size (_buffer->video->w, _buffer->video->h);
-}
-
-RGBPlusAlphaImage::RGBPlusAlphaImage (shared_ptr<const Image> im)
-       : SimpleImage (im->pixel_format(), im->size(), false)
-{
-       assert (im->pixel_format() == PIX_FMT_RGBA);
-
-       _alpha = (uint8_t *) av_malloc (im->size().width * im->size().height);
-
-       uint8_t* in = im->data()[0];
-       uint8_t* out = data()[0];
-       uint8_t* out_alpha = _alpha;
-       for (int y = 0; y < im->size().height; ++y) {
-               uint8_t* in_r = in;
-               for (int x = 0; x < im->size().width; ++x) {
-                       *out++ = *in_r++;
-                       *out++ = *in_r++;
-                       *out++ = *in_r++;
-                       *out_alpha++ = *in_r++;
-               }
-
-               in += im->stride()[0];
-       }
-}
-
-RGBPlusAlphaImage::~RGBPlusAlphaImage ()
+bool
+Image::aligned () const
 {
-       av_free (_alpha);
+       return _aligned;
 }
 
index e19c6f54b7055d916df185cdab98c102f9ea8496..6af74a8c5ee16e641c817f27361b4757118e1295 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>
@@ -31,50 +31,36 @@ extern "C" {
 #include <libavcodec/avcodec.h>
 #include <libavfilter/avfilter.h>
 }
+#include <libdcp/image.h>
 #include "util.h"
-#include "ffmpeg_compatibility.h"
+#include "position.h"
 
 class Scaler;
-class RGBFrameImage;
-class SimpleImage;
 
-/** @class Image
- *  @brief Parent class for wrappers of some image, in some format, that
- *  can present a set of components and a size in pixels.
- *
- *  This class also has some conversion / processing methods.
- *
- *  The main point of this class (and its subclasses) is to abstract
- *  details of FFmpeg's memory management and varying data formats.
- */
-class Image
+class Image : public libdcp::Image
 {
 public:
-       Image (AVPixelFormat p)
-               : _pixel_format (p)
-       {}
+       Image (AVPixelFormat, libdcp::Size, bool);
+       Image (AVFrame *);
+       Image (Image const &);
+       Image (boost::shared_ptr<const Image>, bool);
+       Image& operator= (Image const &);
+       ~Image ();
        
-       virtual ~Image () {}
-
-       /** @return Array of pointers to arrays of the component data */
-       virtual uint8_t ** data () const = 0;
-
-       /** @return Array of sizes of the data in each line, in bytes (without any alignment padding bytes) */
-       virtual int * line_size () const = 0;
-
-       /** @return Array of strides for each line (including any alignment padding bytes) */
-       virtual int * stride () const = 0;
-
-       /** @return Size of the image, in pixels */
-       virtual Size size () const = 0;
+       uint8_t ** data () const;
+       int * line_size () const;
+       int * stride () const;
+       libdcp::Size size () const;
+       bool aligned () const;
 
        int components () const;
+       int line_factor (int) const;
        int lines (int) const;
 
-       boost::shared_ptr<Image> scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler, bool aligned) const;
-       boost::shared_ptr<Image> scale (Size, Scaler const *, bool aligned) const;
+       boost::shared_ptr<Image> scale (libdcp::Size, Scaler const *, AVPixelFormat, 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 alpha_blend (boost::shared_ptr<const Image> image, Position<int> pos);
+       void copy (boost::shared_ptr<const Image> image, Position<int> pos);
        boost::shared_ptr<Image> crop (Crop c, bool aligned) const;
        
        void make_black ();
@@ -86,76 +72,20 @@ public:
                return _pixel_format;
        }
 
-protected:
-       virtual void swap (Image &);
-       float bytes_per_pixel (int) const;
-
-private:       
-       AVPixelFormat _pixel_format; ///< FFmpeg's way of describing the pixel format of this Image
-};
-
-/** @class FilterBufferImage
- *  @brief An Image that is held in an AVFilterBufferRef.
- */
-class FilterBufferImage : public Image
-{
-public:
-       FilterBufferImage (AVPixelFormat, AVFilterBufferRef *);
-       ~FilterBufferImage ();
-
-       uint8_t ** data () const;
-       int * line_size () const;
-       int * stride () const;
-       Size size () const;
-
 private:
-       /* Not allowed */
-       FilterBufferImage (FilterBufferImage const &);
-       FilterBufferImage& operator= (FilterBufferImage const &);
+       friend class pixel_formats_test;
        
-       AVFilterBufferRef* _buffer;
-};
-
-/** @class SimpleImage
- *  @brief An Image for which memory is allocated using a `simple' av_malloc().
- */
-class SimpleImage : public Image
-{
-public:
-       SimpleImage (AVPixelFormat, Size, bool);
-       SimpleImage (SimpleImage const &);
-       SimpleImage& operator= (SimpleImage const &);
-       ~SimpleImage ();
-
-       uint8_t ** data () const;
-       int * line_size () const;
-       int * stride () const;
-       Size size () const;
-
-protected:
        void allocate ();
-       void swap (SimpleImage &);
+       void swap (Image &);
+       float bytes_per_pixel (int) const;
+       void yuv_16_black (uint16_t);
+       static uint16_t swap_16 (uint16_t);
        
-private:
-       Size _size; ///< size in pixels
+       AVPixelFormat _pixel_format; ///< FFmpeg's way of describing the pixel format of this Image
        uint8_t** _data; ///< array of pointers to components
        int* _line_size; ///< array of sizes of the data in each line, in pixels (without any alignment padding bytes)
        int* _stride; ///< array of strides for each line (including any alignment padding bytes)
        bool _aligned;
 };
 
-class RGBPlusAlphaImage : public SimpleImage
-{
-public:
-       RGBPlusAlphaImage (boost::shared_ptr<const Image>);
-       ~RGBPlusAlphaImage ();
-
-       uint8_t* alpha () const {
-               return _alpha;
-       }
-       
-private:
-       uint8_t* _alpha;
-};
-
 #endif
diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc
deleted file mode 100644 (file)
index bad1fb8..0000000
+++ /dev/null
@@ -1,149 +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/filesystem.hpp>
-#include <Magick++.h>
-#include "imagemagick_decoder.h"
-#include "image.h"
-#include "film.h"
-#include "exceptions.h"
-
-using std::cout;
-using boost::shared_ptr;
-
-ImageMagickDecoder::ImageMagickDecoder (
-       boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j)
-       : Decoder (f, o, j)
-       , VideoDecoder (f, o, j)
-{
-       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 ();
-}
-
-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 ());
-       Size const s = Size (image->columns(), image->rows());
-       delete image;
-
-       return s;
-}
-
-bool
-ImageMagickDecoder::pass ()
-{
-       if (_iter == _files.end()) {
-               if (!_film->dcp_length() || video_frame() >= _film->dcp_length().get()) {
-                       return true;
-               }
-
-               repeat_last_video ();
-               return false;
-       }
-       
-       Magick::Image* magick_image = new Magick::Image (_film->content_path ());
-       
-       Size size = native_size ();
-       shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, 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) {
-                       Magick::Color c = magick_image->pixelColor (x, y);
-                       *p++ = c.redQuantum() * 255 / QuantumRange;
-                       *p++ = c.greenQuantum() * 255 / QuantumRange;
-                       *p++ = c.blueQuantum() * 255 / QuantumRange;
-               }
-       }
-
-       delete magick_image;
-
-       image = image->crop (_film->crop(), false);
-       
-       emit_video (image, 0);
-
-       ++_iter;
-       return false;
-}
-
-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)
-{
-       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;
-}
-
-void
-ImageMagickDecoder::film_changed (Film::Property p)
-{
-       if (p == Film::CROP) {
-               OutputChanged ();
-       }
-}
diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h
deleted file mode 100644 (file)
index 6f426f3..0000000
+++ /dev/null
@@ -1,89 +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_decoder.h"
-
-namespace Magick {
-       class Image;
-}
-
-class ImageMagickDecoder : public VideoDecoder
-{
-public:
-       ImageMagickDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
-
-       float frames_per_second () const {
-               /* We don't know */
-               return 0;
-       }
-
-       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;
-       }
-
-       int64_t audio_channel_layout () const {
-               return 0;
-       }
-
-       bool has_subtitles () const {
-               return false;
-       }
-
-       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;
-       }
-
-private:
-       void film_changed (Film::Property);
-       
-       std::list<std::string> _files;
-       std::list<std::string>::iterator _iter;
-};
index 896862d143f7c9c7c26514d4e17d1e7d966652f5..8924fa09c49fcdd09cef5637f5e27496de564f0e 100644 (file)
 #include <libdcp/exceptions.h>
 #include "job.h"
 #include "util.h"
+#include "cross.h"
+#include "ui_signaller.h"
+#include "exceptions.h"
+
+#include "i18n.h"
 
 using std::string;
 using std::list;
+using std::cout;
 using std::stringstream;
 using boost::shared_ptr;
 
-/** @param s Film that we are operating on.
- *  @param req Job that must be completed before this job is run.
- */
-Job::Job (shared_ptr<Film> f, shared_ptr<Job> req)
+Job::Job (shared_ptr<const Film> f)
        : _film (f)
-       , _required (req)
+       , _thread (0)
        , _state (NEW)
        , _start_time (0)
        , _progress_unknown (false)
+       , _last_set (0)
        , _ran_for (0)
 {
        descend (1);
@@ -52,7 +56,7 @@ Job::start ()
 {
        set_state (RUNNING);
        _start_time = time (0);
-       boost::thread (boost::bind (&Job::run_wrapper, this));
+       _thread = new boost::thread (boost::bind (&Job::run_wrapper, this));
 }
 
 /** A wrapper for the ::run() method to catch exceptions */
@@ -67,19 +71,52 @@ Job::run_wrapper ()
                
                set_progress (1);
                set_state (FINISHED_ERROR);
-               set_error (String::compose ("%1 (%2)", e.what(), boost::filesystem::path (e.filename()).leaf()));
+               
+               string m = String::compose (_("An error occurred whilst handling the file %1."), boost::filesystem::path (e.filename()).leaf());
+
+               try {
+                       boost::filesystem::space_info const s = boost::filesystem::space (e.filename());
+                       if (s.available < pow (1024, 3)) {
+                               m += N_("\n\n");
+                               m += _("The drive that the film is stored on is low in disc space.  Free some more space and try again.");
+                       }
+               } catch (...) {
+
+               }
+
+               set_error (e.what(), m);
+
+       } catch (OpenFileError& e) {
+
+               set_progress (1);
+               set_state (FINISHED_ERROR);
+
+               set_error (
+                       String::compose (_("Could not open %1"), e.file().string()),
+                       String::compose (_("DCP-o-matic could not open the file %1.  Perhaps it does not exist or is in an unexpected format."), e.file().string())
+                       );
+
+       } catch (boost::thread_interrupted &) {
+
+               set_state (FINISHED_CANCELLED);
                
        } catch (std::exception& e) {
 
                set_progress (1);
                set_state (FINISHED_ERROR);
-               set_error (e.what ());
+               set_error (
+                       e.what (),
+                       _("It is not known what caused this error.  The best idea is to report the problem to the DCP-o-matic mailing list (carl@dcpomatic.com)")
+                       );
 
        } catch (...) {
 
                set_progress (1);
                set_state (FINISHED_ERROR);
-               set_error ("unknown exception");
+               set_error (
+                       _("Unknown error"),
+                       _("It is not known what caused this error.  The best idea is to report the problem to the DCP-o-matic mailing list (carl@dcpomatic.com)")
+                       );
 
        }
 }
@@ -105,7 +142,7 @@ bool
 Job::finished () const
 {
        boost::mutex::scoped_lock lm (_state_mutex);
-       return _state == FINISHED_OK || _state == FINISHED_ERROR;
+       return _state == FINISHED_OK || _state == FINISHED_ERROR || _state == FINISHED_CANCELLED;
 }
 
 /** @return true if the job has finished successfully */
@@ -124,18 +161,40 @@ Job::finished_in_error () const
        return _state == FINISHED_ERROR;
 }
 
+bool
+Job::finished_cancelled () const
+{
+       boost::mutex::scoped_lock lm (_state_mutex);
+       return _state == FINISHED_CANCELLED;
+}
+
+bool
+Job::paused () const
+{
+       boost::mutex::scoped_lock lm (_state_mutex);
+       return _state == PAUSED;
+}
+       
 /** Set the state of this job.
  *  @param s New state.
  */
 void
 Job::set_state (State s)
 {
-       boost::mutex::scoped_lock lm (_state_mutex);
-       _state = s;
+       bool finished = false;
+       
+       {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               _state = s;
+
+               if (_state == FINISHED_OK || _state == FINISHED_ERROR || _state == FINISHED_CANCELLED) {
+                       _ran_for = elapsed_time ();
+                       finished = true;
+               }
+       }
 
-       if (_state == FINISHED_OK || _state == FINISHED_ERROR) {
-               _ran_for = elapsed_time ();
-               Finished ();
+       if (finished && ui_signaller) {
+               ui_signaller->emit (boost::bind (boost::ref (Finished)));
        }
 }
 
@@ -156,9 +215,25 @@ Job::elapsed_time () const
 void
 Job::set_progress (float p)
 {
+       if (fabs (p - _last_set) < 0.01) {
+               /* Calm excessive progress reporting */
+               return;
+       }
+
+       _last_set = p;
+
        boost::mutex::scoped_lock lm (_progress_mutex);
        _progress_unknown = false;
        _stack.back().normalised = p;
+       boost::this_thread::interruption_point ();
+
+       if (paused ()) {
+               dcpomatic_sleep (1);
+       }
+
+       if (ui_signaller) {
+               ui_signaller->emit (boost::bind (boost::ref (Progress)));
+       }
 }
 
 /** @return fractional overall progress, or -1 if not known */
@@ -211,22 +286,30 @@ Job::descend (float a)
        _stack.push_back (Level (a));
 }
 
-/** @return Any error string that the job has generated */
 string
-Job::error () const
+Job::error_details () const
+{
+       boost::mutex::scoped_lock lm (_state_mutex);
+       return _error_details;
+}
+
+/** @return A summary of any error that the job has generated */
+string
+Job::error_summary () const
 {
        boost::mutex::scoped_lock lm (_state_mutex);
-       return _error;
+       return _error_summary;
 }
 
 /** Set the current error string.
  *  @param e New error string.
  */
 void
-Job::set_error (string e)
+Job::set_error (string s, string d)
 {
        boost::mutex::scoped_lock lm (_state_mutex);
-       _error = e;
+       _error_summary = s;
+       _error_details = d;
 }
 
 /** Say that this job's progress will be unknown until further notice */
@@ -245,15 +328,26 @@ Job::status () const
        int const t = elapsed_time ();
        int const r = remaining_time ();
 
+       int pc = rint (p * 100);
+       if (pc == 100) {
+               /* 100% makes it sound like we've finished when we haven't */
+               pc = 99;
+       }
+
        stringstream s;
-       if (!finished () && p >= 0 && t > 10 && r > 0) {
-               s << rint (p * 100) << "%; " << seconds_to_approximate_hms (r) << " remaining";
-       } else if (!finished () && (t <= 10 || r == 0)) {
-               s << rint (p * 100) << "%";
+       if (!finished ()) {
+               s << pc << N_("%");
+               if (p >= 0 && t > 10 && r > 0) {
+                       /// TRANSLATORS: remaining here follows an amount of time that is remaining
+                       /// on an operation.
+                       s << "; " << seconds_to_approximate_hms (r) << " " << _("remaining");
+               }
        } else if (finished_ok ()) {
-               s << "OK (ran for " << seconds_to_hms (_ran_for) << ")";
+               s << String::compose (_("OK (ran for %1)"), seconds_to_hms (_ran_for));
        } else if (finished_in_error ()) {
-               s << "Error (" << error() << ")";
+               s << String::compose (_("Error (%1)"), error_summary());
+       } else if (finished_cancelled ()) {
+               s << _("Cancelled");
        }
 
        return s.str ();
@@ -265,3 +359,30 @@ Job::remaining_time () const
 {
        return elapsed_time() / overall_progress() - elapsed_time();
 }
+
+void
+Job::cancel ()
+{
+       if (!_thread) {
+               return;
+       }
+
+       _thread->interrupt ();
+       _thread->join ();
+}
+
+void
+Job::pause ()
+{
+       if (running ()) {
+               set_state (PAUSED);
+       }
+}
+
+void
+Job::resume ()
+{
+       if (paused ()) {
+               set_state (RUNNING);
+       }
+}
index f32cfa811ad58c19d10c320bcb323120099354ba..9b8b14a93a2efc434b70297b9a2dbd51773e895a 100644 (file)
  *  @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>
 #include <boost/enable_shared_from_this.hpp>
 #include <boost/signals2.hpp>
+#include <boost/thread.hpp>
 
 class Film;
 
 /** @class Job
  *  @brief A parent class to represent long-running tasks which are run in their own thread.
  */
-class Job : public boost::enable_shared_from_this<Job>
+class Job : public boost::enable_shared_from_this<Job>, public boost::noncopyable
 {
 public:
-       Job (boost::shared_ptr<Film> s, boost::shared_ptr<Job> req);
+       Job (boost::shared_ptr<const Film>);
        virtual ~Job() {}
 
        /** @return user-readable name of this job */
@@ -46,14 +47,20 @@ public:
        virtual void run () = 0;
        
        void start ();
+       void pause ();
+       void resume ();
+       void cancel ();
 
        bool is_new () const;
        bool running () const;
        bool finished () const;
        bool finished_ok () const;
        bool finished_in_error () const;
+       bool finished_cancelled () const;
+       bool paused () const;
 
-       std::string error () const;
+       std::string error_summary () const;
+       std::string error_details () const;
 
        int elapsed_time () const;
        virtual std::string status () const;
@@ -63,11 +70,12 @@ public:
        void ascend ();
        void descend (float);
        float overall_progress () const;
-
-       boost::shared_ptr<Job> required () const {
-               return _required;
+       bool progress_unknown () const {
+               return _progress_unknown;
        }
 
+       boost::signals2::signal<void()> Progress;
+       /** Emitted from the UI thread when the job is finished */
        boost::signals2::signal<void()> Finished;
 
 protected:
@@ -76,30 +84,32 @@ protected:
 
        /** Description of a job's state */
        enum State {
-               NEW,           ///< the job hasn't been started yet
-               RUNNING,       ///< the job is running
-               FINISHED_OK,   ///< the job has finished successfully
-               FINISHED_ERROR ///< the job has finished in error
+               NEW,            ///< the job hasn't been started yet
+               RUNNING,        ///< the job is running
+               PAUSED,         ///< the job has been paused
+               FINISHED_OK,    ///< the job has finished successfully
+               FINISHED_ERROR, ///< the job has finished in error
+               FINISHED_CANCELLED ///< the job was cancelled
        };
        
        void set_state (State);
-       void set_error (std::string e);
+       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:
 
        void run_wrapper ();
 
-       boost::shared_ptr<Job> _required;
+       boost::thread* _thread;
 
        /** mutex for _state and _error */
        mutable boost::mutex _state_mutex;
        /** current state of the job */
        State _state;
-       /** message for an error that has occurred (when state == FINISHED_ERROR) */
-       std::string _error;
+       /** summary of an error that has occurred (when state == FINISHED_ERROR) */
+       std::string _error_summary;
+       std::string _error_details;
 
        /** time that this job was started */
        time_t _start_time;
@@ -119,6 +129,8 @@ private:
        /** true if this job's progress will always be unknown */
        bool _progress_unknown;
 
+       float _last_set;
+
        int _ran_for;
 };
 
index fa02fd37014454cb992e74bb800290c85e762957..a841fa60bd553698a873e7105d5f2086e8e4040b 100644 (file)
@@ -30,7 +30,9 @@
 
 using std::string;
 using std::list;
+using std::cout;
 using boost::shared_ptr;
+using boost::weak_ptr;
 
 JobManager* JobManager::_instance = 0;
 
@@ -43,19 +45,16 @@ JobManager::JobManager ()
 shared_ptr<Job>
 JobManager::add (shared_ptr<Job> j)
 {
-       boost::mutex::scoped_lock lm (_mutex);
-       _jobs.push_back (j);
-       return j;
-}
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _jobs.push_back (j);
+       }
 
-void
-JobManager::add_after (shared_ptr<Job> after, shared_ptr<Job> j)
-{
-       boost::mutex::scoped_lock lm (_mutex);
-       list<shared_ptr<Job> >::iterator i = find (_jobs.begin(), _jobs.end(), after);
-       assert (i != _jobs.end ());
-       ++i;
-       _jobs.insert (i, j);
+       if (ui_signaller) {
+               ui_signaller->emit (boost::bind (boost::ref (JobAdded), weak_ptr<Job> (j)));
+       }
+       
+       return j;
 }
 
 list<shared_ptr<Job> >
@@ -111,13 +110,10 @@ JobManager::scheduler ()
                                }
                                
                                if ((*i)->is_new()) {
-                                       shared_ptr<Job> r = (*i)->required ();
-                                       if (!r || r->finished_ok ()) {
-                                               (*i)->start ();
-
-                                               /* Only start one job at once */
-                                               break;
-                                       }
+                                       (*i)->start ();
+                                       
+                                       /* Only start one job at once */
+                                       break;
                                }
                        }
                }
@@ -129,7 +125,7 @@ JobManager::scheduler ()
                        }
                }
 
-               dvdomatic_sleep (1);
+               dcpomatic_sleep (1);
        }
 }
 
index cc1c1d28fc20b9250e101c4b7251bf10e9faf2e7..0040568c66a5958bb497b86e83e92deba10655da 100644 (file)
@@ -30,21 +30,24 @@ class Job;
 /** @class JobManager
  *  @brief A simple scheduler for jobs.
  */
-class JobManager
+class JobManager : public boost::noncopyable
 {
 public:
 
        boost::shared_ptr<Job> add (boost::shared_ptr<Job>);
-       void add_after (boost::shared_ptr<Job> after, boost::shared_ptr<Job> j);
        std::list<boost::shared_ptr<Job> > get () const;
        bool work_to_do () const;
        bool errors () const;
 
+       boost::signals2::signal<void (boost::weak_ptr<Job>)> JobAdded;
        boost::signals2::signal<void (bool)> ActiveJobsChanged;
 
        static JobManager* instance ();
 
 private:
+       /* This function is part of the test suite */
+       friend void ::wait_for_jobs ();
+       
        JobManager ();
        void scheduler ();
        
index 06cff04956b8b1a28fde9ff31c493def5a5930f7..ef36a902cbbc76027982bc8a054630ca1e693d38 100644 (file)
 #include <time.h>
 #include "log.h"
 
+#include "i18n.h"
+
 using namespace std;
 
 Log::Log ()
-       : _level (VERBOSE)
+       : _level (STANDARD)
 {
 
 }
@@ -48,7 +50,7 @@ Log::log (string m, Level l)
        string a = ctime (&t);
 
        stringstream s;
-       s << a.substr (0, a.length() - 1) << ": " << m;
+       s << a.substr (0, a.length() - 1) << N_(": ") << m;
        do_log (s.str ());
 }
 
@@ -65,7 +67,7 @@ Log::microsecond_log (string m, Level l)
        gettimeofday (&tv, 0);
 
        stringstream s;
-       s << tv.tv_sec << ":" << tv.tv_usec << " " << m;
+       s << tv.tv_sec << N_(":") << tv.tv_usec << N_(" ") << m;
        do_log (s.str ());
 }      
 
@@ -79,10 +81,10 @@ Log::set_level (Level l)
 void
 Log::set_level (string l)
 {
-       if (l == "verbose") {
+       if (l == N_("verbose")) {
                set_level (VERBOSE);
                return;
-       } else if (l == "timing") {
+       } else if (l == N_("timing")) {
                set_level (TIMING);
                return;
        }
@@ -101,6 +103,6 @@ void
 FileLog::do_log (string m)
 {
        ofstream f (_file.c_str(), fstream::app);
-       f << m << "\n";
+       f << m << N_("\n");
 }
 
index 3a2cfcbfd53e7882a61feaa03268738036a60b1f..bd0066a83baee699582c61febc5c5738dba408eb 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.
@@ -30,7 +30,7 @@
 /** @class Log
  *  @brief A very simple logging class.
  */
-class Log
+class Log : public boost::noncopyable
 {
 public:
        Log ();
diff --git a/src/lib/lut.h b/src/lib/lut.h
deleted file mode 100644 (file)
index 26888a2..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
-    Taken from OpenDCP: Builds Digital Cinema Packages
-    Copyright (c) 2010-2011 Terrence Meiczinger, All Rights Reserved
-
-    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 3 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, see <http://www.gnu.org/licenses/>.
-*/
-
-/** @file src/lut.h
- *  @brief Look-up tables for colour conversions (from OpenDCP)
- */
-
-#define BIT_DEPTH      12 
-#define BIT_PRECISION  16 
-#define COLOR_DEPTH    (4095)
-#define DCI_LUT_SIZE   ((COLOR_DEPTH + 1) * BIT_PRECISION)
-#define DCI_GAMMA      (2.6)
-#define DCI_DEGAMMA    (1/DCI_GAMMA)
-#define DCI_COEFFICENT (48.0/52.37)
-
-enum COLOR_PROFILE_ENUM {
-    CP_SRGB = 0,
-    CP_REC709,
-    CP_DC28,
-    CP_MAX
-};
-
-enum LUT_IN_ENUM {
-    LI_SRGB    = 0,
-    LI_REC709,
-    LI_MAX
-};
-
-enum LUT_OUT_ENUM {
-    LO_DCI    = 0,
-    LO_MAX
-};
-
-extern float color_matrix[3][3][3];
-extern float lut_in[LI_MAX][4095+1];
-extern int lut_out[1][DCI_LUT_SIZE];
diff --git a/src/lib/make_dcp_job.h b/src/lib/make_dcp_job.h
deleted file mode 100644 (file)
index 5e4f78a..0000000
+++ /dev/null
@@ -1,46 +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/make_dcp_job.h
- *  @brief A job to create DCPs.
- */
-
-#include "job.h"
-
-class EncodeOptions;
-
-/** @class MakeDCPJob
- *  @brief A job to create DCPs
- */
-class MakeDCPJob : public Job
-{
-public:
-       MakeDCPJob (boost::shared_ptr<Film>, boost::shared_ptr<const EncodeOptions>, boost::shared_ptr<Job> req);
-
-       std::string name () const;
-       void run ();
-
-private:
-       void dcp_progress (float);
-       std::string j2c_path (int, int) const;
-       std::string wav_path (libdcp::Channel) const;
-
-       boost::shared_ptr<const EncodeOptions> _opt;
-};
-
diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc
deleted file mode 100644 (file)
index 2b7a080..0000000
+++ /dev/null
@@ -1,113 +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"
-
-using std::min;
-using boost::shared_ptr;
-
-Matcher::Matcher (Log* log, int sample_rate, float frames_per_second)
-       : AudioVideoProcessor (log)
-       , _sample_rate (sample_rate)
-       , _frames_per_second (frames_per_second)
-       , _video_frames (0)
-       , _audio_frames (0)
-{
-
-}
-
-void
-Matcher::process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s)
-{
-       Video (i, same, s);
-       _video_frames++;
-
-       _pixel_format = i->pixel_format ();
-       _size = i->size ();
-}
-
-void
-Matcher::process_audio (boost::shared_ptr<AudioBuffers> b)
-{
-       Audio (b);
-       _audio_frames += b->frames ();
-
-       _channels = b->channels ();
-}
-
-void
-Matcher::process_end ()
-{
-       if (_audio_frames == 0 || !_pixel_format || !_size || !_channels) {
-               /* We won't do anything */
-               return;
-       }
-       
-       int64_t audio_short_by_frames = video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second) - _audio_frames;
-
-       _log->log (
-               String::compose (
-                       "Matching processor 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
-                       )
-               );
-       
-       if (audio_short_by_frames < 0) {
-               
-               _log->log (String::compose ("%1 too many audio frames", -audio_short_by_frames));
-               
-               /* We have seen more audio than video.  Emit enough black video frames so that we reverse this */
-               int const black_video_frames = ceil (-audio_short_by_frames * _frames_per_second / _sample_rate);
-               
-               _log->log (String::compose ("Emitting %1 frames of black video", black_video_frames));
-
-               shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), false));
-               black->make_black ();
-               for (int i = 0; i < black_video_frames; ++i) {
-                       Video (black, i != 0, shared_ptr<Subtitle>());
-               }
-               
-               /* Now recompute our check value */
-               audio_short_by_frames = video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second) - _audio_frames;
-       }
-       
-       if (audio_short_by_frames > 0) {
-               _log->log (String::compose ("Emitted %1 too few audio frames", audio_short_by_frames));
-
-               /* 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 ();
-               
-               int64_t to_do = audio_short_by_frames;
-               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;
-               }
-       }
-}
diff --git a/src/lib/matcher.h b/src/lib/matcher.h
deleted file mode 100644 (file)
index b94c284..0000000
+++ /dev/null
@@ -1,40 +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"
-#include "ffmpeg_compatibility.h"
-
-class Matcher : public AudioVideoProcessor
-{
-public:
-       Matcher (Log* log, int sample_rate, float frames_per_second);
-       void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s);
-       void process_audio (boost::shared_ptr<AudioBuffers>);
-       void process_end ();
-
-private:
-       int _sample_rate;
-       float _frames_per_second;
-       int _video_frames;
-       int64_t _audio_frames;
-       boost::optional<AVPixelFormat> _pixel_format;
-       boost::optional<Size> _size;
-       boost::optional<int> _channels;
-};
diff --git a/src/lib/moving_image.h b/src/lib/moving_image.h
new file mode 100644 (file)
index 0000000..a81403d
--- /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 MovingImageContent;
+
+class MovingImage
+{
+public:
+       MovingImage (boost::shared_ptr<const MovingImageContent> c)
+               : _moving_image_content (c)
+       {}
+
+       boost::shared_ptr<const MovingImageContent> content () const {
+               return _moving_image_content;
+       }
+
+protected:
+       boost::shared_ptr<const MovingImageContent> _moving_image_content;
+};
diff --git a/src/lib/moving_image_content.cc b/src/lib/moving_image_content.cc
new file mode 100644 (file)
index 0000000..a72ad6e
--- /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.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "moving_image_content.h"
+#include "moving_image_examiner.h"
+#include "config.h"
+#include "compose.hpp"
+#include "film.h"
+#include "job.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::cout;
+using std::list;
+using std::stringstream;
+using std::vector;
+using boost::shared_ptr;
+
+MovingImageContent::MovingImageContent (shared_ptr<const Film> f, boost::filesystem::path p)
+       : Content (f, p)
+       , VideoContent (f, p)
+{
+
+}
+
+MovingImageContent::MovingImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+       : Content (f, node)
+       , VideoContent (f, node)
+{
+       list<shared_ptr<cxml::Node> > c = node->node_children ("File");
+       for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+               _files.push_back ((*i)->content ());
+       }
+}
+
+string
+MovingImageContent::summary () const
+{
+       /* Get the string() here so that the name does not have quotes around it */
+       return String::compose (_("%1 [moving images]"), path().filename().string());
+}
+
+string
+MovingImageContent::technical_summary () const
+{
+       return Content::technical_summary() + " - "
+               + VideoContent::technical_summary() + " - "
+               + "moving";
+}
+
+void
+MovingImageContent::as_xml (xmlpp::Node* node) const
+{
+       node->add_child("Type")->add_child_text ("MovingImage");
+       Content::as_xml (node);
+       VideoContent::as_xml (node);
+
+       for (vector<boost::filesystem::path>::const_iterator i = _files.begin(); i != _files.end(); ++i) {
+               node->add_child("File")->add_child_text (i->filename().string());
+       }
+}
+
+void
+MovingImageContent::examine (shared_ptr<Job> job)
+{
+       job->descend (0.5);
+       Content::examine (job);
+       job->ascend ();
+
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       job->descend (0.5);
+       shared_ptr<MovingImageExaminer> examiner (new MovingImageExaminer (film, shared_from_this(), job));
+       job->ascend ();
+
+       take_from_video_examiner (examiner);
+
+       _video_length = examiner->files().size ();
+       _files = examiner->files ();
+}
+
+Time
+MovingImageContent::full_length () const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+
+       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
+       return video_length() * frc.factor() * TIME_HZ / video_frame_rate();
+}
+
+string
+MovingImageContent::identifier () const
+{
+       stringstream s;
+       s << VideoContent::identifier ();
+       s << "_" << video_length();
+       return s.str ();
+}
diff --git a/src/lib/moving_image_content.h b/src/lib/moving_image_content.h
new file mode 100644 (file)
index 0000000..1a64750
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+    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_MOVING_IMAGE_CONTENT_H
+#define DCPOMATIC_MOVING_IMAGE_CONTENT_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include "video_content.h"
+
+namespace cxml {
+       class Node;
+}
+
+/** A directory of image files which are to be presented as a movie */
+class MovingImageContent : public VideoContent
+{
+public:
+       MovingImageContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+       MovingImageContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+       boost::shared_ptr<MovingImageContent> shared_from_this () {
+               return boost::dynamic_pointer_cast<MovingImageContent> (Content::shared_from_this ());
+       };
+       
+       void examine (boost::shared_ptr<Job>);
+       std::string summary () const;
+       std::string technical_summary () const;
+       void as_xml (xmlpp::Node *) const;
+       Time full_length () const;
+
+       std::string identifier () const;
+
+       std::vector<boost::filesystem::path> const & files () const {
+               return _files;
+       }
+       
+private:
+       std::vector<boost::filesystem::path> _files;
+};
+
+#endif
diff --git a/src/lib/moving_image_decoder.cc b/src/lib/moving_image_decoder.cc
new file mode 100644 (file)
index 0000000..0960960
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+    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 <iostream>
+#include <boost/filesystem.hpp>
+#include <Magick++.h>
+#include "moving_image_content.h"
+#include "moving_image_decoder.h"
+#include "image.h"
+#include "film.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::cout;
+using boost::shared_ptr;
+using libdcp::Size;
+
+MovingImageDecoder::MovingImageDecoder (shared_ptr<const Film> f, shared_ptr<const MovingImageContent> c)
+       : Decoder (f)
+       , VideoDecoder (f, c)
+       , MovingImage (c)
+{
+
+}
+
+void
+MovingImageDecoder::pass ()
+{
+       if (_video_position >= _moving_image_content->video_length ()) {
+               return;
+       }
+
+       boost::filesystem::path path = _moving_image_content->path ();
+       path /= _moving_image_content->files()[_video_position];
+
+       Magick::Image* magick_image = new Magick::Image (path.string());
+       libdcp::Size size (magick_image->columns(), magick_image->rows());
+
+       shared_ptr<Image> image (new Image (PIX_FMT_RGB24, size, 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) {
+                       Magick::Color c = magick_image->pixelColor (x, y);
+                       *p++ = c.redQuantum() * 255 / QuantumRange;
+                       *p++ = c.greenQuantum() * 255 / QuantumRange;
+                       *p++ = c.blueQuantum() * 255 / QuantumRange;
+               }
+       }
+
+       delete magick_image;
+
+       video (image, false, _video_position);
+}
+
+void
+MovingImageDecoder::seek (VideoContent::Frame frame, bool)
+{
+       _video_position = frame;
+}
+
+bool
+MovingImageDecoder::done () const
+{
+       return _video_position >= _moving_image_content->video_length ();
+}
diff --git a/src/lib/moving_image_decoder.h b/src/lib/moving_image_decoder.h
new file mode 100644 (file)
index 0000000..5cc8b32
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+    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 "video_decoder.h"
+#include "moving_image.h"
+
+namespace Magick {
+       class Image;
+}
+
+class MovingImageContent;
+
+class MovingImageDecoder : public VideoDecoder, public MovingImage
+{
+public:
+       MovingImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const MovingImageContent>);
+
+       /* Decoder */
+
+       void pass ();
+       void seek (VideoContent::Frame, bool);
+       bool done () const;
+};
+
diff --git a/src/lib/moving_image_examiner.cc b/src/lib/moving_image_examiner.cc
new file mode 100644 (file)
index 0000000..077545a
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+    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 <boost/lexical_cast.hpp>
+#include <Magick++.h>
+#include "moving_image_content.h"
+#include "moving_image_examiner.h"
+#include "film.h"
+#include "job.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::cout;
+using std::list;
+using std::sort;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+MovingImageExaminer::MovingImageExaminer (shared_ptr<const Film> film, shared_ptr<const MovingImageContent> content, shared_ptr<Job> job)
+       : MovingImage (content)
+       , _film (film)
+       , _video_length (0)
+{
+       list<unsigned int> frames;
+       unsigned int files = 0;
+       
+       for (boost::filesystem::directory_iterator i(content->path()); i != boost::filesystem::directory_iterator(); ++i) {
+               if (boost::filesystem::is_regular_file (i->path ())) {
+                       ++files;
+               }
+       }
+
+       int j = 0;
+       for (boost::filesystem::directory_iterator i(content->path()); i != boost::filesystem::directory_iterator(); ++i) {
+               if (!boost::filesystem::is_regular_file (i->path ())) {
+                       continue;
+               }
+
+               if (valid_image_file (i->path ())) {
+                       int n = lexical_cast<int> (i->path().stem().string());
+                       frames.push_back (n);
+                       _files.push_back (i->path().filename ());
+
+                       if (!_video_size) {
+                               using namespace MagickCore;
+                               Magick::Image* image = new Magick::Image (i->path().string());
+                               _video_size = libdcp::Size (image->columns(), image->rows());
+                               delete image;
+                       }
+               }
+
+               job->set_progress (float (j) / files);
+               ++j;
+       }
+
+       frames.sort ();
+       sort (_files.begin(), _files.end ());
+       
+       if (frames.size() < 2) {
+               throw StringError (String::compose (_("only %1 file(s) found in moving image directory"), frames.size ()));
+       }
+
+       if (frames.front() != 0 && frames.front() != 1) {
+               throw StringError (String::compose (_("first frame in moving image directory is number %1"), frames.front ()));
+       }
+
+       if (frames.back() != frames.size() && frames.back() != (frames.size() - 1)) {
+               throw StringError (String::compose (_("there are %1 images in the directory but the last one is number %2"), frames.size(), frames.back ()));
+       }
+
+       _video_length = frames.size ();
+}
+
+libdcp::Size
+MovingImageExaminer::video_size () const
+{
+       return _video_size.get ();
+}
+
+int
+MovingImageExaminer::video_length () const
+{
+       cout << "ex video length is " << _video_length << "\n";
+       return _video_length;
+}
+
+float
+MovingImageExaminer::video_frame_rate () const
+{
+       return 24;
+}
+
diff --git a/src/lib/moving_image_examiner.h b/src/lib/moving_image_examiner.h
new file mode 100644 (file)
index 0000000..db6845e
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+    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 "moving_image.h"
+#include "video_examiner.h"
+
+namespace Magick {
+       class Image;
+}
+
+class MovingImageContent;
+
+class MovingImageExaminer : public MovingImage, public VideoExaminer
+{
+public:
+       MovingImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const MovingImageContent>, boost::shared_ptr<Job>);
+
+       float video_frame_rate () const;
+       libdcp::Size video_size () const;
+       VideoContent::Frame video_length () const;
+
+       std::vector<boost::filesystem::path> const & files () const {
+               return _files;
+       }
+
+private:
+       boost::weak_ptr<const Film> _film;
+       boost::optional<libdcp::Size> _video_size;
+       VideoContent::Frame _video_length;
+       std::vector<boost::filesystem::path> _files;
+};
diff --git a/src/lib/options.h b/src/lib/options.h
deleted file mode 100644 (file)
index 55b066a..0000000
+++ /dev/null
@@ -1,128 +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/options.h
- *  @brief Options for a transcoding operation.
- */
-
-#include <string>
-#include <iomanip>
-#include <sstream>
-#include <boost/optional.hpp>
-#include "util.h"
-
-/** @class EncodeOptions
- *  @brief EncodeOptions for an encoding operation.
- *
- *  These are settings which may be different, in different circumstances, for
- *  the same film; ie they are options for a particular operation.
- */
-class EncodeOptions
-{
-public:
-
-       EncodeOptions (std::string f, std::string e, std::string m)
-               : padding (0)
-               , video_skip (0)
-               , _frame_out_path (f)
-               , _frame_out_extension (e)
-               , _multichannel_audio_out_path (m)
-       {}
-
-       /** @return The path to write video frames to */
-       std::string frame_out_path () const {
-               return _frame_out_path;
-       }
-
-       /** @param f Source frame index.
-        *  @param t true to return a temporary file path, otherwise a permanent one.
-        *  @return The path to write this video frame to.
-        */
-       std::string frame_out_path (SourceFrame f, bool t) const {
-               std::stringstream s;
-               s << _frame_out_path << "/";
-               s.width (8);
-               s << std::setfill('0') << f << _frame_out_extension;
-
-               if (t) {
-                       s << ".tmp";
-               }
-
-               return s.str ();
-       }
-
-       std::string hash_out_path (SourceFrame f, bool t) const {
-               return frame_out_path (f, t) + ".md5";
-       }
-
-       /** @return Path to write multichannel audio data to */
-       std::string multichannel_audio_out_path () const {
-               return _multichannel_audio_out_path;
-       }
-
-       /** @param c Audio channel index.
-        *  @param t true to return a temporary file path, otherwise a permanent one.
-        *  @return The path to write this audio file to.
-        */
-       std::string multichannel_audio_out_path (int c, bool t) const {
-               std::stringstream s;
-               s << _multichannel_audio_out_path << "/" << (c + 1) << ".wav";
-               if (t) {
-                       s << ".tmp";
-               }
-
-               return s.str ();
-       }
-
-       Size out_size;              ///< size of output images
-       int padding;                ///< number of pixels of padding (in terms of the output size) each side of the image
-
-       /** Range of video frames to encode (in DCP frames) */
-       boost::optional<std::pair<int, int> > video_range;
-       /** Range of audio frames to decode (in the DCP's sampling rate) */
-       boost::optional<std::pair<int64_t, int64_t> > audio_range;
-       
-       /** Skip frames such that we don't decode any frame where (index % decode_video_skip) != 0; e.g.
-        *  1 for every frame, 2 for every other frame, etc.
-        */
-       SourceFrame video_skip; 
-
-private:
-       /** Path of the directory to write video frames to */
-       std::string _frame_out_path;
-       /** Extension to use for video frame files (including the leading .) */
-       std::string _frame_out_extension;
-       /** Path of the directory to write audio files to */
-       std::string _multichannel_audio_out_path;
-};
-
-
-class DecodeOptions
-{
-public:
-       DecodeOptions ()
-               : decode_audio (true)
-               , decode_subtitles (false)
-               , video_sync (true)
-       {}
-       
-       bool decode_audio;
-       bool decode_subtitles;
-       bool video_sync;
-};
diff --git a/src/lib/player.cc b/src/lib/player.cc
new file mode 100644 (file)
index 0000000..70d6fa8
--- /dev/null
@@ -0,0 +1,654 @@
+/*
+    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 "still_image_decoder.h"
+#include "still_image_content.h"
+#include "moving_image_decoder.h"
+#include "moving_image_content.h"
+#include "sndfile_decoder.h"
+#include "sndfile_content.h"
+#include "subtitle_content.h"
+#include "playlist.h"
+#include "job.h"
+#include "image.h"
+#include "ratio.h"
+#include "resampler.h"
+#include "log.h"
+#include "scaler.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->position ())
+               , audio_position (c->position ())
+       {}
+       
+       Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
+               : content (c)
+               , decoder (d)
+               , video_position (c->position ())
+               , audio_position (c->position ())
+       {}
+       
+       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<StillImageContent> (p.content)) {
+               s << "\tstill image";
+       } else if (dynamic_pointer_cast<SndfileContent> (p.content)) {
+               s << "\tsndfile    ";
+       }
+       
+       s << " at " << p.content->position() << " 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_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
+       , _last_emit_was_black (false)
+{
+       _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
+       _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
+       _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
+       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 (_video && dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
+                       if ((*i)->video_position < earliest_t) {
+                               earliest_t = (*i)->video_position;
+                               earliest = *i;
+                               type = VIDEO;
+                       }
+               }
+
+               if (_audio && 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 (earliest=" << earliest_t << ", video_position=" << _video_position << ").\n";
+#endif
+                       emit_black ();
+               } else {
+#ifdef DEBUG_PLAYER
+                       cout << "Pass video " << *earliest << "\n";
+#endif                 
+                       earliest->decoder->pass ();
+               }
+               break;
+
+       case AUDIO:
+               if (earliest_t > _audio_position) {
+#ifdef DEBUG_PLAYER
+                       cout << "no audio here (none until " << earliest_t << "); emitting silence.\n";
+#endif
+                       emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
+               } else {
+#ifdef DEBUG_PLAYER
+                       cout << "Pass audio " << *earliest << "\n";
+#endif
+                       earliest->decoder->pass ();
+
+                       if (earliest->decoder->done()) {
+                               shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
+                               assert (ac);
+                               shared_ptr<Resampler> re = resampler (ac, false);
+                               if (re) {
+                                       shared_ptr<const AudioBuffers> b = re->flush ();
+                                       if (b->frames ()) {
+                                               process_audio (earliest, b, ac->audio_length ());
+                                       }
+                               }
+                       }
+               }
+               break;
+       }
+
+       if (_audio) {
+               Time audio_done_up_to = TIME_MAX;
+               for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
+                       if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
+                               audio_done_up_to = min (audio_done_up_to, (*i)->audio_position);
+                       }
+               }
+
+               TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to);
+               Audio (tb.audio, tb.time);
+               _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+       }
+               
+#ifdef DEBUG_PLAYER
+       cout << "\tpost pass _video_position=" << _video_position << " _audio_position=" << _audio_position << "\n";
+#endif 
+
+       return false;
+}
+
+void
+Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, 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->video_frame_rate());
+       if (frc.skip && (frame % 2) == 1) {
+               return;
+       }
+
+       Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
+       if (content->trimmed (relative_time)) {
+               return;
+       }
+
+       /* Convert to RGB first, as FFmpeg doesn't seem to like handling YUV images with odd widths */
+       shared_ptr<Image> work_image = image->scale (image->size (), _film->scaler(), PIX_FMT_RGB24, true);
+
+       work_image = work_image->crop (content->crop(), true);
+
+       libdcp::Size const image_size = content->ratio()->size (_video_container_size);
+       
+       work_image = work_image->scale (image_size, _film->scaler(), PIX_FMT_RGB24, true);
+
+       Time time = content->position() + relative_time - content->trim_start ();
+           
+       if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
+               work_image->alpha_blend (_out_subtitle.image, _out_subtitle.position);
+       }
+
+       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 Image (PIX_FMT_RGB24, _video_container_size, true));
+               im->make_black ();
+               im->copy (work_image, Position<int> ((_video_container_size.width - image_size.width) / 2, (_video_container_size.height - image_size.height) / 2));
+               work_image = im;
+       }
+
+#ifdef DCPOMATIC_DEBUG
+       _last_video = piece->content;
+#endif
+
+       Video (work_image, eyes, content->colour_conversion(), same, time);
+       time += TIME_HZ / _film->video_frame_rate();
+
+       if (frc.repeat) {
+               Video (work_image, eyes, content->colour_conversion(), true, time);
+               time += TIME_HZ / _film->video_frame_rate();
+       }
+
+       _last_emit_was_black = false;
+
+       _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);
+
+       /* Gain */
+       if (content->audio_gain() != 0) {
+               shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
+               gain->apply_gain (content->audio_gain ());
+               audio = gain;
+       }
+
+       /* Resample */
+       if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
+               shared_ptr<Resampler> r = resampler (content, true);
+               pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
+               audio = ro.first;
+               frame = ro.second;
+       }
+       
+       Time const relative_time = _film->audio_frames_to_time (frame);
+
+       if (content->trimmed (relative_time)) {
+               return;
+       }
+
+       Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time;
+       
+       /* Remap channels */
+       shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->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) {
+               if (i->first < audio->channels() && i->second < dcp_mapped->channels()) {
+                       dcp_mapped->accumulate_channel (audio.get(), i->first, i->second);
+               }
+       }
+
+       audio = dcp_mapped;
+
+       /* We must cut off anything that comes before the start of all time */
+       if (time < 0) {
+               int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
+               if (frames >= audio->frames ()) {
+                       return;
+               }
+
+               shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
+               trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
+
+               audio = trimmed;
+               time = 0;
+       }
+
+       _audio_merger.push (audio, time);
+       piece->audio_position += _film->audio_frames_to_time (audio->frames ());
+}
+
+void
+Player::flush ()
+{
+       TimedAudioBuffers<Time> tb = _audio_merger.flush ();
+       if (tb.audio) {
+               Audio (tb.audio, tb.time);
+               _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+       }
+
+       while (_video_position < _audio_position) {
+               emit_black ();
+       }
+
+       while (_audio_position < _video_position) {
+               emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+       }
+       
+}
+
+/** Seek so that the next pass() will yield (approximately) the requested frame.
+ *  Pass accurate = true to try harder to get close to the request.
+ *  @return true on error
+ */
+void
+Player::seek (Time t, bool accurate)
+{
+       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->position ();
+               s = max (static_cast<Time> (0), s);
+               s = min (vc->length_after_trim(), s);
+
+               (*i)->video_position = (*i)->audio_position = vc->position() + s;
+
+               FrameRateConversion frc (vc->video_frame_rate(), _film->video_frame_rate());
+               /* Here we are converting from time (in the DCP) to a frame number in the content.
+                  Hence we need to use the DCP's frame rate and the double/skip correction, not
+                  the source's rate.
+               */
+               VideoContent::Frame f = (s + vc->trim_start ()) * _film->video_frame_rate() / (frc.factor() * TIME_HZ);
+               dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (f, accurate);
+       }
+
+       _video_position = _audio_position = t;
+       
+       /* XXX: don't seek audio because we don't need to... */
+}
+
+void
+Player::setup_pieces ()
+{
+       list<shared_ptr<Piece> > old_pieces = _pieces;
+
+       _pieces.clear ();
+
+       ContentList content = _playlist->content ();
+       sort (content.begin(), content.end(), ContentSorter ());
+
+       for (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, _4));
+                       fd->Audio.connect (bind (&Player::process_audio, this, piece, _1, _2));
+                       fd->Subtitle.connect (bind (&Player::process_subtitle, this, piece, _1, _2, _3, _4));
+
+                       piece->decoder = fd;
+               }
+               
+               shared_ptr<const StillImageContent> ic = dynamic_pointer_cast<const StillImageContent> (*i);
+               if (ic) {
+                       shared_ptr<StillImageDecoder> id;
+                       
+                       /* See if we can re-use an old StillImageDecoder */
+                       for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
+                               shared_ptr<StillImageDecoder> imd = dynamic_pointer_cast<StillImageDecoder> ((*j)->decoder);
+                               if (imd && imd->content() == ic) {
+                                       id = imd;
+                               }
+                       }
+
+                       if (!id) {
+                               id.reset (new StillImageDecoder (_film, ic));
+                               id->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3, _4));
+                       }
+
+                       piece->decoder = id;
+               }
+
+               shared_ptr<const MovingImageContent> mc = dynamic_pointer_cast<const MovingImageContent> (*i);
+               if (mc) {
+                       shared_ptr<MovingImageDecoder> md;
+
+                       if (!md) {
+                               md.reset (new MovingImageDecoder (_film, mc));
+                               md->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3, _4));
+                       }
+
+                       piece->decoder = md;
+               }
+
+               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 property, bool frequent)
+{
+       shared_ptr<Content> c = w.lock ();
+       if (!c) {
+               return;
+       }
+
+       if (
+               property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
+               property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
+               property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_RATIO
+               ) {
+               
+               _have_valid_pieces = false;
+               Changed (frequent);
+
+       } else if (property == SubtitleContentProperty::SUBTITLE_OFFSET || property == SubtitleContentProperty::SUBTITLE_SCALE) {
+               update_subtitle ();
+               Changed (frequent);
+       } else if (property == VideoContentProperty::VIDEO_FRAME_TYPE) {
+               Changed (frequent);
+       }
+}
+
+void
+Player::playlist_changed ()
+{
+       _have_valid_pieces = false;
+       Changed (false);
+}
+
+void
+Player::set_video_container_size (libdcp::Size s)
+{
+       _video_container_size = s;
+       _black_frame.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
+       _black_frame->make_black ();
+}
+
+shared_ptr<Resampler>
+Player::resampler (shared_ptr<AudioContent> c, bool create)
+{
+       map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
+       if (i != _resamplers.end ()) {
+               return i->second;
+       }
+
+       if (!create) {
+               return shared_ptr<Resampler> ();
+       }
+       
+       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 ()
+{
+#ifdef DCPOMATIC_DEBUG
+       _last_video.reset ();
+#endif
+       
+       Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
+       _video_position += _film->video_frames_to_time (1);
+       _last_emit_was_black = true;
+}
+
+void
+Player::emit_silence (OutputAudioFrame most)
+{
+       if (most == 0) {
+               return;
+       }
+       
+       OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
+       shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
+       silence->make_silent ();
+       Audio (silence, _audio_position);
+       _audio_position += _film->audio_frames_to_time (N);
+}
+
+void
+Player::film_changed (Film::Property p)
+{
+       /* Here we should notice Film properties that affect our output, and
+          alert listeners that our output now would be different to how it was
+          last time we were run.
+       */
+
+       if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER) {
+               Changed (false);
+       }
+}
+
+void
+Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+{
+       _in_subtitle.piece = weak_piece;
+       _in_subtitle.image = image;
+       _in_subtitle.rect = rect;
+       _in_subtitle.from = from;
+       _in_subtitle.to = to;
+
+       update_subtitle ();
+}
+
+void
+Player::update_subtitle ()
+{
+       shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
+       if (!piece) {
+               return;
+       }
+
+       if (!_in_subtitle.image) {
+               _out_subtitle.image.reset ();
+               return;
+       }
+
+       shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
+       assert (sc);
+
+       dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
+       libdcp::Size scaled_size;
+
+       in_rect.y += sc->subtitle_offset ();
+
+       /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
+       scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale ();
+       scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale ();
+
+       /* Then we need a corrective translation, consisting of two parts:
+        *
+        * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
+        *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
+        *
+        * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
+        *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
+        *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
+        *
+        * Combining these two translations gives these expressions.
+        */
+       
+       _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
+       _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
+       
+       _out_subtitle.image = _in_subtitle.image->scale (
+               scaled_size,
+               Scaler::from_id ("bicubic"),
+               _in_subtitle.image->pixel_format (),
+               true
+               );
+       _out_subtitle.from = _in_subtitle.from + piece->content->position ();
+       _out_subtitle.to = _in_subtitle.to + piece->content->position ();
+}
diff --git a/src/lib/player.h b/src/lib/player.h
new file mode 100644 (file)
index 0000000..2261f66
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+    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 "content.h"
+#include "film.h"
+#include "rect.h"
+#include "audio_merger.h"
+#include "audio_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 boost::noncopyable
+{
+public:
+       Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
+
+       void disable_video ();
+       void disable_audio ();
+
+       bool pass ();
+       void seek (Time, bool);
+
+       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 the eye(s) that should see this image.
+        *  Third parameter is the colour conversion that should be used for this image.
+        *  Fourth parameter is true if the image is the same as the last one that was emitted.
+        *  Fifth parameter is the time.
+        */
+       boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, ColourConversion, bool, Time)> Video;
+       
+       /** Emitted when some audio data is ready */
+       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
+
+       /** Emitted when something has changed such that if we went back and emitted
+        *  the last frame again it would look different.  This is not emitted after
+        *  a seek.
+        *
+        *  The parameter is true if these signals are currently likely to be frequent.
+        */
+       boost::signals2::signal<void (bool)> Changed;
+
+private:
+       friend class PlayerWrapper;
+
+       void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame);
+       void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+       void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
+       void setup_pieces ();
+       void playlist_changed ();
+       void content_changed (boost::weak_ptr<Content>, int, bool);
+       void do_seek (Time, bool);
+       void flush ();
+       void emit_black ();
+       void emit_silence (OutputAudioFrame);
+       boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool);
+       void film_changed (Film::Property);
+       void update_subtitle ();
+
+       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;
+
+       AudioMerger<Time, AudioContent::Frame> _audio_merger;
+
+       libdcp::Size _video_container_size;
+       boost::shared_ptr<Image> _black_frame;
+       std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
+
+       struct {
+               boost::weak_ptr<Piece> piece;
+               boost::shared_ptr<Image> image;
+               dcpomatic::Rect<double> rect;
+               Time from;
+               Time to;
+       } _in_subtitle;
+
+       struct {
+               boost::shared_ptr<Image> image;
+               Position<int> position;
+               Time from;
+               Time to;
+       } _out_subtitle;
+
+#ifdef DCPOMATIC_DEBUG
+       boost::shared_ptr<Content> _last_video;
+#endif
+
+       bool _last_emit_was_black;
+
+       boost::signals2::scoped_connection _playlist_changed_connection;
+       boost::signals2::scoped_connection _playlist_content_changed_connection;
+       boost::signals2::scoped_connection _film_changed_connection;
+};
+
+#endif
diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc
new file mode 100644 (file)
index 0000000..de48ff5
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+    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 "still_image_decoder.h"
+#include "still_image_content.h"
+#include "content_factory.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 std::pair;
+using boost::optional;
+using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
+using boost::lexical_cast;
+
+Playlist::Playlist ()
+       : _sequence_video (true)
+       , _sequencing_video (false)
+{
+
+}
+
+Playlist::~Playlist ()
+{
+       _content.clear ();
+       reconnect ();
+}
+
+void
+Playlist::content_changed (weak_ptr<Content> content, int property, bool frequent)
+{
+       if (property == ContentProperty::LENGTH) {
+               maybe_sequence_video ();
+       }
+       
+       ContentChanged (content, property, frequent);
+}
+
+void
+Playlist::maybe_sequence_video ()
+{
+       if (!_sequence_video || _sequencing_video) {
+               return;
+       }
+       
+       _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_position (last);
+               last = (*i)->end ();
+       }
+       
+       _sequencing_video = false;
+}
+
+string
+Playlist::video_identifier () const
+{
+       string t;
+       
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+               shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
+               if (vc) {
+                       t += vc->identifier ();
+               }
+       }
+
+       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) {
+               _content.push_back (content_factory (film, *i));
+       }
+
+       reconnect ();
+}
+
+/** @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"));
+       }
+}
+
+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::remove (ContentList c)
+{
+       for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
+               ContentList::iterator j = _content.begin ();
+               while (j != _content.end() && *j != *i) {
+                       ++j;
+               }
+       
+               if (j != _content.end ()) {
+                       _content.erase (j);
+               }
+       }
+
+       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 */
+       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;
+                       }
+
+                       /* Use the largest difference between DCP and source as the "error" */
+                       this_error = max (this_error, float (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, _3)));
+       }
+}
+
+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->position() < b->position();
+}
+
+/** @return content in an undefined order */
+ContentList
+Playlist::content () const
+{
+       return _content;
+}
+
+void
+Playlist::repeat (ContentList c, int n)
+{
+       pair<Time, Time> range (TIME_MAX, 0);
+       for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
+               range.first = min (range.first, (*i)->position ());
+               range.second = max (range.second, (*i)->position ());
+               range.first = min (range.first, (*i)->end ());
+               range.second = max (range.second, (*i)->end ());
+       }
+
+       Time pos = range.second;
+       for (int i = 0; i < n; ++i) {
+               for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
+                       shared_ptr<Content> copy = (*i)->clone ();
+                       copy->set_position (pos + copy->position() - range.first);
+                       _content.push_back (copy);
+               }
+               pos += range.second - range.first;
+       }
+
+       reconnect ();
+       Changed ();
+}
diff --git a/src/lib/playlist.h b/src/lib/playlist.h
new file mode 100644 (file)
index 0000000..7dbf416
--- /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_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 StillImageMagickContent;
+class StillImageMagickDecoder;
+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.
+ */
+
+struct ContentSorter
+{
+       bool operator() (boost::shared_ptr<Content> a, boost::shared_ptr<Content> b);
+};
+
+class Playlist : public boost::noncopyable
+{
+public:
+       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>);
+       void remove (ContentList);
+
+       bool has_subtitles () const;
+
+       ContentList content () const;
+
+       std::string video_identifier () const;
+
+       Time length () const;
+       
+       int best_dcp_frame_rate () const;
+       Time video_end () const;
+
+       void set_sequence_video (bool);
+       void maybe_sequence_video ();
+
+       void repeat (ContentList, int);
+
+       mutable boost::signals2::signal<void ()> Changed;
+       /** Third parameter is true if signals are currently being emitted frequently */
+       mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> ContentChanged;
+       
+private:
+       void content_changed (boost::weak_ptr<Content>, int, bool);
+       void reconnect ();
+
+       ContentList _content;
+       bool _sequence_video;
+       bool _sequencing_video;
+       std::list<boost::signals2::connection> _content_connections;
+};
+
+#endif
diff --git a/src/lib/po/es_ES.po b/src/lib/po/es_ES.po
new file mode 100644 (file)
index 0000000..0e1776a
--- /dev/null
@@ -0,0 +1,658 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: LIBDCPOMATIC\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-04-02 19:10-0500\n"
+"Last-Translator: Manuel AC <manuel.acevedo@civantos.>\n"
+"Language-Team: Manuel AC <manuel.acevedo@civantos.com>\n"
+"Language: es-ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.5\n"
+
+#: src/lib/sndfile_content.cc:70
+msgid "%1 channels, %2kHz, %3 samples"
+msgstr ""
+
+#: src/lib/ffmpeg_content.cc:193
+#, fuzzy
+msgid "%1 frames; %2 frames per second"
+msgstr "fotogramas por segundo"
+
+#: src/lib/video_content.cc:131
+msgid "%1x%2 pixels (%3:1)"
+msgstr ""
+
+#: src/lib/transcode_job.cc:81
+msgid "0%"
+msgstr "0%"
+
+#: src/lib/ratio.cc:47
+msgid "1.19"
+msgstr "1.19"
+
+#: src/lib/ratio.cc:50
+msgid "1.375"
+msgstr "1.375"
+
+#: src/lib/ratio.cc:51
+msgid "1.66"
+msgstr "1.66"
+
+#: src/lib/ratio.cc:52
+msgid "16:9"
+msgstr "16:9"
+
+#: src/lib/filter.cc:88
+msgid "3D denoiser"
+msgstr "reducción de ruido 3D"
+
+#: src/lib/ratio.cc:48
+msgid "4:3"
+msgstr ""
+
+#: src/lib/ratio.cc:49
+msgid "Academy"
+msgstr "Academy"
+
+#: src/lib/dcp_content_type.cc:53
+msgid "Advertisement"
+msgstr "Publicidad"
+
+#: src/lib/job.cc:72
+msgid "An error occurred whilst handling the file %1."
+msgstr "Ha ocurrido un error con el fichero %1."
+
+#: src/lib/analyse_audio_job.cc:53
+msgid "Analyse audio of %1"
+msgstr "Analizar audio de %1"
+
+#: src/lib/scaler.cc:64
+msgid "Area"
+msgstr "Área"
+
+#: src/lib/scaler.cc:62
+msgid "Bicubic"
+msgstr "Bicúbico"
+
+#: src/lib/scaler.cc:69
+msgid "Bilinear"
+msgstr "Bilineal"
+
+#: src/lib/job.cc:320
+msgid "Cancelled"
+msgstr ""
+
+#: src/lib/exceptions.cc:60
+msgid "Cannot handle pixel format %1 during %2"
+msgstr ""
+
+#: src/lib/util.cc:700
+msgid "Centre"
+msgstr ""
+
+#: src/lib/scp_dcp_job.cc:109
+msgid "Copy DCP to TMS"
+msgstr "Copiar DCP al TMS"
+
+#: src/lib/scp_dcp_job.cc:128
+msgid "Could not connect to server %1 (%2)"
+msgstr "No se pudo conectar al servidor %1 (%2)"
+
+#: src/lib/scp_dcp_job.cc:150
+msgid "Could not create remote directory %1 (%2)"
+msgstr "No se pudo crear la carpeta remota %1 (%2)"
+
+#: src/lib/scp_dcp_job.cc:175
+msgid "Could not open %1 to send"
+msgstr "No se pudo abrir %1 para enviar"
+
+#: src/lib/scp_dcp_job.cc:145
+msgid "Could not start SCP session (%1)"
+msgstr "No se pudo iniciar la sesión SCP (%1)"
+
+#: src/lib/scp_dcp_job.cc:187
+msgid "Could not write to remote file (%1)"
+msgstr "No se pudo escribir el fichero remoto (%1)"
+
+#: src/lib/filter.cc:77
+msgid "Cubic interpolating deinterlacer"
+msgstr "Desentrelazado por interpolación cúbica"
+
+#: src/lib/util.cc:723
+msgid "DCP and source have the same rate.\n"
+msgstr "La fuente y el DCP tienen la misma velocidad.\n"
+
+#: src/lib/util.cc:733
+#, fuzzy
+msgid "DCP will run at %1%% of the source speed.\n"
+msgstr "El DCP se reproducirá al %1%% de la velocidad de la fuente.\n"
+
+#: src/lib/util.cc:726
+msgid "DCP will use every other frame of the source.\n"
+msgstr "El DCP usará fotogramas alternos de la fuente.\n"
+
+#: src/lib/filter.cc:68 src/lib/filter.cc:69 src/lib/filter.cc:70
+#: src/lib/filter.cc:71 src/lib/filter.cc:72 src/lib/filter.cc:73
+msgid "De-blocking"
+msgstr "De-blocking"
+
+#: src/lib/filter.cc:75 src/lib/filter.cc:76 src/lib/filter.cc:77
+#: src/lib/filter.cc:78 src/lib/filter.cc:79 src/lib/filter.cc:80
+#: src/lib/filter.cc:81 src/lib/filter.cc:82 src/lib/filter.cc:83
+msgid "De-interlacing"
+msgstr "Desentrelazado"
+
+#: src/lib/filter.cc:74
+msgid "Deringing filter"
+msgstr "Deringing filter"
+
+#: src/lib/dolby_cp750.cc:27
+msgid "Dolby CP750"
+msgstr "Dolby CP750"
+
+#: src/lib/util.cc:728
+msgid "Each source frame will be doubled in the DCP.\n"
+msgstr "Se doblará cada fotograma de la fuente en el DCP.\n"
+
+#: src/lib/job.cc:318
+msgid "Error (%1)"
+msgstr "Error (%1)"
+
+#: src/lib/examine_content_job.cc:45
+msgid "Examine content"
+msgstr "Examinar contenido"
+
+#: src/lib/filter.cc:72
+msgid "Experimental horizontal deblocking filter 1"
+msgstr "Experimental horizontal deblocking filter 1"
+
+#: src/lib/filter.cc:73
+msgid "Experimental vertical deblocking filter 1"
+msgstr "Experimental vertical deblocking filter 1"
+
+#: src/lib/filter.cc:79
+msgid "FFMPEG deinterlacer"
+msgstr "Desentrelazado FFMPEG"
+
+#: src/lib/filter.cc:80
+msgid "FIR low-pass deinterlacer"
+msgstr "Desentrelazado paso bajo FIR"
+
+#: src/lib/scp_dcp_job.cc:138
+msgid "Failed to authenticate with server (%1)"
+msgstr "Fallo al identificarse con el servidor (%1)"
+
+#: src/lib/scaler.cc:70
+msgid "Fast Bilinear"
+msgstr "Bilineal rápido"
+
+#: src/lib/dcp_content_type.cc:44
+msgid "Feature"
+msgstr "Película"
+
+#: src/lib/ratio.cc:53
+msgid "Flat"
+msgstr "Flat"
+
+#: src/lib/filter.cc:85
+msgid "Force quantizer"
+msgstr "Force quantizer"
+
+#: src/lib/ratio.cc:55
+msgid "Full frame"
+msgstr ""
+
+#: src/lib/scaler.cc:65
+msgid "Gaussian"
+msgstr "Gaussiano"
+
+#: src/lib/filter.cc:86
+msgid "Gradient debander"
+msgstr "Gradient debander"
+
+#: src/lib/filter.cc:89
+msgid "High quality 3D denoiser"
+msgstr "Reductor de ruido 3D de alta calidad"
+
+#: src/lib/filter.cc:68
+msgid "Horizontal deblocking filter"
+msgstr "Horizontal deblocking filter"
+
+#: src/lib/filter.cc:70
+msgid "Horizontal deblocking filter A"
+msgstr "Horizontal deblocking filter A"
+
+#: src/lib/imagemagick_content.cc:50
+msgid "Image: %1"
+msgstr ""
+
+#: src/lib/job.cc:96 src/lib/job.cc:105
+msgid ""
+"It is not known what caused this error.  The best idea is to report the "
+"problem to the DCP-o-matic mailing list (carl@dcpomatic.com)"
+msgstr ""
+"Error desconocido. La mejor idea es informar del problema a la lista de "
+"correo de DCP-o-matic (carl@dcpomatic.com)"
+
+#: src/lib/filter.cc:82
+msgid "Kernel deinterlacer"
+msgstr "Kernel deinterlacer"
+
+#: src/lib/scaler.cc:66
+msgid "Lanczos"
+msgstr "Lanczos"
+
+#: src/lib/util.cc:698
+msgid "Left"
+msgstr ""
+
+#: src/lib/util.cc:702
+msgid "Left surround"
+msgstr ""
+
+#: src/lib/util.cc:701
+msgid "Lfe (sub)"
+msgstr ""
+
+#: src/lib/filter.cc:75
+msgid "Linear blend deinterlacer"
+msgstr "Linear blend deinterlacer"
+
+#: src/lib/filter.cc:76
+msgid "Linear interpolating deinterlacer"
+msgstr "Linear interpolating deinterlacer"
+
+#: src/lib/filter.cc:78
+msgid "Median deinterlacer"
+msgstr "Median deinterlacer"
+
+#: src/lib/filter.cc:74 src/lib/filter.cc:85 src/lib/filter.cc:86
+#: src/lib/filter.cc:87 src/lib/filter.cc:90
+msgid "Misc"
+msgstr "Miscelánea"
+
+#: src/lib/filter.cc:81
+msgid "Motion compensating deinterlacer"
+msgstr "Motion compensating deinterlacer"
+
+#: src/lib/ffmpeg_content.cc:181
+msgid "Movie: %1"
+msgstr ""
+
+#: src/lib/filter.cc:84 src/lib/filter.cc:88 src/lib/filter.cc:89
+#: src/lib/filter.cc:91
+msgid "Noise reduction"
+msgstr "Reducción de ruido"
+
+#: src/lib/job.cc:316
+msgid "OK (ran for %1)"
+msgstr "OK (ejecución %1)"
+
+#: src/lib/filter.cc:91
+msgid "Overcomplete wavelet denoiser"
+msgstr "Overcomplete wavelet denoiser"
+
+#: src/lib/dcp_content_type.cc:51
+msgid "Policy"
+msgstr "Policy"
+
+#: src/lib/dcp_content_type.cc:52
+msgid "Public Service Announcement"
+msgstr "Anuncio de servicio público"
+
+#: src/lib/dcp_content_type.cc:49
+msgid "Rating"
+msgstr "Clasificación"
+
+#: src/lib/util.cc:699
+msgid "Right"
+msgstr ""
+
+#: src/lib/util.cc:703
+msgid "Right surround"
+msgstr ""
+
+#: src/lib/scp_dcp_job.cc:133
+msgid "SSH error (%1)"
+msgstr "error SSH (%1)"
+
+#: src/lib/ratio.cc:54
+msgid "Scope"
+msgstr "Scope"
+
+#: src/lib/dcp_content_type.cc:45
+msgid "Short"
+msgstr "Cortometraje"
+
+#: src/lib/scaler.cc:67
+msgid "Sinc"
+msgstr "Sinc"
+
+#: src/lib/sndfile_content.cc:57
+#, fuzzy
+msgid "Sound file: %1"
+msgstr "no se pudo abrir el fichero para lectura"
+
+#: src/lib/scaler.cc:68
+msgid "Spline"
+msgstr "Spline"
+
+#: src/lib/dcp_content_type.cc:50
+msgid "Teaser"
+msgstr "Teaser"
+
+#: src/lib/filter.cc:90
+msgid "Telecine filter"
+msgstr "Filtro telecine"
+
+#: src/lib/filter.cc:84
+msgid "Temporal noise reducer"
+msgstr "Temporal noise reducer"
+
+#: src/lib/dcp_content_type.cc:47
+msgid "Test"
+msgstr "Test"
+
+#: src/lib/job.cc:78
+msgid ""
+"The drive that the film is stored on is low in disc space.  Free some more "
+"space and try again."
+msgstr ""
+"En el dispositivo donde se encuentra la película queda poco espacio. Libere "
+"espacio en el disco y pruebe de nuevo."
+
+#: src/lib/film.cc:364
+msgid ""
+"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!"
+msgstr ""
+
+#: src/lib/dcp_content_type.cc:46
+msgid "Trailer"
+msgstr "Trailer"
+
+#: src/lib/transcode_job.cc:50
+msgid "Transcode %1"
+msgstr "Codificar %1"
+
+#: src/lib/dcp_content_type.cc:48
+msgid "Transitional"
+msgstr "Transitional"
+
+#: src/lib/job.cc:104
+msgid "Unknown error"
+msgstr "Error desconocido"
+
+#: src/lib/ffmpeg_decoder.cc:264
+msgid "Unrecognised audio sample format (%1)"
+msgstr "Formato de audio desconocido (%1)"
+
+#: src/lib/filter.cc:87
+msgid "Unsharp mask and Gaussian blur"
+msgstr "Máscara de desenfoque Gaussiano"
+
+#: src/lib/filter.cc:69
+msgid "Vertical deblocking filter"
+msgstr "Vertical deblocking filter"
+
+#: src/lib/filter.cc:71
+msgid "Vertical deblocking filter A"
+msgstr "Vertical deblocking filter A"
+
+#: src/lib/scp_dcp_job.cc:101
+msgid "Waiting"
+msgstr "Esperando"
+
+#: src/lib/scaler.cc:63
+msgid "X"
+msgstr "X"
+
+#: src/lib/filter.cc:83
+msgid "Yet Another Deinterlacing Filter"
+msgstr "Yet Another Deinterlacing Filter"
+
+#: src/lib/film.cc:273
+msgid "You must add some content to the DCP before creating it"
+msgstr ""
+
+#: src/lib/film.cc:232
+msgid "cannot contain slashes"
+msgstr "no puede contener barras"
+
+#: src/lib/util.cc:494
+msgid "connect timed out"
+msgstr "tiempo de conexión agotado"
+
+#: src/lib/scp_dcp_job.cc:119
+msgid "connecting"
+msgstr "conectando"
+
+#: src/lib/film.cc:269
+#, fuzzy
+msgid "container"
+msgstr "contenido"
+
+#: src/lib/film.cc:277
+msgid "content type"
+msgstr "tipo de contenido"
+
+#: src/lib/scp_dcp_job.cc:168
+msgid "copying %1"
+msgstr "copiando %1"
+
+#: src/lib/exceptions.cc:36
+#, fuzzy
+msgid "could not create file %1"
+msgstr "No se pudo escribir el fichero remoto (%1)"
+
+#: src/lib/ffmpeg.cc:128
+msgid "could not find audio decoder"
+msgstr "no se encontró el decodificador de audio"
+
+#: src/lib/ffmpeg.cc:76
+msgid "could not find stream information"
+msgstr "no se pudo encontrar información del flujo"
+
+#: src/lib/ffmpeg_decoder.cc:498
+msgid "could not find subtitle decoder"
+msgstr "no se pudo encontrar decodificador de subtítutlos"
+
+#: src/lib/ffmpeg.cc:107
+msgid "could not find video decoder"
+msgstr "no se pudo encontrar decodificador de vídeo"
+
+#: src/lib/sndfile_decoder.cc:45
+#, fuzzy
+msgid "could not open audio file for reading"
+msgstr "no se pudo abrir el fichero para lectura"
+
+#: src/lib/exceptions.cc:29
+#, fuzzy
+msgid "could not open file %1"
+msgstr "no se pudo abrir el fichero para lectura"
+
+#: src/lib/dcp_video_frame.cc:263
+msgid "could not open file for reading"
+msgstr "no se pudo abrir el fichero para lectura"
+
+#: src/lib/exceptions.cc:44
+#, fuzzy
+msgid "could not read from file %1 (%2)"
+msgstr "No se pudo crear la carpeta remota %1 (%2)"
+
+#: src/lib/resampler.cc:76 src/lib/resampler.cc:96
+msgid "could not run sample-rate converter"
+msgstr "no se pudo ejecutar el conversor de velocidad"
+
+#: src/lib/scp_dcp_job.cc:86
+msgid "could not start SCP session (%1)"
+msgstr "no se pudo abrir la sesión SCP (%1)"
+
+#: src/lib/scp_dcp_job.cc:52
+msgid "could not start SSH session"
+msgstr "no se pudo abrir la sesión SSH"
+
+#: src/lib/exceptions.cc:50
+#, fuzzy
+msgid "could not write to file %1 (%2)"
+msgstr "No se pudo escribir el fichero remoto (%1)"
+
+#: src/lib/transcode_job.cc:94
+msgid "frames per second"
+msgstr "fotogramas por segundo"
+
+#: src/lib/util.cc:146
+msgid "hour"
+msgstr "hora"
+
+#: src/lib/util.cc:143 src/lib/util.cc:148
+msgid "hours"
+msgstr "horas"
+
+#: src/lib/util.cc:153
+msgid "minute"
+msgstr "minuto"
+
+#: src/lib/util.cc:155
+msgid "minutes"
+msgstr "minutos"
+
+#: src/lib/util.cc:621
+msgid "missing key %1 in key-value set"
+msgstr "falta la clave %1 en el par clave-valor"
+
+#: src/lib/exceptions.cc:54
+msgid "missing required setting %1"
+msgstr ""
+
+#: src/lib/ffmpeg_decoder.cc:530
+msgid "multi-part subtitles not yet supported"
+msgstr "todavía no se soportan subtítulos en múltiples partes"
+
+#: src/lib/film.cc:232 src/lib/film.cc:281
+msgid "name"
+msgstr "nombre"
+
+#: src/lib/ffmpeg_decoder.cc:545
+msgid "non-bitmap subtitles not yet supported"
+msgstr "todavía no se soportan subtítulos que no son en mapas de bits"
+
+#. / TRANSLATORS: remaining here follows an amount of time that is remaining
+#. / on an operation.
+#: src/lib/job.cc:313
+msgid "remaining"
+msgstr "pendiente"
+
+#: src/lib/util.cc:158
+msgid "seconds"
+msgstr "segundos"
+
+#~ msgid "1.66 within Flat"
+#~ msgstr "1.66 en Flat"
+
+#~ msgid "16:9 within Flat"
+#~ msgstr "16:9 en Flat"
+
+#, fuzzy
+#~ msgid "16:9 within Scope"
+#~ msgstr "16:9 en Flat"
+
+#~ msgid "4:3 within Flat"
+#~ msgstr "4:3 en Flat"
+
+#~ msgid "A/B transcode %1"
+#~ msgstr "Codificación A/B %1"
+
+#~ msgid "Cannot resample audio as libswresample is not present"
+#~ msgstr ""
+#~ "No se puede redimensionar el sonido porque no se encuentra libswresample"
+
+#~ msgid "Examine content of %1"
+#~ msgstr "Examinar contenido de %1"
+
+#~ msgid "Flat without stretch"
+#~ msgstr "Flat sin deformación"
+
+#~ msgid "Rec 709"
+#~ msgstr "Rec 709"
+
+#~ msgid "Scope without stretch"
+#~ msgstr "Scope sin deformación"
+
+#~ msgid "could not open external audio file for reading"
+#~ msgstr "no se pudo leer el fichero externo  de audio"
+
+#~ msgid "external audio files have differing lengths"
+#~ msgstr "los ficheros externos de sonido tienen duraciones diferentes"
+
+#~ msgid "external audio files must be mono"
+#~ msgstr "los ficheros externos de sonido deben ser mono"
+
+#~ msgid "format"
+#~ msgstr "formato"
+
+#~ msgid "no still image files found"
+#~ msgstr "no se encuentran imágenes fijas"
+
+#~ msgid "sRGB"
+#~ msgstr "sRGB"
+
+#~ msgid "still"
+#~ msgstr "imagen fija"
+
+#~ msgid "video"
+#~ msgstr "vídeo"
+
+#~ msgid "1.33"
+#~ msgstr "1.33"
+
+#~ msgid "Source scaled to 1.19:1"
+#~ msgstr "Fuente escalada a 1.19:1"
+
+#~ msgid "Source scaled to 1.33:1"
+#~ msgstr "Fuente escalada a 1.33:1"
+
+#~ msgid "Source scaled to 1.33:1 then pillarboxed to Flat"
+#~ msgstr "Fuente escalada a 1.33:1 con bandas hasta Flat"
+
+#~ msgid "Source scaled to 1.375:1"
+#~ msgstr "Fuente escalada a 1.375:1"
+
+#~ msgid "Source scaled to 1.37:1 (Academy ratio)"
+#~ msgstr "Fuente escalada a 1.37:1 (Academy)"
+
+#~ msgid "Source scaled to 1.66:1"
+#~ msgstr "Fuente escalada a 1.66:1"
+
+#~ msgid "Source scaled to 1.66:1 then pillarboxed to Flat"
+#~ msgstr "Fuente escalada a 1.66:1 con bandas hasta Flat"
+
+#~ msgid "Source scaled to 1.78:1"
+#~ msgstr "Fuente escalada a 1.78:1"
+
+#~ msgid "Source scaled to 1.78:1 then pillarboxed to Flat"
+#~ msgstr "Fuente escalada a 1.78:1 con bandas hasta Flat"
+
+#~ msgid "Source scaled to Flat (1.85:1)"
+#~ msgstr "Fuente escalada a Flat (1.85:1)"
+
+#~ msgid "Source scaled to Scope (2.39:1)"
+#~ msgstr "Fuente escalada a Scope (2.39:1)"
+
+#~ msgid "Source scaled to fit Flat preserving its aspect ratio"
+#~ msgstr "Fuente escalada a Flat conservando el ratio de aspecto"
+
+#~ msgid "Source scaled to fit Scope preserving its aspect ratio"
+#~ msgstr "Fuente escalada a Scope conservando el ratio de aspecto"
+
+#~ msgid "adding to queue of %1"
+#~ msgstr "añadiendo a la cola de %1"
diff --git a/src/lib/po/fr_FR.po b/src/lib/po/fr_FR.po
new file mode 100644 (file)
index 0000000..d134725
--- /dev/null
@@ -0,0 +1,664 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: DCP-o-matic FRENCH\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-07-16 23:11+0100\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/lib/sndfile_content.cc:70
+msgid "%1 channels, %2kHz, %3 samples"
+msgstr "%1 canaux, %2kHz, %3 samples"
+
+#: src/lib/ffmpeg_content.cc:193
+msgid "%1 frames; %2 frames per second"
+msgstr "%1 images ; %2 images par seconde"
+
+#: src/lib/video_content.cc:131
+msgid "%1x%2 pixels (%3:1)"
+msgstr "%1x%2 pixels (%3:1)"
+
+#: src/lib/transcode_job.cc:81
+msgid "0%"
+msgstr "0%"
+
+#: src/lib/ratio.cc:47
+msgid "1.19"
+msgstr "1.19"
+
+#: src/lib/ratio.cc:50
+msgid "1.375"
+msgstr "1.375"
+
+#: src/lib/ratio.cc:51
+msgid "1.66"
+msgstr "1.66"
+
+#: src/lib/ratio.cc:52
+msgid "16:9"
+msgstr "16:9"
+
+#: src/lib/filter.cc:88
+msgid "3D denoiser"
+msgstr "Débruitage 3D"
+
+#: src/lib/ratio.cc:48
+msgid "4:3"
+msgstr "4:3"
+
+#: src/lib/ratio.cc:49
+msgid "Academy"
+msgstr "Academy"
+
+#: src/lib/dcp_content_type.cc:53
+msgid "Advertisement"
+msgstr "Advertisement"
+
+#: src/lib/job.cc:72
+msgid "An error occurred whilst handling the file %1."
+msgstr "Une erreur s'est produite lors du traitement du fichier %1."
+
+#: src/lib/analyse_audio_job.cc:53
+msgid "Analyse audio of %1"
+msgstr "Analyse du son de %1"
+
+#: src/lib/scaler.cc:64
+msgid "Area"
+msgstr "Area"
+
+#: src/lib/scaler.cc:62
+msgid "Bicubic"
+msgstr "Bicubique"
+
+#: src/lib/scaler.cc:69
+msgid "Bilinear"
+msgstr "Bilinéaire"
+
+#: src/lib/job.cc:320
+msgid "Cancelled"
+msgstr "Annulé"
+
+#: src/lib/exceptions.cc:60
+msgid "Cannot handle pixel format %1 during %2"
+msgstr "Format du pixel %1 non géré par %2"
+
+#: src/lib/util.cc:700
+msgid "Centre"
+msgstr "Centre"
+
+#: src/lib/scp_dcp_job.cc:109
+msgid "Copy DCP to TMS"
+msgstr "Copier le DCP dans le TMS"
+
+#: src/lib/scp_dcp_job.cc:128
+msgid "Could not connect to server %1 (%2)"
+msgstr "Connexion au serveur %1 (%2) impossible"
+
+#: src/lib/scp_dcp_job.cc:150
+msgid "Could not create remote directory %1 (%2)"
+msgstr "Création du dossier distant %1 (%2) impossible"
+
+#: src/lib/scp_dcp_job.cc:175
+msgid "Could not open %1 to send"
+msgstr "Ouverture de %1 pour envoi impossible"
+
+#: src/lib/scp_dcp_job.cc:145
+msgid "Could not start SCP session (%1)"
+msgstr "Démarrage de session SCP (%1) impossible"
+
+#: src/lib/scp_dcp_job.cc:187
+msgid "Could not write to remote file (%1)"
+msgstr "Écriture vers fichier distant (%1) impossible"
+
+#: src/lib/filter.cc:77
+msgid "Cubic interpolating deinterlacer"
+msgstr "Désentrelacement cubique interpolé"
+
+#: src/lib/util.cc:723
+msgid "DCP and source have the same rate.\n"
+msgstr "Le DCP et la source ont les mêmes cadences.\n"
+
+#: src/lib/util.cc:733
+#, fuzzy
+msgid "DCP will run at %1%% of the source speed.\n"
+msgstr "La cadence du DCP sera %1%% par rapport à la source.\n"
+
+#: src/lib/util.cc:726
+msgid "DCP will use every other frame of the source.\n"
+msgstr "Le DCP utilisera une image sur deux de la source.\n"
+
+#: src/lib/filter.cc:68
+#: src/lib/filter.cc:69
+#: src/lib/filter.cc:70
+#: src/lib/filter.cc:71
+#: src/lib/filter.cc:72
+#: src/lib/filter.cc:73
+msgid "De-blocking"
+msgstr "De-bloc"
+
+#: src/lib/filter.cc:75
+#: src/lib/filter.cc:76
+#: src/lib/filter.cc:77
+#: src/lib/filter.cc:78
+#: src/lib/filter.cc:79
+#: src/lib/filter.cc:80
+#: src/lib/filter.cc:81
+#: src/lib/filter.cc:82
+#: src/lib/filter.cc:83
+msgid "De-interlacing"
+msgstr "Désentrelacement"
+
+#: src/lib/filter.cc:74
+msgid "Deringing filter"
+msgstr "Filtre anti bourdonnement"
+
+#: src/lib/dolby_cp750.cc:27
+msgid "Dolby CP750"
+msgstr "Dolby CP750"
+
+#: src/lib/util.cc:728
+msgid "Each source frame will be doubled in the DCP.\n"
+msgstr "Chaque image source sera dupliquée dans le DCP.\n"
+
+#: src/lib/job.cc:318
+msgid "Error (%1)"
+msgstr "Erreur (%1)"
+
+#: src/lib/examine_content_job.cc:45
+msgid "Examine content"
+msgstr "Examen du contenu"
+
+#: src/lib/filter.cc:72
+msgid "Experimental horizontal deblocking filter 1"
+msgstr "Filtre dé-bloc horizontal 1"
+
+#: src/lib/filter.cc:73
+msgid "Experimental vertical deblocking filter 1"
+msgstr "Filtre dé-bloc vertical 1"
+
+#: src/lib/filter.cc:79
+msgid "FFMPEG deinterlacer"
+msgstr "Désentrelaceur FFMPEG"
+
+#: src/lib/filter.cc:80
+msgid "FIR low-pass deinterlacer"
+msgstr "Désentrelaceur passe-bas FIR"
+
+#: src/lib/scp_dcp_job.cc:138
+msgid "Failed to authenticate with server (%1)"
+msgstr "L'authentification du serveur (%1) a échouée"
+
+#: src/lib/scaler.cc:70
+msgid "Fast Bilinear"
+msgstr "Bilinéaire rapide"
+
+#: src/lib/dcp_content_type.cc:44
+msgid "Feature"
+msgstr "Feature"
+
+#: src/lib/ratio.cc:53
+msgid "Flat"
+msgstr "Flat"
+
+#: src/lib/filter.cc:85
+msgid "Force quantizer"
+msgstr "Forcer la quantification"
+
+#: src/lib/ratio.cc:55
+msgid "Full frame"
+msgstr "Pleine matrice"
+
+#: src/lib/scaler.cc:65
+msgid "Gaussian"
+msgstr "Gaussien"
+
+#: src/lib/filter.cc:86
+msgid "Gradient debander"
+msgstr "Corrections des bandes du dégradé"
+
+#: src/lib/filter.cc:89
+msgid "High quality 3D denoiser"
+msgstr "Débruiteur 3D haute qualité"
+
+#: src/lib/filter.cc:68
+msgid "Horizontal deblocking filter"
+msgstr "Filtre dé-bloc horizontal"
+
+#: src/lib/filter.cc:70
+msgid "Horizontal deblocking filter A"
+msgstr "Filtre dé-bloc horizontal"
+
+#: src/lib/imagemagick_content.cc:50
+msgid "Image: %1"
+msgstr "Image : %1"
+
+#: src/lib/job.cc:96
+#: src/lib/job.cc:105
+msgid "It is not known what caused this error.  The best idea is to report the problem to the DCP-o-matic mailing list (carl@dcpomatic.com)"
+msgstr "Erreur indéterminée. Merci de rapporter le problème à la liste DCP-o-matic (carl@dcpomatic.com)"
+
+#: src/lib/filter.cc:82
+msgid "Kernel deinterlacer"
+msgstr "Désentrelaceur noyau"
+
+#: src/lib/scaler.cc:66
+msgid "Lanczos"
+msgstr "Lanczos"
+
+#: src/lib/util.cc:698
+msgid "Left"
+msgstr "Gauche"
+
+#: src/lib/util.cc:702
+msgid "Left surround"
+msgstr "Arrière gauche"
+
+#: src/lib/util.cc:701
+msgid "Lfe (sub)"
+msgstr "Basses fréquences"
+
+#: src/lib/filter.cc:75
+msgid "Linear blend deinterlacer"
+msgstr "Désentrelaceur par mélange interpolé"
+
+#: src/lib/filter.cc:76
+msgid "Linear interpolating deinterlacer"
+msgstr "Désentrelaceur linéaire interpolé"
+
+#: src/lib/filter.cc:78
+msgid "Median deinterlacer"
+msgstr "Désentrelaceur médian"
+
+#: src/lib/filter.cc:74
+#: src/lib/filter.cc:85
+#: src/lib/filter.cc:86
+#: src/lib/filter.cc:87
+#: src/lib/filter.cc:90
+msgid "Misc"
+msgstr "Divers"
+
+#: src/lib/filter.cc:81
+msgid "Motion compensating deinterlacer"
+msgstr "Désentrelaceur par compensation de mouvement"
+
+#: src/lib/ffmpeg_content.cc:181
+msgid "Movie: %1"
+msgstr "Film : %1"
+
+#: src/lib/filter.cc:84
+#: src/lib/filter.cc:88
+#: src/lib/filter.cc:89
+#: src/lib/filter.cc:91
+msgid "Noise reduction"
+msgstr "Réduction de bruit"
+
+#: src/lib/job.cc:316
+msgid "OK (ran for %1)"
+msgstr "OK (processus %1)"
+
+#: src/lib/filter.cc:91
+msgid "Overcomplete wavelet denoiser"
+msgstr "Réduction de bruit par ondelettes"
+
+#: src/lib/dcp_content_type.cc:51
+msgid "Policy"
+msgstr "Policy"
+
+#: src/lib/dcp_content_type.cc:52
+msgid "Public Service Announcement"
+msgstr "Public Service Announcement"
+
+#: src/lib/dcp_content_type.cc:49
+msgid "Rating"
+msgstr "Classification"
+
+#: src/lib/util.cc:699
+msgid "Right"
+msgstr "Droite"
+
+#: src/lib/util.cc:703
+msgid "Right surround"
+msgstr "Arrière droite"
+
+#: src/lib/scp_dcp_job.cc:133
+msgid "SSH error (%1)"
+msgstr "Erreur SSH (%1)"
+
+#: src/lib/ratio.cc:54
+msgid "Scope"
+msgstr "Scope"
+
+#: src/lib/dcp_content_type.cc:45
+msgid "Short"
+msgstr "Short"
+
+#: src/lib/scaler.cc:67
+msgid "Sinc"
+msgstr "Sinc"
+
+#: src/lib/sndfile_content.cc:57
+msgid "Sound file: %1"
+msgstr "Fichier son : %1"
+
+#: src/lib/scaler.cc:68
+msgid "Spline"
+msgstr "Spline"
+
+#: src/lib/dcp_content_type.cc:50
+msgid "Teaser"
+msgstr "Teaser"
+
+#: src/lib/filter.cc:90
+msgid "Telecine filter"
+msgstr "Filtre télécinéma"
+
+#: src/lib/filter.cc:84
+msgid "Temporal noise reducer"
+msgstr "Réduction de bruit temporel"
+
+#: src/lib/dcp_content_type.cc:47
+msgid "Test"
+msgstr "Test"
+
+#: src/lib/job.cc:78
+msgid "The drive that the film is stored on is low in disc space.  Free some more space and try again."
+msgstr "Le disque contenant le film est plein. Libérez de l'espace et essayez à nouveau."
+
+#: src/lib/film.cc:364
+msgid "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!"
+msgstr "Ce projet a été créé avec une ancienne version de DCP-o-matic, chargement impossible. Créez un nouveau projet, ajoutez du contenu et  reparamétrez. Désolé !"
+
+#: src/lib/dcp_content_type.cc:46
+msgid "Trailer"
+msgstr "Trailer"
+
+#: src/lib/transcode_job.cc:50
+msgid "Transcode %1"
+msgstr "Transcodage %1"
+
+#: src/lib/dcp_content_type.cc:48
+msgid "Transitional"
+msgstr "Transitional"
+
+#: src/lib/job.cc:104
+msgid "Unknown error"
+msgstr "Erreur inconnue"
+
+#: src/lib/ffmpeg_decoder.cc:264
+msgid "Unrecognised audio sample format (%1)"
+msgstr "Échantillonnage audio (%1) inconnu"
+
+#: src/lib/filter.cc:87
+msgid "Unsharp mask and Gaussian blur"
+msgstr "Adoucissement et flou Gaussien"
+
+#: src/lib/filter.cc:69
+msgid "Vertical deblocking filter"
+msgstr "Filtre dé-bloc vertical"
+
+#: src/lib/filter.cc:71
+msgid "Vertical deblocking filter A"
+msgstr "Filtre dé-bloc vertical A"
+
+#: src/lib/scp_dcp_job.cc:101
+msgid "Waiting"
+msgstr "En cours"
+
+#: src/lib/scaler.cc:63
+msgid "X"
+msgstr "X"
+
+#: src/lib/filter.cc:83
+msgid "Yet Another Deinterlacing Filter"
+msgstr "Un autre filtre de désentrelacement"
+
+#: src/lib/film.cc:273
+msgid "You must add some content to the DCP before creating it"
+msgstr "Ajoutez un contenu pour créer le DCP"
+
+#: src/lib/film.cc:232
+msgid "cannot contain slashes"
+msgstr "slash interdit"
+
+#: src/lib/util.cc:494
+msgid "connect timed out"
+msgstr "temps de connexion expiré"
+
+#: src/lib/scp_dcp_job.cc:119
+msgid "connecting"
+msgstr "connexion"
+
+#: src/lib/film.cc:269
+#, fuzzy
+msgid "container"
+msgstr "contenu"
+
+#: src/lib/film.cc:277
+msgid "content type"
+msgstr "type de contenu"
+
+#: src/lib/scp_dcp_job.cc:168
+msgid "copying %1"
+msgstr "copie de %1"
+
+#: src/lib/exceptions.cc:36
+msgid "could not create file %1"
+msgstr "Écriture vers fichier distant (%1) impossible"
+
+#: src/lib/ffmpeg.cc:128
+msgid "could not find audio decoder"
+msgstr "décodeur audio introuvable"
+
+#: src/lib/ffmpeg.cc:76
+msgid "could not find stream information"
+msgstr "information du flux introuvable"
+
+#: src/lib/ffmpeg_decoder.cc:498
+msgid "could not find subtitle decoder"
+msgstr "décodeur de sous-titre introuvable"
+
+#: src/lib/ffmpeg.cc:107
+msgid "could not find video decoder"
+msgstr "décodeur vidéo introuvable"
+
+#: src/lib/sndfile_decoder.cc:45
+#, fuzzy
+msgid "could not open audio file for reading"
+msgstr "lecture du fichier impossible"
+
+#: src/lib/exceptions.cc:29
+msgid "could not open file %1"
+msgstr "lecture du fichier (%1) impossible"
+
+#: src/lib/dcp_video_frame.cc:263
+msgid "could not open file for reading"
+msgstr "lecture du fichier impossible"
+
+#: src/lib/exceptions.cc:44
+msgid "could not read from file %1 (%2)"
+msgstr "lecture du fichier impossible %1 (%2)"
+
+#: src/lib/resampler.cc:76
+#: src/lib/resampler.cc:96
+msgid "could not run sample-rate converter"
+msgstr "conversion de la fréquence d'échantillonnage impossible"
+
+#: src/lib/scp_dcp_job.cc:86
+msgid "could not start SCP session (%1)"
+msgstr "démarrage de session SCP (%1) impossible"
+
+#: src/lib/scp_dcp_job.cc:52
+msgid "could not start SSH session"
+msgstr "démarrage de session SSH impossible"
+
+#: src/lib/exceptions.cc:50
+msgid "could not write to file %1 (%2)"
+msgstr "Écriture vers fichier distant (%1) impossible (%2)"
+
+#: src/lib/transcode_job.cc:94
+msgid "frames per second"
+msgstr "images par seconde"
+
+#: src/lib/util.cc:146
+msgid "hour"
+msgstr "heure"
+
+#: src/lib/util.cc:143
+#: src/lib/util.cc:148
+msgid "hours"
+msgstr "heures"
+
+#: src/lib/util.cc:153
+msgid "minute"
+msgstr "minute"
+
+#: src/lib/util.cc:155
+msgid "minutes"
+msgstr "minutes"
+
+#: src/lib/util.cc:621
+msgid "missing key %1 in key-value set"
+msgstr "clé %1 non sélectionnée"
+
+#: src/lib/exceptions.cc:54
+msgid "missing required setting %1"
+msgstr "paramètre %1 manquant"
+
+#: src/lib/ffmpeg_decoder.cc:530
+msgid "multi-part subtitles not yet supported"
+msgstr "sous-titres en plusieurs parties non supportés"
+
+#: src/lib/film.cc:232
+#: src/lib/film.cc:281
+msgid "name"
+msgstr "nom"
+
+#: src/lib/ffmpeg_decoder.cc:545
+msgid "non-bitmap subtitles not yet supported"
+msgstr "sous-titres non-bitmap non supportés actuellement"
+
+#. / TRANSLATORS: remaining here follows an amount of time that is remaining
+#. / on an operation.
+#: src/lib/job.cc:313
+msgid "remaining"
+msgstr "restant"
+
+#: src/lib/util.cc:158
+msgid "seconds"
+msgstr "secondes"
+
+#~ msgid "1.66 within Flat"
+#~ msgstr "1.66 dans Flat"
+
+#~ msgid "16:9 within Flat"
+#~ msgstr "16:9 dans Flat"
+
+#, fuzzy
+#~ msgid "16:9 within Scope"
+#~ msgstr "16:9 dans Scope"
+
+#~ msgid "4:3 within Flat"
+#~ msgstr "4:3 dans Flat"
+
+#~ msgid "A/B transcode %1"
+#~ msgstr "Transcodage A/B %1"
+
+#~ msgid "Cannot resample audio as libswresample is not present"
+#~ msgstr "Ré-échantillonnage du son impossible : libswresample est absent"
+
+#~ msgid "Examine content of %1"
+#~ msgstr "Examen du contenu de %1"
+
+#~ msgid "Flat without stretch"
+#~ msgstr "Flat sans déformation"
+
+#~ msgid "Rec 709"
+#~ msgstr "Rec 709"
+
+#~ msgid "Scope without stretch"
+#~ msgstr "Scope sans déformation"
+
+#~ msgid "could not open external audio file for reading"
+#~ msgstr "lecture du fichier audio externe impossible"
+
+#~ msgid "external audio files have differing lengths"
+#~ msgstr "Les fichiers audio externes ont des durées différentes"
+
+#~ msgid "external audio files must be mono"
+#~ msgstr "les fichiers audio externes doivent être en mono"
+
+#~ msgid "format"
+#~ msgstr "format"
+
+#~ msgid "no still image files found"
+#~ msgstr "aucune image fixe trouvée"
+
+#~ msgid "sRGB"
+#~ msgstr "sRGB"
+
+#~ msgid "still"
+#~ msgstr "fixe"
+
+#~ msgid "video"
+#~ msgstr "vidéo"
+
+#~ msgid "1.33"
+#~ msgstr "1.33"
+
+#~ msgid "Source scaled to 1.19:1"
+#~ msgstr "Source mise à l'échelle en 1.19:1"
+
+#~ msgid "Source scaled to 1.33:1"
+#~ msgstr "Source mise à l'échelle en 1.33:1"
+
+#~ msgid "Source scaled to 1.33:1 then pillarboxed to Flat"
+#~ msgstr "Source mise à l'échelle en 1.33:1 puis contenue dans Flat"
+
+#~ msgid "Source scaled to 1.375:1"
+#~ msgstr "Source mise à l'échelle en 1.375:1"
+
+#~ msgid "Source scaled to 1.37:1 (Academy ratio)"
+#~ msgstr "Source mise à l'échelle en 1.37:1 (ratio \"academy\")"
+
+#~ msgid "Source scaled to 1.66:1"
+#~ msgstr "Source mise à l'échelle en 1.66:1"
+
+#~ msgid "Source scaled to 1.66:1 then pillarboxed to Flat"
+#~ msgstr "Source mise à l'échelle en 1.66:1 puis contenue dans Flat"
+
+#~ msgid "Source scaled to 1.78:1"
+#~ msgstr "Source mise à l'échelle en 1.78:1"
+
+#~ msgid "Source scaled to 1.78:1 then pillarboxed to Flat"
+#~ msgstr "Source mise à l'échelle en 1.78:1 puis contenue dans Flat"
+
+#~ msgid "Source scaled to Flat (1.85:1)"
+#~ msgstr "Source mise à l'échelle en Flat (1.85:1)"
+
+#~ msgid "Source scaled to Scope (2.39:1)"
+#~ msgstr "Source mise à l'échelle en Scope (2.39:1)"
+
+#~ msgid "Source scaled to fit Flat preserving its aspect ratio"
+#~ msgstr "Source réduite en Flat afin de préserver ses dimensions"
+
+#~ msgid "Source scaled to fit Scope preserving its aspect ratio"
+#~ msgstr "Source réduite en Scope afin de préserver ses dimensions"
+
+#~ msgid "adding to queue of %1"
+#~ msgstr "Mise en file d'attente de %1"
+
+#~ msgid "decoder sleeps with queue of %1"
+#~ msgstr "décodeur en veille avec %1 en file d'attente"
+
+#~ msgid "decoder wakes with queue of %1"
+#~ msgstr "reprise du décodage avec %1 en file d'attente"
diff --git a/src/lib/po/it_IT.po b/src/lib/po/it_IT.po
new file mode 100644 (file)
index 0000000..53a34e0
--- /dev/null
@@ -0,0 +1,659 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: IT VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-04-28 10:26+0100\n"
+"Last-Translator: Maci <macibro@gmail.com>\n"
+"Language-Team: \n"
+"Language: Italiano\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.5\n"
+
+#: src/lib/sndfile_content.cc:70
+msgid "%1 channels, %2kHz, %3 samples"
+msgstr ""
+
+#: src/lib/ffmpeg_content.cc:193
+#, fuzzy
+msgid "%1 frames; %2 frames per second"
+msgstr "fotogrammi al secondo"
+
+#: src/lib/video_content.cc:131
+msgid "%1x%2 pixels (%3:1)"
+msgstr ""
+
+#: src/lib/transcode_job.cc:81
+msgid "0%"
+msgstr "0%"
+
+#: src/lib/ratio.cc:47
+msgid "1.19"
+msgstr "1.19"
+
+#: src/lib/ratio.cc:50
+msgid "1.375"
+msgstr "1.375"
+
+#: src/lib/ratio.cc:51
+msgid "1.66"
+msgstr "1.66"
+
+#: src/lib/ratio.cc:52
+msgid "16:9"
+msgstr "16:9"
+
+#: src/lib/filter.cc:88
+msgid "3D denoiser"
+msgstr "Riduttore di rumore 3D"
+
+#: src/lib/ratio.cc:48
+msgid "4:3"
+msgstr "4:3"
+
+#: src/lib/ratio.cc:49
+msgid "Academy"
+msgstr "Academy"
+
+#: src/lib/dcp_content_type.cc:53
+msgid "Advertisement"
+msgstr "Pubblicità"
+
+#: src/lib/job.cc:72
+msgid "An error occurred whilst handling the file %1."
+msgstr "Errore durante l'elaborazione del file %1."
+
+#: src/lib/analyse_audio_job.cc:53
+msgid "Analyse audio of %1"
+msgstr "Analizzo l'audio di %1"
+
+#: src/lib/scaler.cc:64
+msgid "Area"
+msgstr "Area"
+
+#: src/lib/scaler.cc:62
+msgid "Bicubic"
+msgstr "Bicubica"
+
+#: src/lib/scaler.cc:69
+msgid "Bilinear"
+msgstr "Bilineare"
+
+#: src/lib/job.cc:320
+msgid "Cancelled"
+msgstr "Cancellato"
+
+#: src/lib/exceptions.cc:60
+msgid "Cannot handle pixel format %1 during %2"
+msgstr "Non posso gestire il formato di pixel %1 durante %2"
+
+#: src/lib/util.cc:700
+msgid "Centre"
+msgstr "Centro"
+
+#: src/lib/scp_dcp_job.cc:109
+msgid "Copy DCP to TMS"
+msgstr "Copia del DCP al TMS"
+
+#: src/lib/scp_dcp_job.cc:128
+msgid "Could not connect to server %1 (%2)"
+msgstr "Non posso connetermi al server %1 (%2)"
+
+#: src/lib/scp_dcp_job.cc:150
+msgid "Could not create remote directory %1 (%2)"
+msgstr "Non posso creare la directory remota %1 (%2)"
+
+#: src/lib/scp_dcp_job.cc:175
+msgid "Could not open %1 to send"
+msgstr "Non posso aprire %1 da inviare"
+
+#: src/lib/scp_dcp_job.cc:145
+msgid "Could not start SCP session (%1)"
+msgstr "Non posso avviare la sessione SCP (%1)"
+
+#: src/lib/scp_dcp_job.cc:187
+msgid "Could not write to remote file (%1)"
+msgstr "Non posso scrivere il file remoto (%1)"
+
+#: src/lib/filter.cc:77
+msgid "Cubic interpolating deinterlacer"
+msgstr "Deinterlacciatore cubico interpolato"
+
+#: src/lib/util.cc:723
+msgid "DCP and source have the same rate.\n"
+msgstr "Il DCP e il sorgente hanno la stessa frequenza.\n"
+
+#: src/lib/util.cc:733
+msgid "DCP will run at %1%% of the source speed.\n"
+msgstr "Il DCP andrà al %1%% della velocità del sorgente.\n"
+
+#: src/lib/util.cc:726
+msgid "DCP will use every other frame of the source.\n"
+msgstr "Il DCP userà ogni altro fotogramma del sorgente.\n"
+
+#: src/lib/filter.cc:68 src/lib/filter.cc:69 src/lib/filter.cc:70
+#: src/lib/filter.cc:71 src/lib/filter.cc:72 src/lib/filter.cc:73
+msgid "De-blocking"
+msgstr "Sbloccaggio"
+
+#: src/lib/filter.cc:75 src/lib/filter.cc:76 src/lib/filter.cc:77
+#: src/lib/filter.cc:78 src/lib/filter.cc:79 src/lib/filter.cc:80
+#: src/lib/filter.cc:81 src/lib/filter.cc:82 src/lib/filter.cc:83
+msgid "De-interlacing"
+msgstr "De-interlacciamento"
+
+#: src/lib/filter.cc:74
+msgid "Deringing filter"
+msgstr "Filtro deringing"
+
+#: src/lib/dolby_cp750.cc:27
+msgid "Dolby CP750"
+msgstr "Dolby CP750"
+
+#: src/lib/util.cc:728
+msgid "Each source frame will be doubled in the DCP.\n"
+msgstr "Ogni fotogramma del sorgente sarà raddoppiato nel DCP.\n"
+
+#: src/lib/job.cc:318
+msgid "Error (%1)"
+msgstr "Errore (%1)"
+
+#: src/lib/examine_content_job.cc:45
+msgid "Examine content"
+msgstr "Esamino il contenuto"
+
+#: src/lib/filter.cc:72
+msgid "Experimental horizontal deblocking filter 1"
+msgstr "Filtro di sblocco sperimentale orizzontale 1"
+
+#: src/lib/filter.cc:73
+msgid "Experimental vertical deblocking filter 1"
+msgstr "Filtro di sblocco sperimentale verticale 1"
+
+#: src/lib/filter.cc:79
+msgid "FFMPEG deinterlacer"
+msgstr "Deinterlacciatore FFMPEG"
+
+#: src/lib/filter.cc:80
+msgid "FIR low-pass deinterlacer"
+msgstr "Deinterlacciatore FIR low-pass"
+
+#: src/lib/scp_dcp_job.cc:138
+msgid "Failed to authenticate with server (%1)"
+msgstr "Autenticazione col server fallita (%1) "
+
+#: src/lib/scaler.cc:70
+msgid "Fast Bilinear"
+msgstr "Bilineare rapida"
+
+#: src/lib/dcp_content_type.cc:44
+msgid "Feature"
+msgstr "Caratteristica"
+
+#: src/lib/ratio.cc:53
+msgid "Flat"
+msgstr "Flat"
+
+#: src/lib/filter.cc:85
+msgid "Force quantizer"
+msgstr "Forza quantizzatore"
+
+#: src/lib/ratio.cc:55
+msgid "Full frame"
+msgstr ""
+
+#: src/lib/scaler.cc:65
+msgid "Gaussian"
+msgstr "Gaussiana"
+
+#: src/lib/filter.cc:86
+msgid "Gradient debander"
+msgstr "Gradiente debander"
+
+#: src/lib/filter.cc:89
+msgid "High quality 3D denoiser"
+msgstr "Riduttore di rumore 3D di alta qualità"
+
+#: src/lib/filter.cc:68
+msgid "Horizontal deblocking filter"
+msgstr "Filtro sblocco orizzontale"
+
+#: src/lib/filter.cc:70
+msgid "Horizontal deblocking filter A"
+msgstr "Filtro A sblocco orizzontale"
+
+#: src/lib/imagemagick_content.cc:50
+msgid "Image: %1"
+msgstr ""
+
+#: src/lib/job.cc:96 src/lib/job.cc:105
+msgid ""
+"It is not known what caused this error.  The best idea is to report the "
+"problem to the DCP-o-matic mailing list (carl@dcpomatic.com)"
+msgstr ""
+"Non sappiamo cosa ha causato questo errore. La cosa migliore è inviare un "
+"report del problema alla mailing list di DCP-o-matic (carl@dcpomatic.com)"
+
+#: src/lib/filter.cc:82
+msgid "Kernel deinterlacer"
+msgstr "Deinterlacciatore Kernel"
+
+#: src/lib/scaler.cc:66
+msgid "Lanczos"
+msgstr "Lanczos"
+
+#: src/lib/util.cc:698
+msgid "Left"
+msgstr "Sinistro"
+
+#: src/lib/util.cc:702
+msgid "Left surround"
+msgstr "Surround sinistro"
+
+#: src/lib/util.cc:701
+msgid "Lfe (sub)"
+msgstr "Lfe(sub)"
+
+#: src/lib/filter.cc:75
+msgid "Linear blend deinterlacer"
+msgstr "Deinterlacciatore lineare miscelato"
+
+#: src/lib/filter.cc:76
+msgid "Linear interpolating deinterlacer"
+msgstr "Deinterlacciatore lineare interpolato"
+
+#: src/lib/filter.cc:78
+msgid "Median deinterlacer"
+msgstr "Deinterlacciatore mediano"
+
+#: src/lib/filter.cc:74 src/lib/filter.cc:85 src/lib/filter.cc:86
+#: src/lib/filter.cc:87 src/lib/filter.cc:90
+msgid "Misc"
+msgstr "Varie"
+
+#: src/lib/filter.cc:81
+msgid "Motion compensating deinterlacer"
+msgstr "Dinterlacciatore compensativo di movimento"
+
+#: src/lib/ffmpeg_content.cc:181
+msgid "Movie: %1"
+msgstr ""
+
+#: src/lib/filter.cc:84 src/lib/filter.cc:88 src/lib/filter.cc:89
+#: src/lib/filter.cc:91
+msgid "Noise reduction"
+msgstr "Riduzione del rumore"
+
+#: src/lib/job.cc:316
+msgid "OK (ran for %1)"
+msgstr "OK (eseguito in %1)"
+
+#: src/lib/filter.cc:91
+msgid "Overcomplete wavelet denoiser"
+msgstr "Overcomplete wavelet denoiser"
+
+#: src/lib/dcp_content_type.cc:51
+msgid "Policy"
+msgstr "Politica"
+
+#: src/lib/dcp_content_type.cc:52
+msgid "Public Service Announcement"
+msgstr "Annuncio di pubblico servizio"
+
+#: src/lib/dcp_content_type.cc:49
+msgid "Rating"
+msgstr "Punteggio"
+
+#: src/lib/util.cc:699
+msgid "Right"
+msgstr "Destro"
+
+#: src/lib/util.cc:703
+msgid "Right surround"
+msgstr "Surround destro"
+
+#: src/lib/scp_dcp_job.cc:133
+msgid "SSH error (%1)"
+msgstr "Errore SSH (%1)"
+
+#: src/lib/ratio.cc:54
+msgid "Scope"
+msgstr "Scope"
+
+#: src/lib/dcp_content_type.cc:45
+msgid "Short"
+msgstr "Corto"
+
+#: src/lib/scaler.cc:67
+msgid "Sinc"
+msgstr "Sinc"
+
+#: src/lib/sndfile_content.cc:57
+#, fuzzy
+msgid "Sound file: %1"
+msgstr "non riesco ad aprire %1"
+
+#: src/lib/scaler.cc:68
+msgid "Spline"
+msgstr "Spline"
+
+#: src/lib/dcp_content_type.cc:50
+msgid "Teaser"
+msgstr "Teaser"
+
+#: src/lib/filter.cc:90
+msgid "Telecine filter"
+msgstr "Filtro telecinema"
+
+#: src/lib/filter.cc:84
+msgid "Temporal noise reducer"
+msgstr "Riduttore temporale di rumore"
+
+#: src/lib/dcp_content_type.cc:47
+msgid "Test"
+msgstr "Prova"
+
+#: src/lib/job.cc:78
+msgid ""
+"The drive that the film is stored on is low in disc space.  Free some more "
+"space and try again."
+msgstr ""
+"Sul disco dove è memorizzato il film non c'è abbastanza spazio. Liberare "
+"altro spazio e riprovare."
+
+#: src/lib/film.cc:364
+msgid ""
+"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!"
+msgstr ""
+
+#: src/lib/dcp_content_type.cc:46
+msgid "Trailer"
+msgstr "Prossimamente"
+
+#: src/lib/transcode_job.cc:50
+msgid "Transcode %1"
+msgstr "Transcodifica %1"
+
+#: src/lib/dcp_content_type.cc:48
+msgid "Transitional"
+msgstr "Di transizione"
+
+#: src/lib/job.cc:104
+msgid "Unknown error"
+msgstr "Errore sconosciuto"
+
+#: src/lib/ffmpeg_decoder.cc:264
+msgid "Unrecognised audio sample format (%1)"
+msgstr "Formato di campionamento audio non riconosciuto (%1)"
+
+#: src/lib/filter.cc:87
+msgid "Unsharp mask and Gaussian blur"
+msgstr "Maschera unsharp e sfocatura Gaussiana"
+
+#: src/lib/filter.cc:69
+msgid "Vertical deblocking filter"
+msgstr "Filtro di sblocco verticale"
+
+#: src/lib/filter.cc:71
+msgid "Vertical deblocking filter A"
+msgstr "Filtro A di sblocco verticale"
+
+#: src/lib/scp_dcp_job.cc:101
+msgid "Waiting"
+msgstr "Aspetta"
+
+#: src/lib/scaler.cc:63
+msgid "X"
+msgstr "X"
+
+#: src/lib/filter.cc:83
+msgid "Yet Another Deinterlacing Filter"
+msgstr "Altro filtro di deinterlacciamento"
+
+#: src/lib/film.cc:273
+msgid "You must add some content to the DCP before creating it"
+msgstr ""
+
+#: src/lib/film.cc:232
+msgid "cannot contain slashes"
+msgstr "non può contenere barre"
+
+#: src/lib/util.cc:494
+msgid "connect timed out"
+msgstr "connessione scaduta"
+
+#: src/lib/scp_dcp_job.cc:119
+msgid "connecting"
+msgstr "mi sto connettendo"
+
+#: src/lib/film.cc:269
+#, fuzzy
+msgid "container"
+msgstr "contenuto"
+
+#: src/lib/film.cc:277
+msgid "content type"
+msgstr "tipo di contenuto"
+
+#: src/lib/scp_dcp_job.cc:168
+msgid "copying %1"
+msgstr "copia %1"
+
+#: src/lib/exceptions.cc:36
+msgid "could not create file %1"
+msgstr "Non posso scrivere il file remoto (%1)"
+
+#: src/lib/ffmpeg.cc:128
+msgid "could not find audio decoder"
+msgstr "non riesco a trovare il decoder audio"
+
+#: src/lib/ffmpeg.cc:76
+msgid "could not find stream information"
+msgstr "non riesco a trovare informazioni sullo streaming"
+
+#: src/lib/ffmpeg_decoder.cc:498
+msgid "could not find subtitle decoder"
+msgstr "non riesco a trovare il decoder dei sottotitoli"
+
+#: src/lib/ffmpeg.cc:107
+msgid "could not find video decoder"
+msgstr "non riesco a trovare il decoder video"
+
+#: src/lib/sndfile_decoder.cc:45
+#, fuzzy
+msgid "could not open audio file for reading"
+msgstr "non riesco ad aprire il file per leggerlo"
+
+#: src/lib/exceptions.cc:29
+msgid "could not open file %1"
+msgstr "non riesco ad aprire %1"
+
+#: src/lib/dcp_video_frame.cc:263
+msgid "could not open file for reading"
+msgstr "non riesco ad aprire il file per leggerlo"
+
+#: src/lib/exceptions.cc:44
+msgid "could not read from file %1 (%2)"
+msgstr "non posso leggere dal file %1 (%2)"
+
+#: src/lib/resampler.cc:76 src/lib/resampler.cc:96
+msgid "could not run sample-rate converter"
+msgstr "non riesco a eseguire il convertitore della frequenza di campionamento"
+
+#: src/lib/scp_dcp_job.cc:86
+msgid "could not start SCP session (%1)"
+msgstr "non posso avviare la sessione SCP (%1)"
+
+#: src/lib/scp_dcp_job.cc:52
+msgid "could not start SSH session"
+msgstr "non posso avviare la sessione SSH"
+
+#: src/lib/exceptions.cc:50
+msgid "could not write to file %1 (%2)"
+msgstr "non posso scrivere il file (%1)"
+
+#: src/lib/transcode_job.cc:94
+msgid "frames per second"
+msgstr "fotogrammi al secondo"
+
+#: src/lib/util.cc:146
+msgid "hour"
+msgstr "ora"
+
+#: src/lib/util.cc:143 src/lib/util.cc:148
+msgid "hours"
+msgstr "ore"
+
+#: src/lib/util.cc:153
+msgid "minute"
+msgstr "minuto"
+
+#: src/lib/util.cc:155
+msgid "minutes"
+msgstr "minuti"
+
+#: src/lib/util.cc:621
+msgid "missing key %1 in key-value set"
+msgstr "persa la chiave %1 tra i valori chiave"
+
+#: src/lib/exceptions.cc:54
+msgid "missing required setting %1"
+msgstr "persa la regolazione richiesta %1"
+
+#: src/lib/ffmpeg_decoder.cc:530
+msgid "multi-part subtitles not yet supported"
+msgstr "sottotitoli multi-part non ancora supportati"
+
+#: src/lib/film.cc:232 src/lib/film.cc:281
+msgid "name"
+msgstr "nome"
+
+#: src/lib/ffmpeg_decoder.cc:545
+msgid "non-bitmap subtitles not yet supported"
+msgstr "sottotitoli non-bitmap non ancora supportati"
+
+#. / TRANSLATORS: remaining here follows an amount of time that is remaining
+#. / on an operation.
+#: src/lib/job.cc:313
+msgid "remaining"
+msgstr "restano"
+
+#: src/lib/util.cc:158
+msgid "seconds"
+msgstr "secondi"
+
+#~ msgid "1.66 within Flat"
+#~ msgstr "1.66 all'interno di Flat"
+
+#~ msgid "16:9 within Flat"
+#~ msgstr "16:9 all'interno di Flat"
+
+#~ msgid "16:9 within Scope"
+#~ msgstr "16:9 all'interno di Scope"
+
+#~ msgid "4:3 within Flat"
+#~ msgstr "4:3 all'interno di  Flat"
+
+#~ msgid "A/B transcode %1"
+#~ msgstr "Transcodifica A/B %1"
+
+#~ msgid "Cannot resample audio as libswresample is not present"
+#~ msgstr "Non posso ricampionare l'audio perchè libswresample non è presente"
+
+#~ msgid "Examine content of %1"
+#~ msgstr "Esamino il contenuto di %1"
+
+#~ msgid "Flat without stretch"
+#~ msgstr "Flat senza stiramento"
+
+#~ msgid "Rec 709"
+#~ msgstr "Rec 709"
+
+#~ msgid "Scope without stretch"
+#~ msgstr "Scope senza stiramento"
+
+#~ msgid "could not open external audio file for reading"
+#~ msgstr "non riesco ad aprire il file dell'audio esterno per leggerlo"
+
+#~ msgid "external audio files have differing lengths"
+#~ msgstr "i files dell'audio esterno hanno durata diversa"
+
+#~ msgid "external audio files must be mono"
+#~ msgstr "i files dell'audio esterno devono essere mono"
+
+#~ msgid "format"
+#~ msgstr "formato"
+
+#~ msgid "no still image files found"
+#~ msgstr "file immagini statiche non trovati"
+
+#~ msgid "sRGB"
+#~ msgstr "sRGB"
+
+#~ msgid "still"
+#~ msgstr "ancora"
+
+#~ msgid "video"
+#~ msgstr "video"
+
+#~ msgid "1.33"
+#~ msgstr "1.33"
+
+#~ msgid "Source scaled to 1.19:1"
+#~ msgstr "Sorgente scalato a 1.19:1"
+
+#~ msgid "Source scaled to 1.33:1"
+#~ msgstr "Sorgente scalato a 1.33:1"
+
+#~ msgid "Source scaled to 1.33:1 then pillarboxed to Flat"
+#~ msgstr "Sorgente scalato a 1.33:1 e poi inviato come Flat"
+
+#~ msgid "Source scaled to 1.375:1"
+#~ msgstr "Sorgente scalato a 1.375:1"
+
+#~ msgid "Source scaled to 1.37:1 (Academy ratio)"
+#~ msgstr "Sorgente scalato a 1.37:1 (Academy ratio)"
+
+#~ msgid "Source scaled to 1.66:1"
+#~ msgstr "Sorgente scalato a 1.66:1"
+
+#~ msgid "Source scaled to 1.66:1 then pillarboxed to Flat"
+#~ msgstr "Sorgente scalato a 1.66:1 e poi inviato come Flat"
+
+#~ msgid "Source scaled to 1.78:1"
+#~ msgstr "Sorgente scalato a 1.78:1"
+
+#~ msgid "Source scaled to 1.78:1 then pillarboxed to Flat"
+#~ msgstr "Sorgente scalato a 1.78:1 e poi inviato come Flat"
+
+#~ msgid "Source scaled to Flat (1.85:1)"
+#~ msgstr "Sorgente scalato a Flat (1.85:1)"
+
+#~ msgid "Source scaled to Scope (2.39:1)"
+#~ msgstr "Sorgente scalato a Scope (2.39:1)"
+
+#~ msgid "Source scaled to fit Flat preserving its aspect ratio"
+#~ msgstr ""
+#~ "Sorgente scalato per adattarsi a Flat mantentendo le sue proporzioni"
+
+#~ msgid "Source scaled to fit Scope preserving its aspect ratio"
+#~ msgstr ""
+#~ "Sorgente scalato per adattarsi a Scope mantentendo le sue proporzioni"
+
+#~ msgid "adding to queue of %1"
+#~ msgstr "aggiungo alla coda %1"
+
+#~ msgid "decoder sleeps with queue of %1"
+#~ msgstr "il decoder è in pausa con la coda di %1"
+
+#~ msgid "decoder wakes with queue of %1"
+#~ msgstr "il decoder riparte con la coda di %1"
diff --git a/src/lib/po/sv_SE.po b/src/lib/po/sv_SE.po
new file mode 100644 (file)
index 0000000..13989e5
--- /dev/null
@@ -0,0 +1,656 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: DCP-o-matic\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-04-10 15:35+0100\n"
+"Last-Translator: Adam Klotblixt <adam.klotblixt@gmail.com>\n"
+"Language-Team: \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.5\n"
+
+#: src/lib/sndfile_content.cc:70
+msgid "%1 channels, %2kHz, %3 samples"
+msgstr ""
+
+#: src/lib/ffmpeg_content.cc:193
+#, fuzzy
+msgid "%1 frames; %2 frames per second"
+msgstr "bilder per sekund"
+
+#: src/lib/video_content.cc:131
+msgid "%1x%2 pixels (%3:1)"
+msgstr ""
+
+#: src/lib/transcode_job.cc:81
+msgid "0%"
+msgstr "0%"
+
+#: src/lib/ratio.cc:47
+msgid "1.19"
+msgstr "1,19"
+
+#: src/lib/ratio.cc:50
+msgid "1.375"
+msgstr "1,375"
+
+#: src/lib/ratio.cc:51
+msgid "1.66"
+msgstr "1,66"
+
+#: src/lib/ratio.cc:52
+msgid "16:9"
+msgstr "16:9"
+
+#: src/lib/filter.cc:88
+msgid "3D denoiser"
+msgstr "3D brusreducering"
+
+#: src/lib/ratio.cc:48
+msgid "4:3"
+msgstr ""
+
+#: src/lib/ratio.cc:49
+msgid "Academy"
+msgstr "Academy"
+
+#: src/lib/dcp_content_type.cc:53
+msgid "Advertisement"
+msgstr "Reklam"
+
+#: src/lib/job.cc:72
+msgid "An error occurred whilst handling the file %1."
+msgstr "Ett fel inträffade vid hantering av filen %1"
+
+#: src/lib/analyse_audio_job.cc:53
+msgid "Analyse audio of %1"
+msgstr "Analysera %1s audio"
+
+#: src/lib/scaler.cc:64
+msgid "Area"
+msgstr "Yta"
+
+#: src/lib/scaler.cc:62
+msgid "Bicubic"
+msgstr "Bikubisk"
+
+#: src/lib/scaler.cc:69
+msgid "Bilinear"
+msgstr "Bilinjär"
+
+#: src/lib/job.cc:320
+msgid "Cancelled"
+msgstr "Avbruten"
+
+#: src/lib/exceptions.cc:60
+msgid "Cannot handle pixel format %1 during %2"
+msgstr "Kan inte hantera pixelformat %1 under %2"
+
+#: src/lib/util.cc:700
+msgid "Centre"
+msgstr "Mitt"
+
+#: src/lib/scp_dcp_job.cc:109
+msgid "Copy DCP to TMS"
+msgstr "Kopiera DCP till TMS"
+
+#: src/lib/scp_dcp_job.cc:128
+msgid "Could not connect to server %1 (%2)"
+msgstr "Kunde inte ansluta till server %1 (%2)"
+
+#: src/lib/scp_dcp_job.cc:150
+msgid "Could not create remote directory %1 (%2)"
+msgstr "Kunde inte skapa fjärrkatalog %1 (%2)"
+
+#: src/lib/scp_dcp_job.cc:175
+msgid "Could not open %1 to send"
+msgstr "Kunde inte öppna %1 för att skicka"
+
+#: src/lib/scp_dcp_job.cc:145
+msgid "Could not start SCP session (%1)"
+msgstr "Kunde inte starta SCP-session (%1)"
+
+#: src/lib/scp_dcp_job.cc:187
+msgid "Could not write to remote file (%1)"
+msgstr "Kunde inte skriva till fjärrfil (%1)"
+
+#: src/lib/filter.cc:77
+msgid "Cubic interpolating deinterlacer"
+msgstr "Kubiskt interpolerande avflätare"
+
+#: src/lib/util.cc:723
+msgid "DCP and source have the same rate.\n"
+msgstr "DCP och källa har samma bildfrekvens.\n"
+
+#: src/lib/util.cc:733
+msgid "DCP will run at %1%% of the source speed.\n"
+msgstr "DCP kommer att köras på %1%% av källans hastighet.\n"
+
+#: src/lib/util.cc:726
+msgid "DCP will use every other frame of the source.\n"
+msgstr "DCP kommer att använda varannan bild från källan.\n"
+
+#: src/lib/filter.cc:68 src/lib/filter.cc:69 src/lib/filter.cc:70
+#: src/lib/filter.cc:71 src/lib/filter.cc:72 src/lib/filter.cc:73
+msgid "De-blocking"
+msgstr "Kantighetsutjämning"
+
+#: src/lib/filter.cc:75 src/lib/filter.cc:76 src/lib/filter.cc:77
+#: src/lib/filter.cc:78 src/lib/filter.cc:79 src/lib/filter.cc:80
+#: src/lib/filter.cc:81 src/lib/filter.cc:82 src/lib/filter.cc:83
+msgid "De-interlacing"
+msgstr "Avflätning"
+
+#: src/lib/filter.cc:74
+msgid "Deringing filter"
+msgstr "Avringningsfilter"
+
+#: src/lib/dolby_cp750.cc:27
+msgid "Dolby CP750"
+msgstr "Dolby CP750"
+
+#: src/lib/util.cc:728
+msgid "Each source frame will be doubled in the DCP.\n"
+msgstr "Varje bild från källan kommer att användas två gånger i DCPn.\n"
+
+#: src/lib/job.cc:318
+msgid "Error (%1)"
+msgstr "Fel (%1)"
+
+#: src/lib/examine_content_job.cc:45
+msgid "Examine content"
+msgstr "Undersök innehållet"
+
+#: src/lib/filter.cc:72
+msgid "Experimental horizontal deblocking filter 1"
+msgstr "Experimentellt filter för horisontal kantighetsutjämning 1"
+
+#: src/lib/filter.cc:73
+msgid "Experimental vertical deblocking filter 1"
+msgstr "Experimentellt filter för vertikal kantighetsutjämning 1"
+
+#: src/lib/filter.cc:79
+msgid "FFMPEG deinterlacer"
+msgstr "FFMPEG avflätare"
+
+#: src/lib/filter.cc:80
+msgid "FIR low-pass deinterlacer"
+msgstr "FIR lågpass-avflätare"
+
+#: src/lib/scp_dcp_job.cc:138
+msgid "Failed to authenticate with server (%1)"
+msgstr "Misslyckades att autentisera med server (%1)"
+
+#: src/lib/scaler.cc:70
+msgid "Fast Bilinear"
+msgstr "Snabb bilinjär"
+
+#: src/lib/dcp_content_type.cc:44
+msgid "Feature"
+msgstr "Långfilm"
+
+#: src/lib/ratio.cc:53
+msgid "Flat"
+msgstr "Flat"
+
+#: src/lib/filter.cc:85
+msgid "Force quantizer"
+msgstr "Tvinga kvantiserare"
+
+#: src/lib/ratio.cc:55
+msgid "Full frame"
+msgstr ""
+
+#: src/lib/scaler.cc:65
+msgid "Gaussian"
+msgstr "Gaussisk"
+
+#: src/lib/filter.cc:86
+msgid "Gradient debander"
+msgstr "Gradientutjämnare"
+
+#: src/lib/filter.cc:89
+msgid "High quality 3D denoiser"
+msgstr "Högkvalitets 3D-brusreducering"
+
+#: src/lib/filter.cc:68
+msgid "Horizontal deblocking filter"
+msgstr "Filter för horisontal kantighetsutjämning"
+
+#: src/lib/filter.cc:70
+msgid "Horizontal deblocking filter A"
+msgstr "Filter för horisontal kantighetsutjämning A"
+
+#: src/lib/imagemagick_content.cc:50
+msgid "Image: %1"
+msgstr ""
+
+#: src/lib/job.cc:96 src/lib/job.cc:105
+msgid ""
+"It is not known what caused this error.  The best idea is to report the "
+"problem to the DCP-o-matic mailing list (carl@dcpomatic.com)"
+msgstr ""
+"Det är inte känt vad som orsakade detta fel. Bästa sättet att rapportera "
+"problemet är till DCP-o-matics mejl-lista (carl@dcpomatic.com)"
+
+#: src/lib/filter.cc:82
+msgid "Kernel deinterlacer"
+msgstr "Kernel-avflätare"
+
+#: src/lib/scaler.cc:66
+msgid "Lanczos"
+msgstr "Lanczos"
+
+#: src/lib/util.cc:698
+msgid "Left"
+msgstr "Vänster"
+
+#: src/lib/util.cc:702
+msgid "Left surround"
+msgstr "Vänster surround"
+
+#: src/lib/util.cc:701
+msgid "Lfe (sub)"
+msgstr "Lfe (sub)"
+
+#: src/lib/filter.cc:75
+msgid "Linear blend deinterlacer"
+msgstr "Linjär blandningsavflätare"
+
+#: src/lib/filter.cc:76
+msgid "Linear interpolating deinterlacer"
+msgstr "Linjär interpolationsavflätare"
+
+#: src/lib/filter.cc:78
+msgid "Median deinterlacer"
+msgstr "Median-avflätare"
+
+#: src/lib/filter.cc:74 src/lib/filter.cc:85 src/lib/filter.cc:86
+#: src/lib/filter.cc:87 src/lib/filter.cc:90
+msgid "Misc"
+msgstr "Diverse"
+
+#: src/lib/filter.cc:81
+msgid "Motion compensating deinterlacer"
+msgstr "Rörelsekompenserande avflätare"
+
+#: src/lib/ffmpeg_content.cc:181
+msgid "Movie: %1"
+msgstr ""
+
+#: src/lib/filter.cc:84 src/lib/filter.cc:88 src/lib/filter.cc:89
+#: src/lib/filter.cc:91
+msgid "Noise reduction"
+msgstr "Brusreducering"
+
+#: src/lib/job.cc:316
+msgid "OK (ran for %1)"
+msgstr "OK (kördes %1)"
+
+#: src/lib/filter.cc:91
+msgid "Overcomplete wavelet denoiser"
+msgstr "Överkomplett wavelet-brusreducering"
+
+#: src/lib/dcp_content_type.cc:51
+msgid "Policy"
+msgstr "Policy"
+
+#: src/lib/dcp_content_type.cc:52
+msgid "Public Service Announcement"
+msgstr "Offentligt Servicemeddelande"
+
+#: src/lib/dcp_content_type.cc:49
+msgid "Rating"
+msgstr "Klassificeringsklipp"
+
+#: src/lib/util.cc:699
+msgid "Right"
+msgstr "Höger"
+
+#: src/lib/util.cc:703
+msgid "Right surround"
+msgstr "Höger surround"
+
+#: src/lib/scp_dcp_job.cc:133
+msgid "SSH error (%1)"
+msgstr "SSH fel (%1)"
+
+#: src/lib/ratio.cc:54
+msgid "Scope"
+msgstr "Scope"
+
+#: src/lib/dcp_content_type.cc:45
+msgid "Short"
+msgstr "Kortfilm"
+
+#: src/lib/scaler.cc:67
+msgid "Sinc"
+msgstr "Sinc"
+
+#: src/lib/sndfile_content.cc:57
+#, fuzzy
+msgid "Sound file: %1"
+msgstr "kunde inte öppna fil %1"
+
+#: src/lib/scaler.cc:68
+msgid "Spline"
+msgstr "Spline"
+
+#: src/lib/dcp_content_type.cc:50
+msgid "Teaser"
+msgstr "Teaser"
+
+#: src/lib/filter.cc:90
+msgid "Telecine filter"
+msgstr "Telecine-filter"
+
+#: src/lib/filter.cc:84
+msgid "Temporal noise reducer"
+msgstr "Temporal brusreducering"
+
+#: src/lib/dcp_content_type.cc:47
+msgid "Test"
+msgstr "Test"
+
+#: src/lib/job.cc:78
+msgid ""
+"The drive that the film is stored on is low in disc space.  Free some more "
+"space and try again."
+msgstr ""
+"Enheten som filmen lagras på har för lite ledigt utrymme. Frigör utrymme och "
+"försök igen."
+
+#: src/lib/film.cc:364
+msgid ""
+"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!"
+msgstr ""
+
+#: src/lib/dcp_content_type.cc:46
+msgid "Trailer"
+msgstr "Trailer"
+
+#: src/lib/transcode_job.cc:50
+msgid "Transcode %1"
+msgstr "Konvertera %1"
+
+#: src/lib/dcp_content_type.cc:48
+msgid "Transitional"
+msgstr "Övergångsklipp"
+
+#: src/lib/job.cc:104
+msgid "Unknown error"
+msgstr "Okänt fel"
+
+# Svengelska
+#: src/lib/ffmpeg_decoder.cc:264
+#, fuzzy
+msgid "Unrecognised audio sample format (%1)"
+msgstr "Okänt audio-sampelformat (%1)"
+
+#: src/lib/filter.cc:87
+msgid "Unsharp mask and Gaussian blur"
+msgstr "Oskärpemask och Gaussisk suddighet"
+
+#: src/lib/filter.cc:69
+msgid "Vertical deblocking filter"
+msgstr "Filter för vertikal kantighetsutjämning"
+
+#: src/lib/filter.cc:71
+msgid "Vertical deblocking filter A"
+msgstr "Filter för vertikal kantighetsutjämning A"
+
+#: src/lib/scp_dcp_job.cc:101
+msgid "Waiting"
+msgstr "Väntar"
+
+#: src/lib/scaler.cc:63
+msgid "X"
+msgstr "X"
+
+# Filtret heter så, ska ej översättas
+#: src/lib/filter.cc:83
+msgid "Yet Another Deinterlacing Filter"
+msgstr "Yet Another Deinterlacing Filter"
+
+#: src/lib/film.cc:273
+msgid "You must add some content to the DCP before creating it"
+msgstr ""
+
+#: src/lib/film.cc:232
+msgid "cannot contain slashes"
+msgstr "får inte innehålla snedstreck"
+
+# Svengelska
+#: src/lib/util.cc:494
+#, fuzzy
+msgid "connect timed out"
+msgstr "uppkopplingen tajmade ur"
+
+#: src/lib/scp_dcp_job.cc:119
+msgid "connecting"
+msgstr "kopplar upp"
+
+#: src/lib/film.cc:269
+#, fuzzy
+msgid "container"
+msgstr "innehåll"
+
+#: src/lib/film.cc:277
+msgid "content type"
+msgstr "innehållstyp"
+
+#: src/lib/scp_dcp_job.cc:168
+msgid "copying %1"
+msgstr "kopierar %1"
+
+#: src/lib/exceptions.cc:36
+msgid "could not create file %1"
+msgstr "kunde inte skapa fil %1"
+
+#: src/lib/ffmpeg.cc:128
+msgid "could not find audio decoder"
+msgstr "kunde inte hitta audio-avkodare"
+
+#: src/lib/ffmpeg.cc:76
+msgid "could not find stream information"
+msgstr "kunde inte hitta information om strömmen"
+
+#: src/lib/ffmpeg_decoder.cc:498
+msgid "could not find subtitle decoder"
+msgstr "kunde inte hitta undertext-avkodare"
+
+#: src/lib/ffmpeg.cc:107
+msgid "could not find video decoder"
+msgstr "kunde inte hitta video-avkodare"
+
+#: src/lib/sndfile_decoder.cc:45
+#, fuzzy
+msgid "could not open audio file for reading"
+msgstr "kunde inte öppna fil för läsning"
+
+#: src/lib/exceptions.cc:29
+msgid "could not open file %1"
+msgstr "kunde inte öppna fil %1"
+
+#: src/lib/dcp_video_frame.cc:263
+msgid "could not open file for reading"
+msgstr "kunde inte öppna fil för läsning"
+
+#: src/lib/exceptions.cc:44
+msgid "could not read from file %1 (%2)"
+msgstr "kunde inte läsa från fil %1 (%2)"
+
+#: src/lib/resampler.cc:76 src/lib/resampler.cc:96
+msgid "could not run sample-rate converter"
+msgstr "kunde inte köra sampelhastighetskonverteraren"
+
+#: src/lib/scp_dcp_job.cc:86
+msgid "could not start SCP session (%1)"
+msgstr "kunde inte starta SCP-session (%1)"
+
+#: src/lib/scp_dcp_job.cc:52
+msgid "could not start SSH session"
+msgstr "kunde inte starta SSH-session"
+
+#: src/lib/exceptions.cc:50
+msgid "could not write to file %1 (%2)"
+msgstr "kunde inte skriva till fil %1 (%2)"
+
+#: src/lib/transcode_job.cc:94
+msgid "frames per second"
+msgstr "bilder per sekund"
+
+#: src/lib/util.cc:146
+msgid "hour"
+msgstr "timme"
+
+#: src/lib/util.cc:143 src/lib/util.cc:148
+msgid "hours"
+msgstr "timmar"
+
+#: src/lib/util.cc:153
+msgid "minute"
+msgstr "minut"
+
+#: src/lib/util.cc:155
+msgid "minutes"
+msgstr "minuter"
+
+#: src/lib/util.cc:621
+msgid "missing key %1 in key-value set"
+msgstr "saknad nyckel %1 i nyckel-värde grupp"
+
+#: src/lib/exceptions.cc:54
+msgid "missing required setting %1"
+msgstr "saknad nödvändig inställning %1"
+
+#: src/lib/ffmpeg_decoder.cc:530
+msgid "multi-part subtitles not yet supported"
+msgstr "undertexter i flera delar stöds inte ännu"
+
+#: src/lib/film.cc:232 src/lib/film.cc:281
+msgid "name"
+msgstr "namn"
+
+#: src/lib/ffmpeg_decoder.cc:545
+msgid "non-bitmap subtitles not yet supported"
+msgstr "icke-rastergrafiska undertexter stöds inte ännu"
+
+#. / TRANSLATORS: remaining here follows an amount of time that is remaining
+#. / on an operation.
+#: src/lib/job.cc:313
+msgid "remaining"
+msgstr "återstående tid"
+
+#: src/lib/util.cc:158
+msgid "seconds"
+msgstr "sekunder"
+
+#~ msgid "1.66 within Flat"
+#~ msgstr "1,66 innanför Flat"
+
+#~ msgid "16:9 within Flat"
+#~ msgstr "16:9 innanför Flat"
+
+#~ msgid "16:9 within Scope"
+#~ msgstr "16:9 innanför Scope"
+
+#~ msgid "4:3 within Flat"
+#~ msgstr "4:3 innanför Flat"
+
+#~ msgid "A/B transcode %1"
+#~ msgstr "A/B konvertera %1"
+
+#~ msgid "Cannot resample audio as libswresample is not present"
+#~ msgstr ""
+#~ "Kan inte omsampla ljudet eftersom libswresample inte finns tillgängligt"
+
+#~ msgid "Examine content of %1"
+#~ msgstr "Undersök innehållet i %1"
+
+#~ msgid "Flat without stretch"
+#~ msgstr "Flat utan utsträckning"
+
+#~ msgid "Rec 709"
+#~ msgstr "Rec 709"
+
+#~ msgid "Scope without stretch"
+#~ msgstr "Scope utan utsträckning"
+
+#~ msgid "could not open external audio file for reading"
+#~ msgstr "kunde inte öppna extern audio-fil för läsning"
+
+#~ msgid "external audio files have differing lengths"
+#~ msgstr "externa audio-filer har olika längder"
+
+#~ msgid "external audio files must be mono"
+#~ msgstr "externa audio-filer måste vara mono"
+
+#~ msgid "format"
+#~ msgstr "format"
+
+#~ msgid "no still image files found"
+#~ msgstr "inga stillbildsfiler hittade"
+
+#~ msgid "sRGB"
+#~ msgstr "sRGB"
+
+#~ msgid "still"
+#~ msgstr "stillbild"
+
+#~ msgid "video"
+#~ msgstr "video"
+
+#~ msgid "1.33"
+#~ msgstr "1,33"
+
+#~ msgid "Source scaled to 1.19:1"
+#~ msgstr "Källan skalad till 1,19:1"
+
+#~ msgid "Source scaled to 1.33:1"
+#~ msgstr "Källan skalad till 1,33:1"
+
+#~ msgid "Source scaled to 1.33:1 then pillarboxed to Flat"
+#~ msgstr "Källan skalad till 1,33:1, med sorgkanter innanför Flat"
+
+#~ msgid "Source scaled to 1.375:1"
+#~ msgstr "Källan skalad till 1,375:1"
+
+#~ msgid "Source scaled to 1.37:1 (Academy ratio)"
+#~ msgstr "Källan skalad till 1,37:1 (Academy-förhållande)"
+
+#~ msgid "Source scaled to 1.66:1"
+#~ msgstr "Källan skalad till 1,66:1"
+
+#~ msgid "Source scaled to 1.66:1 then pillarboxed to Flat"
+#~ msgstr "Källan skalad till 1,66:1, med sorgkanter innanför Flat"
+
+#~ msgid "Source scaled to 1.78:1"
+#~ msgstr "Källan skalad till 1,78:1"
+
+#~ msgid "Source scaled to 1.78:1 then pillarboxed to Flat"
+#~ msgstr "Källan skalad till 1,78:1, med sorgkanter innanför Flat"
+
+#~ msgid "Source scaled to Flat (1.85:1)"
+#~ msgstr "Källan skalad till Flat (1,85:1)"
+
+#~ msgid "Source scaled to Scope (2.39:1)"
+#~ msgstr "Källan skalad till Scope (2,39:1)"
+
+#~ msgid "Source scaled to fit Flat preserving its aspect ratio"
+#~ msgstr ""
+#~ "Källan skalad för att rymmas inom Flat utan att ändra bildförhållandet"
+
+#~ msgid "Source scaled to fit Scope preserving its aspect ratio"
+#~ msgstr ""
+#~ "Källan skalad för att rymmas inom Scope utan att ändra bildförhållandet"
diff --git a/src/lib/position.h b/src/lib/position.h
new file mode 100644 (file)
index 0000000..8768bf5
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+    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_POSITION_H
+#define DCPOMATIC_POSITION_H
+
+/** @struct Position
+ *  @brief A position.
+ */
+template <class T>
+class Position
+{
+public:
+       Position ()
+               : x (0)
+               , y (0)
+       {}
+
+       Position (T x_, T y_)
+               : x (x_)
+               , y (y_)
+       {}
+
+       /** x coordinate */
+       T x;
+       /** y coordinate */
+       T y;
+};
+
+#endif
diff --git a/src/lib/processor.h b/src/lib/processor.h
deleted file mode 100644 (file)
index 19d7c4b..0000000
+++ /dev/null
@@ -1,98 +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 (Log* log)
-               : _log (log)
-       {}
-
-       virtual ~Processor() {}
-
-       /** Will be called at the end of a processing run */
-       virtual void process_end () {}
-
-protected:
-       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 (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 (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 (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..c331eda
--- /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.
+
+*/
+
+#ifndef DCPOMATIC_RATIO_H
+#define DCPOMATIC_RATIO_H
+
+#include <vector>
+#include <boost/utility.hpp>
+#include <libdcp/util.h>
+
+class Ratio : public boost::noncopyable
+{
+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/rect.h b/src/lib/rect.h
new file mode 100644 (file)
index 0000000..6f4709c
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+    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_RECT_H
+#define DCPOMATIC_RECT_H
+
+#include "position.h"
+
+/* Put this inside a namespace as Apple put a Rect in the global namespace */
+
+namespace dcpomatic
+{
+       
+/** @struct Rect
+ *  @brief A rectangle.
+ */
+template <class T>     
+class Rect
+{
+public:
+       
+       Rect ()
+               : x (0)
+               , y (0)
+               , width (0)
+               , height (0)
+       {}
+
+       Rect (T x_, T y_, T w_, T h_)
+               : x (x_)
+               , y (y_)
+               , width (w_)
+               , height (h_)
+       {}
+
+       T x;
+       T y;
+       T width;
+       T height;
+
+       Position<T> position () const {
+               return Position<T> (x, y);
+       }
+
+       Rect<T> intersection (Rect<T> const & other) const {
+               T const tx = max (x, other.x);
+               T 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 contains (Position<T> p) const {
+               return (p.x >= x && p.x <= (x + width) && p.y >= y && p.y <= (y + height));
+       }
+};
+
+}
+
+#endif
diff --git a/src/lib/resampler.cc b/src/lib/resampler.cc
new file mode 100644 (file)
index 0000000..7bc933f
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+    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/channel_layout.h"
+}      
+#include "resampler.h"
+#include "audio_buffers.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::cout;
+using std::pair;
+using std::make_pair;
+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);
+}
+
+pair<shared_ptr<const AudioBuffers>, AudioContent::Frame>
+Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
+{
+       AudioContent::Frame const resamp_time = swr_next_pts (_swr_context, frame * _out_rate) / _in_rate;
+               
+       /* 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 make_pair (resampled, resamp_time);
+}      
+
+shared_ptr<const AudioBuffers>
+Resampler::flush ()
+{
+       shared_ptr<AudioBuffers> out (new AudioBuffers (_channels, 0));
+       int out_offset = 0;
+       int64_t const pass_size = 256;
+       shared_ptr<AudioBuffers> pass (new AudioBuffers (_channels, 256));
+
+       while (1) {
+               int const frames = swr_convert (_swr_context, (uint8_t **) pass->data(), pass_size, 0, 0);
+               
+               if (frames < 0) {
+                       throw EncodeError (_("could not run sample-rate converter"));
+               }
+               
+               if (frames == 0) {
+                       break;
+               }
+
+               out->ensure_size (out_offset + frames);
+               out->copy_from (pass.get(), frames, 0, out_offset);
+               out_offset += frames;
+               out->set_frames (out_offset);
+       }
+
+       return out;
+}
diff --git a/src/lib/resampler.h b/src/lib/resampler.h
new file mode 100644 (file)
index 0000000..69ec83b
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+    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/utility.hpp>
+extern "C" {
+#include <libswresample/swresample.h>
+}
+#include "types.h"
+#include "audio_content.h"
+
+class AudioBuffers;
+
+class Resampler : public boost::noncopyable
+{
+public:
+       Resampler (int, int, int);
+       ~Resampler ();
+
+       std::pair<boost::shared_ptr<const AudioBuffers>, AudioContent::Frame> run (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+       boost::shared_ptr<const AudioBuffers> flush ();
+
+private:       
+       SwrContext* _swr_context;
+       int _in_rate;
+       int _out_rate;
+       int _channels;
+};
index c81456a15034f0d382adab0d3eb311636e949e97..40a0f05b93e68ca5794b4e59a937f8e98dffcc13 100644 (file)
@@ -28,6 +28,8 @@ extern "C" {
 }
 #include "scaler.h"
 
+#include "i18n.h"
+
 using namespace std;
 
 vector<Scaler const *> Scaler::_scalers;
@@ -57,15 +59,15 @@ Scaler::all ()
 void
 Scaler::setup_scalers ()
 {
-       _scalers.push_back (new Scaler (SWS_BICUBIC, "bicubic", "Bicubic"));
-       _scalers.push_back (new Scaler (SWS_X, "x", "X"));
-       _scalers.push_back (new Scaler (SWS_AREA, "area", "Area"));
-       _scalers.push_back (new Scaler (SWS_GAUSS, "gauss", "Gaussian"));
-       _scalers.push_back (new Scaler (SWS_LANCZOS, "lanczos", "Lanczos"));
-       _scalers.push_back (new Scaler (SWS_SINC, "sinc", "Sinc"));
-       _scalers.push_back (new Scaler (SWS_SPLINE, "spline", "Spline"));
-       _scalers.push_back (new Scaler (SWS_BILINEAR, "bilinear", "Bilinear"));
-       _scalers.push_back (new Scaler (SWS_FAST_BILINEAR, "fastbilinear", "Fast Bilinear"));
+       _scalers.push_back (new Scaler (SWS_BICUBIC, N_("bicubic"), _("Bicubic")));
+       _scalers.push_back (new Scaler (SWS_X, N_("x"), _("X")));
+       _scalers.push_back (new Scaler (SWS_AREA, N_("area"), _("Area")));
+       _scalers.push_back (new Scaler (SWS_GAUSS, N_("gauss"), _("Gaussian")));
+       _scalers.push_back (new Scaler (SWS_LANCZOS, N_("lanczos"), _("Lanczos")));
+       _scalers.push_back (new Scaler (SWS_SINC, N_("sinc"), _("Sinc")));
+       _scalers.push_back (new Scaler (SWS_SPLINE, N_("spline"), _("Spline")));
+       _scalers.push_back (new Scaler (SWS_BILINEAR, N_("bilinear"), _("Bilinear")));
+       _scalers.push_back (new Scaler (SWS_FAST_BILINEAR, N_("fastbilinear"), _("Fast Bilinear")));
 }
 
 /** @param id One of our ids.
index c80f4b7dbf76a21679ca8033b8283f838d938ca6..6a039edd8d037878503ea79fdf971c5e2d1eb782 100644 (file)
  *  @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>
+#include <boost/utility.hpp>
 
 /** @class Scaler
  *  @brief Class to describe one of FFmpeg's software scalers
  */
-class Scaler
+class Scaler : public boost::noncopyable
 {
 public:
        Scaler (int f, std::string i, std::string n);
index 22129f56cdf05e2edadda7baedce3378e012160f..528d393a36d73e559e0ca8b9d45b1e973ea87a3a 100644 (file)
@@ -34,6 +34,8 @@
 #include "log.h"
 #include "film.h"
 
+#include "i18n.h"
+
 using std::string;
 using std::stringstream;
 using std::min;
@@ -47,7 +49,7 @@ public:
        {
                session = ssh_new ();
                if (session == 0) {
-                       throw NetworkError ("Could not start SSH session");
+                       throw NetworkError (_("could not start SSH session"));
                }
        }
 
@@ -81,7 +83,7 @@ public:
        {
                scp = ssh_scp_new (s, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, Config::instance()->tms_path().c_str ());
                if (!scp) {
-                       throw NetworkError (String::compose ("Could not start SCP session (%1)", ssh_get_error (s)));
+                       throw NetworkError (String::compose (_("could not start SCP session (%1)"), ssh_get_error (s)));
                }
        }
 
@@ -94,9 +96,9 @@ public:
 };
 
 
-SCPDCPJob::SCPDCPJob (shared_ptr<Film> f, shared_ptr<Job> req)
-       : Job (f, req)
-       , _status ("Waiting")
+SCPDCPJob::SCPDCPJob (shared_ptr<const Film> f)
+       : Job (f)
+       , _status (_("Waiting"))
 {
 
 }
@@ -104,17 +106,17 @@ SCPDCPJob::SCPDCPJob (shared_ptr<Film> f, shared_ptr<Job> req)
 string
 SCPDCPJob::name () const
 {
-       return "Copy DCP to TMS";
+       return _("Copy DCP to TMS");
 }
 
 void
 SCPDCPJob::run ()
 {
-       _film->log()->log ("SCP DCP job starting");
+       _film->log()->log (N_("SCP DCP job starting"));
        
        SSHSession ss;
        
-       set_status ("connecting");
+       set_status (_("connecting"));
        
        ssh_options_set (ss.session, SSH_OPTIONS_HOST, Config::instance()->tms_ip().c_str ());
        ssh_options_set (ss.session, SSH_OPTIONS_USER, Config::instance()->tms_user().c_str ());
@@ -123,29 +125,29 @@ SCPDCPJob::run ()
        
        int r = ss.connect ();
        if (r != SSH_OK) {
-               throw NetworkError (String::compose ("Could not connect to server %1 (%2)", Config::instance()->tms_ip(), ssh_get_error (ss.session)));
+               throw NetworkError (String::compose (_("Could not connect to server %1 (%2)"), Config::instance()->tms_ip(), ssh_get_error (ss.session)));
        }
        
        int const state = ssh_is_server_known (ss.session);
        if (state == SSH_SERVER_ERROR) {
-               throw NetworkError (String::compose ("SSH error (%1)", ssh_get_error (ss.session)));
+               throw NetworkError (String::compose (_("SSH error (%1)"), ssh_get_error (ss.session)));
        }
        
        r = ssh_userauth_password (ss.session, 0, Config::instance()->tms_password().c_str ());
        if (r != SSH_AUTH_SUCCESS) {
-               throw NetworkError (String::compose ("Failed to authenticate with server (%1)", ssh_get_error (ss.session)));
+               throw NetworkError (String::compose (_("Failed to authenticate with server (%1)"), ssh_get_error (ss.session)));
        }
        
        SSHSCP sc (ss.session);
        
        r = ssh_scp_init (sc.scp);
        if (r != SSH_OK) {
-               throw NetworkError (String::compose ("Could not start SCP session (%1)", ssh_get_error (ss.session)));
+               throw NetworkError (String::compose (_("Could not start SCP session (%1)"), ssh_get_error (ss.session)));
        }
        
        r = ssh_scp_push_directory (sc.scp, _film->dcp_name().c_str(), S_IRWXU);
        if (r != SSH_OK) {
-               throw NetworkError (String::compose ("Could not create remote directory %1 (%2)", _film->dcp_name(), ssh_get_error (ss.session)));
+               throw NetworkError (String::compose (_("Could not create remote directory %1 (%2)"), _film->dcp_name(), ssh_get_error (ss.session)));
        }
        
        string const dcp_dir = _film->dir (_film->dcp_name());
@@ -161,33 +163,30 @@ SCPDCPJob::run ()
        
        for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (dcp_dir); i != boost::filesystem::directory_iterator(); ++i) {
                
-               /* Aah, the sweet smell of progress */
-#if BOOST_FILESYSTEM_VERSION == 3              
                string const leaf = boost::filesystem::path(*i).leaf().generic_string ();
-#else
-               string const leaf = i->leaf ();
-#endif
                
-               set_status ("copying " + leaf);
+               set_status (String::compose (_("copying %1"), leaf));
                
                boost::uintmax_t to_do = boost::filesystem::file_size (*i);
                ssh_scp_push_file (sc.scp, leaf.c_str(), to_do, S_IRUSR | S_IWUSR);
 
-               FILE* f = fopen (boost::filesystem::path (*i).string().c_str(), "rb");
+               FILE* f = fopen (boost::filesystem::path (*i).string().c_str(), N_("rb"));
                if (f == 0) {
-                       throw NetworkError (String::compose ("Could not open %1 to send", *i));
+                       throw NetworkError (String::compose (_("Could not open %1 to send"), *i));
                }
 
                while (to_do > 0) {
                        int const t = min (to_do, buffer_size);
                        size_t const read = fread (buffer, 1, t, f);
                        if (read != size_t (t)) {
+                               fclose (f);
                                throw ReadFileError (boost::filesystem::path (*i).string());
                        }
                        
                        r = ssh_scp_write (sc.scp, buffer, t);
                        if (r != SSH_OK) {
-                               throw NetworkError (String::compose ("Could not write to remote file (%1)", ssh_get_error (ss.session)));
+                               fclose (f);
+                               throw NetworkError (String::compose (_("Could not write to remote file (%1)"), ssh_get_error (ss.session)));
                        }
                        to_do -= t;
                        bytes_transferred += t;
@@ -199,7 +198,7 @@ SCPDCPJob::run ()
        }
        
        set_progress (1);
-       set_status ("");
+       set_status (N_(""));
        set_state (FINISHED_OK);
 }
 
@@ -210,7 +209,7 @@ SCPDCPJob::status () const
        stringstream s;
        s << Job::status ();
        if (!_status.empty ()) {
-               s << "; " << _status;
+               s << N_("; ") << _status;
        }
        return s.str ();
 }
index 5d0bfe7b4187939a633f88e6cecdbc450171b6ed..bdc83af187f85a0091d31e107d554ee9dd07ef4c 100644 (file)
@@ -26,7 +26,7 @@
 class SCPDCPJob : public Job
 {
 public:
-       SCPDCPJob (boost::shared_ptr<Film>, boost::shared_ptr<Job> req);
+       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 bea75cff8a41e80294b73cbf7d0c6783236be832..0212dbbedd8547fe1b3f6da04e7b14fd9764edf3 100644 (file)
 #include <iostream>
 #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"
 #include "image.h"
 #include "dcp_video_frame.h"
 #include "config.h"
-#include "subtitle.h"
+
+#include "i18n.h"
 
 using std::string;
 using std::stringstream;
@@ -45,34 +48,41 @@ using boost::algorithm::is_any_of;
 using boost::algorithm::split;
 using boost::thread;
 using boost::bind;
+using boost::scoped_array;
+using boost::optional;
+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.
  */
-ServerDescription *
+optional<ServerDescription>
 ServerDescription::create_from_metadata (string v)
 {
        vector<string> b;
-       split (b, v, is_any_of (" "));
+       split (b, v, is_any_of (N_(" ")));
 
        if (b.size() != 2) {
-               return 0;
+               return optional<ServerDescription> ();
        }
 
-       return new ServerDescription (b[0], atoi (b[1].c_str ()));
+       return 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 << " " << _threads;
-       return s.str ();
-}
-
-Server::Server (Log* log)
+Server::Server (shared_ptr<Log> log)
        : _log (log)
 {
 
@@ -81,53 +91,26 @@ Server::Server (Log* log)
 int
 Server::process (shared_ptr<Socket> socket)
 {
-       char buffer[512];
-       socket->read_indefinite ((uint8_t *) buffer, sizeof (buffer), 30);
-       socket->consume (strlen (buffer) + 1);
+       uint32_t length = socket->read_uint32 ();
+       scoped_array<char> buffer (new char[length]);
+       socket->read (reinterpret_cast<uint8_t*> (buffer.get()), length);
        
-       stringstream s (buffer);
-       multimap<string, string> kv = read_key_value (s);
-
-       if (get_required_string (kv, "encode") != "please") {
+       stringstream s (buffer.get());
+       shared_ptr<cxml::Document> xml (new cxml::Document ("EncodingRequest"));
+       xml->read_stream (s);
+       if (xml->number_child<int> ("Version") != SERVER_LINK_VERSION) {
+               _log->log ("Mismatched server/client versions");
                return -1;
        }
 
-       Size in_size (get_required_int (kv, "input_width"), get_required_int (kv, "input_height"));
-       int pixel_format_int = get_required_int (kv, "input_pixel_format");
-       Size out_size (get_required_int (kv, "output_width"), get_required_int (kv, "output_height"));
-       int padding = get_required_int (kv, "padding");
-       int subtitle_offset = get_required_int (kv, "subtitle_offset");
-       float subtitle_scale = get_required_float (kv, "subtitle_scale");
-       string scaler_id = get_required_string (kv, "scaler");
-       int frame = get_required_int (kv, "frame");
-       int frames_per_second = get_required_int (kv, "frames_per_second");
-       string post_process = get_optional_string (kv, "post_process");
-       int colour_lut_index = get_required_int (kv, "colour_lut");
-       int j2k_bandwidth = get_required_int (kv, "j2k_bandwidth");
-       Position subtitle_position (get_optional_int (kv, "subtitle_x"), get_optional_int (kv, "subtitle_y"));
-       Size subtitle_size (get_optional_int (kv, "subtitle_width"), get_optional_int (kv, "subtitle_height"));
-
-       /* 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));
-
-       image->read_from_socket (socket);
+       libdcp::Size size (
+               xml->number_child<int> ("Width"), xml->number_child<int> ("Height")
+               );
 
-       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));
-       }
+       shared_ptr<Image> image (new Image (PIX_FMT_RGB24, size, true));
 
-       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->read_from_socket (socket);
+       DCPVideoFrame dcp_video_frame (image, xml, _log);
        
        shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally ();
        try {
@@ -135,13 +118,13 @@ Server::process (shared_ptr<Socket> socket)
        } catch (std::exception& e) {
                _log->log (String::compose (
                                   "Send failed; frame %1, data size %2, pixel format %3, image size %4x%5, %6 components",
-                                  frame, encoded->size(), image->pixel_format(), image->size().width, image->size().height, image->components()
+                                  dcp_video_frame.frame(), encoded->size(), image->pixel_format(), image->size().width, image->size().height, image->components()
                                   )
                        );
                throw;
        }
 
-       return frame;
+       return dcp_video_frame.frame ();
 }
 
 void
@@ -166,7 +149,7 @@ Server::worker_thread ()
                try {
                        frame = process (socket);
                } catch (std::exception& e) {
-                       _log->log (String::compose ("Error: %1", e.what()));
+                       _log->log (String::compose (N_("Error: %1"), e.what()));
                }
                
                socket.reset ();
@@ -176,7 +159,7 @@ Server::worker_thread ()
                if (frame >= 0) {
                        struct timeval end;
                        gettimeofday (&end, 0);
-                       _log->log (String::compose ("Encoded frame %1 in %2", frame, seconds (end) - seconds (start)));
+                       _log->log (String::compose (N_("Encoded frame %1 in %2"), frame, seconds (end) - seconds (start)));
                }
                
                _worker_condition.notify_all ();
@@ -186,7 +169,7 @@ Server::worker_thread ()
 void
 Server::run (int num_threads)
 {
-       _log->log (String::compose ("Server starting with %1 threads", num_threads));
+       _log->log (String::compose (N_("Server starting with %1 threads"), num_threads));
        
        for (int i = 0; i < num_threads; ++i) {
                _worker_threads.push_back (new thread (bind (&Server::worker_thread, this)));
index 32ba8dc4b96757fe0d3938317e2fe1c0c9abff2b..77b51d0798ff11da731783b63f9c624301540be1 100644 (file)
@@ -17,6 +17,9 @@
 
 */
 
+#ifndef DCPOMATIC_SERVER_H
+#define DCPOMATIC_SERVER_H
+
 /** @file src/server.h
  *  @brief Class to describe a server to which we can send
  *  encoding work, and a class to implement such a server.
 #include <boost/thread.hpp>
 #include <boost/asio.hpp>
 #include <boost/thread/condition.hpp>
+#include <boost/optional.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.
  */
 class ServerDescription
 {
 public:
+       ServerDescription ()
+               : _host_name ("")
+               , _threads (1)
+       {}
+       
        /** @param h Server host name or IP address in string form.
         *  @param t Number of threads to use on the server.
         */
@@ -44,6 +58,10 @@ public:
                , _threads (t)
        {}
 
+       ServerDescription (boost::shared_ptr<const cxml::Node>);
+
+       /* Default copy constructor is fine */
+       
        /** @return server's host name or IP address in string form */
        std::string host_name () const {
                return _host_name;
@@ -62,9 +80,9 @@ public:
                _threads = t;
        }
 
-       std::string as_metadata () const;
+       void as_xml (xmlpp::Node *) const;
        
-       static ServerDescription * create_from_metadata (std::string v);
+       static boost::optional<ServerDescription> create_from_metadata (std::string);
 
 private:
        /** server's host name */
@@ -73,10 +91,10 @@ private:
        int _threads;
 };
 
-class Server
+class Server : public boost::noncopyable
 {
 public:
-       Server (Log* log);
+       Server (boost::shared_ptr<Log> log);
 
        void run (int num_threads);
 
@@ -88,5 +106,7 @@ private:
        std::list<boost::shared_ptr<Socket> > _queue;
        boost::mutex _worker_mutex;
        boost::condition _worker_condition;
-       Log* _log;
+       boost::shared_ptr<Log> _log;
 };
+
+#endif
diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc
new file mode 100644 (file)
index 0000000..d57cf04
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+    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 "film.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_mapping (node->node_child ("AudioMapping"))
+{
+       _audio_channels = node->number_child<int> ("AudioChannels");
+       _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
+       _audio_frame_rate = node->number_child<int> ("AudioFrameRate");
+}
+
+string
+SndfileContent::summary () const
+{
+       /* Get the string() here so that the name does not have quotes around it */
+       return String::compose (_("%1 [audio]"), path().filename().string());
+}
+
+string
+SndfileContent::technical_summary () const
+{
+       return Content::technical_summary() + " - "
+               + AudioContent::technical_summary ()
+               + " - sndfile";
+}
+
+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");
+}
+
+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);
+
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               /* XXX: do this in signal_changed...? */
+               _audio_mapping = AudioMapping (_audio_channels);
+               _audio_mapping.make_default ();
+       }
+       
+       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> (content_audio_frame_rate ()));
+       _audio_mapping.as_xml (node->add_child("AudioMapping"));
+}
+
+Time
+SndfileContent::full_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->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..191d625
--- /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_SNDFILE_CONTENT_H
+#define DCPOMATIC_SNDFILE_CONTENT_H
+
+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 technical_summary () const;
+       std::string information () const;
+       void as_xml (xmlpp::Node *) const;
+       Time full_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;
+};
+
+#endif
diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc
new file mode 100644 (file)
index 0000000..1fc1eca
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+    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 <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::min;
+using std::cout;
+using boost::shared_ptr;
+
+SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c)
+       : Decoder (f)
+       , AudioDecoder (f)
+       , _sndfile_content (c)
+       , _deinterleave_buffer (0)
+{
+       _info.format = 0;
+       _sndfile = sf_open (_sndfile_content->path().string().c_str(), SFM_READ, &_info);
+       if (!_sndfile) {
+               throw DecodeError (_("could not open audio file for reading"));
+       }
+
+       _done = 0;
+       _remaining = _info.frames;
+}
+
+SndfileDecoder::~SndfileDecoder ()
+{
+       sf_close (_sndfile);
+       delete[] _deinterleave_buffer;
+}
+
+void
+SndfileDecoder::pass ()
+{
+       /* 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 = _sndfile_content->content_audio_frame_rate() / 2;
+       sf_count_t const this_time = min (block, _remaining);
+
+       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, _done);
+       _done += this_time;
+       _remaining -= this_time;
+}
+
+int
+SndfileDecoder::audio_channels () const
+{
+       return _info.channels;
+}
+
+AudioContent::Frame
+SndfileDecoder::audio_length () const
+{
+       return _info.frames;
+}
+
+int
+SndfileDecoder::audio_frame_rate () const
+{
+       return _info.samplerate;
+}
+
+bool
+SndfileDecoder::done () const
+{
+       return _audio_position >= _sndfile_content->audio_length ();
+}
diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h
new file mode 100644 (file)
index 0000000..77fa6d1
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+    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 <sndfile.h>
+#include "decoder.h"
+#include "audio_decoder.h"
+
+class SndfileContent;
+
+class SndfileDecoder : public AudioDecoder
+{
+public:
+       SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
+       ~SndfileDecoder ();
+
+       void pass ();
+       bool done () const;
+
+       int audio_channels () const;
+       AudioContent::Frame audio_length () const;
+       int audio_frame_rate () const;
+
+private:
+       boost::shared_ptr<const SndfileContent> _sndfile_content;
+       SNDFILE* _sndfile;
+       SF_INFO _info;
+       AudioContent::Frame _done;
+       AudioContent::Frame _remaining;
+       float* _deinterleave_buffer;
+};
index 2edf388409755294153daf07d74e1002cebbc62c..8f265224372ceeae9eb8e9f0d2884e4e434bd036 100644 (file)
  *  @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>
+#include <boost/utility.hpp>
 
 /** @class SoundProcessor
  *  @brief Class to describe a sound processor.
  */
-class SoundProcessor
+class SoundProcessor : public boost::noncopyable
 {
 public:
        SoundProcessor (std::string i, std::string n);
diff --git a/src/lib/stack.cpp b/src/lib/stack.cpp
new file mode 100644 (file)
index 0000000..a8183d3
--- /dev/null
@@ -0,0 +1,461 @@
+// Copyright 2007 Edd Dawson.
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+#include <cassert>
+#include <cstring>
+#include <cstdlib>
+#include <iomanip>
+#include <ostream>
+#include <stdexcept>
+#include <sstream>
+
+#include "stack.hpp"
+
+#if defined(_WIN32)
+#   include <windows.h>
+#   include <imagehlp.h>
+
+#   if defined(__MINGW32__)
+#       define PACKAGE 1
+#       define PACKAGE_VERSION 1
+#       include <bfd.h> // link against libbfd and libiberty
+#       include <psapi.h> // link against psapi
+#       include <cxxabi.h>
+#   endif
+
+#elif defined(__GNUC__)
+#   include <dlfcn.h>
+#   include <cxxabi.h>
+#endif
+
+namespace
+{
+    const char * const unknown_function = "[unknown function]";
+    const char * const unknown_module = "[unknown module]";
+
+#if defined(__GNUC__)
+    std::string demangle(const char *name)
+    {
+        if (!name)
+            return unknown_function;
+
+        int status = 0;
+        char *d = 0;
+        std::string ret = name;
+        try
+        {
+            if ((d = abi::__cxa_demangle(name, 0, 0, &status)))
+                ret = d;
+        }
+        catch (const std::bad_alloc &) {  }
+
+        std::free(d);
+        return ret;
+    }
+#endif
+
+#if defined(_WIN32)
+
+    // Derive from this to disallow copying of your class.
+    // c.f. boost::noncopyable
+    class uncopyable
+    {
+        protected:
+            uncopyable() { }
+
+        private:
+            uncopyable(const uncopyable &);
+            uncopyable &operator= (const uncopyable &);
+    };
+
+#if defined(__MINGW32__)
+
+    // Provides a means to translate a program counter offset in to the name of the corresponding function.
+    class bfd_context : uncopyable
+    {
+        private:
+            struct find_data
+            {
+                std::string func;
+                unsigned int line;
+                asymbol **symbol_table;
+                bfd_vma counter;
+            };
+
+        public:
+            bfd_context() :
+                abfd_(0),
+                sec_(0),
+                symbol_table_(0)
+            {
+                char procname[MAX_PATH];
+                GetModuleFileNameA(NULL, procname, sizeof procname);
+
+                bfd_init();
+                abfd_ = bfd_openr(procname, 0);
+                if (!abfd_)
+                    throw std::runtime_error("Failed to parse object data for the executable");
+
+                char **formats = 0;
+                bool b1 = bfd_check_format(abfd_, bfd_object);
+                bool b2 = bfd_check_format_matches(abfd_, bfd_object, &formats);
+                bool b3 = bfd_get_file_flags(abfd_) & HAS_SYMS;
+
+                if (!(b1 && b2 && b3))
+                {
+                    bfd_close(abfd_);
+                    free(formats);
+                    throw std::runtime_error("Failed to parse object data for the executable");
+                }
+                free(formats);
+
+                // Load symbol table
+                unsigned dummy = 0;
+                if (bfd_read_minisymbols(abfd_, FALSE, reinterpret_cast<void **>(&symbol_table_), &dummy) == 0 &&
+                    bfd_read_minisymbols(abfd_, TRUE, reinterpret_cast<void **>(&symbol_table_), &dummy) < 0)
+                {
+                    free(symbol_table_);
+                    bfd_close(abfd_);
+                    throw std::runtime_error("Failed to parse object data for the executable");
+                }
+            }
+
+            ~bfd_context()
+            {
+                free(symbol_table_);
+                bfd_close(abfd_);
+            }
+
+            std::pair<std::string, unsigned int> get_function_name_and_line(DWORD offset)
+            {
+                find_data data;
+                data.symbol_table = symbol_table_;
+                data.counter = offset;
+
+                bfd_map_over_sections(abfd_, &find_function_name_in_section, &data);
+
+                return std::make_pair(data.func, data.line);
+            }
+
+        private:
+            static void find_function_name_in_section(bfd *abfd, asection *sec, void *opaque_data)
+            {
+                assert(sec);
+                assert(opaque_data);
+                find_data &data = *static_cast<find_data *>(opaque_data);
+
+                if (!data.func.empty()) return; // already found it
+
+                if (!(bfd_get_section_flags(abfd, sec) & SEC_ALLOC)) return;
+
+                bfd_vma vma = bfd_get_section_vma(abfd, sec);
+                if (data.counter < vma || vma + bfd_get_section_size(sec) <= data.counter) return;
+
+                const char *func = 0;
+                const char *file = 0;
+                unsigned line = 0;
+
+                if (bfd_find_nearest_line(abfd, sec, data.symbol_table, data.counter - vma, &file, &func, &line) && func) {
+                    data.func = demangle(func);
+                    data.line = line;
+                }
+            }
+
+        private:
+            bfd *abfd_;
+            asection *sec_;
+            asymbol **symbol_table_;
+    };
+
+#endif // __MINGW32__
+
+    // g++ spouts warnings if you use {0} to initialize PODs. So we use this instead:
+    const struct
+    {
+        template<typename POD>
+        operator POD () const { POD p; std::memset(&p, 0, sizeof p); return p; }
+    }
+    empty_pod = { };
+
+    // Wraps a FARPROC. Implicitly convertible to any kind of pointer-to-function.
+    // Avoids having reinterpret casts all over the place.
+    struct auto_cast_function_ptr
+    {
+        auto_cast_function_ptr(FARPROC f) : fptr_(f) { }
+
+        template<typename FuncPtr>
+        operator FuncPtr() const { return reinterpret_cast<FuncPtr>(fptr_); }
+
+        FARPROC fptr_;
+    };
+
+    // A wrapper around a DLL. Can dynamically get function pointers with the function() function!
+    class windows_dll : uncopyable
+    {
+        public:
+            explicit windows_dll(const std::string &libname) :
+                name_(libname),
+                lib_(LoadLibraryA(name_.c_str()))
+            {
+                if (!lib_) throw std::runtime_error("Failed to load dll " + name_);
+            }
+
+            ~windows_dll() { FreeLibrary(lib_); }
+
+            const std::string &name() const { return name_; }
+
+            auto_cast_function_ptr function(const char *func_name) const
+            {
+                FARPROC proc = GetProcAddress(lib_, func_name);
+                if (!proc) throw std::runtime_error(std::string("failed to load function ") + func_name + " from library " + name_);
+
+                return proc;
+            }
+
+        private:
+            std::string name_;
+            HMODULE lib_;
+    };
+
+    // An object that makes sure debugging symbols are available
+    class symbol_context : uncopyable
+    {
+        public:
+            symbol_context()
+            {
+                if (!SymInitialize(GetCurrentProcess(), 0, TRUE))
+                    throw std::runtime_error("Failed to initialize symbol context");
+            }
+            ~symbol_context() { SymCleanup(GetCurrentProcess()); }
+    };
+
+    // A simple Windows mutex class. Use a lock object to lock the mutex for the duration of a scope.
+    class mutex : uncopyable
+    {
+        public:
+            mutex() { InitializeCriticalSection(&cs_); }
+            ~mutex() { DeleteCriticalSection(&cs_); }
+
+        private:
+            friend class lock;
+            void lock() { EnterCriticalSection(&cs_); }
+            void unlock() { LeaveCriticalSection(&cs_); }
+
+            CRITICAL_SECTION cs_;
+    }
+    g_fill_frames_mtx;
+
+    // A lock for the mutex
+    class lock : uncopyable
+    {
+        public:
+            lock(mutex &m) : m_(m) { m.lock(); }
+            ~lock() { m_.unlock(); }
+        private:
+            mutex &m_;
+    };
+
+
+    void fill_frames(std::list<dbg::stack_frame> &frames, dbg::stack::depth_type limit)
+    {
+        lock lk(g_fill_frames_mtx);
+
+        symbol_context sc;
+#ifdef __MINGW32__
+        bfd_context bfdc;
+#endif
+
+        STACKFRAME frame = empty_pod;
+        CONTEXT context = empty_pod;
+        context.ContextFlags = CONTEXT_FULL;
+
+        windows_dll kernel32("kernel32.dll");
+        void (WINAPI *RtlCaptureContext_)(CONTEXT*) = kernel32.function("RtlCaptureContext");
+
+        RtlCaptureContext_(&context);
+
+#if defined(_M_AMD64)
+        frame.AddrPC.Offset = context.Rip;
+        frame.AddrPC.Mode = AddrModeFlat;
+        frame.AddrStack.Offset = context.Rsp;
+        frame.AddrStack.Mode = AddrModeFlat;
+        frame.AddrFrame.Offset = context.Rbp;
+        frame.AddrFrame.Mode = AddrModeFlat;
+#else
+        frame.AddrPC.Offset = context.Eip;
+        frame.AddrPC.Mode = AddrModeFlat;
+        frame.AddrStack.Offset = context.Esp;
+        frame.AddrStack.Mode = AddrModeFlat;
+        frame.AddrFrame.Offset = context.Ebp;
+        frame.AddrFrame.Mode = AddrModeFlat;
+#endif
+
+        HANDLE process = GetCurrentProcess();
+        HANDLE thread = GetCurrentThread();
+
+        bool skip = true;
+        bool has_limit = limit != 0;
+        char symbol_buffer[sizeof(IMAGEHLP_SYMBOL) + 255];
+        char module_name_raw[MAX_PATH];
+
+#if defined(_M_AMD64)
+        const DWORD machine = IMAGE_FILE_MACHINE_AMD64;
+#else
+        const DWORD machine = IMAGE_FILE_MACHINE_I386;
+#endif
+
+        while(StackWalk(machine, process, thread, &frame, &context, 0, SymFunctionTableAccess, SymGetModuleBase, 0))
+        {
+            if (skip)
+            {
+                skip = false;
+                continue;
+            }
+
+            if (has_limit && limit-- == 0) break;
+
+            IMAGEHLP_SYMBOL *symbol = reinterpret_cast<IMAGEHLP_SYMBOL *>(symbol_buffer);
+            symbol->SizeOfStruct = (sizeof *symbol) + 255;
+            symbol->MaxNameLength = 254;
+
+#if defined(_WIN64)
+            DWORD64 module_base = SymGetModuleBase(process, frame.AddrPC.Offset);
+#else
+            DWORD module_base = SymGetModuleBase(process, frame.AddrPC.Offset);
+#endif
+            std::string module_name = unknown_module;
+            if (module_base && GetModuleFileNameA(reinterpret_cast<HINSTANCE>(module_base), module_name_raw, MAX_PATH))
+                module_name = module_name_raw;
+
+#if defined(__MINGW32__)
+                std::pair<std::string, unsigned int> func_and_line = bfdc.get_function_name_and_line(frame.AddrPC.Offset);
+
+                if (func_and_line.first.empty())
+                {
+#if defined(_WIN64)
+                   DWORD64 dummy = 0;
+#else              
+                    DWORD dummy = 0;
+#endif             
+                    BOOL got_symbol = SymGetSymFromAddr(process, frame.AddrPC.Offset, &dummy, symbol);
+                    func_and_line.first = got_symbol ? symbol->Name : unknown_function;
+                }
+#else
+                DWORD dummy = 0;
+                BOOL got_symbol = SymGetSymFromAddr(process, frame.AddrPC.Offset, &dummy, symbol);
+                std::string func = got_symbol ? symbol->Name : unknown_function;
+#endif
+
+            dbg::stack_frame f(reinterpret_cast<const void *>(frame.AddrPC.Offset), func_and_line.first, func_and_line.second, module_name);
+            frames.push_back(f);
+        }
+    }
+#elif defined(__GNUC__)
+#   if defined(__i386__) || defined(__amd64__)
+
+    void fill_frames(std::list<dbg::stack_frame> &frames, dbg::stack::depth_type limit)
+    {
+        // Based on code found at:
+        // http://www.tlug.org.za/wiki/index.php/Obtaining_a_stack_trace_in_C_upon_SIGSEGV
+
+        Dl_info info;
+        void **frame = static_cast<void **>(__builtin_frame_address(0));
+        void **bp = static_cast<void **>(*frame);
+        void *ip = frame[1];
+
+        bool has_limit = limit != 0;
+        bool skip = true;
+
+        while(bp && ip && dladdr(ip, &info))
+        {
+            if (skip)
+                skip = false;
+            else
+            {
+                if (has_limit && limit-- == 0) break;
+                frames.push_back(dbg::stack_frame(ip, demangle(info.dli_sname), info.dli_fname));
+
+                if(info.dli_sname && !std::strcmp(info.dli_sname, "main")) break;
+            }
+
+            ip = bp[1];
+            bp = static_cast<void**>(bp[0]);
+        }
+    }
+
+#   elif defined(__ppc__)
+
+    void fill_frames(std::list<dbg::stack_frame> &frames, dbg::stack::depth_type limit)
+    {
+        // Based on code found at:
+        // http://www.informit.com/articles/article.aspx?p=606582&seqNum=4&rl=1
+
+        void *ip = __builtin_return_address(0);
+        void **frame = static_cast<void **>(__builtin_frame_address(1));
+        bool has_limit = limit != 0;
+        Dl_info info;
+
+        do
+        {
+            if (has_limit && limit-- == 0) break;
+
+            if (dladdr(ip, &info))
+                frames.push_back(dbg::stack_frame(ip, demangle(info.dli_sname), info.dli_fname));
+
+            if (frame && (frame = static_cast<void**>(*frame))) ip = *(frame + 2);
+        }
+        while (frame && ip);
+    }
+
+#   else
+        // GNU, but not x86, x64 nor PPC
+#       error "Sorry but dbg::stack is not supported on this architecture"
+#   endif
+#else
+    // Unsupported compiler
+#   error "Sorry but dbg::stack is not supported on this compiler"
+#endif
+
+} // close anonymous namespace
+
+
+
+namespace dbg
+{
+    stack_frame::stack_frame(const void *instruction, const std::string &function, unsigned int line, const std::string &module) :
+        instruction(instruction),
+        function(function),
+        line(line),
+        module(module)
+    {
+    }
+
+    std::ostream &operator<< (std::ostream &out, const stack_frame &frame)
+    {
+        return out << frame.instruction << ": " << frame.function << ":" << frame.line << " in " << frame.module;
+    }
+
+    stack::stack(depth_type limit)
+    {
+        fill_frames(frames_, limit);
+    }
+
+    stack::const_iterator stack::begin() const
+    {
+        return frames_.begin();
+    }
+
+    stack::const_iterator stack::end() const
+    {
+        return frames_.end();
+    }
+
+    stack::depth_type stack::depth() const
+    {
+        return frames_.size();
+    }
+
+} // close namespace dbg
+
diff --git a/src/lib/stack.hpp b/src/lib/stack.hpp
new file mode 100644 (file)
index 0000000..73a13bf
--- /dev/null
@@ -0,0 +1,58 @@
+// Copyright 2007 Edd Dawson.
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef STACK_HPP_0022_01092007
+#define STACK_HPP_0022_01092007
+
+#include <string>
+#include <list>
+#include <iosfwd>
+
+namespace dbg
+{
+    //! stack_frame objects are collected by a stack object. They contain information about the instruction pointer,
+    //! the name of the corresponding function and the "module" (executable or library) in which the function resides.
+    struct stack_frame
+    {
+        stack_frame(const void *instruction, const std::string &function, unsigned int line, const std::string &module);
+
+        const void *instruction;
+        std::string function;
+        unsigned int line;
+        std::string module;
+    };
+
+    //! Allows you to write a stack_frame object to an std::ostream
+    std::ostream &operator<< (std::ostream &out, const stack_frame &frame);
+
+    //! Instantiate a dbg::stack object to collect information about the current call stack. Once created, a stack object
+    //! may be freely copied about and will continue to contain the information about the scope in which collection occurred.
+    class stack
+    {
+        public:
+            typedef std::list<stack_frame>::size_type depth_type;
+            typedef std::list<stack_frame>::const_iterator const_iterator;
+
+            //! Collect information about the current call stack. Information on the most recent frames will be collected
+            //! up to the specified limit. 0 means unlimited.
+            //! An std::runtime_error may be thrown on failure.
+            stack(depth_type limit = 0);
+
+            //! Returns an iterator referring to the "top" stack frame
+            const_iterator begin() const;
+
+            //! Returns an iterator referring to one past the "bottom" stack frame
+            const_iterator end() const;
+
+            //! Returns the number of frames collected
+            depth_type depth() const;
+
+        private:
+            std::list<stack_frame> frames_;
+    };
+
+} // close namespace dbg
+
+#endif // STACK_HPP_0022_01092007
diff --git a/src/lib/still_image.h b/src/lib/still_image.h
new file mode 100644 (file)
index 0000000..366d693
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+    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 DCPOMATIC_STILL_IMAGE_H
+#define DCPOMATIC_STILL_IMAGE_H
+
+class StillImageContent;
+
+class StillImage
+{
+public:
+       StillImage (boost::shared_ptr<const StillImageContent> c)
+               : _still_image_content (c)
+       {}
+
+       boost::shared_ptr<const StillImageContent> content () const {
+               return _still_image_content;
+       }
+
+protected:
+       boost::shared_ptr<const StillImageContent> _still_image_content;
+};
+
+#endif
diff --git a/src/lib/still_image_content.cc b/src/lib/still_image_content.cc
new file mode 100644 (file)
index 0000000..0cf80b5
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+    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 "still_image_content.h"
+#include "still_image_examiner.h"
+#include "config.h"
+#include "compose.hpp"
+#include "film.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::cout;
+using std::stringstream;
+using boost::shared_ptr;
+
+StillImageContent::StillImageContent (shared_ptr<const Film> f, boost::filesystem::path p)
+       : Content (f, p)
+       , VideoContent (f, p)
+{
+
+}
+
+StillImageContent::StillImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+       : Content (f, node)
+       , VideoContent (f, node)
+{
+       
+}
+
+string
+StillImageContent::summary () const
+{
+       /* Get the string() here so that the name does not have quotes around it */
+       return String::compose (_("%1 [still]"), path().filename().string());
+}
+
+string
+StillImageContent::technical_summary () const
+{
+       return Content::technical_summary() + " - "
+               + VideoContent::technical_summary() + " - "
+               + "still";
+}
+
+void
+StillImageContent::as_xml (xmlpp::Node* node) const
+{
+       node->add_child("Type")->add_child_text ("StillImage");
+       Content::as_xml (node);
+       VideoContent::as_xml (node);
+}
+
+void
+StillImageContent::examine (shared_ptr<Job> job)
+{
+       Content::examine (job);
+
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       shared_ptr<StillImageExaminer> examiner (new StillImageExaminer (film, shared_from_this()));
+
+       take_from_video_examiner (examiner);
+       set_video_length (Config::instance()->default_still_length() * video_frame_rate());
+}
+
+void
+StillImageContent::set_video_length (VideoContent::Frame len)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _video_length = len;
+       }
+
+       signal_changed (ContentProperty::LENGTH);
+}
+
+Time
+StillImageContent::full_length () const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
+       return video_length() * frc.factor() * TIME_HZ / video_frame_rate();
+}
+
+string
+StillImageContent::identifier () const
+{
+       stringstream s;
+       s << VideoContent::identifier ();
+       s << "_" << video_length();
+       return s.str ();
+}
diff --git a/src/lib/still_image_content.h b/src/lib/still_image_content.h
new file mode 100644 (file)
index 0000000..ccd7fbc
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+    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_STILL_IMAGE_CONTENT_H
+#define DCPOMATIC_STILL_IMAGE_CONTENT_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include "video_content.h"
+
+namespace cxml {
+       class Node;
+}
+
+/** A single image which is to be held on screen for some time (i.e. a slide) */
+class StillImageContent : public VideoContent
+{
+public:
+       StillImageContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+       StillImageContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+       boost::shared_ptr<StillImageContent> shared_from_this () {
+               return boost::dynamic_pointer_cast<StillImageContent> (Content::shared_from_this ());
+       };
+
+       void examine (boost::shared_ptr<Job>);
+       std::string summary () const;
+       std::string technical_summary () const;
+       void as_xml (xmlpp::Node *) const;
+       Time full_length () const;
+
+       std::string identifier () const;
+       
+       void set_video_length (VideoContent::Frame);
+};
+
+#endif
diff --git a/src/lib/still_image_decoder.cc b/src/lib/still_image_decoder.cc
new file mode 100644 (file)
index 0000000..6e82f9a
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+    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 <iostream>
+#include <boost/filesystem.hpp>
+#include <Magick++.h>
+#include "still_image_content.h"
+#include "still_image_decoder.h"
+#include "image.h"
+#include "film.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::cout;
+using boost::shared_ptr;
+using libdcp::Size;
+
+StillImageDecoder::StillImageDecoder (shared_ptr<const Film> f, shared_ptr<const StillImageContent> c)
+       : Decoder (f)
+       , VideoDecoder (f, c)
+       , StillImage (c)
+{
+
+}
+
+void
+StillImageDecoder::pass ()
+{
+       if (_video_position >= _still_image_content->video_length ()) {
+               return;
+       }
+
+       if (_image) {
+               video (_image, true, _video_position);
+               return;
+       }
+
+       Magick::Image* magick_image = new Magick::Image (_still_image_content->path().string ());
+       _video_size = libdcp::Size (magick_image->columns(), magick_image->rows());
+       
+       _image.reset (new Image (PIX_FMT_RGB24, _video_size.get(), true));
+
+       using namespace MagickCore;
+       
+       uint8_t* p = _image->data()[0];
+       for (int y = 0; y < _video_size->height; ++y) {
+               uint8_t* q = p;
+               for (int x = 0; x < _video_size->width; ++x) {
+                       Magick::Color c = magick_image->pixelColor (x, y);
+                       *q++ = c.redQuantum() * 255 / QuantumRange;
+                       *q++ = c.greenQuantum() * 255 / QuantumRange;
+                       *q++ = c.blueQuantum() * 255 / QuantumRange;
+               }
+               p += _image->stride()[0];
+       }
+
+       delete magick_image;
+
+       video (_image, false, _video_position);
+}
+
+void
+StillImageDecoder::seek (VideoContent::Frame frame, bool)
+{
+       _video_position = frame;
+}
+
+bool
+StillImageDecoder::done () const
+{
+       return _video_position >= _still_image_content->video_length ();
+}
diff --git a/src/lib/still_image_decoder.h b/src/lib/still_image_decoder.h
new file mode 100644 (file)
index 0000000..db41b03
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+    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 "video_decoder.h"
+#include "still_image.h"
+
+namespace Magick {
+       class Image;
+}
+
+class StillImageContent;
+
+class StillImageDecoder : public VideoDecoder, public StillImage
+{
+public:
+       StillImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const StillImageContent>);
+
+       /* Decoder */
+
+       void pass ();
+       void seek (VideoContent::Frame, bool);
+       bool done () const;
+
+private:
+       boost::shared_ptr<Image> _image;
+       mutable boost::optional<libdcp::Size> _video_size;
+};
+
diff --git a/src/lib/still_image_examiner.cc b/src/lib/still_image_examiner.cc
new file mode 100644 (file)
index 0000000..5f45d63
--- /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 "still_image_content.h"
+#include "still_image_examiner.h"
+#include "film.h"
+
+#include "i18n.h"
+
+using std::cout;
+using boost::shared_ptr;
+
+StillImageExaminer::StillImageExaminer (shared_ptr<const Film> f, shared_ptr<const StillImageContent> c)
+       : StillImage (c)
+       , _film (f)
+{
+       using namespace MagickCore;
+       Magick::Image* image = new Magick::Image (_still_image_content->path().string());
+       _video_size = libdcp::Size (image->columns(), image->rows());
+       delete image;
+}
+
+libdcp::Size
+StillImageExaminer::video_size () const
+{
+       return _video_size;
+}
+
+int
+StillImageExaminer::video_length () const
+{
+       return _still_image_content->video_length ();
+}
+
+float
+StillImageExaminer::video_frame_rate () const
+{
+       boost::shared_ptr<const Film> f = _film.lock ();
+       if (!f) {
+               return 24;
+       }
+
+       return f->video_frame_rate ();
+}
+
diff --git a/src/lib/still_image_examiner.h b/src/lib/still_image_examiner.h
new file mode 100644 (file)
index 0000000..fa3456e
--- /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 "still_image.h"
+#include "video_examiner.h"
+
+namespace Magick {
+       class Image;
+}
+
+class StillImageContent;
+
+class StillImageExaminer : public StillImage, public VideoExaminer
+{
+public:
+       StillImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const StillImageContent>);
+
+       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;
+};
diff --git a/src/lib/stream.cc b/src/lib/stream.cc
deleted file mode 100644 (file)
index 4f12f41..0000000
+++ /dev/null
@@ -1,90 +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 "external_audio_decoder.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 ("%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 = ExternalAudioStream::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
diff --git a/src/lib/subtitle.cc b/src/lib/subtitle.cc
deleted file mode 100644 (file)
index c52d3ac..0000000
+++ /dev/null
@@ -1,147 +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/subtitle.cc
- *  @brief Representations of subtitles.
- */
-
-#include "subtitle.h"
-#include "image.h"
-#include "exceptions.h"
-
-using namespace std;
-using namespace boost;
-
-/** Construct a TimedSubtitle.  This is a subtitle image, position,
- *  and a range of time over which it should be shown.
- *  @param sub AVSubtitle to read.
- */
-TimedSubtitle::TimedSubtitle (AVSubtitle const & sub)
-{
-       assert (sub.rects > 0);
-       
-       /* Subtitle PTS in seconds (within the source, not taking into account any of the
-          source that we may have chopped off for the DCP)
-       */
-       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);
-
-       if (sub.num_rects > 1) {
-               throw DecodeError ("multi-part subtitles not yet supported");
-       }
-
-       AVSubtitleRect const * rect = sub.rects[0];
-
-       if (rect->type != SUBTITLE_BITMAP) {
-               throw DecodeError ("non-bitmap subtitles not yet supported");
-       }
-       
-       shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGBA, Size (rect->w, rect->h), true));
-
-       /* Start of the first line in the subtitle */
-       uint8_t* sub_p = rect->pict.data[0];
-       /* sub_p looks up into a RGB palette which is here */
-       uint32_t const * palette = (uint32_t *) rect->pict.data[1];
-       /* Start of the output data */
-       uint32_t* out_p = (uint32_t *) image->data()[0];
-       
-       for (int y = 0; y < rect->h; ++y) {
-               uint8_t* sub_line_p = sub_p;
-               uint32_t* out_line_p = out_p;
-               for (int x = 0; x < rect->w; ++x) {
-                       *out_line_p++ = palette[*sub_line_p++];
-               }
-               sub_p += rect->pict.linesize[0];
-               out_p += image->stride()[0] / sizeof (uint32_t);
-       }
-
-       _subtitle.reset (new Subtitle (Position (rect->x, rect->y), image));
-}      
-
-/** @param t Time in seconds from the start of the source */
-bool
-TimedSubtitle::displayed_at (double t) const
-{
-       return t >= _from && t <= _to;
-}
-
-/** Construct a subtitle, which is an image and a position.
- *  @param p Position within the (uncropped) source frame.
- *  @param i Image of the subtitle (should be RGBA).
- */
-Subtitle::Subtitle (Position p, shared_ptr<Image> i)
-       : _position (p)
-       , _image (i)
-{
-
-}
-
-/** Given the area of a subtitle, work out the area it should
- *  take up when its video frame is scaled up, and it is optionally
- *  itself scaled and offset.
- *  @param target_x_scale the x scaling of the video frame that the subtitle is in.
- *  @param target_y_scale the y scaling of the video frame that the subtitle is in.
- *  @param sub_area The area of the subtitle within the original source.
- *  @param subtitle_offset y offset to apply to the subtitle position (+ve is down)
- *  in the coordinate space of the source.
- *  @param subtitle_scale scaling factor to apply to the subtitle image.
- */
-Rect
-subtitle_transformed_area (
-       float target_x_scale, float target_y_scale,
-       Rect sub_area, int subtitle_offset, float subtitle_scale
-       )
-{
-       Rect tx;
-
-       sub_area.y += subtitle_offset;
-
-       /* We will scale the subtitle by the same amount as the video frame, and also by the additional
-          subtitle_scale
-       */
-       tx.width = sub_area.width * target_x_scale * subtitle_scale;
-       tx.height = sub_area.height * target_y_scale * subtitle_scale;
-
-       /* Then we need a corrective translation, consisting of two parts:
-        *
-        * 1.  that which is the result of the scaling of the subtitle by target_x_scale and target_y_scale; this will be
-        *     sub_area.x * target_x_scale and sub_area.y * target_y_scale.
-        *
-        * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
-        *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
-        *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
-        *
-        * Combining these two translations gives these expressions.
-        */
-       
-       tx.x = target_x_scale * (sub_area.x + (sub_area.width * (1 - subtitle_scale) / 2));
-       tx.y = target_y_scale * (sub_area.y + (sub_area.height * (1 - subtitle_scale) / 2));
-
-       return tx;
-}
-
-/** @return area that this subtitle takes up, in the original uncropped source's coordinate space */
-Rect
-Subtitle::area () const
-{
-       return Rect (_position.x, _position.y, _image->size().width, _image->size().height);
-}
diff --git a/src/lib/subtitle.h b/src/lib/subtitle.h
deleted file mode 100644 (file)
index 38ba4e7..0000000
+++ /dev/null
@@ -1,81 +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/subtitle.h
- *  @brief Representations of subtitles.
- */
-
-#include <list>
-#include <boost/shared_ptr.hpp>
-#include "util.h"
-
-struct AVSubtitle;
-class Image;
-
-/** A subtitle, consisting of an image and a position */
-class Subtitle
-{
-public:
-       Subtitle (Position p, boost::shared_ptr<Image> i);
-
-       void set_position (Position p) {
-               _position = p;
-       }
-
-       Position position () const {
-               return _position;
-       }
-       
-       boost::shared_ptr<Image> image () const {
-               return _image;
-       }
-
-       Rect area () const;
-       
-private:
-       Position _position;
-       boost::shared_ptr<Image> _image;
-};
-
-Rect
-subtitle_transformed_area (
-       float target_x_scale, float target_y_scale,
-       Rect sub_area, int subtitle_offset, float subtitle_scale
-       );
-
-/** A Subtitle class with details of the time over which it should be shown */
-class TimedSubtitle
-{
-public:
-       TimedSubtitle (AVSubtitle const &);
-
-       bool displayed_at (double t) const;
-       
-       boost::shared_ptr<Subtitle> subtitle () const {
-               return _subtitle;
-       }
-
-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;
-};
diff --git a/src/lib/subtitle_content.cc b/src/lib/subtitle_content.cc
new file mode 100644 (file)
index 0000000..9fefbbf
--- /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 <libcxml/cxml.h>
+#include "subtitle_content.h"
+
+using std::string;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+int const SubtitleContentProperty::SUBTITLE_OFFSET = 500;
+int const SubtitleContentProperty::SUBTITLE_SCALE = 501;
+
+SubtitleContent::SubtitleContent (shared_ptr<const Film> f, boost::filesystem::path p)
+       : Content (f, p)
+       , _subtitle_offset (0)
+       , _subtitle_scale (1)
+{
+
+}
+
+SubtitleContent::SubtitleContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+       : Content (f, node)
+       , _subtitle_offset (0)
+       , _subtitle_scale (1)
+{
+       _subtitle_offset = node->number_child<float> ("SubtitleOffset");
+       _subtitle_scale = node->number_child<float> ("SubtitleScale");
+}
+
+void
+SubtitleContent::as_xml (xmlpp::Node* root) const
+{
+       root->add_child("SubtitleOffset")->add_child_text (lexical_cast<string> (_subtitle_offset));
+       root->add_child("SubtitleScale")->add_child_text (lexical_cast<string> (_subtitle_scale));
+}
+
+void
+SubtitleContent::set_subtitle_offset (double o)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _subtitle_offset = o;
+       }
+       signal_changed (SubtitleContentProperty::SUBTITLE_OFFSET);
+}
+
+void
+SubtitleContent::set_subtitle_scale (double s)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _subtitle_scale = s;
+       }
+       signal_changed (SubtitleContentProperty::SUBTITLE_SCALE);
+}
diff --git a/src/lib/subtitle_content.h b/src/lib/subtitle_content.h
new file mode 100644 (file)
index 0000000..c29485f
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+    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_SUBTITLE_CONTENT_H
+#define DCPOMATIC_SUBTITLE_CONTENT_H
+
+#include "content.h"
+
+class SubtitleContentProperty
+{
+public:
+       static int const SUBTITLE_OFFSET;
+       static int const SUBTITLE_SCALE;
+};
+
+class SubtitleContent : public virtual Content
+{
+public:
+       SubtitleContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+       SubtitleContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+       
+       void as_xml (xmlpp::Node *) const;
+
+       void set_subtitle_offset (double);
+       void set_subtitle_scale (double);
+
+       double subtitle_offset () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _subtitle_offset;
+       }
+
+       double subtitle_scale () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _subtitle_scale;
+       }
+       
+private:
+       friend class ffmpeg_pts_offset_test;
+       
+       /** y offset for placing subtitles, as a proportion of the container height;
+           +ve is further down the frame, -ve is further up.
+       */
+       double _subtitle_offset;
+       /** scale factor to apply to subtitles */
+       double _subtitle_scale;
+};
+
+#endif
diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc
new file mode 100644 (file)
index 0000000..c06f3d7
--- /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/shared_ptr.hpp>
+#include "subtitle_decoder.h"
+
+using boost::shared_ptr;
+
+SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
+       : Decoder (f)
+{
+
+}
+
+
+/** Called by subclasses when a subtitle is ready.
+ *  Image may be 0 to say that there is no current subtitle.
+ */
+void
+SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+{
+       Subtitle (image, rect, from, to);
+}
diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h
new file mode 100644 (file)
index 0000000..eeeadbd
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+    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 "decoder.h"
+#include "rect.h"
+#include "types.h"
+
+class Film;
+class TimedSubtitle;
+class Image;
+
+class SubtitleDecoder : public virtual Decoder
+{
+public:
+       SubtitleDecoder (boost::shared_ptr<const Film>);
+
+       boost::signals2::signal<void (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time)> Subtitle;
+
+protected:
+       void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
+};
index a45e80dcb0bdb46e9fe03b58459b0bd485c8cc5e..69a7e3aa94b29e4988051f6ca61898d996b94f09 100644 (file)
@@ -26,6 +26,8 @@
 #include "timer.h"
 #include "util.h"
 
+#include "i18n.h"
+
 using namespace std;
 
 /** @param n Name to use when giving output */
@@ -40,7 +42,7 @@ PeriodTimer::~PeriodTimer ()
 {
        struct timeval stop;
        gettimeofday (&stop, 0);
-       cout << "T: " << _name << ": " << (seconds (stop) - seconds (_start)) << "\n";
+       cout << N_("T: ") << _name << N_(": ") << (seconds (stop) - seconds (_start)) << N_("\n");
 }
 
 /** @param n Name to use when giving output.
@@ -80,10 +82,10 @@ StateTimer::~StateTimer ()
        }
 
        
-       set_state ("");
+       set_state (N_(""));
 
-       cout << _name << ":\n";
+       cout << _name << N_(":\n");
        for (map<string, double>::iterator i = _totals.begin(); i != _totals.end(); ++i) {
-               cout << "\t" << i->first << " " << i->second << "\n";
+               cout << N_("\t") << i->first << " " << i->second << N_("\n");
        }
 }
index f509a7492888178d6192d0c4af8ec2e38d30d7d4..4a5aa12de331c0fe5b024144bc18923e76ccc8eb 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>
@@ -53,7 +53,7 @@ private:
  *  spends in one of a set of states.
  *
  *  Once constructed, the caller can call set_state() whenever
- *  its state changes.  When StateTimer is destroyed, it will
+ *  its state changes. When StateTimer is destroyed, it will
  *  output (to cout) a summary of the time spent in each state.
  */
 class StateTimer
index dfb9b107192748f7dbe8c6b4f2280cd69fa05057..c9ec2053d46353a500cd6c4178af395a6c303906 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"
 
 using std::string;
 using std::stringstream;
@@ -37,13 +37,9 @@ using std::setprecision;
 using boost::shared_ptr;
 
 /** @param s Film to use.
- *  @param o Options.
- *  @param req Job that must be completed before this job is run.
  */
-TranscodeJob::TranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
-       : Job (f, req)
-       , _decode_opt (od)
-       , _encode_opt (oe)
+TranscodeJob::TranscodeJob (shared_ptr<const Film> f)
+       : Job (f)
 {
        
 }
@@ -51,7 +47,7 @@ TranscodeJob::TranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions>
 string
 TranscodeJob::name () const
 {
-       return String::compose ("Transcode %1", _film->name());
+       return String::compose (_("Transcode %1"), _film->name());
 }
 
 void
@@ -59,22 +55,20 @@ TranscodeJob::run ()
 {
        try {
 
-               _film->log()->log ("Transcode job starting");
-               _film->log()->log (String::compose ("Audio delay is %1ms", _film->audio_delay()));
+               _film->log()->log (N_("Transcode job starting"));
 
-               _encoder.reset (new Encoder (_film, _encode_opt));
-               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);
 
-               _film->log()->log ("Transcode job completed successfully");
+               _film->log()->log (N_("Transcode job completed successfully"));
 
        } catch (std::exception& e) {
 
                set_progress (1);
                set_state (FINISHED_ERROR);
-               _film->log()->log (String::compose ("Transcode job failed (%1)", e.what()));
+               _film->log()->log (String::compose (N_("Transcode job failed (%1)"), e.what()));
 
                throw;
        }
@@ -83,16 +77,11 @@ TranscodeJob::run ()
 string
 TranscodeJob::status () const
 {
-       if (!_encoder) {
-               return "0%";
+       if (!_transcoder) {
+               return _("0%");
        }
 
-       if (_encoder->skipping () && !finished ()) {
-               return "skipping already-encoded frames";
-       }
-               
-       
-       float const fps = _encoder->current_frames_per_second ();
+       float const fps = _transcoder->current_encoding_rate ();
        if (fps == 0) {
                return Job::status ();
        }
@@ -102,7 +91,12 @@ TranscodeJob::status () const
        s << Job::status ();
 
        if (!finished ()) {
-               s << "; " << fixed << setprecision (1) << fps << " frames per second";
+               if (_transcoder->state() == Encoder::TRANSCODING) {
+                       s << "; " << fixed << setprecision (1) << fps << N_(" ") << _("frames per second");
+               } else {
+                       /* TRANSLATORS: this means `computing a hash' as in a digest of a block of data */
+                       s << "; " << _("hashing");
+               }
        }
        
        return s.str ();
@@ -111,16 +105,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->dcp_length()) {
+       if (fps == 0) {
                return 0;
        }
 
-       /* We assume that dcp_length() is valid, if it is set */
-       SourceFrame const left = _film->dcp_trim_start() + _film->dcp_length().get() - _encoder->video_frame();
+       /* Compute approximate proposed length here, as it's only here that we need it */
+       OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - _transcoder->video_frames_out();
        return left / fps;
 }
index 97f655e15c212e78aba5ad9f6564dbfc84f9f7e9..9128206d29dc9c9e42460cafecfa4748f0a1c0db 100644 (file)
@@ -24,9 +24,7 @@
 #include <boost/shared_ptr.hpp>
 #include "job.h"
 
-class Encoder;
-class DecodeOptions;
-class EncodeOptions;
+class Transcoder;
 
 /** @class TranscodeJob
  *  @brief A job which transcodes from one format to another.
@@ -34,17 +32,14 @@ class EncodeOptions;
 class TranscodeJob : public Job
 {
 public:
-       TranscodeJob (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> od, boost::shared_ptr<const EncodeOptions> oe, boost::shared_ptr<Job> req);
+       TranscodeJob (boost::shared_ptr<const Film> f);
        
        std::string name () const;
        void run ();
        std::string status () const;
 
-protected:
+private:
        int remaining_time () const;
 
-private:
-       boost::shared_ptr<const DecodeOptions> _decode_opt;
-       boost::shared_ptr<const EncodeOptions> _encode_opt;
-       boost::shared_ptr<Encoder> _encoder;
+       boost::shared_ptr<Transcoder> _transcoder;
 };
index 87a1fb3f28c8435587e7601b11de13cde3b08a30..63ba77939f2cf7ca033286c6ee88a575fa698aa4 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 "player.h"
+#include "job.h"
 
 using std::string;
-using std::cout;
 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, Eyes eyes, ColourConversion conversion, bool same)
+{
+       shared_ptr<Encoder> e = encoder.lock ();
+       if (e) {
+               e->process_video (image, eyes, conversion, 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, shared_ptr<const 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, j))
+       , _player (f->make_player ())
+       , _encoder (new Encoder (f, j))
 {
-       assert (_encoder);
-
-       if (f->audio_stream()) {
-               shared_ptr<AudioStream> st = f->audio_stream();
-               _matcher.reset (new Matcher (f->log(), st->sample_rate(), f->frames_per_second()));
-               _delay_line.reset (new DelayLine (f->log(), st->channels(), f->audio_delay() * st->sample_rate() / 1000));
-               _gain.reset (new Gain (f->log(), f->audio_gain()));
-       }
-
-       /* Set up the decoder to use the film's set streams */
-       _decoders.video->set_subtitle_stream (f->subtitle_stream ());
-       if (_decoders.audio) {
-               _decoders.audio->set_audio_stream (f->audio_stream ());
-       }
-
-       if (_matcher) {
-               _decoders.video->connect_video (_matcher);
-               _matcher->connect_video (_encoder);
-       } else {
-               _decoders.video->connect_video (_encoder);
-       }
-       
-       if (_matcher && _delay_line && _decoders.audio) {
-               _decoders.audio->connect_audio (_delay_line);
-               _delay_line->connect_audio (_matcher);
-               _matcher->connect_audio (_gain);
-               _gain->connect_audio (_encoder);
-       }
+       _player->Video.connect (bind (video_proxy, _encoder, _1, _2, _3, _4));
+       _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 ();
-       try {
-               bool done[2] = { false, false };
-               
-               while (1) {
-                       if (!done[0]) {
-                               done[0] = _decoders.video->pass ();
-                               _decoders.video->set_progress ();
-                       }
+       while (!_player->pass ()) {}
+       _encoder->process_end ();
+}
 
-                       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;
-                       }
+float
+Transcoder::current_encoding_rate () const
+{
+       return _encoder->current_encoding_rate ();
+}
 
-                       if (done[0] && done[1]) {
-                               break;
-                       }
-               }
-               
-       } catch (...) {
-               _encoder->process_end ();
-               throw;
-       }
-       
-       if (_delay_line) {
-               _delay_line->process_end ();
-       }
-       if (_matcher) {
-               _matcher->process_end ();
-       }
-       if (_gain) {
-               _gain->process_end ();
-       }
-       _encoder->process_end ();
+int
+Transcoder::video_frames_out () const
+{
+       return _encoder->video_frames_out ();
+}
+
+Encoder::State
+Transcoder::state () const
+{
+       return _encoder->state ();
 }
index b50113742369c817aa3e0b57d28158f31d111b41..7bf214a88de6ccbbb3b53bacc43fb8c8791de410 100644 (file)
 
 */
 
-/** @file  src/transcoder.h
- *  @brief A class which takes a FilmState and some Options, then uses those to transcode a 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"
+#include "types.h"
+#include "encoder.h"
 
 class Film;
 class Job;
 class Encoder;
-class FilmState;
-class Matcher;
 class VideoFilter;
-class Gain;
-class VideoDecoder;
-class AudioDecoder;
-class DelayLine;
-class EncodeOptions;
-class DecodeOptions;
+class Player;
 
-/** @class Transcoder
- *  @brief A class which takes a FilmState and some Options, then uses those to transcode a Film.
- *
- *  A decoder is selected according to the content type, and the encoder can be specified
- *  as a parameter to the constructor.
- */
-class Transcoder
+/** @class Transcoder */
+class Transcoder : public boost::noncopyable
 {
 public:
        Transcoder (
-               boost::shared_ptr<Film> f,
-               boost::shared_ptr<const 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;
+       Encoder::State state () 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;
 };
diff --git a/src/lib/types.cc b/src/lib/types.cc
new file mode 100644 (file)
index 0000000..bc4f5f8
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+    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;
+using std::string;
+
+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 r Resolution.
+ *  @return Untranslated string representation.
+ */
+string
+resolution_to_string (Resolution r)
+{
+       switch (r) {
+       case RESOLUTION_2K:
+               return "2K";
+       case RESOLUTION_4K:
+               return "4K";
+       }
+
+       assert (false);
+       return "";
+}
+
+
+Resolution
+string_to_resolution (string s)
+{
+       if (s == "2K") {
+               return RESOLUTION_2K;
+       }
+
+       if (s == "4K") {
+               return RESOLUTION_4K;
+       }
+
+       assert (false);
+       return RESOLUTION_2K;
+}
diff --git a/src/lib/types.h b/src/lib/types.h
new file mode 100644 (file)
index 0000000..01560ba
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+    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;
+class AudioBuffers;
+
+/** The version number of the protocol used to communicate
+ *  with servers.  Intended to be bumped when incompatibilities
+ *  are introduced.
+ */
+#define SERVER_LINK_VERSION 1
+
+typedef int64_t Time;
+#define TIME_MAX INT64_MAX
+#define TIME_HZ         ((Time) 96000)
+typedef int64_t OutputAudioFrame;
+typedef int    OutputVideoFrame;
+typedef std::vector<boost::shared_ptr<Content> > ContentList;
+
+template<class T>
+struct TimedAudioBuffers
+{
+       TimedAudioBuffers ()
+               : time (0)
+       {}
+       
+       TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, T t)
+               : audio (a)
+               , time (t)
+       {}
+       
+       boost::shared_ptr<AudioBuffers> audio;
+       T time;
+};
+
+enum VideoFrameType
+{
+       VIDEO_FRAME_TYPE_2D,
+       VIDEO_FRAME_TYPE_3D_LEFT_RIGHT
+};
+
+enum Eyes
+{
+       EYES_BOTH,
+       EYES_LEFT,
+       EYES_RIGHT,
+       EYES_COUNT
+};
+
+/** @struct Crop
+ *  @brief A description of the crop of an image or video.
+ */
+struct Crop
+{
+       Crop () : left (0), right (0), top (0), bottom (0) {}
+       Crop (int l, int r, int t, int b) : left (l), right (r), top (t), bottom (b) {}
+
+       /** 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);
+
+enum Resolution {
+       RESOLUTION_2K,
+       RESOLUTION_4K
+};
+
+std::string resolution_to_string (Resolution);
+Resolution string_to_resolution (std::string);
+
+#endif
index 221bcbe9581ad2cdfaacdcaaeabf4487ab6e6723..7e0f575135fc893681cc08e3b481847d112c3824 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>
@@ -27,7 +27,7 @@
 /** A class to allow signals to be emitted from non-UI threads and handled
  *  by a UI thread.
  */
-class UISignaller
+class UISignaller : public boost::noncopyable
 {
 public:
        /** Create a UISignaller.  Must be called from the UI thread */
@@ -60,7 +60,10 @@ public:
        }
 
        /** This should wake the UI and make it call ui_idle() */
-       virtual void wake_ui () = 0;
+       virtual void wake_ui () {
+               /* This is only a sensible implementation when there is no GUI... */
+               ui_idle ();
+       }
 
 private:
        /** A io_service which is used as the conduit for messages */
index ef6f46575f32ada59bb6a7923d0fa56277db5cbf..b8bc1fc9e467680646f924c2ab3385d195911efb 100644 (file)
@@ -26,7 +26,8 @@
 #include <iomanip>
 #include <iostream>
 #include <fstream>
-#ifdef DVDOMATIC_POSIX
+#include <climits>
+#ifdef DCPOMATIC_POSIX
 #include <execinfo.h>
 #include <cxxabi.h>
 #endif
@@ -38,6 +39,7 @@
 #include <boost/lexical_cast.hpp>
 #include <boost/thread.hpp>
 #include <boost/filesystem.hpp>
+#include <glib.h>
 #include <openjpeg.h>
 #include <openssl/md5.h>
 #include <magick/MagickCore.h>
@@ -55,15 +57,44 @@ 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 "ratio.h"
+#include "job.h"
+#ifdef DCPOMATIC_WINDOWS
+#include "stack.hpp"
+#endif
 
-using namespace std;
-using namespace boost;
-
-thread::id ui_thread;
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::setfill;
+using std::ostream;
+using std::endl;
+using std::vector;
+using std::hex;
+using std::setw;
+using std::ifstream;
+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;
+
+static boost::thread::id ui_thread;
+static boost::filesystem::path backtrace_file;
 
 /** Convert some number of seconds to a string representation
  *  in hours, minutes and seconds.
@@ -81,11 +112,11 @@ seconds_to_hms (int s)
        m -= (h * 60);
 
        stringstream hms;
-       hms << h << ":";
+       hms << h << N_(":");
        hms.width (2);
-       hms << setfill ('0') << m << ":";
+       hms << std::setfill ('0') << m << N_(":");
        hms.width (2);
-       hms << setfill ('0') << s;
+       hms << std::setfill ('0') << s;
 
        return hms.str ();
 }
@@ -105,40 +136,40 @@ seconds_to_approximate_hms (int s)
        
        if (h > 0) {
                if (m > 30) {
-                       ap << (h + 1) << " hours";
+                       ap << (h + 1) << N_(" ") << _("hours");
                } else {
                        if (h == 1) {
-                               ap << "1 hour";
+                               ap << N_("1 ") << _("hour");
                        } else {
-                               ap << h << " hours";
+                               ap << h << N_(" ") << _("hours");
                        }
                }
        } else if (m > 0) {
                if (m == 1) {
-                       ap << "1 minute";
+                       ap << N_("1 ") << _("minute");
                } else {
-                       ap << m << " minutes";
+                       ap << m << N_(" ") << _("minutes");
                }
        } else {
-               ap << s << " seconds";
+               ap << s << N_(" ") << _("seconds");
        }
 
        return ap.str ();
 }
 
-#ifdef DVDOMATIC_POSIX
+#ifdef DCPOMATIC_POSIX
 /** @param l Mangled C++ identifier.
  *  @return Demangled version.
  */
 static string
 demangle (string l)
 {
-       string::size_type const b = l.find_first_of ("(");
+       string::size_type const b = l.find_first_of (N_("("));
        if (b == string::npos) {
                return l;
        }
 
-       string::size_type const p = l.find_last_of ("+");
+       string::size_type const p = l.find_last_of (N_("+"));
        if (p == string::npos) {
                return l;
        }
@@ -172,16 +203,12 @@ void
 stacktrace (ostream& out, int levels)
 {
        void *array[200];
-       size_t size;
-       char **strings;
-       size_t i;
-     
-       size = backtrace (array, 200);
-       strings = backtrace_symbols (array, size);
+       size_t size = backtrace (array, 200);
+       char** strings = backtrace_symbols (array, size);
      
        if (strings) {
-               for (i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) {
-                       out << "  " << demangle (strings[i]) << endl;
+               for (size_t i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) {
+                       out << N_("  ") << demangle (strings[i]) << "\n";
                }
                
                free (strings);
@@ -196,7 +223,7 @@ static string
 ffmpeg_version_to_string (int v)
 {
        stringstream s;
-       s << ((v & 0xff0000) >> 16) << "." << ((v & 0xff00) >> 8) << "." << (v & 0xff);
+       s << ((v & 0xff0000) >> 16) << N_(".") << ((v & 0xff00) >> 8) << N_(".") << (v & 0xff);
        return s.str ();
 }
 
@@ -205,16 +232,16 @@ string
 dependency_version_summary ()
 {
        stringstream s;
-       s << "libopenjpeg " << opj_version () << ", "
-         << "libavcodec " << ffmpeg_version_to_string (avcodec_version()) << ", "
-         << "libavfilter " << ffmpeg_version_to_string (avfilter_version()) << ", "
-         << "libavformat " << ffmpeg_version_to_string (avformat_version()) << ", "
-         << "libavutil " << ffmpeg_version_to_string (avutil_version()) << ", "
-         << "libpostproc " << ffmpeg_version_to_string (postproc_version()) << ", "
-         << "libswscale " << ffmpeg_version_to_string (swscale_version()) << ", "
-         << MagickVersion << ", "
-         << "libssh " << ssh_version (0) << ", "
-         << "libdcp " << libdcp::version << " git " << libdcp::git_commit;
+       s << N_("libopenjpeg ") << opj_version () << N_(", ")
+         << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
+         << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
+         << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
+         << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
+         << N_("libpostproc ") << ffmpeg_version_to_string (postproc_version()) << N_(", ")
+         << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
+         << MagickVersion << N_(", ")
+         << N_("libssh ") << ssh_version (0) << N_(", ")
+         << N_("libdcp ") << libdcp::version << N_(" git ") << libdcp::git_commit;
 
        return s.str ();
 }
@@ -225,33 +252,82 @@ seconds (struct timeval t)
        return t.tv_sec + (double (t.tv_usec) / 1e6);
 }
 
-/** Call the required functions to set up DVD-o-matic's static arrays, etc.
+#ifdef DCPOMATIC_WINDOWS
+LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *)
+{
+       dbg::stack s;
+       ofstream f (backtrace_file.string().c_str());
+       std::copy(s.begin(), s.end(), std::ostream_iterator<dbg::stack_frame>(f, "\n"));
+       return EXCEPTION_CONTINUE_SEARCH;
+}
+#endif
+
+/** Call the required functions to set up DCP-o-matic's static arrays, etc.
  *  Must be called from the UI thread, if there is one.
  */
 void
-dvdomatic_setup ()
+dcpomatic_setup ()
 {
-       libdcp::init ();
+#ifdef DCPOMATIC_WINDOWS
+       backtrace_file /= g_get_user_config_dir ();
+       backtrace_file /= "backtrace.txt";
+       SetUnhandledExceptionFilter(exception_handler);
+#endif 
+       
+       avfilter_register_all ();
        
-       Format::setup_formats ();
+       Ratio::setup_ratios ();
        DCPContentType::setup_dcp_content_types ();
        Scaler::setup_scalers ();
        Filter::setup_filters ();
        SoundProcessor::setup_sound_processors ();
 
-       ui_thread = this_thread::get_id ();
+       ui_thread = boost::this_thread::get_id ();
 }
 
-/** @param start Start position for the crop within the image.
- *  @param size Size of the cropped area.
- *  @return FFmpeg crop filter string.
- */
-string
-crop_string (Position start, Size size)
+#ifdef DCPOMATIC_WINDOWS
+boost::filesystem::path
+mo_path ()
 {
-       stringstream s;
-       s << "crop=" << size.width << ":" << size.height << ":" << start.x << ":" << start.y;
-       return s.str ();
+       wchar_t buffer[512];
+       GetModuleFileName (0, buffer, 512 * sizeof(wchar_t));
+       boost::filesystem::path p (buffer);
+       p = p.parent_path ();
+       p = p.parent_path ();
+       p /= "locale";
+       return p;
+}
+#endif
+
+void
+dcpomatic_setup_gettext_i18n (string lang)
+{
+#ifdef DCPOMATIC_POSIX
+       lang += ".UTF8";
+#endif
+
+       if (!lang.empty ()) {
+               /* Override our environment language; this is essential on
+                  Windows.
+               */
+               char cmd[64];
+               snprintf (cmd, sizeof(cmd), "LANGUAGE=%s", lang.c_str ());
+               putenv (cmd);
+               snprintf (cmd, sizeof(cmd), "LANG=%s", lang.c_str ());
+               putenv (cmd);
+       }
+
+       setlocale (LC_ALL, "");
+       textdomain ("libdcpomatic");
+
+#ifdef DCPOMATIC_WINDOWS
+       bindtextdomain ("libdcpomatic", mo_path().string().c_str());
+       bind_textdomain_codeset ("libdcpomatic", "UTF8");
+#endif 
+
+#ifdef DCPOMATIC_POSIX
+       bindtextdomain ("libdcpomatic", POSIX_LOCALE_PREFIX);
+#endif
 }
 
 /** @param s A string.
@@ -266,7 +342,7 @@ split_at_spaces_considering_quotes (string s)
        for (string::size_type i = 0; i < s.length(); ++i) {
                if (s[i] == ' ' && !in_quotes) {
                        out.push_back (c);
-                       c = "";
+                       c = N_("");
                } else if (s[i] == '"') {
                        in_quotes = !in_quotes;
                } else {
@@ -289,7 +365,7 @@ md5_digest (void const * data, int size)
        
        stringstream s;
        for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
-               s << hex << setfill('0') << setw(2) << ((int) digest[i]);
+               s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]);
        }
 
        return s.str ();
@@ -299,16 +375,16 @@ md5_digest (void const * data, int size)
  *  @return MD5 digest of file's contents.
  */
 string
-md5_digest (string file)
+md5_digest (boost::filesystem::path file)
 {
-       ifstream f (file.c_str(), ios::binary);
+       ifstream f (file.string().c_str(), std::ios::binary);
        if (!f.good ()) {
-               throw OpenFileError (file);
+               throw OpenFileError (file.string());
        }
        
-       f.seekg (0, ios::end);
+       f.seekg (0, std::ios::end);
        int bytes = f.tellg ();
-       f.seekg (0, ios::beg);
+       f.seekg (0, std::ios::beg);
 
        int const buffer_size = 64 * 1024;
        char buffer[buffer_size];
@@ -327,275 +403,197 @@ md5_digest (string file)
 
        stringstream s;
        for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
-               s << hex << setfill('0') << setw(2) << ((int) digest[i]);
+               s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]);
        }
 
        return s.str ();
 }
 
-/** @param fps Arbitrary frames-per-second value.
- *  @return DCPFrameRate for this frames-per-second.
- */
-DCPFrameRate
-dcp_frame_rate (float fps)
+/** @param job Optional job for which to report progress */
+string
+md5_digest_directory (boost::filesystem::path directory, shared_ptr<Job> job)
 {
-       DCPFrameRate dfr;
+       int const buffer_size = 64 * 1024;
+       char buffer[buffer_size];
 
-       dfr.run_fast = (fps != rint (fps));
-       dfr.frames_per_second = rint (fps);
-       dfr.skip = 1;
+       MD5_CTX md5_context;
+       MD5_Init (&md5_context);
 
-       /* XXX: somewhat arbitrary */
-       if (fps == 50) {
-               dfr.frames_per_second = 25;
-               dfr.skip = 2;
+       int files = 0;
+       if (job) {
+               for (boost::filesystem::directory_iterator i(directory); i != boost::filesystem::directory_iterator(); ++i) {
+                       ++files;
+               }
        }
 
-       return dfr;
-}
+       int j = 0;
+       for (boost::filesystem::directory_iterator i(directory); i != boost::filesystem::directory_iterator(); ++i) {
+               ifstream f (i->path().string().c_str(), std::ios::binary);
+               if (!f.good ()) {
+                       throw OpenFileError (i->path().string());
+               }
+       
+               f.seekg (0, std::ios::end);
+               int bytes = f.tellg ();
+               f.seekg (0, std::ios::beg);
+
+               while (bytes > 0) {
+                       int const t = min (bytes, buffer_size);
+                       f.read (buffer, t);
+                       MD5_Update (&md5_context, buffer, t);
+                       bytes -= t;
+               }
 
-/** @param An arbitrary sampling rate.
- *  @return The appropriate DCP-approved sampling rate (48kHz or 96kHz).
- */
-int
-dcp_audio_sample_rate (int fs)
-{
-       if (fs <= 48000) {
-               return 48000;
+               if (job) {
+                       job->set_progress (float (j) / files);
+                       ++j;
+               }
        }
 
-       return 96000;
-}
+       unsigned char digest[MD5_DIGEST_LENGTH];
+       MD5_Final (digest, &md5_context);
 
-int
-dcp_audio_channels (int f)
-{
-       if (f == 1) {
-               /* The source is mono, so to put the mono channel into
-                  the centre we need to generate a 5.1 soundtrack.
-               */
-               return 6;
+       stringstream s;
+       for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
+               s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]);
        }
 
-       return f;
+       return s.str ();
 }
 
-
-bool operator== (Size const & a, Size const & b)
+static bool
+about_equal (float a, float b)
 {
-       return (a.width == b.width && a.height == b.height);
-}
+       /* A film of F seconds at f FPS will be Ff frames;
+          Consider some delta FPS d, so if we run the same
+          film at (f + d) FPS it will last F(f + d) seconds.
 
-bool operator!= (Size const & a, Size const & b)
-{
-       return !(a == b);
-}
+          Hence the difference in length over the length of the film will
+          be F(f + d) - Ff frames
+           = Ff + Fd - Ff frames
+           = Fd frames
+           = Fd/f seconds
+          So if we accept a difference of 1 frame, ie 1/f seconds, we can
+          say that
 
-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);
-}
+          1/f = Fd/f
+       ie 1 = Fd
+       ie d = 1/F
+          So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
+          FPS error is 1/F ~= 0.0001 ~= 10-e4
+       */
 
-bool operator!= (Crop const & a, Crop const & b)
-{
-       return !(a == b);
+       return (fabs (a - b) < 1e-4);
 }
 
-/** @param index Colour LUT index.
- *  @return Human-readable name.
+/** @param An arbitrary audio frame rate.
+ *  @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
  */
-string
-colour_lut_index_to_name (int index)
+int
+dcp_audio_frame_rate (int fs)
 {
-       switch (index) {
-       case 0:
-               return "sRGB";
-       case 1:
-               return "Rec 709";
+       if (fs <= 48000) {
+               return 48000;
        }
 
-       assert (false);
-       return "";
+       return 96000;
 }
 
-Socket::Socket ()
+Socket::Socket (int timeout)
        : _deadline (_io_service)
        , _socket (_io_service)
-       , _buffer_data (0)
+       , _timeout (timeout)
 {
-       _deadline.expires_at (posix_time::pos_infin);
+       _deadline.expires_at (boost::posix_time::pos_infin);
        check ();
 }
 
 void
 Socket::check ()
 {
-       if (_deadline.expires_at() <= asio::deadline_timer::traits_type::now ()) {
+       if (_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now ()) {
                _socket.close ();
-               _deadline.expires_at (posix_time::pos_infin);
+               _deadline.expires_at (boost::posix_time::pos_infin);
        }
 
        _deadline.async_wait (boost::bind (&Socket::check, this));
 }
 
-/** Blocking connect with timeout.
+/** Blocking connect.
  *  @param endpoint End-point to connect to.
- *  @param timeout Time-out in seconds.
  */
 void
-Socket::connect (asio::ip::basic_resolver_entry<asio::ip::tcp> const & endpoint, int timeout)
+Socket::connect (boost::asio::ip::basic_resolver_entry<boost::asio::ip::tcp> const & endpoint)
 {
-       _deadline.expires_from_now (posix_time::seconds (timeout));
-       system::error_code ec = asio::error::would_block;
-       _socket.async_connect (endpoint, lambda::var(ec) = lambda::_1);
+       _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+       boost::system::error_code ec = boost::asio::error::would_block;
+       _socket.async_connect (endpoint, boost::lambda::var(ec) = boost::lambda::_1);
        do {
                _io_service.run_one();
-       } while (ec == asio::error::would_block);
+       } while (ec == boost::asio::error::would_block);
 
        if (ec || !_socket.is_open ()) {
-               throw NetworkError ("connect timed out");
+               throw NetworkError (_("connect timed out"));
        }
 }
 
-/** Blocking write with timeout.
+/** Blocking write.
  *  @param data Buffer to write.
  *  @param size Number of bytes to write.
- *  @param timeout Time-out, in seconds.
  */
 void
-Socket::write (uint8_t const * data, int size, int timeout)
+Socket::write (uint8_t const * data, int size)
 {
-       _deadline.expires_from_now (posix_time::seconds (timeout));
-       system::error_code ec = asio::error::would_block;
+       _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+       boost::system::error_code ec = boost::asio::error::would_block;
 
-       asio::async_write (_socket, asio::buffer (data, size), lambda::var(ec) = lambda::_1);
+       boost::asio::async_write (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
+       
        do {
                _io_service.run_one ();
-       } while (ec == asio::error::would_block);
-
-       if (ec) {
-               throw NetworkError ("write timed out");
-       }
-}
-
-/** Blocking read with timeout.
- *  @param data Buffer to read to.
- *  @param size Number of bytes to read.
- *  @param timeout Time-out, in seconds.
- */
-int
-Socket::read (uint8_t* data, int size, int timeout)
-{
-       _deadline.expires_from_now (posix_time::seconds (timeout));
-       system::error_code ec = asio::error::would_block;
+       } while (ec == boost::asio::error::would_block);
 
-       int amount_read = 0;
-
-       _socket.async_read_some (
-               asio::buffer (data, size),
-               (lambda::var(ec) = lambda::_1, lambda::var(amount_read) = lambda::_2)
-               );
-
-       do {
-               _io_service.run_one ();
-       } while (ec == asio::error::would_block);
-       
        if (ec) {
-               amount_read = 0;
+               throw NetworkError (ec.message ());
        }
-
-       return amount_read;
 }
 
-/** Mark some data as being `consumed', so that it will not be returned
- *  as data again.
- *  @param size Amount of data to consume, in bytes.
- */
 void
-Socket::consume (int size)
+Socket::write (uint32_t v)
 {
-       assert (_buffer_data >= size);
-       
-       _buffer_data -= size;
-       if (_buffer_data > 0) {
-               /* Shift still-valid data to the start of the buffer */
-               memmove (_buffer, _buffer + size, _buffer_data);
-       }
+       v = htonl (v);
+       write (reinterpret_cast<uint8_t*> (&v), 4);
 }
 
-/** Read a definite amount of data from our socket, and mark
- *  it as consumed.
- *  @param data Where to put the data.
+/** Blocking read.
+ *  @param data Buffer to read to.
  *  @param size Number of bytes to read.
  */
 void
-Socket::read_definite_and_consume (uint8_t* data, int size, int timeout)
-{
-       int const from_buffer = min (_buffer_data, size);
-       if (from_buffer > 0) {
-               /* Get data from our buffer */
-               memcpy (data, _buffer, from_buffer);
-               consume (from_buffer);
-               /* Update our output state */
-               data += from_buffer;
-               size -= from_buffer;
-       }
-
-       /* read() the rest */
-       while (size > 0) {
-               int const n = read (data, size, timeout);
-               if (n <= 0) {
-                       throw NetworkError ("could not read");
-               }
-
-               data += n;
-               size -= n;
-       }
-}
-
-/** Read as much data as is available, up to some limit.
- *  @param data Where to put the data.
- *  @param size Maximum amount of data to read.
- */
-void
-Socket::read_indefinite (uint8_t* data, int size, int timeout)
+Socket::read (uint8_t* data, int size)
 {
-       assert (size < int (sizeof (_buffer)));
+       _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+       boost::system::error_code ec = boost::asio::error::would_block;
 
-       /* Amount of extra data we need to read () */
-       int to_read = size - _buffer_data;
-       while (to_read > 0) {
-               /* read as much of it as we can (into our buffer) */
-               int const n = read (_buffer + _buffer_data, to_read, timeout);
-               if (n <= 0) {
-                       throw NetworkError ("could not read");
-               }
+       boost::asio::async_read (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
 
-               to_read -= n;
-               _buffer_data += n;
+       do {
+               _io_service.run_one ();
+       } while (ec == boost::asio::error::would_block);
+       
+       if (ec) {
+               throw NetworkError (ec.message ());
        }
-
-       assert (_buffer_data >= size);
-
-       /* copy data into the output buffer */
-       assert (size >= _buffer_data);
-       memcpy (data, _buffer, size);
 }
 
-/** @param other A Rect.
- *  @return The intersection of this with `other'.
- */
-Rect
-Rect::intersection (Rect const & other) const
+uint32_t
+Socket::read_uint32 ()
 {
-       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
-               );
+       uint32_t v;
+       read (reinterpret_cast<uint8_t *> (&v), 4);
+       return ntohl (v);
 }
 
 /** Round a number up to the nearest multiple of another number.
@@ -611,12 +609,6 @@ stride_round_up (int c, int const * stride, int t)
        return a - (a % t);
 }
 
-int
-stride_lookup (int c, int const * stride)
-{
-       return stride[c];
-}
-
 /** Read a sequence of key / value pairs from a text stream;
  *  the keys are the first words on the line, and the values are
  *  the remainder of the line following the key.  Lines beginning
@@ -658,13 +650,13 @@ string
 get_required_string (multimap<string, string> const & kv, string k)
 {
        if (kv.count (k) > 1) {
-               throw StringError ("unexpected multiple keys in key-value set");
+               throw StringError (N_("unexpected multiple keys in key-value set"));
        }
 
        multimap<string, string>::const_iterator i = kv.find (k);
        
        if (i == kv.end ()) {
-               throw StringError (String::compose ("missing key %1 in key-value set", k));
+               throw StringError (String::compose (_("missing key %1 in key-value set"), k));
        }
 
        return i->second;
@@ -688,12 +680,12 @@ string
 get_optional_string (multimap<string, string> const & kv, string k)
 {
        if (kv.count (k) > 1) {
-               throw StringError ("unexpected multiple keys in key-value set");
+               throw StringError (N_("unexpected multiple keys in key-value set"));
        }
 
        multimap<string, string>::const_iterator i = kv.find (k);
        if (i == kv.end ()) {
-               return "";
+               return N_("");
        }
 
        return i->second;
@@ -703,7 +695,7 @@ int
 get_optional_int (multimap<string, string> const & kv, string k)
 {
        if (kv.count (k) > 1) {
-               throw StringError ("unexpected multiple keys in key-value set");
+               throw StringError (N_("unexpected multiple keys in key-value set"));
        }
 
        multimap<string, string>::const_iterator i = kv.find (k);
@@ -714,193 +706,97 @@ 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));
-       }
-}
-
-/** AudioBuffers destructor */
-AudioBuffers::~AudioBuffers ()
+/** Trip an assert if the caller is not in the UI thread */
+void
+ensure_ui_thread ()
 {
-       for (int i = 0; i < _channels; ++i) {
-               delete[] _data[i];
-       }
-
-       delete[] _data;
+       assert (boost::this_thread::get_id() == ui_thread);
 }
 
-/** @param c Channel index.
- *  @return Buffer for this channel.
+/** @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'.
  */
-float*
-AudioBuffers::data (int c) const
+int64_t
+video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
 {
-       assert (c >= 0 && c < _channels);
-       return _data[c];
+       return ((int64_t) v * audio_sample_rate / frames_per_second);
 }
 
-/** 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)
+string
+audio_channel_name (int c)
 {
-       assert (f <= _allocated_frames);
-       _frames = f;
-}
+       assert (MAX_AUDIO_CHANNELS == 6);
 
-/** Make all samples on all channels silent */
-void
-AudioBuffers::make_silent ()
-{
-       for (int i = 0; i < _channels; ++i) {
-               make_silent (i);
-       }
+       /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
+          enhancement channel (sub-woofer)./
+       */
+       string const channels[] = {
+               _("Left"),
+               _("Right"),
+               _("Centre"),
+               _("Lfe (sub)"),
+               _("Left surround"),
+               _("Right surround"),
+       };
+
+       return channels[c];
 }
 
-/** Make all samples on a given channel silent.
- *  @param c Channel.
- */
-void
-AudioBuffers::make_silent (int c)
+FrameRateConversion::FrameRateConversion (float source, int dcp)
+       : skip (false)
+       , repeat (false)
+       , change_speed (false)
 {
-       assert (c >= 0 && c < _channels);
-       
-       for (int i = 0; i < _frames; ++i) {
-               _data[c][i] = 0;
+       if (fabs (source / 2.0 - dcp) < (fabs (source - dcp))) {
+               skip = true;
+       } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) {
+               repeat = true;
        }
-}
 
-/** 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());
+       change_speed = !about_equal (source * factor(), dcp);
 
-       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);
+       if (!skip && !repeat && !change_speed) {
+               description = _("Content and DCP have the same rate.\n");
+       } else {
+               if (skip) {
+                       description = _("DCP will use every other frame of the content.\n");
+               } else if (repeat) {
+                       description = _("Each content frame will be doubled in the DCP.\n");
+               }
 
-       for (int i = 0; i < _channels; ++i) {
-               memcpy (_data[i] + write_offset, from->_data[i] + read_offset, frames_to_copy * sizeof(float));
+               if (change_speed) {
+                       float const pc = dcp * 100 / (source * factor());
+                       description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc);
+               }
        }
 }
 
-/** 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)
+LocaleGuard::LocaleGuard ()
+       : _old (0)
 {
-       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));
-       }
-}
+       char const * old = setlocale (LC_NUMERIC, 0);
 
-/** Trip an assert if the caller is not in the UI thread */
-void
-ensure_ui_thread ()
-{
-       assert (this_thread::get_id() == ui_thread);
+       if (old) {
+               _old = strdup (old);
+               if (strcmp (_old, "C")) {
+                       setlocale (LC_NUMERIC, "C");
+               }
+       }
 }
 
-/** @param v Source 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)
+LocaleGuard::~LocaleGuard ()
 {
-       return ((int64_t) v * audio_sample_rate / frames_per_second);
+       setlocale (LC_NUMERIC, _old);
+       free (_old);
 }
 
-/** @param f Filename.
- *  @return true if this file is a still image, false if it is something else.
- */
 bool
-still_image_file (string f)
+valid_image_file (boost::filesystem::path f)
 {
-#if BOOST_FILESYSTEM_VERSION == 3
-       string ext = boost::filesystem::path(f).extension().string();
-#else
-       string ext = boost::filesystem::path(f).extension();
-#endif
-
+       string ext = f.extension().string();
        transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
-       
-       return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png");
+       return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp" || ext == ".tga");
 }
 
-/** @return A pair containing CPU model name and the number of processors */
-pair<string, int>
-cpu_info ()
-{
-       pair<string, int> info;
-       info.second = 0;
-       
-#ifdef DVDOMATIC_POSIX
-       ifstream f ("/proc/cpuinfo");
-       while (f.good ()) {
-               string l;
-               getline (f, l);
-               if (boost::algorithm::starts_with (l, "model name")) {
-                       string::size_type const c = l.find (':');
-                       if (c != string::npos) {
-                               info.first = l.substr (c + 2);
-                       }
-               } else if (boost::algorithm::starts_with (l, "processor")) {
-                       ++info.second;
-               }
-       }
-#endif 
-
-       return info;
-}
index 024c40fb555f32b4aa3045bbcb944682629fb749..a83426206f88e7398423a152b408573cb31066cb 100644 (file)
  *  @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>
 #include <boost/shared_ptr.hpp>
 #include <boost/asio.hpp>
+#include <boost/optional.hpp>
+#include <boost/filesystem.hpp>
+#include <libdcp/util.h>
 extern "C" {
 #include <libavcodec/avcodec.h>
 #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(...)
 #endif
 
+#undef check
+
 /** The maximum number of audio channels that we can cope with */
 #define MAX_AUDIO_CHANNELS 6
 
-class Scaler;
+class Job;
 
 extern std::string seconds_to_hms (int);
 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 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_directory (boost::filesystem::path, boost::shared_ptr<Job>);
 extern std::string md5_digest (void const *, int);
 extern void ensure_ui_thread ();
+extern std::string audio_channel_name (int);
+extern bool valid_image_file (boost::filesystem::path);
+#ifdef DCPOMATIC_WINDOWS
+extern boost::filesystem::path mo_path ();
+#endif
 
-typedef int SourceFrame;
-
-struct DCPFrameRate
-{
-       /** frames per second for the DCP */
-       int frames_per_second;
-       /** Skip every `skip' frames.  e.g. if this is 1, we skip nothing;
-        *  if it's 2, we skip every other frame.
-        */
-       int skip;
-       /** true if this DCP will run its video faster than the source
-        *  (e.g. if the source is 29.97fps and we will run the DCP at 30fps)
-        */
-       bool run_fast;
-};
-
-enum ContentType {
-       STILL, ///< content is still images
-       VIDEO  ///< content is a video
-};
-
-/** @class Size
- *  @brief Representation of the size of something */
-struct Size
-{
-       /** Construct a zero Size */
-       Size ()
-               : width (0)
-               , height (0)
-       {}
-
-       /** @param w Width.
-        *  @param h Height.
-        */
-       Size (int w, int h)
-               : width (w)
-               , height (h)
-       {}
-
-       /** width */
-       int width;
-       /** height */
-       int height;
-};
-
-extern bool operator== (Size const & a, Size const & b);
-extern bool operator!= (Size const & a, Size const & b);
-
-/** @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;
-};
-
-/** @struct Rect
- *  @brief A rectangle.
- */
-struct Rect
+struct FrameRateConversion
 {
-       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);
+       FrameRateConversion (float, int);
+
+       /** @return factor by which to multiply a source frame rate
+           to get the effective rate after any skip or repeat has happened.
+       */
+       float factor () const {
+               if (skip) {
+                       return 0.5;
+               } else if (repeat) {
+                       return 2;
+               }
+
+               return 1;
        }
 
-       Size size () const {
-               return Size (width, height);
-       }
+       /** true to skip every other frame */
+       bool skip;
+       /** true to repeat every frame once */
+       bool repeat;
+       /** true if this DCP will run its video faster or slower than the source
+        *  without taking into account `repeat' nor `skip'.
+        *  (e.g. change_speed will be true if
+        *          source is 29.97fps, DCP is 30fps
+        *          source is 14.50fps, DCP is 30fps
+        *  but not if
+        *          source is 15.00fps, DCP is 30fps
+        *          source is 12.50fps, DCP is 25fps)
+        */
+       bool change_speed;
 
-       Rect intersection (Rect const & other) const;
+       std::string description;
 };
 
-extern std::string crop_string (Position, Size);
-extern int dcp_audio_sample_rate (int);
-extern DCPFrameRate dcp_frame_rate (float);
-extern int dcp_audio_channels (int);
-extern std::string colour_lut_index_to_name (int index);
+extern int dcp_audio_frame_rate (int);
 extern int stride_round_up (int, int const *, int);
-extern int stride_lookup (int c, int const * stride);
 extern std::multimap<std::string, std::string> read_key_value (std::istream& s);
 extern int get_required_int (std::multimap<std::string, std::string> const & kv, std::string k);
 extern float get_required_float (std::multimap<std::string, std::string> const & kv, std::string k);
@@ -197,90 +117,52 @@ 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, and the ability to peek into
- *  data being read.
+ *  most notably, sync read/write calls with timeouts.
  */
 class Socket
 {
 public:
-       Socket ();
+       Socket (int timeout = 30);
 
        /** @return Our underlying socket */
        boost::asio::ip::tcp::socket& socket () {
                return _socket;
        }
 
-       void connect (boost::asio::ip::basic_resolver_entry<boost::asio::ip::tcp> const & endpoint, int timeout);
-       void write (uint8_t const * data, int size, int timeout);
+       void connect (boost::asio::ip::basic_resolver_entry<boost::asio::ip::tcp> const & endpoint);
+
+       void write (uint32_t n);
+       void write (uint8_t const * data, int size);
        
-       void read_definite_and_consume (uint8_t* data, int size, int timeout);
-       void read_indefinite (uint8_t* data, int size, int timeout);
-       void consume (int amount);
+       void read (uint8_t* data, int size);
+       uint32_t read_uint32 ();
        
 private:
        void check ();
-       int read (uint8_t* data, int size, int timeout);
 
        Socket (Socket const &);
 
        boost::asio::io_service _io_service;
        boost::asio::deadline_timer _deadline;
        boost::asio::ip::tcp::socket _socket;
-       /** a buffer for small reads */
-       uint8_t _buffer[1024];
-       /** amount of valid data in the buffer */
-       int _buffer_data;
+       int _timeout;
 };
 
-/** @class AudioBuffers
- *  @brief A class to hold multi-channel audio data in float format.
- */
-class AudioBuffers
+extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
+
+class LocaleGuard
 {
 public:
-       AudioBuffers (int channels, int frames);
-       AudioBuffers (AudioBuffers const &);
-       ~AudioBuffers ();
-
-       float** data () const {
-               return _data;
-       }
+       LocaleGuard ();
+       ~LocaleGuard ();
        
-       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;
+       char* _old;
 };
 
-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 std::pair<std::string, int> cpu_info ();
 
 #endif
 
index 71639e3bc60a2590dce5ffa70f9c1c827c25c701..b70be8343ec11b4fe86bc9e16c74b6604f5d3fa1 100644 (file)
@@ -1,3 +1,4 @@
 
-extern char const * dvdomatic_version;
-extern char const * dvdomatic_git_commit;
+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..3f6e171
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+    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 "config.h"
+#include "colour_conversion.h"
+
+#include "i18n.h"
+
+int const VideoContentProperty::VIDEO_SIZE       = 0;
+int const VideoContentProperty::VIDEO_FRAME_RATE  = 1;
+int const VideoContentProperty::VIDEO_FRAME_TYPE  = 2;
+int const VideoContentProperty::VIDEO_CROP       = 3;
+int const VideoContentProperty::VIDEO_RATIO      = 4;
+int const VideoContentProperty::COLOUR_CONVERSION = 5;
+
+using std::string;
+using std::stringstream;
+using std::setprecision;
+using std::cout;
+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)
+       , _video_frame_type (VIDEO_FRAME_TYPE_2D)
+       , _ratio (Ratio::from_id ("185"))
+       , _colour_conversion (Config::instance()->colour_conversions().front().conversion)
+{
+
+}
+
+VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p)
+       : Content (f, p)
+       , _video_length (0)
+       , _video_frame_rate (0)
+       , _video_frame_type (VIDEO_FRAME_TYPE_2D)
+       , _ratio (Ratio::from_id ("185"))
+       , _colour_conversion (Config::instance()->colour_conversions().front().conversion)
+{
+
+}
+
+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");
+       _video_frame_type = static_cast<VideoFrameType> (node->number_child<int> ("VideoFrameType"));
+       _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 ());
+       }
+       _colour_conversion = ColourConversion (node->node_child ("ColourConversion"));
+}
+
+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("VideoFrameType")->add_child_text (lexical_cast<string> (static_cast<int> (_video_frame_type)));
+       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 ());
+       }
+       _colour_conversion.as_xml (node->add_child("ColourConversion"));
+}
+
+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_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);
+}
+
+/** @return string which includes everything about how this content looks */
+string
+VideoContent::identifier () const
+{
+       stringstream s;
+       s << Content::digest()
+         << "_" << crop().left
+         << "_" << crop().right
+         << "_" << crop().top
+         << "_" << crop().bottom
+         << "_" << colour_conversion().identifier ();
+
+       if (ratio()) {
+               s << "_" << ratio()->id ();
+       }
+
+       return s.str ();
+}
+
+void
+VideoContent::set_video_frame_type (VideoFrameType t)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _video_frame_type = t;
+       }
+
+       signal_changed (VideoContentProperty::VIDEO_FRAME_TYPE);
+}
+
+string
+VideoContent::technical_summary () const
+{
+       return String::compose ("video: length %1, size %2x%3, rate %4", video_length(), video_size().width, video_size().height, video_frame_rate());
+}
+
+libdcp::Size
+VideoContent::video_size_after_3d_split () const
+{
+       libdcp::Size const s = video_size ();
+       switch (video_frame_type ()) {
+       case VIDEO_FRAME_TYPE_2D:
+               return s;
+       case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
+               return libdcp::Size (s.width / 2, s.height);
+       }
+
+       assert (false);
+}
+
+void
+VideoContent::set_colour_conversion (ColourConversion c)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _colour_conversion = c;
+       }
+
+       signal_changed (VideoContentProperty::COLOUR_CONVERSION);
+}
diff --git a/src/lib/video_content.h b/src/lib/video_content.h
new file mode 100644 (file)
index 0000000..72c7262
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+    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"
+#include "colour_conversion.h"
+
+class VideoExaminer;
+class Ratio;
+
+class VideoContentProperty
+{
+public:
+       static int const VIDEO_SIZE;
+       static int const VIDEO_FRAME_RATE;
+       static int const VIDEO_FRAME_TYPE;
+       static int const VIDEO_CROP;
+       static int const VIDEO_RATIO;
+       static int const COLOUR_CONVERSION;
+};
+
+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>);
+
+       void as_xml (xmlpp::Node *) const;
+       std::string technical_summary () const;
+       virtual std::string information () const;
+       virtual std::string identifier () 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_video_frame_type (VideoFrameType);
+
+       void set_left_crop (int);
+       void set_right_crop (int);
+       void set_top_crop (int);
+       void set_bottom_crop (int);
+
+       void set_colour_conversion (ColourConversion);
+
+       VideoFrameType video_frame_type () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _video_frame_type;
+       }
+
+       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;
+       }
+
+       ColourConversion colour_conversion () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _colour_conversion;
+       }
+
+       libdcp::Size video_size_after_3d_split () const;
+
+protected:
+       void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
+
+       VideoContent::Frame _video_length;
+
+private:
+       friend class ffmpeg_pts_offset_test;
+       friend class best_dcp_frame_rate_test_single;
+       friend class best_dcp_frame_rate_test_double;
+       friend class audio_sampling_rate_test;
+       
+       libdcp::Size _video_size;
+       float _video_frame_rate;
+       VideoFrameType _video_frame_type;
+       Crop _crop;
+       Ratio const * _ratio;
+       ColourConversion _colour_conversion;
+};
+
+#endif
index e0a7576eeaf5c51670cf1702c6b7b4597467db15..eaa4534e44b64ab6c6e503b06bfae280e8dde08e 100644 (file)
 */
 
 #include "video_decoder.h"
-#include "subtitle.h"
-#include "film.h"
 #include "image.h"
-#include "log.h"
-#include "options.h"
-#include "job.h"
 
+#include "i18n.h"
+
+using std::cout;
 using boost::shared_ptr;
-using boost::optional;
 
-VideoDecoder::VideoDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
-       : Decoder (f, o, j)
-       , _video_frame (0)
-       , _last_source_time (0)
+VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
+       : Decoder (f)
+       , _video_content (c)
+       , _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, 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 ();
+       switch (_video_content->video_frame_type ()) {
+       case VIDEO_FRAME_TYPE_2D:
+               Video (image, EYES_BOTH, same, frame);
+               break;
+       case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
+       {
+               int const half = image->size().width / 2;
+               Video (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame);
+               Video (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame);
+               break;
        }
-
-       signal_video (image, false, sub);
-       _last_source_time = t;
-}
-
-void
-VideoDecoder::repeat_last_video ()
-{
-       if (!_last_image) {
-               _last_image.reset (new SimpleImage (pixel_format(), native_size(), false));
-               _last_image->make_black ();
        }
-
-       signal_video (_last_image, true, _last_subtitle);
-}
-
-void
-VideoDecoder::signal_video (shared_ptr<Image> image, bool same, shared_ptr<Subtitle> sub)
-{
-       TIMING ("Decoder emits %1", _video_frame);
-       Video (image, same, sub);
-       ++_video_frame;
-
-       _last_image = image;
-       _last_subtitle = sub;
-}
-
-void
-VideoDecoder::emit_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));
-       }
-}
-
-void
-VideoDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
-       _subtitle_stream = s;
+       _video_position = frame + 1;
 }
 
-void
-VideoDecoder::set_progress () const
-{
-       if (_job && _film->length()) {
-               _job->set_progress (float (_video_frame) / _film->length().get());
-       }
-}
index 7726d2057fe9b3eec7c5a20914d4b159ef4ce071..142320a049c8e7a23bf7aff2d618149d6259941d 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 <boost/signals2.hpp>
+#include <boost/shared_ptr.hpp>
 #include "decoder.h"
+#include "video_content.h"
+#include "util.h"
 
-class VideoDecoder : public VideoSource, public virtual Decoder
+class VideoContent;
+class Image;
+
+class VideoDecoder : public virtual Decoder
 {
 public:
-       VideoDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
-
-       /** @return video frames per second, or 0 if unknown */
-       virtual float frames_per_second () const = 0;
-       /** @return native size in pixels */
-       virtual 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;
-
-       virtual void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
-
-       void set_progress () const;
+       VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
+
+       /** Seek so that the next pass() will yield (approximately) the requested frame.
+        *  Pass accurate = true to try harder to get close to the request.
+        */
+       virtual void seek (VideoContent::Frame frame, bool accurate) = 0;
+
+       /** Emitted when a video frame is ready.
+        *  First parameter is the video image.
+        *  Second parameter is the eye(s) which should see this image.
+        *  Third parameter is true if the image is the same as the last one that was emitted for this Eyes value.
+        *  Fourth parameter is the frame within our source.
+        */
+       boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, 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>, double);
-       void emit_subtitle (boost::shared_ptr<TimedSubtitle>);
-       void repeat_last_video ();
-
-       /** 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:
-       void signal_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>);
-
-       int _video_frame;
-       double _last_source_time;
-       
-       boost::shared_ptr<TimedSubtitle> _timed_subtitle;
 
-       boost::shared_ptr<Image> _last_image;
-       boost::shared_ptr<Subtitle> _last_subtitle;
+       void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+       boost::shared_ptr<const VideoContent> _video_content;
+       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..039c494
--- /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 <libdcp/types.h>
+#include "types.h"
+#include "video_content.h"
+
+class VideoExaminer
+{
+public:
+       virtual ~VideoExaminer () {}
+       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 7c128cf..0000000
+++ /dev/null
@@ -1,40 +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<Image> i, bool same, boost::shared_ptr<Subtitle> s) = 0;
-};
-
-#endif
diff --git a/src/lib/video_source.cc b/src/lib/video_source.cc
deleted file mode 100644 (file)
index 56742e2..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.
-
-*/
-
-#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));
-}
diff --git a/src/lib/video_source.h b/src/lib/video_source.h
deleted file mode 100644 (file)
index 8936291..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.
-
-*/
-
-/** @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 Subtitle;
-class Image;
-
-/** @class VideoSink
- *  @param A class that emits video data.
- */
-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<Image>, bool, boost::shared_ptr<Subtitle>)> Video;
-
-       void connect_video (boost::shared_ptr<VideoSink>);
-};
-
-#endif
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
new file mode 100644 (file)
index 0000000..5f94d5d
--- /dev/null
@@ -0,0 +1,525 @@
+/*
+    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 <fstream>
+#include <cerrno>
+#include <libdcp/picture_asset.h>
+#include <libdcp/sound_asset.h>
+#include <libdcp/picture_frame.h>
+#include <libdcp/reel.h>
+#include <libdcp/dcp.h>
+#include <libdcp/cpl.h>
+#include "writer.h"
+#include "compose.hpp"
+#include "film.h"
+#include "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"
+
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::ifstream;
+using std::list;
+using std::cout;
+using boost::shared_ptr;
+
+int const Writer::_maximum_frames_in_memory = 8;
+
+Writer::Writer (shared_ptr<const Film> f, shared_ptr<Job> j)
+       : _film (f)
+       , _job (j)
+       , _first_nonexistant_frame (0)
+       , _thread (0)
+       , _finish (false)
+       , _queued_full_in_memory (0)
+       , _last_written_frame (-1)
+       , _last_written_eyes (EYES_RIGHT)
+       , _full_written (0)
+       , _fake_written (0)
+       , _repeat_written (0)
+       , _pushed_to_disk (0)
+{
+       /* Remove any old DCP */
+       boost::filesystem::remove_all (_film->dir (_film->dcp_name ()));
+       
+       check_existing_picture_mxf ();
+       
+       /* Create our picture asset in a subdirectory, named according to those
+          film's parameters which affect the video output.  We will hard-link
+          it into the DCP later.
+       */
+
+       if (f->three_d ()) {
+               _picture_asset.reset (
+                       new libdcp::StereoPictureAsset (
+                               _film->internal_video_mxf_dir (),
+                               _film->internal_video_mxf_filename (),
+                               _film->video_frame_rate (),
+                               _film->container()->size (_film->full_frame ())
+                               )
+                       );
+               
+       } else {
+               _picture_asset.reset (
+                       new libdcp::MonoPictureAsset (
+                               _film->internal_video_mxf_dir (),
+                               _film->internal_video_mxf_filename (),
+                               _film->video_frame_rate (),
+                               _film->container()->size (_film->full_frame ())
+                               )
+                       );
+
+       }
+
+       _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0, _film->interop ());
+       
+       _sound_asset.reset (
+               new libdcp::SoundAsset (
+                       _film->dir (_film->dcp_name()),
+                       _film->audio_mxf_filename (),
+                       _film->video_frame_rate (),
+                       _film->audio_channels (),
+                       _film->audio_frame_rate ()
+                       )
+               );
+       
+       _sound_asset_writer = _sound_asset->start_write (_film->interop ());
+
+       _thread = new boost::thread (boost::bind (&Writer::thread, this));
+
+       _job->descend (0.9);
+}
+
+void
+Writer::write (shared_ptr<const EncodedData> encoded, int frame, Eyes eyes)
+{
+       boost::mutex::scoped_lock lock (_mutex);
+
+       QueueItem qi;
+       qi.type = QueueItem::FULL;
+       qi.encoded = encoded;
+       qi.frame = frame;
+
+       if (_film->three_d() && eyes == EYES_BOTH) {
+               /* 2D material in a 3D DCP; fake the 3D */
+               qi.eyes = EYES_LEFT;
+               _queue.push_back (qi);
+               ++_queued_full_in_memory;
+               qi.eyes = EYES_RIGHT;
+               _queue.push_back (qi);
+               ++_queued_full_in_memory;
+       } else {
+               qi.eyes = eyes;
+               _queue.push_back (qi);
+               ++_queued_full_in_memory;
+       }
+       
+       _condition.notify_all ();
+}
+
+void
+Writer::fake_write (int frame, Eyes eyes)
+{
+       boost::mutex::scoped_lock lock (_mutex);
+
+       ifstream ifi (_film->info_path (frame, eyes).c_str());
+       libdcp::FrameInfo info (ifi);
+       
+       QueueItem qi;
+       qi.type = QueueItem::FAKE;
+       qi.size = info.size;
+       qi.frame = frame;
+       if (_film->three_d() && eyes == EYES_BOTH) {
+               qi.eyes = EYES_LEFT;
+               _queue.push_back (qi);
+               qi.eyes = EYES_RIGHT;
+               _queue.push_back (qi);
+       } else {
+               qi.eyes = eyes;
+               _queue.push_back (qi);
+       }
+
+       _condition.notify_all ();
+}
+
+/** This method is not thread safe */
+void
+Writer::write (shared_ptr<const AudioBuffers> audio)
+{
+       _sound_asset_writer->write (audio->data(), audio->frames());
+}
+
+/** This must be called from Writer::thread() with an appropriate lock held,
+ *  and with _queue sorted.
+ */
+bool
+Writer::have_sequenced_image_at_queue_head () const
+{
+       if (_queue.empty ()) {
+               return false;
+       }
+
+       /* The queue should contain only EYES_LEFT/EYES_RIGHT pairs or EYES_BOTH */
+
+       if (_queue.front().eyes == EYES_BOTH) {
+               /* 2D */
+               return _queue.front().frame == (_last_written_frame + 1);
+       }
+
+       /* 3D */
+
+       if (_last_written_eyes == EYES_LEFT && _queue.front().frame == _last_written_frame && _queue.front().eyes == EYES_RIGHT) {
+               return true;
+       }
+
+       if (_last_written_eyes == EYES_RIGHT && _queue.front().frame == (_last_written_frame + 1) && _queue.front().eyes == EYES_LEFT) {
+               return true;
+       }
+
+       return false;
+}
+
+void
+Writer::thread ()
+try
+{
+       while (1)
+       {
+               boost::mutex::scoped_lock lock (_mutex);
+
+               while (1) {
+                       
+                       _queue.sort ();
+                       
+                       if (_finish || _queued_full_in_memory > _maximum_frames_in_memory || have_sequenced_image_at_queue_head ()) {
+                               break;
+                       }
+
+                       TIMING (N_("writer sleeps with a queue of %1"), _queue.size());
+                       _condition.wait (lock);
+                       TIMING (N_("writer wakes with a queue of %1"), _queue.size());
+               }
+
+               if (_finish && _queue.empty()) {
+                       return;
+               }
+
+               /* Write any frames that we can write; i.e. those that are in sequence */
+               while (have_sequenced_image_at_queue_head ()) {
+                       QueueItem qi = _queue.front ();
+                       _queue.pop_front ();
+                       if (qi.type == QueueItem::FULL && qi.encoded) {
+                               --_queued_full_in_memory;
+                       }
+
+                       lock.unlock ();
+                       switch (qi.type) {
+                       case QueueItem::FULL:
+                       {
+                               _film->log()->log (String::compose (N_("Writer FULL-writes %1 to MXF"), qi.frame));
+                               if (!qi.encoded) {
+                                       qi.encoded.reset (new EncodedData (_film->j2c_path (qi.frame, qi.eyes, false)));
+                               }
+
+                               libdcp::FrameInfo fin = _picture_asset_writer->write (qi.encoded->data(), qi.encoded->size());
+                               qi.encoded->write_info (_film, qi.frame, qi.eyes, fin);
+                               _last_written[qi.eyes] = qi.encoded;
+                               ++_full_written;
+                               break;
+                       }
+                       case QueueItem::FAKE:
+                               _film->log()->log (String::compose (N_("Writer FAKE-writes %1 to MXF"), qi.frame));
+                               _picture_asset_writer->fake_write (qi.size);
+                               _last_written[qi.eyes].reset ();
+                               ++_fake_written;
+                               break;
+                       case QueueItem::REPEAT:
+                       {
+                               _film->log()->log (String::compose (N_("Writer REPEAT-writes %1 to MXF"), qi.frame));
+                               libdcp::FrameInfo fin = _picture_asset_writer->write (
+                                       _last_written[qi.eyes]->data(),
+                                       _last_written[qi.eyes]->size()
+                                       );
+                               
+                               _last_written[qi.eyes]->write_info (_film, qi.frame, qi.eyes, fin);
+                               ++_repeat_written;
+                               break;
+                       }
+                       }
+                       lock.lock ();
+
+                       _last_written_frame = qi.frame;
+                       _last_written_eyes = qi.eyes;
+                       
+                       if (_film->length()) {
+                               _job->set_progress (
+                                       float (_full_written + _fake_written + _repeat_written) / _film->time_to_video_frames (_film->length())
+                                       );
+                       }
+               }
+
+               while (_queued_full_in_memory > _maximum_frames_in_memory) {
+                       /* Too many frames in memory which can't yet be written to the stream.
+                          Write some FULL frames to disk.
+                       */
+
+                       /* Find one */
+                       list<QueueItem>::reverse_iterator i = _queue.rbegin ();
+                       while (i != _queue.rend() && (i->type != QueueItem::FULL || !i->encoded)) {
+                               ++i;
+                       }
+
+                       assert (i != _queue.rend());
+                       QueueItem qi = *i;
+
+                       ++_pushed_to_disk;
+                       
+                       lock.unlock ();
+
+                       _film->log()->log (
+                               String::compose (
+                                       "Writer full (awaiting %1 [last eye was %2]); pushes %3 to disk",
+                                       _last_written_frame + 1,
+                                       _last_written_eyes, qi.frame)
+                               );
+                       
+                       qi.encoded->write (_film, qi.frame, qi.eyes);
+                       lock.lock ();
+                       qi.encoded.reset ();
+                       --_queued_full_in_memory;
+               }
+       }
+}
+catch (...)
+{
+       store_current ();
+}
+
+void
+Writer::finish ()
+{
+       if (!_thread) {
+               return;
+       }
+       
+       boost::mutex::scoped_lock lock (_mutex);
+       _finish = true;
+       _condition.notify_all ();
+       lock.unlock ();
+
+       _thread->join ();
+       if (thrown ()) {
+               rethrow ();
+       }
+       
+       delete _thread;
+       _thread = 0;
+
+       _picture_asset_writer->finalize ();
+       _sound_asset_writer->finalize ();
+       
+       int const frames = _last_written_frame + 1;
+
+       _picture_asset->set_duration (frames);
+
+       /* Hard-link the video MXF into the DCP */
+
+       boost::filesystem::path from;
+       from /= _film->internal_video_mxf_dir();
+       from /= _film->internal_video_mxf_filename();
+       
+       boost::filesystem::path to;
+       to /= _film->dir (_film->dcp_name());
+       to /= _film->video_mxf_filename ();
+
+       boost::system::error_code ec;
+       boost::filesystem::create_hard_link (from, to, ec);
+       if (ec) {
+               /* hard link failed; copy instead */
+               boost::filesystem::copy_file (from, to);
+               _film->log()->log ("Hard-link failed; fell back to copying");
+       }
+
+       /* And update the asset */
+
+       _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
+       _picture_asset->set_file_name (_film->video_mxf_filename ());
+       _sound_asset->set_duration (frames);
+       
+       libdcp::DCP dcp (_film->dir (_film->dcp_name()));
+
+       shared_ptr<libdcp::CPL> cpl (
+               new libdcp::CPL (
+                       _film->dir (_film->dcp_name()),
+                       _film->dcp_name(),
+                       _film->dcp_content_type()->libdcp_kind (),
+                       frames,
+                       _film->video_frame_rate ()
+                       )
+               );
+       
+       dcp.add_cpl (cpl);
+
+       cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
+                                                        _picture_asset,
+                                                        _sound_asset,
+                                                        shared_ptr<libdcp::SubtitleAsset> ()
+                                                        )
+                              ));
+
+       /* Compute the digests for the assets now so that we can keep track of progress.
+          We did _job->descend (0.9) in our constructor */
+       _job->ascend ();
+
+       _job->descend (0.1);
+       _picture_asset->compute_digest (boost::bind (&Job::set_progress, _job.get(), _1));
+       _job->ascend ();
+
+       _job->descend (0.1);
+       _sound_asset->compute_digest (boost::bind (&Job::set_progress, _job.get(), _1));
+       _job->ascend ();
+
+       libdcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
+       meta.set_issue_date_now ();
+       dcp.write_xml (_film->interop (), meta);
+
+       _film->log()->log (String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk));
+}
+
+/** Tell the writer that frame `f' should be a repeat of the frame before it */
+void
+Writer::repeat (int f, Eyes e)
+{
+       boost::mutex::scoped_lock lock (_mutex);
+
+       QueueItem qi;
+       qi.type = QueueItem::REPEAT;
+       qi.frame = f;
+       if (_film->three_d() && e == EYES_BOTH) {
+               qi.eyes = EYES_LEFT;
+               _queue.push_back (qi);
+               qi.eyes = EYES_RIGHT;
+               _queue.push_back (qi);
+       } else {
+               qi.eyes = e;
+               _queue.push_back (qi);
+       }
+
+       _condition.notify_all ();
+}
+
+bool
+Writer::check_existing_picture_mxf_frame (FILE* mxf, int f, Eyes eyes)
+{
+       /* Read the frame info as written */
+       ifstream ifi (_film->info_path (f, eyes).c_str());
+       libdcp::FrameInfo info (ifi);
+       if (info.size == 0) {
+               _film->log()->log (String::compose ("Existing frame %1 has no info file", f));
+               return false;
+       }
+       
+       /* Read the data from the MXF and hash it */
+       fseek (mxf, info.offset, SEEK_SET);
+       EncodedData data (info.size);
+       size_t const read = fread (data.data(), 1, data.size(), mxf);
+       if (read != static_cast<size_t> (data.size ())) {
+               _film->log()->log (String::compose ("Existing frame %1 is incomplete", f));
+               return false;
+       }
+       
+       string const existing_hash = md5_digest (data.data(), data.size());
+       if (existing_hash != info.hash) {
+               _film->log()->log (String::compose ("Existing frame %1 failed hash check", f));
+               return false;
+       }
+
+       return true;
+}
+
+void
+Writer::check_existing_picture_mxf ()
+{
+       /* Try to open the existing MXF */
+       boost::filesystem::path p;
+       p /= _film->internal_video_mxf_dir ();
+       p /= _film->internal_video_mxf_filename ();
+       FILE* mxf = fopen (p.string().c_str(), "rb");
+       if (!mxf) {
+               _film->log()->log (String::compose ("Could not open existing MXF at %1 (errno=%2)", p.string(), errno));
+               return;
+       }
+
+       while (1) {
+
+               if (_film->three_d ()) {
+                       if (!check_existing_picture_mxf_frame (mxf, _first_nonexistant_frame, EYES_LEFT)) {
+                               break;
+                       }
+                       if (!check_existing_picture_mxf_frame (mxf, _first_nonexistant_frame, EYES_RIGHT)) {
+                               break;
+                       }
+               } else {
+                       if (!check_existing_picture_mxf_frame (mxf, _first_nonexistant_frame, EYES_BOTH)) {
+                               break;
+                       }
+               }
+
+               _film->log()->log (String::compose ("Have existing frame %1", _first_nonexistant_frame));
+               ++_first_nonexistant_frame;
+       }
+
+       fclose (mxf);
+}
+
+/** @param frame Frame index.
+ *  @return true if we can fake-write this frame.
+ */
+bool
+Writer::can_fake_write (int frame) const
+{
+       /* We have to do a proper write of the first frame so that we can set up the JPEG2000
+          parameters in the MXF writer.
+       */
+       return (frame != 0 && frame < _first_nonexistant_frame);
+}
+
+bool
+operator< (QueueItem const & a, QueueItem const & b)
+{
+       if (a.frame != b.frame) {
+               return a.frame < b.frame;
+       }
+
+       return static_cast<int> (a.eyes) < static_cast<int> (b.eyes);
+}
+
+bool
+operator== (QueueItem const & a, QueueItem const & b)
+{
+       return a.frame == b.frame && a.eyes == b.eyes;
+}
diff --git a/src/lib/writer.h b/src/lib/writer.h
new file mode 100644 (file)
index 0000000..d922cfc
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+    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 <list>
+#include <boost/shared_ptr.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/condition.hpp>
+#include "exceptions.h"
+#include "types.h"
+
+class Film;
+class EncodedData;
+class AudioBuffers;
+class Job;
+
+namespace libdcp {
+       class MonoPictureAsset;
+       class MonoPictureAssetWriter;
+       class StereoPictureAsset;
+       class StereoPictureAssetWriter;
+       class PictureAsset;
+       class PictureAssetWriter;
+       class SoundAsset;
+       class SoundAssetWriter;
+}
+
+struct QueueItem
+{
+public:
+       enum Type {
+               /** a normal frame with some JPEG200 data */
+               FULL,
+               /** a frame whose data already exists in the MXF,
+                   and we fake-write it; i.e. we update the writer's
+                   state but we use the data that is already on disk.
+               */
+               FAKE,
+               /** this is a repeat of the last frame to be written */
+               REPEAT
+       } type;
+
+       /** encoded data for FULL */
+       boost::shared_ptr<const EncodedData> encoded;
+       /** size of data for FAKE */
+       int size;
+       /** frame index */
+       int frame;
+       Eyes eyes;
+};
+
+bool operator< (QueueItem const & a, QueueItem const & b);
+bool operator== (QueueItem const & a, QueueItem const & b);
+
+class Writer : public ExceptionStore, public boost::noncopyable
+{
+public:
+       Writer (boost::shared_ptr<const Film>, boost::shared_ptr<Job>);
+
+       bool can_fake_write (int) const;
+       
+       void write (boost::shared_ptr<const EncodedData>, int, Eyes);
+       void fake_write (int, Eyes);
+       void write (boost::shared_ptr<const AudioBuffers>);
+       void repeat (int f, Eyes);
+       void finish ();
+
+private:
+
+       void thread ();
+       void check_existing_picture_mxf ();
+       bool check_existing_picture_mxf_frame (FILE *, int, Eyes);
+       bool have_sequenced_image_at_queue_head () const;
+
+       /** our 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;
+
+       /** our thread, or 0 */
+       boost::thread* _thread;
+       /** true if our thread should finish */
+       bool _finish;
+       /** queue of things to write to disk */
+       std::list<QueueItem> _queue;
+       /** number of FULL frames whose JPEG200 data is currently held in RAM */
+       int _queued_full_in_memory;
+       /** mutex for thread state */
+       mutable boost::mutex _mutex;
+       /** condition to manage thread wakeups */
+       boost::condition _condition;
+       /** the data of the last written frame, or 0 if there isn't one */
+       boost::shared_ptr<const EncodedData> _last_written[EYES_COUNT];
+       /** the index of the last written frame */
+       int _last_written_frame;
+       Eyes _last_written_eyes;
+       /** maximum number of frames to hold in memory, for when we are managing
+           ordering
+       */
+       static const int _maximum_frames_in_memory;
+
+       /** number of FULL written frames */
+       int _full_written;
+       /** number of FAKE written frames */
+       int _fake_written;
+       /** number of REPEAT written frames */
+       int _repeat_written;
+       /** number of frames pushed to disk and then recovered
+           due to the limit of frames to be held in memory.
+       */
+       int _pushed_to_disk;
+       
+       boost::shared_ptr<libdcp::PictureAsset> _picture_asset;
+       boost::shared_ptr<libdcp::PictureAssetWriter> _picture_asset_writer;
+       boost::shared_ptr<libdcp::SoundAsset> _sound_asset;
+       boost::shared_ptr<libdcp::SoundAssetWriter> _sound_asset_writer;
+};
index b2b639f06d375928c9872e12b288136fa3f2da0f..6c45d8b1ed3cdf5d4731f444385b9e6a8f733400 100644 (file)
@@ -1,61 +1,94 @@
+import os
+import i18n
+
+sources = """
+          analyse_audio_job.cc
+          audio_analysis.cc
+          audio_buffers.cc
+          audio_content.cc
+          audio_decoder.cc
+          audio_mapping.cc
+          colour_conversion.cc
+          config.cc
+          content.cc
+          content_factory.cc
+          cross.cc
+          dci_metadata.cc
+          dcp_content_type.cc
+          dcp_video_frame.cc
+          decoder.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
+          image.cc
+          job.cc
+          job_manager.cc
+          log.cc
+          moving_image_content.cc
+          moving_image_decoder.cc
+          moving_image_examiner.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
+          still_image_content.cc
+          still_image_decoder.cc
+          still_image_examiner.cc
+          subtitle_content.cc
+          subtitle_decoder.cc
+          timer.cc
+          transcode_job.cc
+          transcoder.cc
+          types.cc
+          ui_signaller.cc
+          util.cc
+          video_content.cc
+          video_decoder.cc
+          writer.cc
+          """
+
 def build(bld):
     if bld.env.STATIC:
         obj = bld(features = 'cxx cxxstlib')
     else:
         obj = bld(features = 'cxx cxxshlib')
 
-    obj.name = 'libdvdomatic'
-    obj.export_includes = ['.']
-    obj.uselib = 'AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE SNDFILE BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 OPENJPEG POSTPROC TIFF MAGICK SSH DCP GLIB'
+    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 CXML GLIB LZMA XML++
+                 """
+
+    obj.source = sources + ' version.cc'
+
     if bld.env.TARGET_WINDOWS:
-        obj.uselib += ' WINSOCK2'
-    obj.source = """
-                ab_transcode_job.cc
-                ab_transcoder.cc
-                 audio_decoder.cc
-                 audio_source.cc
-                 check_hashes_job.cc
-                config.cc
-                 combiner.cc
-                 cross.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
-                external_audio_decoder.cc
-                 filter_graph.cc
-                 ffmpeg_compatibility.cc
-                 ffmpeg_decoder.cc
-                film.cc
-                filter.cc
-                format.cc
-                 gain.cc
-                 image.cc
-                 imagemagick_decoder.cc
-                job.cc
-                job_manager.cc
-                log.cc
-                lut.cc
-                make_dcp_job.cc
-                 matcher.cc
-                 scp_dcp_job.cc
-                scaler.cc
-                server.cc
-                 sound_processor.cc
-                 stream.cc
-                 subtitle.cc
-                 timer.cc
-                transcode_job.cc
-                transcoder.cc
-                 ui_signaller.cc
-                util.cc
-                version.cc
-                 video_decoder.cc
-                 video_source.cc
-                """
-
-    obj.target = 'dvdomatic'
+        obj.uselib += ' WINSOCK2 BFD DBGHELP IBERTY SHLWAPI'
+        obj.source += ' stack.cpp'
+    if bld.env.STATIC:
+        obj.uselib += ' XML++'
+
+    obj.target = 'dcpomatic'
+
+    i18n.po_to_mo(os.path.join('src', 'lib'), 'libdcpomatic', bld)
+
+def pot(bld):
+    i18n.pot(os.path.join('src', 'lib'), sources, 'libdcpomatic')
+
+def pot_merge(bld):
+    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..f61ef19
--- /dev/null
@@ -0,0 +1,624 @@
+/*
+    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 "wx/kdm_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"
+#include "lib/job_manager.h"
+#include "lib/transcode_job.h"
+#include "lib/exceptions.h"
+
+using std::cout;
+using std::string;
+using std::wstring;
+using std::stringstream;
+using std::map;
+using std::make_pair;
+using std::list;
+using std::exception;
+using std::ofstream;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+
+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:
+       /* Not defined */
+       FilmChangedDialog (FilmChangedDialog const &);
+       
+       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 ();
+}
+
+#define ALWAYS                  0x0
+#define NEEDS_FILM              0x1
+#define NOT_DURING_DCP_CREATION 0x2
+
+map<wxMenuItem*, int> menu_items;
+       
+void
+add_item (wxMenu* menu, wxString text, int id, int sens)
+{
+       wxMenuItem* item = menu->Append (id, text);
+       menu_items.insert (make_pair (item, sens));
+}
+
+void
+set_menu_sensitivity ()
+{
+       list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
+       list<shared_ptr<Job> >::iterator i = jobs.begin();
+       while (i != jobs.end() && dynamic_pointer_cast<TranscodeJob> (*i) == 0) {
+               ++i;
+       }
+       bool const dcp_creation = (i != jobs.end ());
+
+       for (map<wxMenuItem*, int>::iterator j = menu_items.begin(); j != menu_items.end(); ++j) {
+
+               bool enabled = true;
+
+               if ((j->second & NEEDS_FILM) && film == 0) {
+                       enabled = false;
+               }
+
+               if ((j->second & NOT_DURING_DCP_CREATION) && dcp_creation) {
+                       enabled = false;
+               }
+               
+               j->first->Enable (enabled);
+       }
+}
+
+enum {
+       ID_file_new = 1,
+       ID_file_open,
+       ID_file_save,
+       ID_file_properties,
+       ID_jobs_make_dcp,
+       ID_jobs_make_kdms,
+       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
+
+#ifdef __WXOSX__       
+       add_item (file, _("&Exit"), wxID_EXIT, ALWAYS);
+#else
+       add_item (file, _("&Quit"), wxID_EXIT, ALWAYS);
+#endif 
+       
+
+#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 | NOT_DURING_DCP_CREATION);
+       add_item (jobs_menu, _("Make &KDMs..."), ID_jobs_make_kdms, NEEDS_FILM);
+       add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM | NOT_DURING_DCP_CREATION);
+       add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION);
+
+       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"));
+}
+
+class Frame : public wxFrame
+{
+public:
+       Frame (wxString const & title)
+               : wxFrame (NULL, -1, title)
+       {
+               wxMenuBar* bar = new wxMenuBar;
+               setup_menu (bar);
+               SetMenuBar (bar);
+
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_new, this),             ID_file_new);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_open, this),            ID_file_open);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_save, this),            ID_file_save);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_properties, this),      ID_file_properties);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_exit, this),            wxID_EXIT);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::edit_preferences, this),     wxID_PREFERENCES);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::jobs_make_dcp, this),        ID_jobs_make_dcp);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::jobs_make_kdms, this),       ID_jobs_make_kdms);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::jobs_send_dcp_to_tms, this), ID_jobs_send_dcp_to_tms);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::jobs_show_dcp, this),        ID_jobs_show_dcp);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::help_about, this),           wxID_ABOUT);
+
+               Bind (wxEVT_MENU_OPEN, boost::bind (&Frame::menu_opened, this, _1));
+               Bind (wxEVT_CLOSE_WINDOW, boost::bind (&Frame::close, this, _1));
+
+               /* Use a panel as the only child of the Frame so that we avoid
+                  the dark-grey background on Windows.
+               */
+               wxPanel* overall_panel = new wxPanel (this, wxID_ANY);
+
+               film_editor = new FilmEditor (film, overall_panel);
+               film_viewer = new FilmViewer (film, overall_panel);
+               JobManagerView* job_manager_view = new JobManagerView (overall_panel, 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 ("");
+               }
+
+               JobManager::instance()->ActiveJobsChanged.connect (boost::bind (set_menu_sensitivity));
+
+               set_film ();
+               overall_panel->SetSizer (main_sizer);
+       }
+
+private:
+
+       void menu_opened (wxMenuEvent& ev)
+       {
+               if (ev.GetMenu() != jobs_menu) {
+                       return;
+               }
+
+               bool const have_dcp = false;//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 ()
+       {
+               NewFilmDialog* d = new NewFilmDialog (this);
+               int const r = d->ShowModal ();
+               
+               if (r == wxID_OK) {
+
+                       if (boost::filesystem::is_directory (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;
+                               }
+                       } else if (boost::filesystem::is_regular_file (d->get_path())) {
+                               error_dialog (
+                                       this,
+                                       String::compose (wx_to_std (_("%1 already exists as a file, so you cannot use it for a new film.")), 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 ()
+       {
+               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->read_metadata ();
+                               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 ()
+       {
+               film->write_metadata ();
+       }
+
+       void file_properties ()
+       {
+               PropertiesDialog* d = new PropertiesDialog (this, film);
+               d->ShowModal ();
+               d->Destroy ();
+       }
+       
+       void file_exit ()
+       {
+               if (!should_close ()) {
+                       return;
+               }
+               
+               maybe_save_then_delete_film ();
+               Close (true);
+       }
+
+       void edit_preferences ()
+       {
+               ConfigDialog* d = new ConfigDialog (this);
+               d->ShowModal ();
+               d->Destroy ();
+               Config::instance()->write ();
+       }
+
+       void jobs_make_dcp ()
+       {
+               JobWrapper::make_dcp (this, film);
+       }
+
+       void jobs_make_kdms ()
+       {
+               if (!film) {
+                       return;
+               }
+               
+               KDMDialog* d = new KDMDialog (this);
+               if (d->ShowModal () == wxID_OK) {
+                       try {
+                               film->make_kdms (
+                                       d->screens (),
+                                       d->from (),
+                                       d->until (),
+                                       d->directory ()
+                                       );
+                       } catch (KDMError& e) {
+                               error_dialog (this, e.what ());
+                       }
+               }
+               
+               d->Destroy ();
+       }
+       
+       void jobs_send_dcp_to_tms ()
+       {
+               film->send_dcp_to_tms ();
+       }
+
+       void jobs_show_dcp ()
+       {
+#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) {
+                       r = system (string ("nautilus " + film->directory()).c_str ());
+                       if (WEXITSTATUS (r)) {
+                               error_dialog (this, _("Could not show DCP (could not run nautilus)"));
+                       }
+               } else {
+                       int r = system ("which konqueror");
+                       if (WEXITSTATUS (r) == 0) {
+                               r = system (string ("konqueror " + film->directory()).c_str ());
+                               if (WEXITSTATUS (r)) {
+                                       error_dialog (this, _("Could not show DCP (could not run konqueror)"));
+                               }
+                       }
+               }
+#endif         
+       }
+
+       void help_about ()
+       {
+               AboutDialog* d = new AboutDialog (this);
+               d->ShowModal ();
+               d->Destroy ();
+       }
+
+       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 ();
+       }       
+};
+
+#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 ()
+       try
+       {
+               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->Bind (wxEVT_IDLE, boost::bind (&App::idle, this));
+
+               return true;
+       }
+       catch (exception& e)
+       {
+               error_dialog (0, wxString::Format ("DCP-o-matic could not start: %s", e.what ()));
+               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 ()
+       {
+               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..23d5a48
--- /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);
+
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_add_film, this), ID_file_add_film);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_quit, this),     ID_file_quit);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::help_about, this),    ID_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->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&Frame::add_film, this));
+               buttons->Add (add, 1, wxALL, 6);
+
+               sizer->Add (buttons, 0, wxALL, 6);
+
+               panel->SetSizer (sizer);
+
+               Bind (wxEVT_CLOSE_WINDOW, boost::bind (&Frame::close, this, _1));
+       }
+
+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 ()
+       {
+               add_film ();
+       }
+       
+       void file_quit ()
+       {
+               if (should_close ()) {
+                       Close (true);
+               }
+       }
+
+       void help_about ()
+       {
+               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 ()
+       {
+               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->Bind (wxEVT_IDLE, boost::bind (&App::idle, this));
+
+               return true;
+       }
+
+       void idle ()
+       {
+               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..7695e1e
--- /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 "lib/film.h"
+#include "lib/filter.h"
+#include "lib/transcode_job.h"
+#include "lib/job_manager.h"
+#include "lib/util.h"
+#include "lib/scaler.h"
+#include "lib/version.h"
+#include "lib/cross.h"
+#include "lib/config.h"
+#include "lib/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..78354c4
--- /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/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 ();
+
+               Bind (wxEVT_TIMER, boost::bind (&StatusDialog::update, this), ID_timer);
+               _timer.Start (1000);
+       }
+
+private:
+       void update ()
+       {
+               _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         
+
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&TaskBarIcon::status, this), ID_status);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&TaskBarIcon::quit, this), ID_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 ()
+       {
+               StatusDialog* d = new StatusDialog;
+               d->Show ();
+       }
+
+       void quit ()
+       {
+               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..eff10a8
--- /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 "lib/config.h"
+#include "lib/dcp_video_frame.h"
+#include "lib/exceptions.h"
+#include "lib/util.h"
+#include "lib/config.h"
+#include "lib/scaler.h"
+#include "lib/image.h"
+#include "lib/log.h"
+#include "lib/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 b6662f2..0000000
+++ /dev/null
@@ -1,466 +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/filesystem.hpp>
-#include <wx/aboutdlg.h>
-#include <wx/stdpaths.h>
-#include <wx/cmdline.h>
-#include "wx/film_viewer.h"
-#include "wx/film_editor.h"
-#include "wx/job_manager_view.h"
-#include "wx/config_dialog.h"
-#include "wx/job_wrapper.h"
-#include "wx/wx_util.h"
-#include "wx/new_film_dialog.h"
-#include "wx/properties_dialog.h"
-#include "wx/wx_ui_signaller.h"
-#include "wx/kdm_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/version.h"
-#include "lib/ui_signaller.h"
-#include "lib/log.h"
-
-using std::cout;
-using std::string;
-using std::stringstream;
-using std::map;
-using std::make_pair;
-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 void set_menu_sensitivity ();
-
-class FilmChangedDialog
-{
-public:
-       FilmChangedDialog ()
-       {
-               stringstream s;
-               s << "Save changes to film \"" << film->name() << "\" before closing?";
-               _dialog = new wxMessageDialog (0, std_to_wx (s.str()), wxT ("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, std::string text, int id, Sensitivity sens)
-{
-       wxMenuItem* item = menu->Append (id, std_to_wx (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_file_quit,
-       ID_edit_preferences,
-       ID_jobs_make_dcp,
-       ID_jobs_make_kdms,
-       ID_jobs_send_dcp_to_tms,
-       ID_jobs_examine_content,
-       ID_jobs_make_dcp_from_existing_transcode,
-       ID_help_about
-};
-
-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);
-       file->AppendSeparator ();
-       add_item (file, "&Quit", ID_file_quit, ALWAYS);
-
-       wxMenu* edit = new wxMenu;
-       add_item (edit, "&Preferences...", ID_edit_preferences, ALWAYS);
-
-       wxMenu* jobs = new wxMenu;
-       add_item (jobs, "&Make DCP", ID_jobs_make_dcp, NEEDS_FILM);
-       add_item (jobs, "Make &KDMs...", ID_jobs_make_kdms, NEEDS_FILM);
-       add_item (jobs, "&Send DCP to TMS", ID_jobs_send_dcp_to_tms, NEEDS_FILM);
-       jobs->AppendSeparator ();
-       add_item (jobs, "&Examine content", ID_jobs_examine_content, NEEDS_FILM);
-       add_item (jobs, "Make DCP from existing &transcode", ID_jobs_make_dcp_from_existing_transcode, NEEDS_FILM);
-
-       wxMenu* help = new wxMenu;
-       add_item (help, "About", ID_help_about, ALWAYS);
-
-       m->Append (file, _("&File"));
-       m->Append (edit, _("&Edit"));
-       m->Append (jobs, _("&Jobs"));
-       m->Append (help, _("&Help"));
-}
-
-bool
-window_closed (wxCommandEvent &)
-{
-       maybe_save_then_delete_film ();
-       return false;
-}
-
-class Frame : public wxFrame
-{
-public:
-       Frame (wxString const & title)
-               : wxFrame (NULL, -1, title)
-       {
-               wxMenuBar* bar = new wxMenuBar;
-               setup_menu (bar);
-               SetMenuBar (bar);
-
-               Connect (ID_file_new, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_new));
-               Connect (ID_file_open, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_open));
-               Connect (ID_file_save, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_save));
-               Connect (ID_file_properties, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_properties));
-               Connect (ID_file_quit, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_quit));
-               Connect (ID_edit_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_make_kdms, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_make_kdms));
-               Connect (ID_jobs_send_dcp_to_tms, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_send_dcp_to_tms));
-               Connect (ID_jobs_examine_content, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_examine_content));
-               Connect (ID_jobs_make_dcp_from_existing_transcode, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_make_dcp_from_existing_transcode));
-               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);
-
-               film_editor = new FilmEditor (film, panel);
-               film_viewer = new FilmViewer (film, panel);
-               JobManagerView* job_manager_view = new JobManagerView (panel);
-
-               wxSizer* rhs_sizer = new wxBoxSizer (wxVERTICAL);
-               rhs_sizer->Add (film_viewer, 3, wxEXPAND | wxALL);
-               rhs_sizer->Add (job_manager_view, 1, wxEXPAND | wxALL);
-
-               wxBoxSizer* main_sizer = new wxBoxSizer (wxHORIZONTAL);
-               main_sizer->Add (film_editor, 0, wxALL, 6);
-               main_sizer->Add (rhs_sizer, 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 ();
-       }
-
-       void set_film ()
-       {
-               film_viewer->set_film (film);
-               film_editor->set_film (film);
-               set_menu_sensitivity ();
-       }
-
-       void file_changed (string f)
-       {
-               stringstream s;
-               s << "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())) {
-                               error_dialog (this, String::compose ("The directory %1 already exists.", d->get_path()));
-                               return;
-                       }
-                       
-                       maybe_save_then_delete_film ();
-                       film.reset (new Film (d->get_path (), false));
-                       film->log()->set_level (log_level);
-#if BOOST_FILESYSTEM_VERSION == 3              
-                       film->set_name (boost::filesystem::path (d->get_path()).filename().generic_string());
-#else          
-                       film->set_name (boost::filesystem::path (d->get_path()).filename());
-#endif
-                       set_film ();
-               }
-               
-               d->Destroy ();
-       }
-
-       void file_open (wxCommandEvent &)
-       {
-               wxDirDialog* c = new wxDirDialog (this, wxT ("Select film to open"), wxStandardPaths::Get().GetDocumentsDir(), wxDEFAULT_DIALOG_STYLE | wxDD_DIR_MUST_EXIST);
-               int const r = c->ShowModal ();
-               
-               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) {
-                               error_dialog (this, String::compose ("Could not open film at %1 (%2)", wx_to_std (c->GetPath()), e.what()));
-                       }
-               }
-
-               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_quit (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, true);
-       }
-
-       void jobs_make_kdms (wxCommandEvent &)
-       {
-               if (!film) {
-                       return;
-               }
-               
-               KDMDialog* d = new KDMDialog (this);
-               if (d->ShowModal () == wxID_OK) {
-                       film->make_kdms (
-                               d->screens (),
-                               d->from (),
-                               d->until (),
-                               d->directory ()
-                               );
-               }
-               
-               d->Destroy ();
-       }
-       
-       void jobs_make_dcp_from_existing_transcode (wxCommandEvent &)
-       {
-               JobWrapper::make_dcp (this, film, false);
-       }
-       
-       void jobs_send_dcp_to_tms (wxCommandEvent &)
-       {
-               film->send_dcp_to_tms ();
-       }
-       
-       void jobs_examine_content (wxCommandEvent &)
-       {
-               film->examine_content ();
-       }
-       
-       void help_about (wxCommandEvent &)
-       {
-               wxAboutDialogInfo info;
-               info.SetName (_("DVD-o-matic"));
-               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) 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);
-               info.SetWebSite (wxT ("http://carlh.net/software/dvdomatic"));
-               wxAboutBox (info);
-       }
-};
-
-#if wxMINOR_VERSION == 9
-static const wxCmdLineEntryDesc command_line_description[] = {
-       { wxCMD_LINE_OPTION, "l", "log", "set log level (silent, verbose or timing)", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
-        { wxCMD_LINE_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_POSIX         
-               unsetenv ("UBUNTU_MENUPROXY");
-#endif         
-               
-               wxInitAllImageHandlers ();
-               
-               dvdomatic_setup ();
-
-               if (!film_to_load.empty() && boost::filesystem::is_directory (film_to_load)) {
-                       film.reset (new Film (film_to_load));
-                       film->log()->set_level (log_level);
-               }
-
-               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/makedcp.cc b/src/tools/makedcp.cc
deleted file mode 100644 (file)
index 900c31b..0000000
+++ /dev/null
@@ -1,216 +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/test_mode.h>
-#include <libdcp/version.h>
-#include "format.h"
-#include "film.h"
-#include "filter.h"
-#include "transcode_job.h"
-#include "make_dcp_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 and quit\n"
-            << "  -t, --test         run in test mode (repeatable UUID generation, timestamps etc.)\n"
-            << "  -n, --no-progress  do not print progress to stdout\n"
-            << "  -r, --no-remote    do not use any remote servers\n"
-            << "\n"
-            << "<FILM> is the film directory.\n";
-}
-
-int
-main (int argc, char* argv[])
-{
-       string film_dir;
-       bool test_mode = false;
-       bool progress = true;
-       bool no_remote = false;
-       int log_level = 1;
-
-       int option_index = 0;
-       while (1) {
-               static struct option long_options[] = {
-                       { "version", no_argument, 0, 'v'},
-                       { "help", no_argument, 0, 'h'},
-                       { "deps", no_argument, 0, 'd'},
-                       { "test", no_argument, 0, 't'},
-                       { "no-progress", no_argument, 0, 'n'},
-                       { "no-remote", no_argument, 0, 'r'},
-                       { "log-level", required_argument, 0, 'l' },
-                       { 0, 0, 0, 0 }
-               };
-
-               int c = getopt_long (argc, argv, "vhdtnrl:", long_options, &option_index);
-
-               if (c == -1) {
-                       break;
-               }
-
-               switch (c) {
-               case 'v':
-                       cout << "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 't':
-                       test_mode = true;
-                       break;
-               case 'n':
-                       progress = false;
-                       break;
-               case 'r':
-                       no_remote = true;
-                       break;
-               case 'l':
-                       log_level = atoi (optarg);
-                       break;
-               }
-       }
-
-       if (optind >= argc) {
-               help (argv[0]);
-               exit (EXIT_FAILURE);
-       }
-
-       film_dir = argv[optind];
-                       
-       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";
-
-       if (test_mode) {
-               libdcp::enable_test_mode ();
-               cout << dependency_version_summary() << "\n";
-       }
-
-       shared_ptr<Film> film;
-       try {
-               film.reset (new Film (film_dir, true));
-       } catch (std::exception& e) {
-               cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n";
-               exit (EXIT_FAILURE);
-       }
-
-       film->log()->set_level ((Log::Level) log_level);
-
-       cout << "\nMaking ";
-       if (film->dcp_ab()) {
-               cout << "A/B ";
-       }
-       cout << "DCP for " << film->name() << "\n";
-       cout << "Test mode: " << (test_mode ? "yes" : "no") << "\n";
-       cout << "Content: " << film->content() << "\n";
-       pair<string, string> const f = Filter::ffmpeg_strings (film->filters ());
-       cout << "Filters: " << f.first << " " << f.second << "\n";
-
-       film->make_dcp (true);
-
-       bool should_stop = false;
-       bool first = true;
-       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;
-                       }
-
-                       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 0;
-}
-
-         
diff --git a/src/tools/po/es_ES.po b/src/tools/po/es_ES.po
new file mode 100644 (file)
index 0000000..fb379ab
--- /dev/null
@@ -0,0 +1,145 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: DCPOMATIC\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-03-23 21:08-0500\n"
+"Last-Translator: Manuel AC <manuel.acevedo@civantos.>\n"
+"Language-Team: Manuel AC <manuel.acevedo@civantos.com>\n"
+"Language: es-ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.5\n"
+
+#: src/tools/dcpomatic.cc:309
+msgid "%1 already exists as a file, so you cannot use it for a new film."
+msgstr ""
+
+#: src/tools/dcpomatic.cc:196
+msgid "&Edit"
+msgstr "&Editar"
+
+#: src/tools/dcpomatic.cc:169
+msgid "&Exit"
+msgstr ""
+
+#: src/tools/dcpomatic.cc:194
+msgid "&File"
+msgstr "&Archivo"
+
+#: src/tools/dcpomatic.cc:199
+msgid "&Help"
+msgstr "&Ayuda"
+
+#: src/tools/dcpomatic.cc:198
+msgid "&Jobs"
+msgstr "&Tareas"
+
+#: src/tools/dcpomatic.cc:183
+msgid "&Make DCP"
+msgstr "&Crear DCP"
+
+#: src/tools/dcpomatic.cc:159
+msgid "&Open..."
+msgstr "&Abrir..."
+
+#: src/tools/dcpomatic.cc:176 src/tools/dcpomatic.cc:179
+msgid "&Preferences..."
+msgstr "&Preferencias..."
+
+#: src/tools/dcpomatic.cc:163
+msgid "&Properties..."
+msgstr "&Propiedades..."
+
+#: src/tools/dcpomatic.cc:171
+msgid "&Quit"
+msgstr "&Salir"
+
+#: src/tools/dcpomatic.cc:161
+msgid "&Save"
+msgstr "&Guardar"
+
+#: src/tools/dcpomatic.cc:184
+msgid "&Send DCP to TMS"
+msgstr "&Enviar DCP al TMS"
+
+#: src/tools/dcpomatic.cc:191
+msgid "About"
+msgstr "Acerca de"
+
+#: src/tools/dcpomatic.cc:189
+#, fuzzy
+msgid "About DCP-o-matic"
+msgstr "DVD-o-matic"
+
+#: src/tools/dcpomatic.cc:479
+#, fuzzy
+msgid "Could not load film %1 (%2)"
+msgstr "No se pudo cargar la película %s (%s)"
+
+#: src/tools/dcpomatic.cc:348
+#, c-format
+msgid "Could not open film at %s (%s)"
+msgstr "No se pudo cargar la película en %s (%s)"
+
+#: src/tools/dcpomatic.cc:280 src/tools/dcpomatic.cc:490
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/tools/dcpomatic.cc:77
+msgid "Film changed"
+msgstr "Película cambiada"
+
+#: src/tools/dcpomatic.cc:158
+msgid "New..."
+msgstr "Nuevo..."
+
+#: src/tools/dcpomatic.cc:185
+msgid "S&how DCP"
+msgstr "&Mostrar DCP"
+
+#: src/tools/dcpomatic.cc:76
+#, c-format
+msgid "Save changes to film \"%s\" before closing?"
+msgstr ""
+
+#: src/tools/dcpomatic.cc:327
+msgid "Select film to open"
+msgstr "Selecciona la película a abrir"
+
+#: src/tools/dcpomatic.cc:299
+msgid ""
+"The directory %1 already exists and is not empty.  Are you sure you want to "
+"use it?"
+msgstr ""
+
+#: src/tools/dcpomatic.cc:332
+msgid ""
+"You did not select a folder.  Make sure that you select a folder before "
+"clicking Open."
+msgstr ""
+
+#~ msgid "&Analyse audio"
+#~ msgstr "&Analizar audio"
+
+#~ msgid ""
+#~ "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole "
+#~ "Laursen"
+#~ msgstr ""
+#~ "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole "
+#~ "Laursen"
+
+#~ 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 "
+#~ "abierto."
+
+#, fuzzy
+#~ msgid "The directory %1 already exists."
+#~ msgstr "La carpeta %s ya existe."
diff --git a/src/tools/po/fr_FR.po b/src/tools/po/fr_FR.po
new file mode 100644 (file)
index 0000000..71bd155
--- /dev/null
@@ -0,0 +1,138 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: DCP-o-matic FRENCH\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-07-16 23:13+0100\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/tools/dcpomatic.cc:309
+msgid "%1 already exists as a file, so you cannot use it for a new film."
+msgstr "Le fichier %1 existe déjà, vous ne pouvez l'utiliser pour un nouveau projet."
+
+#: src/tools/dcpomatic.cc:196
+msgid "&Edit"
+msgstr "&Edition"
+
+#: src/tools/dcpomatic.cc:169
+msgid "&Exit"
+msgstr "&Quitter"
+
+#: src/tools/dcpomatic.cc:194
+msgid "&File"
+msgstr "&Fichier"
+
+#: src/tools/dcpomatic.cc:199
+msgid "&Help"
+msgstr "&Aide"
+
+#: src/tools/dcpomatic.cc:198
+msgid "&Jobs"
+msgstr "&Travaux"
+
+#: src/tools/dcpomatic.cc:183
+msgid "&Make DCP"
+msgstr "&Créer le DCP"
+
+#: src/tools/dcpomatic.cc:159
+msgid "&Open..."
+msgstr "&Ouvrir..."
+
+#: src/tools/dcpomatic.cc:176
+#: src/tools/dcpomatic.cc:179
+msgid "&Preferences..."
+msgstr "&Préférences..."
+
+#: src/tools/dcpomatic.cc:163
+msgid "&Properties..."
+msgstr "&Propriétés..."
+
+#: src/tools/dcpomatic.cc:171
+msgid "&Quit"
+msgstr "&Quitter"
+
+#: src/tools/dcpomatic.cc:161
+msgid "&Save"
+msgstr "&Enregistrer"
+
+#: src/tools/dcpomatic.cc:184
+msgid "&Send DCP to TMS"
+msgstr "&Envoyer le DCP dans le TMS"
+
+#: src/tools/dcpomatic.cc:191
+msgid "About"
+msgstr "A Propos"
+
+#: src/tools/dcpomatic.cc:189
+msgid "About DCP-o-matic"
+msgstr "À propos de DCP-o-matic"
+
+#: src/tools/dcpomatic.cc:479
+msgid "Could not load film %1 (%2)"
+msgstr "Impossible de charger le film %1 (%2)"
+
+#: src/tools/dcpomatic.cc:348
+#, c-format
+msgid "Could not open film at %s (%s)"
+msgstr "Impossible d'ouvrir le film à %s (%s)"
+
+#: src/tools/dcpomatic.cc:280
+#: src/tools/dcpomatic.cc:490
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/tools/dcpomatic.cc:77
+msgid "Film changed"
+msgstr "Film changé"
+
+#: src/tools/dcpomatic.cc:158
+msgid "New..."
+msgstr "Nouveau..."
+
+#: src/tools/dcpomatic.cc:185
+msgid "S&how DCP"
+msgstr "Voir le DCP"
+
+#: src/tools/dcpomatic.cc:76
+#, c-format
+msgid "Save changes to film \"%s\" before closing?"
+msgstr "Enregistrer les changements du film \"%s\" avant de fermer ?"
+
+#: src/tools/dcpomatic.cc:327
+msgid "Select film to open"
+msgstr "Sélectionner le film à ouvrir"
+
+#: src/tools/dcpomatic.cc:299
+msgid "The directory %1 already exists and is not empty.  Are you sure you want to use it?"
+msgstr "Le dossier %1 existe et n'est pas vide. Etes-vous sûr de vouloir l'utiliser ?"
+
+#: src/tools/dcpomatic.cc:332
+msgid "You did not select a folder.  Make sure that you select a folder before clicking Open."
+msgstr "Aucun dossier sélectionné. Selectionnez un dossier avant de cliquer sur Ouvrir"
+
+#~ msgid "&Analyse audio"
+#~ msgstr "&Analyser le son"
+
+#~ msgid ""
+#~ "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole "
+#~ "Laursen"
+#~ msgstr ""
+#~ "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole "
+#~ "Laursen"
+
+#~ msgid "Free, open-source DCP generation from almost anything."
+#~ msgstr "Création de DCP libre et open-source à partir de presque tout."
+
+#, fuzzy
+#~ msgid "The directory %1 already exists."
+#~ msgstr "Le dossier %s existe déjà."
diff --git a/src/tools/po/it_IT.po b/src/tools/po/it_IT.po
new file mode 100644 (file)
index 0000000..f32ce97
--- /dev/null
@@ -0,0 +1,142 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: IT VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-04-28 10:31+0100\n"
+"Last-Translator: Maci <macibro@gmail.com>\n"
+"Language-Team: \n"
+"Language: Italiano\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.5\n"
+
+#: src/tools/dcpomatic.cc:309
+msgid "%1 already exists as a file, so you cannot use it for a new film."
+msgstr ""
+
+#: src/tools/dcpomatic.cc:196
+msgid "&Edit"
+msgstr "&Modifica"
+
+#: src/tools/dcpomatic.cc:169
+msgid "&Exit"
+msgstr ""
+
+#: src/tools/dcpomatic.cc:194
+msgid "&File"
+msgstr "&File"
+
+#: src/tools/dcpomatic.cc:199
+msgid "&Help"
+msgstr "&Aiuto"
+
+#: src/tools/dcpomatic.cc:198
+msgid "&Jobs"
+msgstr "&Lavori"
+
+#: src/tools/dcpomatic.cc:183
+msgid "&Make DCP"
+msgstr "&Crea DCP"
+
+#: src/tools/dcpomatic.cc:159
+msgid "&Open..."
+msgstr "&Apri..."
+
+#: src/tools/dcpomatic.cc:176 src/tools/dcpomatic.cc:179
+msgid "&Preferences..."
+msgstr "&Preferenze..."
+
+#: src/tools/dcpomatic.cc:163
+msgid "&Properties..."
+msgstr "&Proprieta'..."
+
+#: src/tools/dcpomatic.cc:171
+msgid "&Quit"
+msgstr "&Esci"
+
+#: src/tools/dcpomatic.cc:161
+msgid "&Save"
+msgstr "&Salva"
+
+#: src/tools/dcpomatic.cc:184
+msgid "&Send DCP to TMS"
+msgstr "&Invia DCP a TMS"
+
+#: src/tools/dcpomatic.cc:191
+msgid "About"
+msgstr "Informazioni"
+
+#: src/tools/dcpomatic.cc:189
+#, fuzzy
+msgid "About DCP-o-matic"
+msgstr "DVD-o-matic"
+
+#: src/tools/dcpomatic.cc:479
+msgid "Could not load film %1 (%2)"
+msgstr "Non posso caricare il film %s (%s)"
+
+#: src/tools/dcpomatic.cc:348
+#, c-format
+msgid "Could not open film at %s (%s)"
+msgstr "Non posso aprire il film in %s (%s)"
+
+#: src/tools/dcpomatic.cc:280 src/tools/dcpomatic.cc:490
+#, fuzzy
+msgid "DCP-o-matic"
+msgstr "DVD-o-matic"
+
+#: src/tools/dcpomatic.cc:77
+msgid "Film changed"
+msgstr "Film modificato"
+
+#: src/tools/dcpomatic.cc:158
+msgid "New..."
+msgstr "Nuovo"
+
+#: src/tools/dcpomatic.cc:185
+msgid "S&how DCP"
+msgstr "&Mostra DCP"
+
+#: src/tools/dcpomatic.cc:76
+#, c-format
+msgid "Save changes to film \"%s\" before closing?"
+msgstr "Salvare i cambiamenti del film \"%s\" prima di chiudere?"
+
+#: src/tools/dcpomatic.cc:327
+msgid "Select film to open"
+msgstr "Seleziona il film da aprire"
+
+#: src/tools/dcpomatic.cc:299
+msgid ""
+"The directory %1 already exists and is not empty.  Are you sure you want to "
+"use it?"
+msgstr ""
+
+#: src/tools/dcpomatic.cc:332
+msgid ""
+"You did not select a folder.  Make sure that you select a folder before "
+"clicking Open."
+msgstr ""
+
+#~ msgid "&Analyse audio"
+#~ msgstr "&Analizza audio"
+
+#~ msgid ""
+#~ "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole "
+#~ "Laursen"
+#~ msgstr ""
+#~ "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole "
+#~ "Laursen"
+
+#~ msgid "Free, open-source DCP generation from almost anything."
+#~ msgstr "Genera DCP da quasi tutto, free e open-source."
+
+#~ msgid "The directory %1 already exists."
+#~ msgstr "La directory %s esiste gia'."
diff --git a/src/tools/po/sv_SE.po b/src/tools/po/sv_SE.po
new file mode 100644 (file)
index 0000000..a7a5f82
--- /dev/null
@@ -0,0 +1,145 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: DCP-o-matic\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-04-09 10:12+0100\n"
+"Last-Translator: Adam Klotblixt <adam.klotblixt@gmail.com>\n"
+"Language-Team: \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.5\n"
+
+#: src/tools/dcpomatic.cc:309
+msgid "%1 already exists as a file, so you cannot use it for a new film."
+msgstr ""
+
+#: src/tools/dcpomatic.cc:196
+msgid "&Edit"
+msgstr "&Redigera"
+
+#: src/tools/dcpomatic.cc:169
+msgid "&Exit"
+msgstr ""
+
+#: src/tools/dcpomatic.cc:194
+msgid "&File"
+msgstr "&Fil"
+
+#: src/tools/dcpomatic.cc:199
+msgid "&Help"
+msgstr "&Hjälp"
+
+#: src/tools/dcpomatic.cc:198
+msgid "&Jobs"
+msgstr "&Jobb"
+
+#: src/tools/dcpomatic.cc:183
+msgid "&Make DCP"
+msgstr "&Skapa DCP"
+
+#: src/tools/dcpomatic.cc:159
+msgid "&Open..."
+msgstr "&Öppna"
+
+#: src/tools/dcpomatic.cc:176 src/tools/dcpomatic.cc:179
+msgid "&Preferences..."
+msgstr "&Inställningar"
+
+#: src/tools/dcpomatic.cc:163
+msgid "&Properties..."
+msgstr "&Egenskaper"
+
+#: src/tools/dcpomatic.cc:171
+msgid "&Quit"
+msgstr "&Avsluta"
+
+#: src/tools/dcpomatic.cc:161
+msgid "&Save"
+msgstr "&Spara"
+
+#: src/tools/dcpomatic.cc:184
+msgid "&Send DCP to TMS"
+msgstr "&Skicka DCP till TMS"
+
+#: src/tools/dcpomatic.cc:191
+msgid "About"
+msgstr "Om"
+
+#: src/tools/dcpomatic.cc:189
+#, fuzzy
+msgid "About DCP-o-matic"
+msgstr "DVD-o-matic"
+
+#: src/tools/dcpomatic.cc:479
+msgid "Could not load film %1 (%2)"
+msgstr "Kunde inte öppna filmen %1 (%2)"
+
+#: src/tools/dcpomatic.cc:348
+#, c-format
+msgid "Could not open film at %s (%s)"
+msgstr "Kunde inte öppna filmen vid %s (%s)"
+
+#: src/tools/dcpomatic.cc:280 src/tools/dcpomatic.cc:490
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/tools/dcpomatic.cc:77
+msgid "Film changed"
+msgstr "Film ändrad"
+
+#: src/tools/dcpomatic.cc:158
+msgid "New..."
+msgstr "Ny..."
+
+#: src/tools/dcpomatic.cc:185
+msgid "S&how DCP"
+msgstr "&Visa DCP"
+
+#: src/tools/dcpomatic.cc:76
+#, fuzzy, c-format
+msgid "Save changes to film \"%s\" before closing?"
+msgstr "Spara ändringarna till filmen \"%s\" före avslut?"
+
+#: src/tools/dcpomatic.cc:327
+msgid "Select film to open"
+msgstr "Välj film att öppna"
+
+#: src/tools/dcpomatic.cc:299
+msgid ""
+"The directory %1 already exists and is not empty.  Are you sure you want to "
+"use it?"
+msgstr ""
+
+#: src/tools/dcpomatic.cc:332
+msgid ""
+"You did not select a folder.  Make sure that you select a folder before "
+"clicking Open."
+msgstr ""
+"Du har inte valt en folder. Se till att välja en folder innan du klickar på "
+"Öppna."
+
+#~ msgid "&Analyse audio"
+#~ msgstr "&Analysera audio"
+
+#~ msgid ""
+#~ "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole "
+#~ "Laursen"
+#~ msgstr ""
+#~ "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole "
+#~ "Laursen"
+
+#~ msgid "Free, open-source DCP generation from almost anything."
+#~ msgstr ""
+#~ "Fri, öppen-källkodsprogramvara för DCP-generering från nästan vad som "
+#~ "helst."
+
+#~ msgid "The directory %1 already exists."
+#~ msgstr "Katalogen %1 finns redan."
diff --git a/src/tools/server_test.cc b/src/tools/server_test.cc
new file mode 100644 (file)
index 0000000..029e626
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+    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 <exception>
+#include <getopt.h>
+#include "lib/ratio.h"
+#include "lib/film.h"
+#include "lib/filter.h"
+#include "lib/util.h"
+#include "lib/scaler.h"
+#include "lib/server.h"
+#include "lib/dcp_video_frame.h"
+#include "lib/decoder.h"
+#include "lib/exceptions.h"
+#include "lib/scaler.h"
+#include "lib/log.h"
+#include "lib/video_decoder.h"
+#include "lib/player.h"
+
+using std::cout;
+using std::cerr;
+using std::string;
+using std::pair;
+using boost::shared_ptr;
+
+static shared_ptr<Film> film;
+static ServerDescription* server;
+static shared_ptr<FileLog> log_ (new FileLog ("servomatictest.log"));
+static int frame = 0;
+
+void
+process_video (shared_ptr<const Image> image, Eyes eyes, ColourConversion conversion, Time)
+{
+       shared_ptr<DCPVideoFrame> local  (new DCPVideoFrame (image, frame, eyes, conversion, film->video_frame_rate(), 250000000, log_));
+       shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (image, frame, eyes, conversion, film->video_frame_rate(), 250000000, log_));
+
+       cout << "Frame " << frame << ": ";
+       cout.flush ();
+
+       ++frame;
+
+       shared_ptr<EncodedData> local_encoded = local->encode_locally ();
+       shared_ptr<EncodedData> remote_encoded;
+
+       string remote_error;
+       try {
+               remote_encoded = remote->encode_remotely (*server);
+       } catch (NetworkError& e) {
+               remote_error = e.what ();
+       }
+
+       if (!remote_error.empty ()) {
+               cout << "\033[0;31mnetwork problem: " << remote_error << "\033[0m\n";
+               return;
+       }
+
+       if (local_encoded->size() != remote_encoded->size()) {
+               cout << "\033[0;31msizes differ\033[0m\n";
+               return;
+       }
+               
+       uint8_t* p = local_encoded->data();
+       uint8_t* q = remote_encoded->data();
+       for (int i = 0; i < local_encoded->size(); ++i) {
+               if (*p++ != *q++) {
+                       cout << "\033[0;31mdata differ\033[0m at byte " << i << "\n";
+                       return;
+               }
+       }
+
+       cout << "\033[0;32mgood\033[0m\n";
+}
+
+static void
+help (string n)
+{
+       cerr << "Syntax: " << n << " [--help] --film <film> --server <host>\n";
+       exit (EXIT_FAILURE);
+}
+
+int
+main (int argc, char* argv[])
+{
+       string film_dir;
+       string server_host;
+
+       while (1) {
+               static struct option long_options[] = {
+                       { "help", no_argument, 0, 'h'},
+                       { "server", required_argument, 0, 's'},
+                       { "film", required_argument, 0, 'f'},
+                       { 0, 0, 0, 0 }
+               };
+
+               int option_index = 0;
+               int c = getopt_long (argc, argv, "hs:f:", long_options, &option_index);
+
+               if (c == -1) {
+                       break;
+               }
+
+               switch (c) {
+               case 'h':
+                       help (argv[0]);
+                       exit (EXIT_SUCCESS);
+               case 's':
+                       server_host = optarg;
+                       break;
+               case 'f':
+                       film_dir = optarg;
+                       break;
+               }
+       }
+       
+       if (server_host.empty() || film_dir.empty()) {
+               help (argv[0]);
+               exit (EXIT_FAILURE);
+       }
+
+       dcpomatic_setup ();
+
+       server = new ServerDescription (server_host, 1);
+       film.reset (new Film (film_dir));
+       film->read_metadata ();
+
+       shared_ptr<Player> player = film->make_player ();
+       player->disable_audio ();
+
+       try {
+               player->Video.connect (boost::bind (process_video, _1, _2, _3, _5));
+               bool done = false;
+               while (!done) {
+                       done = player->pass ();
+               }
+       } catch (std::exception& e) {
+               cerr << "Error: " << e.what() << "\n";
+       }
+
+       return 0;
+}
diff --git a/src/tools/servomatic_cli.cc b/src/tools/servomatic_cli.cc
deleted file mode 100644 (file)
index f8e7131..0000000
+++ /dev/null
@@ -1,94 +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 namespace std;
-
-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 ();
-       FileLog log ("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 610ba80..0000000
+++ /dev/null
@@ -1,151 +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 namespace std;
-using namespace boost;
-
-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 MemoryLog memory_log;
-
-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 ()
-       {
-               wxIcon icon (std_to_wx ("taskbar_icon"));
-               SetIcon (icon, std_to_wx ("DVD-o-matic encode server"));
-
-               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)
-       {}
-
-private:       
-       
-       bool OnInit ()
-       {
-               dvdomatic_setup ();
-
-               new TaskBarIcon;
-
-               _thread = new thread (bind (&App::main_thread, this));
-               return true;
-       }
-
-       void main_thread ()
-       {
-               Server server (&memory_log);
-               server.run (Config::instance()->num_local_encoding_threads ());
-       }
-
-       boost::thread* _thread;
-};
-
-IMPLEMENT_APP (App)
diff --git a/src/tools/servomatictest.cc b/src/tools/servomatictest.cc
deleted file mode 100644 (file)
index 88c2a83..0000000
+++ /dev/null
@@ -1,149 +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 <exception>
-#include <getopt.h>
-#include "format.h"
-#include "film.h"
-#include "filter.h"
-#include "util.h"
-#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"
-
-using namespace std;
-using namespace boost;
-
-static Server* server;
-static Log log_ ("servomatictest.log");
-
-void
-process_video (shared_ptr<Image> image, bool, int frame)
-{
-       shared_ptr<DCPVideoFrame> local (new DCPVideoFrame (image, Size (1024, 1024), 0, Scaler::from_id ("bicubic"), frame, 24, "", 0, 250000000, &log_));
-       shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (image, Size (1024, 1024), 0, Scaler::from_id ("bicubic"), frame, 24, "", 0, 250000000, &log_));
-
-       cout << "Frame " << frame << ": ";
-       cout.flush ();
-
-       shared_ptr<EncodedData> local_encoded = local->encode_locally ();
-       shared_ptr<EncodedData> remote_encoded;
-
-       string remote_error;
-       try {
-               remote_encoded = remote->encode_remotely (server);
-       } catch (NetworkError& e) {
-               remote_error = e.what ();
-       }
-
-       if (!remote_error.empty ()) {
-               cout << "\033[0;31mnetwork problem: " << remote_error << "\033[0m\n";
-               return;
-       }
-
-       if (local_encoded->size() != remote_encoded->size()) {
-               cout << "\033[0;31msizes differ\033[0m\n";
-               return;
-       }
-               
-       uint8_t* p = local_encoded->data();
-       uint8_t* q = remote_encoded->data();
-       for (int i = 0; i < local_encoded->size(); ++i) {
-               if (*p++ != *q++) {
-                       cout << "\033[0;31mdata differ\033[0m at byte " << i << "\n";
-                       return;
-               }
-       }
-
-       cout << "\033[0;32mgood\033[0m\n";
-}
-
-static void
-help (string n)
-{
-       cerr << "Syntax: " << n << " [--help] --film <film> --server <host>\n";
-       exit (EXIT_FAILURE);
-}
-
-int
-main (int argc, char* argv[])
-{
-       string film_dir;
-       string server_host;
-
-       while (1) {
-               static struct option long_options[] = {
-                       { "help", no_argument, 0, 'h'},
-                       { "server", required_argument, 0, 's'},
-                       { "film", required_argument, 0, 'f'},
-                       { 0, 0, 0, 0 }
-               };
-
-               int option_index = 0;
-               int c = getopt_long (argc, argv, "hs:f:", long_options, &option_index);
-
-               if (c == -1) {
-                       break;
-               }
-
-               switch (c) {
-               case 'h':
-                       help (argv[0]);
-                       exit (EXIT_SUCCESS);
-               case 's':
-                       server_host = optarg;
-                       break;
-               case 'f':
-                       film_dir = optarg;
-                       break;
-               }
-       }
-       
-       if (server_host.empty() || film_dir.empty()) {
-               help (argv[0]);
-               exit (EXIT_FAILURE);
-       }
-
-       dvdomatic_setup ();
-
-       server = new Server (server_host, 1);
-       Film film (film_dir, true);
-
-       shared_ptr<Options> opt (new Options ("fred", "jim", "sheila"));
-       opt->out_size = Size (1024, 1024);
-       opt->decode_audio = false;
-
-       shared_ptr<Decoder> decoder = decoder_factory (film.state_copy(), opt, 0, &log_);
-       try {
-               decoder->Video.connect (sigc::ptr_fun (process_video));
-               decoder->go ();
-       } catch (std::exception& e) {
-               cerr << "Error: " << e.what() << "\n";
-       }
-
-       return 0;
-}
diff --git a/src/tools/test.cc b/src/tools/test.cc
deleted file mode 100644 (file)
index 4baaeb7..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 <stdint.h>
-#include <boost/shared_ptr.hpp>
-#include "image.h"
-#include "server.h"
-
-using namespace boost;
-
-int main ()
-{
-       uint8_t* rgb = new uint8_t[256];
-       shared_ptr<Image> image (new Image (rgb, 0, 32, 32, 24));
-       Server* s = new Server ("localhost", 2);
-       image->encode_remotely (s);
-       return 0;
-}
index 5a837f8450b57aaa4b3371641503c666d3e6071d..42fc90adbeac9e073f7ab101fcd5eef6c40141dd 100644 (file)
@@ -1,19 +1,34 @@
+import os
+import glob
+from waflib import Logs
+import i18n
+
 def build(bld):
-    for t in ['makedcp', 'servomatic_cli']:
+    for t in ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test']:
         obj = bld(features = 'cxx cxxprogram')
-        obj.uselib = 'BOOST_THREAD OPENJPEG DCP AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC'
+        obj.uselib = 'BOOST_THREAD OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC 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', 'servomatic_gui']:
+        for t in ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server']:
             obj = bld(features = 'cxx cxxprogram')
-            obj.uselib = 'DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC'
+            obj.uselib = 'DCP 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 += ' ../../windows/dvdomatic.rc'
+                obj.source += ' ../../platform/windows/dcpomatic.rc'
             obj.target = t
+
+        i18n.po_to_mo(os.path.join('src', 'tools'), 'dcpomatic', bld)
+
+def pot(bld):
+    i18n.pot(os.path.join('src', 'tools'), 'dcpomatic.cc', 'dcpomatic')
+
+def pot_merge(bld):
+    i18n.pot_merge(os.path.join('src', 'tools'), 'dcpomatic')
index 3f17b3e6ce026106366bca7733169797f46378bd..a4cf349f9cf1b7b44b35306069638827e9d5b712 100644 (file)
@@ -7,3 +7,13 @@ def build(bld):
     bld.recurse('tools')
     if not bld.env.DISABLE_GUI:
         bld.recurse('wx')
+
+def pot(bld):
+    bld.recurse('lib')
+    bld.recurse('wx')
+    bld.recurse('tools')
+
+def pot_merge(bld):
+    bld.recurse('lib')
+    bld.recurse('wx')
+    bld.recurse('tools')
diff --git a/src/wx/about_dialog.cc b/src/wx/about_dialog.cc
new file mode 100644 (file)
index 0000000..45f2f6c
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <wx/notebook.h>
+#include <wx/hyperlink.h>
+#include "lib/version.h"
+#include "lib/compose.hpp"
+#include "about_dialog.h"
+#include "wx_util.h"
+
+using std::vector;
+
+AboutDialog::AboutDialog (wxWindow* parent)
+       : wxDialog (parent, wxID_ANY, _("About DCP-o-matic"))
+{
+       wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+       
+       wxFont title_font (*wxNORMAL_FONT);
+       title_font.SetPointSize (title_font.GetPointSize() + 12);
+       title_font.SetWeight (wxFONTWEIGHT_BOLD);
+
+       wxFont subtitle_font (*wxNORMAL_FONT);
+       subtitle_font.SetPointSize (subtitle_font.GetPointSize() + 2);
+
+       wxFont version_font (*wxNORMAL_FONT);
+       version_font.SetWeight (wxFONTWEIGHT_BOLD);
+       
+       wxStaticText* t = new wxStaticText (this, wxID_ANY, _("DCP-o-matic"));
+       t->SetFont (title_font);
+       sizer->Add (t, wxSizerFlags().Centre().Border(wxALL, 16));
+
+       wxString s;
+       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", dcpomatic_version, dcpomatic_git_commit)));
+       }
+       t->SetFont (version_font);
+       sizer->Add (t, wxSizerFlags().Centre().Border(wxALL, 2));
+       sizer->AddSpacer (12);
+
+       t = new wxStaticText (
+               this, wxID_ANY,
+               _("Free, open-source DCP generation from almost anything."),
+               wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER
+               );
+       t->SetFont (subtitle_font);
+       
+       sizer->Add (t, wxSizerFlags().Centre().Border(wxALL, 8));
+
+       wxHyperlinkCtrl* h = new wxHyperlinkCtrl (
+               this, wxID_ANY,
+               wxT ("dcpomatic.com"),
+               wxT ("http://dcpomatic.com")
+               );
+
+       sizer->Add (h, wxSizerFlags().Centre().Border(wxALL, 8));
+
+       t = new wxStaticText (
+               this, wxID_ANY,
+               _("(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen"),
+               wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER
+               );
+       
+       sizer->Add (t, wxSizerFlags().Centre().Border(wxLEFT | wxRIGHT, 16));
+
+       _notebook = new wxNotebook (this, wxID_ANY);
+
+       wxArrayString written_by;
+       written_by.Add (wxT ("Carl Hetherington"));
+       written_by.Add (wxT ("Terrence Meiczinger"));
+       written_by.Add (wxT ("Paul Davis"));
+       written_by.Add (wxT ("Ole Laursen"));
+       add_section (_("Written by"), written_by);
+
+       wxArrayString translated_by;
+       translated_by.Add (wxT ("Olivier Perriere"));
+       translated_by.Add (wxT ("Lilian Lefranc"));
+       translated_by.Add (wxT ("Thierry Journet"));
+       translated_by.Add (wxT ("Massimiliano Broggi"));
+       translated_by.Add (wxT ("Manuel AC"));
+       translated_by.Add (wxT ("Adam Klotblixt"));
+       add_section (_("Translated by"), translated_by);
+
+       wxArrayString supported_by;
+       supported_by.Add (wxT ("Carsten Kurz"));
+       supported_by.Add (wxT ("Wolfgang Woehl"));
+       supported_by.Add (wxT ("Manual AC"));
+       supported_by.Add (wxT ("Theo Lipfert"));
+       supported_by.Add (wxT ("Olivier Lemaire"));
+       supported_by.Add (wxT ("Mattias Mattsson"));
+       supported_by.Add (wxT ("Andrä Steiner"));
+       supported_by.Add (wxT ("Jonathan Jensen"));
+       supported_by.Add (wxT ("Kjarten Michaelsen"));
+       supported_by.Add (wxT ("Jussi Siponen"));
+       supported_by.Add (wxT ("Cinema Clarici"));
+       supported_by.Add (wxT ("Evan Freeze"));
+       supported_by.Add (wxT ("Flor Guillaume"));
+       supported_by.Add (wxT ("Adam Klotblixt "));
+       supported_by.Add (wxT ("Lilian Lefranc"));
+       supported_by.Add (wxT ("Gavin Lewarne"));
+       supported_by.Add (wxT ("Lasse Salling"));
+       supported_by.Add (wxT ("Andres Fink"));
+       supported_by.Add (wxT ("Kieran Carroll"));
+       supported_by.Add (wxT ("Kambiz Afshar"));
+       supported_by.Add (wxT ("Sean Leigh"));
+       supported_by.Add (wxT ("Wolfram Weber"));
+       add_section (_("Supported by"), supported_by);
+
+       sizer->Add (_notebook, wxSizerFlags().Centre().Border(wxALL, 16).Expand());
+       
+       SetSizerAndFit (sizer);
+}
+
+void
+AboutDialog::add_section (wxString name, wxArrayString credits)
+{
+       static bool first = true;
+       int const N = 3;
+
+       wxPanel* panel = new wxPanel (_notebook, wxID_ANY);
+       wxSizer* overall_sizer = new wxBoxSizer (wxHORIZONTAL);
+
+       vector<wxSizer*> sizers;
+       
+       for (int i = 0; i < N; ++i) {
+               sizers.push_back (new wxBoxSizer (wxVERTICAL));
+               overall_sizer->Add (sizers.back (), 1, wxEXPAND | wxALL, 6);
+       }
+
+       int c = 0;
+       for (size_t i = 0; i < credits.Count(); ++i) {
+               add_label_to_sizer (sizers[c], panel, credits[i], false);
+               ++c;
+               if (c == N) {
+                       c = 0;
+               }
+       }
+
+       panel->SetSizerAndFit (overall_sizer);
+       _notebook->AddPage (panel, name, first);
+       first = false;
+}
diff --git a/src/wx/about_dialog.h b/src/wx/about_dialog.h
new file mode 100644 (file)
index 0000000..a78abb9
--- /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 <wx/wx.h>
+
+class wxNotebook;
+
+class AboutDialog : public wxDialog
+{
+public:
+       AboutDialog (wxWindow *);
+
+private:
+       void add_section (wxString, wxArrayString);
+
+       wxNotebook* _notebook;
+};
+
diff --git a/src/wx/audio_dialog.cc b/src/wx/audio_dialog.cc
new file mode 100644 (file)
index 0000000..c7a0815
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+    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/filesystem.hpp>
+#include "lib/audio_analysis.h"
+#include "lib/film.h"
+#include "lib/audio_content.h"
+#include "audio_dialog.h"
+#include "audio_plot.h"
+#include "wx_util.h"
+
+using boost::shared_ptr;
+using boost::bind;
+using boost::optional;
+
+AudioDialog::AudioDialog (wxWindow* parent)
+       : wxDialog (parent, wxID_ANY, _("Audio"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
+       , _plot (0)
+{
+       wxBoxSizer* sizer = new wxBoxSizer (wxHORIZONTAL);
+
+       _plot = new AudioPlot (this);
+       sizer->Add (_plot, 1, wxALL | wxEXPAND, 12);
+
+       wxBoxSizer* side = new wxBoxSizer (wxVERTICAL);
+
+       {
+               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)));
+               side->Add (_channel_checkbox[i], 1, wxEXPAND | wxALL, 3);
+               _channel_checkbox[i]->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AudioDialog::channel_clicked, this, _1));
+       }
+
+       {
+               wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Type"));
+               side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
+       }
+       
+       wxString const types[] = {
+               _("Peak"),
+               _("RMS")
+       };
+
+       for (int i = 0; i < AudioPoint::COUNT; ++i) {
+               _type_checkbox[i] = new wxCheckBox (this, wxID_ANY, types[i]);
+               side->Add (_type_checkbox[i], 1, wxEXPAND | wxALL, 3);
+               _type_checkbox[i]->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AudioDialog::type_clicked, this, _1));
+       }
+
+       {
+               wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Smoothing"));
+               side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
+       }
+       
+       _smoothing = new wxSlider (this, wxID_ANY, AudioPlot::max_smoothing / 2, 1, AudioPlot::max_smoothing);
+       _smoothing->Bind (wxEVT_SCROLL_THUMBTRACK, boost::bind (&AudioDialog::smoothing_changed, this));
+       side->Add (_smoothing, 1, wxEXPAND);
+
+       sizer->Add (side, 0, wxALL, 12);
+
+       SetSizer (sizer);
+       sizer->Layout ();
+       sizer->SetSizeHints (this);
+}
+
+void
+AudioDialog::set_content (shared_ptr<AudioContent> c)
+{
+       _content_changed_connection.disconnect ();
+       
+       _content = c;
+
+       try_to_load_analysis ();
+       _plot->set_gain (_content->audio_gain ());
+
+       _content_changed_connection = _content->Changed.connect (bind (&AudioDialog::content_changed, this, _2));
+
+       SetTitle (wxString::Format (_("DCP-o-matic audio - %s"), std_to_wx(_content->path().filename().string()).data()));
+}
+
+void
+AudioDialog::try_to_load_analysis ()
+{
+       if (!boost::filesystem::exists (_content->audio_analysis_path()) && IsShown ()) {
+               _content->analyse_audio (bind (&AudioDialog::analysis_finished, this));
+               return;
+       }
+       
+       shared_ptr<AudioAnalysis> a;
+       
+       a.reset (new AudioAnalysis (_content->audio_analysis_path ()));
+       _plot->set_analysis (a);
+
+       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);
+               _plot->set_type_visible (i, true);
+       }
+}
+
+void
+AudioDialog::analysis_finished ()
+{
+       if (!boost::filesystem::exists (_content->audio_analysis_path())) {
+               /* We analysed and still nothing showed up, so maybe it was cancelled or it failed.
+                  Give up.
+               */
+               _plot->set_message (_("Could not analyse audio."));
+               return;
+       }
+
+       try_to_load_analysis ();
+}
+
+void
+AudioDialog::channel_clicked (wxCommandEvent& ev)
+{
+       int c = 0;
+       while (c < MAX_AUDIO_CHANNELS && ev.GetEventObject() != _channel_checkbox[c]) {
+               ++c;
+       }
+
+       assert (c < MAX_AUDIO_CHANNELS);
+
+       _plot->set_channel_visible (c, _channel_checkbox[c]->GetValue ());
+}
+
+void
+AudioDialog::content_changed (int p)
+{
+       if (p == AudioContentProperty::AUDIO_GAIN) {
+               _plot->set_gain (_content->audio_gain ());
+       }
+}
+
+void
+AudioDialog::type_clicked (wxCommandEvent& ev)
+{
+       int t = 0;
+       while (t < AudioPoint::COUNT && ev.GetEventObject() != _type_checkbox[t]) {
+               ++t;
+       }
+
+       assert (t < AudioPoint::COUNT);
+
+       _plot->set_type_visible (t, _type_checkbox[t]->GetValue ());
+}
+
+void
+AudioDialog::smoothing_changed ()
+{
+       _plot->set_smoothing (_smoothing->GetValue ());
+}
diff --git a/src/wx/audio_dialog.h b/src/wx/audio_dialog.h
new file mode 100644 (file)
index 0000000..8623192
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+    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/signals2.hpp>
+#include <wx/wx.h>
+#include "lib/film.h"
+#include "lib/audio_analysis.h"
+
+class AudioPlot;
+class Film;
+
+class AudioDialog : public wxDialog
+{
+public:
+       AudioDialog (wxWindow *);
+
+       void set_content (boost::shared_ptr<AudioContent>);
+
+private:
+       void content_changed (int);
+       void channel_clicked (wxCommandEvent &);
+       void type_clicked (wxCommandEvent &);
+       void smoothing_changed ();
+       void try_to_load_analysis ();
+       void analysis_finished ();
+
+       boost::shared_ptr<AudioContent> _content;
+       AudioPlot* _plot;
+       wxCheckBox* _channel_checkbox[MAX_AUDIO_CHANNELS];
+       wxCheckBox* _type_checkbox[AudioPoint::COUNT];
+       wxSlider* _smoothing;
+       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..3136b86
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+    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 "lib/util.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
+#else
+#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)
+       {
+               dc.SetPen (*wxThePenList->FindOrCreatePen (wxColour (255, 255, 255), 0, wxPENSTYLE_SOLID));
+               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);
+       _grid->HideRowLabels ();
+       _grid->DisableDragRowSize ();
+       _grid->DisableDragColSize ();
+       _grid->EnableEditing (false);
+       _grid->SetCellHighlightPenWidth (0);
+       _grid->SetDefaultRenderer (new NoSelectionStringRenderer);
+
+       set_column_labels ();
+
+       _sizer = new wxBoxSizer (wxVERTICAL);
+       _sizer->Add (_grid, 1, wxEXPAND | wxALL);
+       SetSizerAndFit (_sizer);
+
+       Bind (wxEVT_GRID_CELL_LEFT_CLICK, boost::bind (&AudioMappingView::left_click, this, _1));
+}
+
+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"));
+       }
+
+       _map = AudioMapping (_map.content_channels ());
+       
+       for (int i = 0; i < _grid->GetNumberRows(); ++i) {
+               for (int j = 1; j < _grid->GetNumberCols(); ++j) {
+                       if (_grid->GetCellValue (i, j) == wxT ("1")) {
+                               _map.add (i, static_cast<libdcp::Channel> (j - 1));
+                       }
+               }
+       }
+
+       Changed (_map);
+}
+
+void
+AudioMappingView::set (AudioMapping map)
+{
+       _map = map;
+       
+       if (_grid->GetNumberRows ()) {
+               _grid->DeleteRows (0, _grid->GetNumberRows ());
+       }
+
+       _grid->InsertRows (0, _map.content_channels ());
+
+       for (int r = 0; r < _map.content_channels(); ++r) {
+               for (int c = 1; c < 7; ++c) {
+                       _grid->SetCellRenderer (r, c, new CheckBoxRenderer);
+               }
+       }
+       
+       for (int i = 0; i < _map.content_channels(); ++i) {
+               _grid->SetCellValue (i, 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) {
+                       int const c = static_cast<int>(*j) + 1;
+                       if (c < _grid->GetNumberCols ()) {
+                               _grid->SetCellValue (i, c, wxT("1"));
+                       }
+               }
+       }
+}
+
+void
+AudioMappingView::set_channels (int c)
+{
+       c++;
+
+       if (c < _grid->GetNumberCols ()) {
+               _grid->DeleteCols (c, _grid->GetNumberCols() - c);
+       } else if (c > _grid->GetNumberCols ()) {
+               _grid->InsertCols (_grid->GetNumberCols(), c - _grid->GetNumberCols());
+               set_column_labels ();
+       }
+
+       set (_map);
+}
+
+void
+AudioMappingView::set_column_labels ()
+{
+       int const c = _grid->GetNumberCols ();
+       
+       _grid->SetColLabelValue (0, _("Content channel"));
+       
+       if (c > 0) {
+               _grid->SetColLabelValue (1, _("L"));
+       }
+       
+       if (c > 1) {
+               _grid->SetColLabelValue (2, _("R"));
+       }
+       
+       if (c > 2) {
+               _grid->SetColLabelValue (3, _("C"));
+       }
+       
+       if (c > 3) {
+               _grid->SetColLabelValue (4, _("Lfe"));
+       }
+       
+       if (c > 4) {
+               _grid->SetColLabelValue (5, _("Ls"));
+       }
+       
+       if (c > 5) {
+               _grid->SetColLabelValue (6, _("Rs"));
+       }
+
+       _grid->AutoSize ();
+}
diff --git a/src/wx/audio_mapping_view.h b/src/wx/audio_mapping_view.h
new file mode 100644 (file)
index 0000000..80534a6
--- /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 <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);
+       void set_channels (int);
+
+       boost::signals2::signal<void (AudioMapping)> Changed;
+
+private:
+       void left_click (wxGridEvent &);
+       void set_column_labels ();
+
+       wxGrid* _grid;
+       wxSizer* _sizer;
+       AudioMapping _map;
+};
diff --git a/src/wx/audio_panel.cc b/src/wx/audio_panel.cc
new file mode 100644 (file)
index 0000000..b492190
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+    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/lexical_cast.hpp>
+#include <wx/spinctrl.h>
+#include "lib/config.h"
+#include "lib/sound_processor.h"
+#include "lib/ffmpeg_content.h"
+#include "audio_dialog.h"
+#include "audio_panel.h"
+#include "audio_mapping_view.h"
+#include "wx_util.h"
+#include "gain_calculator_dialog.h"
+#include "film_editor.h"
+
+using std::vector;
+using std::cout;
+using std::string;
+using boost::dynamic_pointer_cast;
+using boost::lexical_cast;
+using boost::shared_ptr;
+
+AudioPanel::AudioPanel (FilmEditor* e)
+       : FilmEditorPanel (e, _("Audio"))
+       , _audio_dialog (0)
+{
+       wxFlexGridSizer* grid = new wxFlexGridSizer (3, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       _sizer->Add (grid, 0, wxALL, 8);
+
+       _show = new wxButton (this, wxID_ANY, _("Show Audio..."));
+       grid->Add (_show, 1);
+       grid->AddSpacer (0);
+       grid->AddSpacer (0);
+
+       add_label_to_sizer (grid, this, _("Audio Gain"), true);
+       {
+               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _gain = new wxSpinCtrl (this);
+               s->Add (_gain, 1);
+               add_label_to_sizer (s, this, _("dB"), false);
+               grid->Add (s, 1);
+       }
+       
+       _gain_calculate_button = new wxButton (this, wxID_ANY, _("Calculate..."));
+       grid->Add (_gain_calculate_button);
+
+       add_label_to_sizer (grid, this, _("Audio Delay"), false);
+       {
+               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _delay = new wxSpinCtrl (this);
+               s->Add (_delay, 1);
+               /// TRANSLATORS: this is an abbreviation for milliseconds, the unit of time
+               add_label_to_sizer (s, this, _("ms"), false);
+               grid->Add (s);
+       }
+
+       grid->AddSpacer (0);
+
+       add_label_to_sizer (grid, this, _("Audio Stream"), true);
+       _stream = new wxChoice (this, wxID_ANY);
+       grid->Add (_stream, 1, wxEXPAND);
+       _description = new wxStaticText (this, wxID_ANY, wxT (""));
+       grid->AddSpacer (0);
+       
+       grid->Add (_description, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
+       grid->AddSpacer (0);
+       grid->AddSpacer (0);
+       
+       _mapping = new AudioMappingView (this);
+       _sizer->Add (_mapping, 1, wxEXPAND | wxALL, 6);
+
+       _gain->SetRange (-60, 60);
+       _delay->SetRange (-1000, 1000);
+
+       _delay->Bind                 (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&AudioPanel::delay_changed, this));
+       _stream->Bind                (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&AudioPanel::stream_changed, this));
+       _show->Bind                  (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&AudioPanel::show_clicked, this));
+       _gain->Bind                  (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&AudioPanel::gain_changed, this));
+       _gain_calculate_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&AudioPanel::gain_calculate_button_clicked, this));
+
+       _mapping->Changed.connect (boost::bind (&AudioPanel::mapping_changed, this, _1));
+}
+
+
+void
+AudioPanel::film_changed (Film::Property property)
+{
+       switch (property) {
+       case Film::AUDIO_CHANNELS:
+               _mapping->set_channels (_editor->film()->audio_channels ());
+               _sizer->Layout ();
+               break;
+       default:
+               break;
+       }
+}
+
+void
+AudioPanel::film_content_changed (shared_ptr<Content> c, int property)
+{
+       shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (c);
+       shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
+
+       if (_audio_dialog && _editor->selected_audio_content()) {
+               _audio_dialog->set_content (_editor->selected_audio_content ());
+       }
+       
+       if (property == AudioContentProperty::AUDIO_GAIN) {
+               checked_set (_gain, ac ? ac->audio_gain() : 0);
+       } else if (property == AudioContentProperty::AUDIO_DELAY) {
+               checked_set (_delay, ac ? ac->audio_delay() : 0);
+       } else if (property == AudioContentProperty::AUDIO_MAPPING) {
+               _mapping->set (ac ? ac->audio_mapping () : AudioMapping ());
+               _sizer->Layout ();
+       } else if (property == FFmpegContentProperty::AUDIO_STREAM) {
+               setup_stream_description ();
+       } else if (property == FFmpegContentProperty::AUDIO_STREAMS) {
+               _stream->Clear ();
+               if (fc) {
+                       vector<shared_ptr<FFmpegAudioStream> > a = fc->audio_streams ();
+                       for (vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin(); i != a.end(); ++i) {
+                               _stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
+                       }
+                       
+                       if (fc->audio_stream()) {
+                               checked_set (_stream, lexical_cast<string> (fc->audio_stream()->id));
+                               setup_stream_description ();
+                       }
+               }
+       }
+}
+
+void
+AudioPanel::gain_changed ()
+{
+       shared_ptr<AudioContent> ac = _editor->selected_audio_content ();
+       if (!ac) {
+               return;
+       }
+
+       ac->set_audio_gain (_gain->GetValue ());
+}
+
+void
+AudioPanel::delay_changed ()
+{
+       shared_ptr<AudioContent> ac = _editor->selected_audio_content ();
+       if (!ac) {
+               return;
+       }
+
+       ac->set_audio_delay (_delay->GetValue ());
+}
+
+void
+AudioPanel::gain_calculate_button_clicked ()
+{
+       GainCalculatorDialog* d = new GainCalculatorDialog (this);
+       d->ShowModal ();
+
+       if (d->wanted_fader() == 0 || d->actual_fader() == 0) {
+               d->Destroy ();
+               return;
+       }
+       
+       _gain->SetValue (
+               Config::instance()->sound_processor()->db_for_fader_change (
+                       d->wanted_fader (),
+                       d->actual_fader ()
+                       )
+               );
+
+       /* This appears to be necessary, as the change is not signalled,
+          I think.
+       */
+       gain_changed ();
+       
+       d->Destroy ();
+}
+
+void
+AudioPanel::show_clicked ()
+{
+       if (_audio_dialog) {
+               _audio_dialog->Destroy ();
+               _audio_dialog = 0;
+       }
+
+       shared_ptr<Content> c = _editor->selected_content ();
+       if (!c) {
+               return;
+       }
+
+       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
+AudioPanel::stream_changed ()
+{
+       shared_ptr<Content> c = _editor->selected_content ();
+       if (!c) {
+               return;
+       }
+       
+       shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
+       if (!fc) {
+               return;
+       }
+
+       if (_stream->GetSelection() == -1) {
+               return;
+       }
+       
+       vector<shared_ptr<FFmpegAudioStream> > a = fc->audio_streams ();
+       vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin ();
+       string const s = string_client_data (_stream->GetClientObject (_stream->GetSelection ()));
+       while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
+               ++i;
+       }
+
+       if (i != a.end ()) {
+               fc->set_audio_stream (*i);
+       }
+
+       setup_stream_description ();
+}
+
+void
+AudioPanel::setup_stream_description ()
+{
+       shared_ptr<Content> c = _editor->selected_content ();
+       if (!c) {
+               return;
+       }
+       
+       shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
+       if (!fc) {
+               return;
+       }
+
+       if (!fc->audio_stream ()) {
+               _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");
+               _description->SetLabel (s);
+       }
+}
+
+void
+AudioPanel::mapping_changed (AudioMapping m)
+{
+       shared_ptr<AudioContent> c = _editor->selected_audio_content ();
+       if (!c) {
+               return;
+       }
+
+       c->set_audio_mapping (m);
+}
+
diff --git a/src/wx/audio_panel.h b/src/wx/audio_panel.h
new file mode 100644 (file)
index 0000000..e1dc283
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+    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 "lib/audio_mapping.h"
+#include "film_editor_panel.h"
+
+class wxSpinCtrl;
+class wxButton;
+class wxChoice;
+class wxStaticText;
+class AudioMappingView;
+class AudioDialog;
+
+class AudioPanel : public FilmEditorPanel
+{
+public:
+       AudioPanel (FilmEditor *);
+
+       void film_changed (Film::Property);
+       void film_content_changed (boost::shared_ptr<Content>, int);
+       
+private:
+       void gain_changed ();
+       void gain_calculate_button_clicked ();
+       void show_clicked ();
+       void delay_changed ();
+       void stream_changed ();
+       void mapping_changed (AudioMapping);
+       void setup_stream_description ();
+
+       wxSpinCtrl* _gain;
+       wxButton* _gain_calculate_button;
+       wxButton* _show;
+       wxSpinCtrl* _delay;
+       wxChoice* _stream;
+       wxStaticText* _description;
+       AudioMappingView* _mapping;
+       AudioDialog* _audio_dialog;
+};
diff --git a/src/wx/audio_plot.cc b/src/wx/audio_plot.cc
new file mode 100644 (file)
index 0000000..7ed7923
--- /dev/null
@@ -0,0 +1,281 @@
+/*
+    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 <boost/bind.hpp>
+#include <wx/graphics.h>
+#include "audio_plot.h"
+#include "lib/audio_decoder.h"
+#include "lib/audio_analysis.h"
+#include "wx/wx_util.h"
+
+using std::cout;
+using std::vector;
+using std::list;
+using std::max;
+using std::min;
+using boost::bind;
+using boost::shared_ptr;
+
+int const AudioPlot::_minimum = -70;
+int const AudioPlot::max_smoothing = 128;
+
+AudioPlot::AudioPlot (wxWindow* parent)
+       : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
+       , _gain (0)
+       , _smoothing (max_smoothing / 2)
+       , _message (_("Please wait; audio is being analysed..."))
+{
+#ifndef __WXOSX__      
+       SetDoubleBuffered (true);
+#endif 
+
+       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+               _channel_visible[i] = false;
+       }
+
+       for (int i = 0; i < AudioPoint::COUNT; ++i) {
+               _type_visible[i] = false;
+       }
+
+       _colours.push_back (wxColour (  0,   0,   0));
+       _colours.push_back (wxColour (255,   0,   0));
+       _colours.push_back (wxColour (  0, 255,   0));
+       _colours.push_back (wxColour (139,   0, 204));
+       _colours.push_back (wxColour (  0,   0, 255));
+       _colours.push_back (wxColour (100, 100, 100));
+       
+       Bind (wxEVT_PAINT, boost::bind (&AudioPlot::paint, this));
+       
+       SetMinSize (wxSize (640, 512));
+}
+
+void
+AudioPlot::set_analysis (shared_ptr<AudioAnalysis> a)
+{
+       _analysis = a;
+
+       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+               _channel_visible[i] = false;
+       }
+
+       for (int i = 0; i < AudioPoint::COUNT; ++i) {
+               _type_visible[i] = false;
+       }
+       
+       Refresh ();
+}
+
+void
+AudioPlot::set_channel_visible (int c, bool v)
+{
+       _channel_visible[c] = v;
+       Refresh ();
+}
+
+void
+AudioPlot::set_type_visible (int t, bool v)
+{
+       _type_visible[t] = v;
+       Refresh ();
+}
+
+void
+AudioPlot::set_message (wxString s)
+{
+       _message = s;
+       Refresh ();
+}
+
+void
+AudioPlot::paint ()
+{
+       wxPaintDC dc (this);
+
+       wxGraphicsContext* gc = wxGraphicsContext::Create (dc);
+       if (!gc) {
+               return;
+       }
+
+       if (!_analysis || _analysis->channels() == 0) {
+               gc->SetFont (gc->CreateFont (*wxNORMAL_FONT));
+               gc->DrawText (_message, 32, 32);
+               return;
+       }
+
+       wxGraphicsPath grid = gc->CreatePath ();
+       gc->SetFont (gc->CreateFont (*wxSMALL_FONT));
+       wxDouble db_label_height;
+       wxDouble db_label_descent;
+       wxDouble db_label_leading;
+       gc->GetTextExtent (wxT ("-80dB"), &_db_label_width, &db_label_height, &db_label_descent, &db_label_leading);
+
+       _db_label_width += 8;
+       
+       int const data_width = GetSize().GetWidth() - _db_label_width;
+       /* Assume all channels have the same number of points */
+       _x_scale = data_width / float (_analysis->points (0));
+       _height = GetSize().GetHeight ();
+       _y_origin = 32;
+       _y_scale = (_height - _y_origin) / -_minimum;
+
+       for (int i = _minimum; i <= 0; i += 10) {
+               int const y = (_height - (i - _minimum) * _y_scale) - _y_origin;
+               grid.MoveToPoint (_db_label_width - 4, y);
+               grid.AddLineToPoint (_db_label_width + data_width, y);
+               gc->DrawText (std_to_wx (String::compose ("%1dB", i)), 0, y - (db_label_height / 2));
+       }
+
+       gc->SetPen (*wxLIGHT_GREY_PEN);
+       gc->StrokePath (grid);
+
+       gc->DrawText (_("Time"), data_width, _height - _y_origin + db_label_height / 2);
+
+       
+       if (_type_visible[AudioPoint::PEAK]) {
+               for (int c = 0; c < MAX_AUDIO_CHANNELS; ++c) {
+                       wxGraphicsPath p = gc->CreatePath ();
+                       if (_channel_visible[c] && c < _analysis->channels()) {
+                               plot_peak (p, c);
+                       }
+                       wxColour const col = _colours[c];
+                       gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (col.Red(), col.Green(), col.Blue(), col.Alpha() / 2), 1, wxPENSTYLE_SOLID));
+                       gc->StrokePath (p);
+               }
+       }
+
+       if (_type_visible[AudioPoint::RMS]) {
+               for (int c = 0; c < MAX_AUDIO_CHANNELS; ++c) {
+                       wxGraphicsPath p = gc->CreatePath ();
+                       if (_channel_visible[c] && c < _analysis->channels()) {
+                               plot_rms (p, c);
+                       }
+                       wxColour const col = _colours[c];
+                       gc->SetPen (*wxThePenList->FindOrCreatePen (col, 1, wxPENSTYLE_SOLID));
+                       gc->StrokePath (p);
+               }
+       }
+
+       wxGraphicsPath axes = gc->CreatePath ();
+       axes.MoveToPoint (_db_label_width, 0);
+       axes.AddLineToPoint (_db_label_width, _height - _y_origin);
+       axes.AddLineToPoint (_db_label_width + data_width, _height - _y_origin);
+       gc->SetPen (*wxBLACK_PEN);
+       gc->StrokePath (axes);
+
+       delete gc;
+}
+
+float
+AudioPlot::y_for_linear (float p) const
+{
+       return _height - (20 * log10(p) - _minimum + _gain) * _y_scale - _y_origin;
+}
+
+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;
+       int const N = _analysis->points(channel);
+       for (int i = 0; i < N; ++i) {
+               float const p = _analysis->get_point(channel, i)[AudioPoint::PEAK];
+               peak -= 0.01f * (1 - log10 (_smoothing) / log10 (max_smoothing));
+               if (p > peak) {
+                       peak = p;
+               } else if (peak < 0) {
+                       peak = 0;
+               }
+               
+               path.AddLineToPoint (_db_label_width + i * _x_scale, y_for_linear (peak));
+       }
+}
+
+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;
+
+       int const N = _analysis->points(channel);
+
+       float const first = _analysis->get_point(channel, 0)[AudioPoint::RMS];
+       float const last = _analysis->get_point(channel, N - 1)[AudioPoint::RMS];
+
+       int const before = _smoothing / 2;
+       int const after = _smoothing - before;
+       
+       /* Pre-load the smoothing list */
+       for (int i = 0; i < before; ++i) {
+               smoothing.push_back (first);
+       }
+       for (int i = 0; i < after; ++i) {
+               if (i < N) {
+                       smoothing.push_back (_analysis->get_point(channel, i)[AudioPoint::RMS]);
+               } else {
+                       smoothing.push_back (last);
+               }
+       }
+       
+       for (int i = 0; i < N; ++i) {
+
+               int const next_for_window = i + after;
+
+               if (next_for_window < N) {
+                       smoothing.push_back (_analysis->get_point(channel, i)[AudioPoint::RMS]);
+               } else {
+                       smoothing.push_back (last);
+               }
+
+               smoothing.pop_front ();
+
+               float p = 0;
+               for (list<float>::const_iterator j = smoothing.begin(); j != smoothing.end(); ++j) {
+                       p += pow (*j, 2);
+               }
+
+               p = sqrt (p / smoothing.size ());
+
+               path.AddLineToPoint (_db_label_width + i * _x_scale, y_for_linear (p));
+       }
+}
+
+void
+AudioPlot::set_gain (float g)
+{
+       _gain = g;
+       Refresh ();
+}
+
+void
+AudioPlot::set_smoothing (int s)
+{
+       _smoothing = s;
+       Refresh ();
+}
diff --git a/src/wx/audio_plot.h b/src/wx/audio_plot.h
new file mode 100644 (file)
index 0000000..094f8e1
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+    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 <vector>
+#include <boost/shared_ptr.hpp>
+#include <wx/wx.h>
+#include "lib/util.h"
+#include "lib/audio_analysis.h"
+
+class AudioPlot : public wxPanel
+{
+public:
+       AudioPlot (wxWindow *);
+
+       void set_analysis (boost::shared_ptr<AudioAnalysis>);
+       void set_channel_visible (int c, bool v);
+       void set_type_visible (int t, bool v);
+       void set_gain (float);
+       void set_smoothing (int);
+       void set_message (wxString);
+
+       static const int max_smoothing;
+
+private:
+       void paint ();
+
+       boost::shared_ptr<AudioAnalysis> _analysis;
+       bool _channel_visible[MAX_AUDIO_CHANNELS];
+       bool _type_visible[AudioPoint::COUNT];
+       /** gain to apply in dB */
+       float _gain;
+       int _smoothing;
+       std::vector<wxColour> _colours;
+
+       void plot_peak (wxGraphicsPath &, int) const;
+       void plot_rms (wxGraphicsPath &, int) const;
+       float y_for_linear (float) const;
+
+       double _db_label_width;
+       int _height;
+       int _y_origin;
+       float _x_scale;
+       float _y_scale;
+
+       wxString _message;
+
+       static const int _minimum;
+};
index 9e3b1507df7cea016bcb3f5f3f265ad6eefcaed3..2c0b0b4a4f5b5fe90c7311b261301d8e403f44a6 100644 (file)
@@ -28,11 +28,11 @@ CinemaDialog::CinemaDialog (wxWindow* parent, string title, string name, string
        wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
        table->AddGrowableCol (1, 1);
 
-       add_label_to_sizer (table, this, "Name");
+       add_label_to_sizer (table, this, "Name", true);
        _name = new wxTextCtrl (this, wxID_ANY, std_to_wx (name), wxDefaultPosition, wxSize (256, -1));
        table->Add (_name, 1, wxEXPAND);
 
-       add_label_to_sizer (table, this, "Email address for KDM delivery");
+       add_label_to_sizer (table, this, "Email address for KDM delivery", true);
        _email = new wxTextCtrl (this, wxID_ANY, std_to_wx (email), wxDefaultPosition, wxSize (256, -1));
        table->Add (_email, 1, wxEXPAND);
 
diff --git a/src/wx/colour_conversion_editor.cc b/src/wx/colour_conversion_editor.cc
new file mode 100644 (file)
index 0000000..823f5ad
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+    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 <wx/spinctrl.h>
+#include <wx/gbsizer.h>
+#include "lib/colour_conversion.h"
+#include "wx_util.h"
+#include "colour_conversion_editor.h"
+
+using std::string;
+using std::cout;
+using std::stringstream;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+ColourConversionEditor::ColourConversionEditor (wxWindow* parent)
+       : wxPanel (parent, wxID_ANY)
+{
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       SetSizer (overall_sizer);
+
+       wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       overall_sizer->Add (table, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+
+       int r = 0;
+
+       add_label_to_grid_bag_sizer (table, this, _("Input gamma"), true, wxGBPosition (r, 0));
+       _input_gamma = new wxSpinCtrlDouble (this);
+       table->Add (_input_gamma, wxGBPosition (r, 1));
+       ++r;
+
+       _input_gamma_linearised = new wxCheckBox (this, wxID_ANY, _("Linearise input gamma curve for low values"));
+       table->Add (_input_gamma_linearised, wxGBPosition (r, 0), wxGBSpan (1, 2));
+       ++r;
+
+        wxClientDC dc (parent);
+        wxSize size = dc.GetTextExtent (wxT ("-0.12345678901"));
+        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);
+
+       add_label_to_grid_bag_sizer (table, this, _("Matrix"), true, wxGBPosition (r, 0));
+       wxFlexGridSizer* matrix_sizer = new wxFlexGridSizer (3, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       for (int i = 0; i < 3; ++i) {
+               for (int j = 0; j < 3; ++j) {
+                       _matrix[i][j] = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, size, 0, validator);
+                       matrix_sizer->Add (_matrix[i][j]);
+               }
+       }
+       table->Add (matrix_sizer, wxGBPosition (r, 1));
+       ++r;
+
+       add_label_to_grid_bag_sizer (table, this, _("Output gamma"), true, wxGBPosition (r, 0));
+       wxBoxSizer* output_sizer = new wxBoxSizer (wxHORIZONTAL);
+       /* TRANSLATORS: this means the mathematical reciprocal operation, i.e. we are dividing 1 by the control that
+          comes after it.
+       */
+       add_label_to_sizer (output_sizer, this, _("1 / "), false);
+       _output_gamma = new wxSpinCtrlDouble (this);
+       output_sizer->Add (_output_gamma);
+       table->Add (output_sizer, wxGBPosition (r, 1));
+       ++r;
+
+       _input_gamma->SetRange(0.1, 4.0);
+       _input_gamma->SetDigits (1);
+       _input_gamma->SetIncrement (0.1);
+       _output_gamma->SetRange(0.1, 4.0);
+       _output_gamma->SetDigits (1);
+       _output_gamma->SetIncrement (0.1);
+
+       _input_gamma->Bind (wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, boost::bind (&ColourConversionEditor::changed, this));
+       _input_gamma_linearised->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&ColourConversionEditor::changed, this));
+       for (int i = 0; i < 3; ++i) {
+               for (int j = 0; j < 3; ++j) {
+                       _matrix[i][j]->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ColourConversionEditor::changed, this));
+               }
+       }
+       _output_gamma->Bind (wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, boost::bind (&ColourConversionEditor::changed, this));
+}
+
+void
+ColourConversionEditor::set (ColourConversion conversion)
+{
+       _input_gamma->SetValue (conversion.input_gamma);
+       _input_gamma_linearised->SetValue (conversion.input_gamma_linearised);
+       for (int i = 0; i < 3; ++i) {
+               for (int j = 0; j < 3; ++j) {
+                       stringstream s;
+                       s.setf (std::ios::fixed, std::ios::floatfield);
+                       s.precision (7);
+                       s << conversion.matrix (i, j);
+                       _matrix[i][j]->SetValue (std_to_wx (s.str ()));
+               }
+       }
+       _output_gamma->SetValue (conversion.output_gamma);
+}
+
+ColourConversion
+ColourConversionEditor::get () const
+{
+       ColourConversion conversion;
+       
+       conversion.input_gamma = _input_gamma->GetValue ();
+       conversion.input_gamma_linearised = _input_gamma_linearised->GetValue ();
+
+       for (int i = 0; i < 3; ++i) {
+               for (int j = 0; j < 3; ++j) {
+                       string const v = wx_to_std (_matrix[i][j]->GetValue ());
+                       if (v.empty ()) {
+                               conversion.matrix (i, j) = 0;
+                       } else {
+                               conversion.matrix (i, j) = lexical_cast<double> (v);
+                       }
+               }
+       }
+       
+       conversion.output_gamma = _output_gamma->GetValue ();
+
+       return conversion;
+}
+
+void
+ColourConversionEditor::changed ()
+{
+       Changed ();
+}
+
diff --git a/src/wx/colour_conversion_editor.h b/src/wx/colour_conversion_editor.h
new file mode 100644 (file)
index 0000000..8567cac
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+    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_COLOUR_CONVERSION_EDITOR_H
+#define DCPOMATIC_COLOUR_CONVERSION_EDITOR_H
+
+#include <boost/signals2.hpp>
+#include <wx/wx.h>
+
+class wxSpinCtrlDouble;
+class ColourConversion;
+
+class ColourConversionEditor : public wxPanel
+{
+public:
+       ColourConversionEditor (wxWindow *);
+
+       void set (ColourConversion);
+       ColourConversion get () const;
+
+       boost::signals2::signal<void ()> Changed;
+
+private:
+       void changed ();
+       
+       wxSpinCtrlDouble* _input_gamma;
+       wxCheckBox* _input_gamma_linearised;
+       wxTextCtrl* _matrix[3][3];
+       wxSpinCtrlDouble* _output_gamma;
+};
+
+#endif
index 9de8e700194642eaeff484c075f098c486b66919..7f1efa52f6082df074ec0e049c263c46843a1aa5 100644 (file)
 */
 
 /** @file src/config_dialog.cc
- *  @brief A dialogue to edit DVD-o-matic configuration.
+ *  @brief A dialogue to edit DCP-o-matic configuration.
  */
 
 #include <iostream>
 #include <boost/lexical_cast.hpp>
 #include <boost/filesystem.hpp>
 #include <wx/stdpaths.h>
+#include <wx/notebook.h>
+#include <libdcp/colour_matrix.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"
+#include "lib/colour_conversion.h"
 #include "config_dialog.h"
 #include "wx_util.h"
 #include "filter_dialog.h"
 #include "server_dialog.h"
 #include "dir_picker_ctrl.h"
+#include "dci_metadata_dialog.h"
+#include "preset_colour_conversion_dialog.h"
 
-using namespace std;
+using std::vector;
+using std::string;
+using std::list;
 using boost::bind;
+using boost::shared_ptr;
+using boost::lexical_cast;
 
 ConfigDialog::ConfigDialog (wxWindow* parent)
-       : wxDialog (parent, wxID_ANY, _("DVD-o-matic Preferences"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+       : wxDialog (parent, wxID_ANY, _("DCP-o-matic Preferences"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
 {
-       wxFlexGridSizer* table = new wxFlexGridSizer (3, 6, 6);
-       table->AddGrowableCol (1, 1);
+       wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+       _notebook = new wxNotebook (this, wxID_ANY);
+       s->Add (_notebook, 1);
+
+       make_misc_panel ();
+       _notebook->AddPage (_misc_panel, _("Miscellaneous"), true);
+       make_servers_panel ();
+       _notebook->AddPage (_servers_panel, _("Encoding servers"), false);
+       make_colour_conversions_panel ();
+       _notebook->AddPage (_colour_conversions_panel, _("Colour conversions"), false);
+       make_metadata_panel ();
+       _notebook->AddPage (_metadata_panel, _("Metadata"), false);
+       make_tms_panel ();
+       _notebook->AddPage (_tms_panel, _("TMS"), false);
 
-       add_label_to_sizer (table, this, "TMS IP address");
-       _tms_ip = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_tms_ip, 1, wxEXPAND);
-       table->AddSpacer (0);
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       overall_sizer->Add (s, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
 
-       add_label_to_sizer (table, this, "TMS target path");
-       _tms_path = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_tms_path, 1, wxEXPAND);
-       table->AddSpacer (0);
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
+       if (buttons) {
+               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
 
-       add_label_to_sizer (table, this, "TMS user name");
-       _tms_user = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_tms_user, 1, wxEXPAND);
-       table->AddSpacer (0);
+       SetSizer (overall_sizer);
+       overall_sizer->Layout ();
+       overall_sizer->SetSizeHints (this);
+}
 
-       add_label_to_sizer (table, this, "TMS password");
-       _tms_password = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_tms_password, 1, wxEXPAND);
-       table->AddSpacer (0);
+void
+ConfigDialog::make_misc_panel ()
+{
+       _misc_panel = new wxPanel (_notebook);
+       wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+       _misc_panel->SetSizer (s);
 
-       add_label_to_sizer (table, this, "Threads to use for encoding on this host");
-       _num_local_encoding_threads = new wxSpinCtrl (this);
-       table->Add (_num_local_encoding_threads, 1, wxEXPAND);
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       table->AddGrowableCol (1, 1);
+       s->Add (table, 1, wxALL | wxEXPAND, 8);
+
+       _set_language = new wxCheckBox (_misc_panel, wxID_ANY, _("Set language"));
+       table->Add (_set_language, 1);
+       _language = new wxChoice (_misc_panel, wxID_ANY);
+       _language->Append (wxT ("English"));
+       _language->Append (wxT ("Français"));
+       _language->Append (wxT ("Italiano"));
+       _language->Append (wxT ("Español"));
+       _language->Append (wxT ("Svenska"));
+       table->Add (_language);
+
+       wxStaticText* restart = add_label_to_sizer (table, _misc_panel, _("(restart DCP-o-matic to see language changes)"), false);
+       wxFont font = restart->GetFont();
+       font.SetStyle (wxFONTSTYLE_ITALIC);
+       font.SetPointSize (font.GetPointSize() - 1);
+       restart->SetFont (font);
        table->AddSpacer (0);
 
-       add_label_to_sizer (table, this, "Default directory for new films");
-#ifdef __WXMSW__
-       _default_directory = new DirPickerCtrl (this);
+       add_label_to_sizer (table, _misc_panel, _("Threads to use for encoding on this host"), true);
+       _num_local_encoding_threads = new wxSpinCtrl (_misc_panel);
+       table->Add (_num_local_encoding_threads, 1);
+
+       {
+               add_label_to_sizer (table, _misc_panel, _("Default duration of still images"), true);
+               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _default_still_length = new wxSpinCtrl (_misc_panel);
+               s->Add (_default_still_length);
+               add_label_to_sizer (s, _misc_panel, _("s"), false);
+               table->Add (s, 1);
+       }
+
+       add_label_to_sizer (table, _misc_panel, _("Default directory for new films"), true);
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
+       _default_directory = new DirPickerCtrl (_misc_panel);
 #else  
-       _default_directory = new wxDirPickerCtrl (this, wxDD_DIR_MUST_EXIST);
+       _default_directory = new wxDirPickerCtrl (_misc_panel, wxDD_DIR_MUST_EXIST);
 #endif
        table->Add (_default_directory, 1, wxEXPAND);
-       table->AddSpacer (0);
 
-       add_label_to_sizer (table, this, "Reference scaler for A/B");
-       _reference_scaler = new wxComboBox (this, 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 ()));
-       }
+       add_label_to_sizer (table, _misc_panel, _("Default DCI name details"), true);
+       _default_dci_metadata_button = new wxButton (_misc_panel, wxID_ANY, _("Edit..."));
+       table->Add (_default_dci_metadata_button);
 
-       table->Add (_reference_scaler, 1, wxEXPAND);
-       table->AddSpacer (0);
+       add_label_to_sizer (table, _misc_panel, _("Default container"), true);
+       _default_container = new wxChoice (_misc_panel, wxID_ANY);
+       table->Add (_default_container);
+
+       add_label_to_sizer (table, _misc_panel, _("Default content type"), true);
+       _default_dcp_content_type = new wxChoice (_misc_panel, wxID_ANY);
+       table->Add (_default_dcp_content_type);
 
        {
-               add_label_to_sizer (table, this, "Reference filters for A/B");
-               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _reference_filters = new wxStaticText (this, wxID_ANY, wxT (""));
-               s->Add (_reference_filters, 1, wxEXPAND);
-               _reference_filters_button = new wxButton (this, wxID_ANY, _("Edit..."));
-               s->Add (_reference_filters_button, 0);
-               table->Add (s, 1, wxEXPAND);
-               table->AddSpacer (0);
+               add_label_to_sizer (table, _misc_panel, _("Default JPEG2000 bandwidth"), true);
+               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _default_j2k_bandwidth = new wxSpinCtrl (_misc_panel);
+               s->Add (_default_j2k_bandwidth);
+               add_label_to_sizer (s, _misc_panel, _("MBps"), false);
+               table->Add (s, 1);
        }
+       
+       Config* config = Config::instance ();
 
-       add_label_to_sizer (table, this, "Encoding Servers");
-       _servers = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (220, 100), wxLC_REPORT | wxLC_SINGLE_SEL);
-       wxListItem ip;
-       ip.SetId (0);
-       ip.SetText (_("IP address"));
-       ip.SetWidth (120);
-       _servers->InsertColumn (0, ip);
-       ip.SetId (1);
-       ip.SetText (_("Threads"));
-       ip.SetWidth (80);
-       _servers->InsertColumn (1, ip);
-       table->Add (_servers, 1, wxEXPAND | wxALL);
+       _set_language->SetValue (config->language ());
+
+       if (config->language().get_value_or ("") == "fr") {
+               _language->SetSelection (1);
+       } else if (config->language().get_value_or ("") == "it") {
+               _language->SetSelection (2);
+       } else if (config->language().get_value_or ("") == "es") {
+               _language->SetSelection (3);
+       } else if (config->language().get_value_or ("") == "sv") {
+               _language->SetSelection (4);
+       } else {
+               _language->SetSelection (0);
+       }
 
-       {
-               wxSizer* s = new wxBoxSizer (wxVERTICAL);
-               _add_server = new wxButton (this, wxID_ANY, _("Add"));
-               s->Add (_add_server);
-               _edit_server = new wxButton (this, wxID_ANY, _("Edit"));
-               s->Add (_edit_server);
-               _remove_server = new wxButton (this, wxID_ANY, _("Remove"));
-               s->Add (_remove_server);
-               table->Add (s, 0);
+       setup_language_sensitivity ();
+
+       _set_language->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&ConfigDialog::set_language_changed, this));
+       _language->Bind     (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&ConfigDialog::language_changed,     this));
+
+       _num_local_encoding_threads->SetRange (1, 128);
+       _num_local_encoding_threads->SetValue (config->num_local_encoding_threads ());
+       _num_local_encoding_threads->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&ConfigDialog::num_local_encoding_threads_changed, this));
+
+       _default_still_length->SetRange (1, 3600);
+       _default_still_length->SetValue (config->default_still_length ());
+       _default_still_length->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&ConfigDialog::default_still_length_changed, this));
+
+       _default_directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir()))));
+       _default_directory->Bind (wxEVT_COMMAND_DIRPICKER_CHANGED, boost::bind (&ConfigDialog::default_directory_changed, this));
+
+       _default_dci_metadata_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ConfigDialog::edit_default_dci_metadata_clicked, this));
+
+       vector<Ratio const *> ratio = Ratio::all ();
+       int n = 0;
+       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;
        }
-               
-       Config* config = Config::instance ();
 
+       _default_container->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&ConfigDialog::default_container_changed, this));
+       
+       vector<DCPContentType const *> const ct = DCPContentType::all ();
+       n = 0;
+       for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
+               _default_dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
+               if (*i == config->default_dcp_content_type ()) {
+                       _default_dcp_content_type->SetSelection (n);
+               }
+               ++n;
+       }
+
+       _default_dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&ConfigDialog::default_dcp_content_type_changed, this));
+
+       _default_j2k_bandwidth->SetRange (50, 250);
+       _default_j2k_bandwidth->SetValue (config->default_j2k_bandwidth() / 1e6);
+       _default_j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&ConfigDialog::default_j2k_bandwidth_changed, this));
+}
+
+void
+ConfigDialog::make_tms_panel ()
+{
+       _tms_panel = new wxPanel (_notebook);
+       wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+       _tms_panel->SetSizer (s);
+
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       table->AddGrowableCol (1, 1);
+       s->Add (table, 1, wxALL | wxEXPAND, 8);
+
+       add_label_to_sizer (table, _tms_panel, _("IP address"), true);
+       _tms_ip = new wxTextCtrl (_tms_panel, wxID_ANY);
+       table->Add (_tms_ip, 1, wxEXPAND);
+
+       add_label_to_sizer (table, _tms_panel, _("Target path"), true);
+       _tms_path = new wxTextCtrl (_tms_panel, wxID_ANY);
+       table->Add (_tms_path, 1, wxEXPAND);
+
+       add_label_to_sizer (table, _tms_panel, _("User name"), true);
+       _tms_user = new wxTextCtrl (_tms_panel, wxID_ANY);
+       table->Add (_tms_user, 1, wxEXPAND);
+
+       add_label_to_sizer (table, _tms_panel, _("Password"), true);
+       _tms_password = new wxTextCtrl (_tms_panel, wxID_ANY);
+       table->Add (_tms_password, 1, wxEXPAND);
+
+       Config* config = Config::instance ();
+       
        _tms_ip->SetValue (std_to_wx (config->tms_ip ()));
-       _tms_ip->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::tms_ip_changed), 0, this);
+       _tms_ip->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::tms_ip_changed, this));
        _tms_path->SetValue (std_to_wx (config->tms_path ()));
-       _tms_path->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::tms_path_changed), 0, this);
+       _tms_path->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::tms_path_changed, this));
        _tms_user->SetValue (std_to_wx (config->tms_user ()));
-       _tms_user->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::tms_user_changed), 0, this);
+       _tms_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::tms_user_changed, this));
        _tms_password->SetValue (std_to_wx (config->tms_password ()));
-       _tms_password->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::tms_password_changed), 0, this);
+       _tms_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::tms_password_changed, this));
+}
 
-       _num_local_encoding_threads->SetRange (1, 128);
-       _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);
+void
+ConfigDialog::make_metadata_panel ()
+{
+       _metadata_panel = new wxPanel (_notebook);
+       wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+       _metadata_panel->SetSizer (s);
 
-       _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);
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       table->AddGrowableCol (1, 1);
+       s->Add (table, 1, wxALL | wxEXPAND, 8);
 
-       _reference_scaler->SetSelection (Scaler::as_index (config->reference_scaler ()));
-       _reference_scaler->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (ConfigDialog::reference_scaler_changed), 0, this);
+       add_label_to_sizer (table, _metadata_panel, _("Issuer"), true);
+       _issuer = new wxTextCtrl (_metadata_panel, wxID_ANY);
+       table->Add (_issuer, 1, wxEXPAND);
 
-       pair<string, string> p = Filter::ffmpeg_strings (config->reference_filters ());
-       _reference_filters->SetLabel (std_to_wx (p.first + " " + p.second));
-       _reference_filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_reference_filters_clicked), 0, this);
+       add_label_to_sizer (table, _metadata_panel, _("Creator"), true);
+       _creator = new wxTextCtrl (_metadata_panel, wxID_ANY);
+       table->Add (_creator, 1, wxEXPAND);
 
-       vector<ServerDescription*> servers = config->servers ();
-       for (vector<ServerDescription*>::iterator i = servers.begin(); i != servers.end(); ++i) {
-               add_server_to_control (*i);
+       Config* config = Config::instance ();
+
+       _issuer->SetValue (std_to_wx (config->dcp_metadata().issuer));
+       _issuer->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::issuer_changed, this));
+       _creator->SetValue (std_to_wx (config->dcp_metadata().creator));
+       _creator->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::creator_changed, this));
+}
+
+static std::string
+server_column (ServerDescription s, int c)
+{
+       switch (c) {
+       case 0:
+               return s.host_name ();
+       case 1:
+               return lexical_cast<string> (s.threads ());
        }
-       
-       _add_server->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::add_server_clicked), 0, this);
-       _edit_server->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_server_clicked), 0, this);
-       _remove_server->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::remove_server_clicked), 0, this);
 
-       _servers->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler (ConfigDialog::server_selection_changed), 0, this);
-       _servers->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler (ConfigDialog::server_selection_changed), 0, this);
-       wxListEvent ev;
-       server_selection_changed (ev);
+       return "";
+}
 
-       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
-       overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6);
+void
+ConfigDialog::make_servers_panel ()
+{
+       vector<string> columns;
+       columns.push_back (wx_to_std (_("IP address")));
+       columns.push_back (wx_to_std (_("Threads")));
+       _servers_panel = new EditableList<ServerDescription, ServerDialog> (
+               _notebook,
+               columns,
+               boost::bind (&Config::servers, Config::instance()),
+               boost::bind (&Config::set_servers, Config::instance(), _1),
+               boost::bind (&server_column, _1, _2)
+               );
+}
 
-       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
-       if (buttons) {
-               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+void
+ConfigDialog::language_changed ()
+{
+       switch (_language->GetSelection ()) {
+       case 0:
+               Config::instance()->set_language ("en");
+               break;
+       case 1:
+               Config::instance()->set_language ("fr");
+               break;
+       case 2:
+               Config::instance()->set_language ("it");
+               break;
+       case 3:
+               Config::instance()->set_language ("es");
+               break;
+       case 4:
+               Config::instance()->set_language ("sv");
+               break;
        }
-
-       SetSizer (overall_sizer);
-       overall_sizer->Layout ();
-       overall_sizer->SetSizeHints (this);
 }
 
 void
-ConfigDialog::tms_ip_changed (wxCommandEvent &)
+ConfigDialog::tms_ip_changed ()
 {
        Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
 }
 
 void
-ConfigDialog::tms_path_changed (wxCommandEvent &)
+ConfigDialog::tms_path_changed ()
 {
        Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
 }
 
 void
-ConfigDialog::tms_user_changed (wxCommandEvent &)
+ConfigDialog::tms_user_changed ()
 {
        Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
 }
 
 void
-ConfigDialog::tms_password_changed (wxCommandEvent &)
+ConfigDialog::tms_password_changed ()
 {
        Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
 }
 
 void
-ConfigDialog::num_local_encoding_threads_changed (wxCommandEvent &)
+ConfigDialog::num_local_encoding_threads_changed ()
 {
        Config::instance()->set_num_local_encoding_threads (_num_local_encoding_threads->GetValue ());
 }
 
 void
-ConfigDialog::default_directory_changed (wxCommandEvent &)
+ConfigDialog::default_directory_changed ()
 {
        Config::instance()->set_default_directory (wx_to_std (_default_directory->GetPath ()));
 }
 
 void
-ConfigDialog::add_server_to_control (ServerDescription* s)
-{
-       wxListItem item;
-       int const n = _servers->GetItemCount ();
-       item.SetId (n);
-       _servers->InsertItem (item);
-       _servers->SetItem (n, 0, std_to_wx (s->host_name ()));
-       _servers->SetItem (n, 1, std_to_wx (boost::lexical_cast<string> (s->threads ())));
-}
-
-void
-ConfigDialog::add_server_clicked (wxCommandEvent &)
+ConfigDialog::edit_default_dci_metadata_clicked ()
 {
-       ServerDialog* d = new ServerDialog (this, 0);
+       DCIMetadataDialog* d = new DCIMetadataDialog (this, Config::instance()->default_dci_metadata ());
        d->ShowModal ();
-       ServerDescription* s = d->server ();
+       Config::instance()->set_default_dci_metadata (d->dci_metadata ());
        d->Destroy ();
-       
-       add_server_to_control (s);
-       vector<ServerDescription*> o = Config::instance()->servers ();
-       o.push_back (s);
-       Config::instance()->set_servers (o);
 }
 
 void
-ConfigDialog::edit_server_clicked (wxCommandEvent &)
+ConfigDialog::set_language_changed ()
 {
-       int i = _servers->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
-       if (i == -1) {
-               return;
+       setup_language_sensitivity ();
+       if (_set_language->GetValue ()) {
+               language_changed ();
+       } else {
+               Config::instance()->unset_language ();
        }
+}
 
-       wxListItem item;
-       item.SetId (i);
-       item.SetColumn (0);
-       _servers->GetItem (item);
-
-       vector<ServerDescription*> servers = Config::instance()->servers ();
-       assert (i >= 0 && i < int (servers.size ()));
-
-       ServerDialog* d = new ServerDialog (this, servers[i]);
-       d->ShowModal ();
-       d->Destroy ();
+void
+ConfigDialog::setup_language_sensitivity ()
+{
+       _language->Enable (_set_language->GetValue ());
+}
 
-       _servers->SetItem (i, 0, std_to_wx (servers[i]->host_name ()));
-       _servers->SetItem (i, 1, std_to_wx (boost::lexical_cast<string> (servers[i]->threads ())));
+void
+ConfigDialog::default_still_length_changed ()
+{
+       Config::instance()->set_default_still_length (_default_still_length->GetValue ());
 }
 
 void
-ConfigDialog::remove_server_clicked (wxCommandEvent &)
+ConfigDialog::default_container_changed ()
 {
-       int i = _servers->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
-       if (i >= 0) {
-               _servers->DeleteItem (i);
-       }
+       vector<Ratio const *> ratio = Ratio::all ();
+       Config::instance()->set_default_container (ratio[_default_container->GetSelection()]);
+}
 
-       vector<ServerDescription*> o = Config::instance()->servers ();
-       o.erase (o.begin() + i);
-       Config::instance()->set_servers (o);
+void
+ConfigDialog::default_dcp_content_type_changed ()
+{
+       vector<DCPContentType const *> ct = DCPContentType::all ();
+       Config::instance()->set_default_dcp_content_type (ct[_default_dcp_content_type->GetSelection()]);
 }
 
 void
-ConfigDialog::server_selection_changed (wxListEvent &)
+ConfigDialog::issuer_changed ()
 {
-       int const i = _servers->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
-       _edit_server->Enable (i >= 0);
-       _remove_server->Enable (i >= 0);
+       libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+       m.issuer = wx_to_std (_issuer->GetValue ());
+       Config::instance()->set_dcp_metadata (m);
 }
 
 void
-ConfigDialog::reference_scaler_changed (wxCommandEvent &)
+ConfigDialog::creator_changed ()
 {
-       int const n = _reference_scaler->GetSelection ();
-       if (n >= 0) {
-               Config::instance()->set_reference_scaler (Scaler::from_index (n));
-       }
+       libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+       m.creator = wx_to_std (_creator->GetValue ());
+       Config::instance()->set_dcp_metadata (m);
 }
 
 void
-ConfigDialog::edit_reference_filters_clicked (wxCommandEvent &)
+ConfigDialog::default_j2k_bandwidth_changed ()
 {
-       FilterDialog* d = new FilterDialog (this, Config::instance()->reference_filters ());
-       d->ActiveChanged.connect (boost::bind (&ConfigDialog::reference_filters_changed, this, _1));
-       d->ShowModal ();
-       d->Destroy ();
+       Config::instance()->set_default_j2k_bandwidth (_default_j2k_bandwidth->GetValue() * 1e6);
+}
+
+static std::string
+colour_conversion_column (PresetColourConversion c)
+{
+       return c.name;
 }
 
 void
-ConfigDialog::reference_filters_changed (vector<Filter const *> f)
+ConfigDialog::make_colour_conversions_panel ()
 {
-       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 + " " + p.second));
+       vector<string> columns;
+       columns.push_back (wx_to_std (_("Name")));
+       _colour_conversions_panel = new EditableList<PresetColourConversion, PresetColourConversionDialog> (
+               _notebook,
+               columns,
+               boost::bind (&Config::colour_conversions, Config::instance()),
+               boost::bind (&Config::set_colour_conversions, Config::instance(), _1),
+               boost::bind (&colour_conversion_column, _1)
+               );
 }
index 32123a0d7eb92748d3b0e3cd701dca15c614bb53..82c4ee2a03b7a972f3796261dabc468046fe8848 100644 (file)
 */
 
 /** @file src/config_dialog.h
- *  @brief A dialogue to edit DVD-o-matic configuration.
+ *  @brief A dialogue to edit DCP-o-matic configuration.
  */
 
 #include <wx/wx.h>
 #include <wx/spinctrl.h>
 #include <wx/listctrl.h>
 #include <wx/filepicker.h>
+#include "wx_util.h"
+#include "editable_list.h"
 
 class DirPickerCtrl;
-
+class wxNotebook;
 class ServerDescription;
+class PresetColourConversion;
+class PresetColourConversionDialog;
+class ServerDialog;
 
 /** @class ConfigDialog
- *  @brief A dialogue to edit DVD-o-matic configuration.
+ *  @brief A dialogue to edit DCP-o-matic configuration.
  */
 class ConfigDialog : public wxDialog
 {
@@ -39,38 +44,54 @@ public:
        ConfigDialog (wxWindow *);
 
 private:
-       void tms_ip_changed (wxCommandEvent &);
-       void tms_path_changed (wxCommandEvent &);
-       void tms_user_changed (wxCommandEvent &);
-       void tms_password_changed (wxCommandEvent &);
-       void num_local_encoding_threads_changed (wxCommandEvent &);
-       void default_directory_changed (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 set_language_changed ();
+       void language_changed ();
+       void tms_ip_changed ();
+       void tms_path_changed ();
+       void tms_user_changed ();
+       void tms_password_changed ();
+       void num_local_encoding_threads_changed ();
+       void default_still_length_changed ();
+       void default_directory_changed ();
+       void edit_default_dci_metadata_clicked ();
+       void default_container_changed ();
+       void default_dcp_content_type_changed ();
+       void issuer_changed ();
+       void creator_changed ();
+       void default_j2k_bandwidth_changed ();
+
+       void setup_language_sensitivity ();
+
+       void make_misc_panel ();
+       void make_tms_panel ();
+       void make_metadata_panel ();
+       void make_servers_panel ();
+       void make_colour_conversions_panel ();
 
-       void add_server_to_control (ServerDescription *);
-       
+       wxNotebook* _notebook;
+       wxPanel* _misc_panel;
+       wxPanel* _tms_panel;
+       EditableList<PresetColourConversion, PresetColourConversionDialog>* _colour_conversions_panel;
+       EditableList<ServerDescription, ServerDialog>* _servers_panel;
+       wxPanel* _metadata_panel;
+       wxCheckBox* _set_language;
+       wxChoice* _language;
+       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 __WXMSW__       
+       wxSpinCtrl* _default_still_length;
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
        DirPickerCtrl* _default_directory;
 #else
        wxDirPickerCtrl* _default_directory;
-#endif 
-       wxComboBox* _reference_scaler;
-       wxStaticText* _reference_filters;
-       wxButton* _reference_filters_button;
-       wxListCtrl* _servers;
-       wxButton* _add_server;
-       wxButton* _edit_server;
-       wxButton* _remove_server;
+#endif
+       wxButton* _default_dci_metadata_button;
+       wxTextCtrl* _issuer;
+       wxTextCtrl* _creator;
+       wxSpinCtrl* _default_j2k_bandwidth;
 };
 
diff --git a/src/wx/content_colour_conversion_dialog.cc b/src/wx/content_colour_conversion_dialog.cc
new file mode 100644 (file)
index 0000000..d8e768b
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+    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/statline.h>
+#include "lib/colour_conversion.h"
+#include "lib/config.h"
+#include "wx_util.h"
+#include "content_colour_conversion_dialog.h"
+#include "colour_conversion_editor.h"
+
+using std::string;
+using std::vector;
+using std::cout;
+using boost::optional;
+
+ContentColourConversionDialog::ContentColourConversionDialog (wxWindow* parent)
+       : wxDialog (parent, wxID_ANY, _("Colour conversion"))
+       , _editor (new ColourConversionEditor (this))
+       , _setting (false)
+{
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       SetSizer (overall_sizer);
+
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       _preset_check = new wxCheckBox (this, wxID_ANY, _("Use preset"));
+       table->Add (_preset_check, 0, wxALIGN_CENTER_VERTICAL);
+       _preset_choice = new wxChoice (this, wxID_ANY);
+       table->Add (_preset_choice);
+
+       overall_sizer->Add (table, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+       overall_sizer->Add (new wxStaticLine (this, wxID_ANY), 0, wxEXPAND);
+       overall_sizer->Add (_editor);
+
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
+       if (buttons) {
+               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+
+       overall_sizer->Layout ();
+       overall_sizer->SetSizeHints (this);
+
+       _preset_check->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&ContentColourConversionDialog::preset_check_clicked, this));
+       _preset_choice->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&ContentColourConversionDialog::preset_choice_changed, this));
+
+       _editor->Changed.connect (boost::bind (&ContentColourConversionDialog::check_for_preset, this));
+
+       vector<PresetColourConversion> presets = Config::instance()->colour_conversions ();
+       for (vector<PresetColourConversion>::const_iterator i = presets.begin(); i != presets.end(); ++i) {
+               _preset_choice->Append (std_to_wx (i->name));
+       }
+}
+
+ColourConversion
+ContentColourConversionDialog::get () const
+{
+       return _editor->get ();
+}
+
+void
+ContentColourConversionDialog::set (ColourConversion c)
+{
+       _setting = true;
+       _editor->set (c);
+       _setting = false;
+       
+       check_for_preset ();
+}
+
+void
+ContentColourConversionDialog::check_for_preset ()
+{
+       if (_setting) {
+               return;
+       }
+       
+       optional<size_t> preset = _editor->get().preset ();
+
+       _preset_check->SetValue (preset);
+       _preset_choice->Enable (preset);
+       _preset_choice->SetSelection (preset.get_value_or (-1));
+}
+
+void
+ContentColourConversionDialog::preset_check_clicked ()
+{
+       if (_preset_check->GetValue ()) {
+               _preset_choice->SetSelection (0);
+               preset_choice_changed ();
+       } else {
+               _preset_choice->SetSelection (-1);
+               _preset_choice->Enable (false);
+       }
+}
+
+void
+ContentColourConversionDialog::preset_choice_changed ()
+{
+       vector<PresetColourConversion> presets = Config::instance()->colour_conversions ();
+       int const s = _preset_choice->GetSelection();
+       if (s != -1) {
+               set (presets[s].conversion);
+       }
+}
+
+       
diff --git a/src/wx/content_colour_conversion_dialog.h b/src/wx/content_colour_conversion_dialog.h
new file mode 100644 (file)
index 0000000..e6069f1
--- /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 <wx/wx.h>
+
+class ColourConversionEditor;
+
+class ContentColourConversionDialog : public wxDialog
+{
+public:
+       ContentColourConversionDialog (wxWindow *);
+
+       void set (ColourConversion);
+       ColourConversion get () const;
+
+private:
+       void check_for_preset ();
+       void preset_check_clicked ();
+       void preset_choice_changed ();
+       
+       wxCheckBox* _preset_check;
+       wxChoice* _preset_choice;
+       ColourConversionEditor* _editor;
+       bool _setting;
+};
diff --git a/src/wx/content_menu.cc b/src/wx/content_menu.cc
new file mode 100644 (file)
index 0000000..1a409fa
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+    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 "lib/playlist.h"
+#include "lib/film.h"
+#include "content_menu.h"
+#include "repeat_dialog.h"
+
+using std::cout;
+using boost::shared_ptr;
+
+enum {
+       ID_repeat,
+       ID_remove
+};
+
+ContentMenu::ContentMenu (shared_ptr<Film> f, wxWindow* p)
+       : _menu (new wxMenu)
+       , _film (f)
+       , _parent (p)
+{
+       _menu->Append (ID_repeat, _("Repeat..."));
+       _menu->AppendSeparator ();
+       _menu->Append (ID_remove, _("Remove"));
+
+       _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, &ContentMenu::repeat, this, ID_repeat);
+       _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, &ContentMenu::remove, this, ID_remove);
+}
+
+ContentMenu::~ContentMenu ()
+{
+       delete _menu;
+}
+
+void
+ContentMenu::popup (ContentList c, wxPoint p)
+{
+       _content = c;
+       _parent->PopupMenu (_menu, p);
+}
+
+void
+ContentMenu::repeat (wxCommandEvent &)
+{
+       if (_content.empty ()) {
+               return;
+       }
+               
+       RepeatDialog d (_parent);
+       d.ShowModal ();
+
+       shared_ptr<const Film> film = _film.lock ();
+       if (!film) {
+               return;
+       }
+
+       film->playlist()->repeat (_content, d.number ());
+       d.Destroy ();
+
+       _content.clear ();
+}
+
+void
+ContentMenu::remove (wxCommandEvent &)
+{
+       if (_content.empty ()) {
+               return;
+       }
+
+       shared_ptr<const Film> film = _film.lock ();
+       if (!film) {
+               return;
+       }
+
+       film->playlist()->remove (_content);
+
+       _content.clear ();
+}
+
diff --git a/src/wx/content_menu.h b/src/wx/content_menu.h
new file mode 100644 (file)
index 0000000..127fbea
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+    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_MENU_H
+#define DCPOMATIC_CONTENT_MENU_H
+
+#include <wx/wx.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include "lib/types.h"
+
+class Film;
+
+class ContentMenu
+{
+public:
+       ContentMenu (boost::shared_ptr<Film>, wxWindow *);
+       ~ContentMenu ();
+
+       void popup (ContentList, wxPoint);
+
+private:
+       void repeat (wxCommandEvent &);
+       void remove (wxCommandEvent &);
+       
+       wxMenu* _menu;
+       boost::weak_ptr<Film> _film;
+       wxWindow* _parent;
+       ContentList _content;
+};
+
+#endif
diff --git a/src/wx/dci_metadata_dialog.cc b/src/wx/dci_metadata_dialog.cc
new file mode 100644 (file)
index 0000000..e28ddd8
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+    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 <wx/wx.h>
+#include <wx/sizer.h>
+#include <wx/spinctrl.h>
+#include "lib/film.h"
+#include "dci_metadata_dialog.h"
+#include "wx_util.h"
+
+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, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       table->AddGrowableCol (1, 1);
+
+       add_label_to_sizer (table, this, _("Content version"), true);
+       _content_version = new wxSpinCtrl (this, wxID_ANY);
+       table->Add (_content_version, 1, wxEXPAND);
+
+       add_label_to_sizer (table, this, _("Audio Language (e.g. EN)"), true);
+       _audio_language = new wxTextCtrl (this, wxID_ANY);
+       table->Add (_audio_language, 1, wxEXPAND);
+
+       add_label_to_sizer (table, this, _("Subtitle Language (e.g. FR)"), true);
+       _subtitle_language = new wxTextCtrl (this, wxID_ANY);
+       table->Add (_subtitle_language, 1, wxEXPAND);
+       
+       add_label_to_sizer (table, this, _("Territory (e.g. UK)"), true);
+       _territory = new wxTextCtrl (this, wxID_ANY);
+       table->Add (_territory, 1, wxEXPAND);
+
+       add_label_to_sizer (table, this, _("Rating (e.g. 15)"), true);
+       _rating = new wxTextCtrl (this, wxID_ANY);
+       table->Add (_rating, 1, wxEXPAND);
+
+       add_label_to_sizer (table, this, _("Studio (e.g. TCF)"), true);
+       _studio = new wxTextCtrl (this, wxID_ANY);
+       table->Add (_studio, 1, wxEXPAND);
+
+       add_label_to_sizer (table, this, _("Facility (e.g. DLA)"), true);
+       _facility = new wxTextCtrl (this, wxID_ANY);
+       table->Add (_facility, 1, wxEXPAND);
+
+       add_label_to_sizer (table, this, _("Package Type (e.g. OV)"), true);
+       _package_type = new wxTextCtrl (this, wxID_ANY);
+       table->Add (_package_type, 1, wxEXPAND);
+
+       _content_version->SetRange (1, 1024);
+
+       _content_version->SetValue (dm.content_version);
+       _audio_language->SetValue (std_to_wx (dm.audio_language));
+       _subtitle_language->SetValue (std_to_wx (dm.subtitle_language));
+       _territory->SetValue (std_to_wx (dm.territory));
+       _rating->SetValue (std_to_wx (dm.rating));
+       _studio->SetValue (std_to_wx (dm.studio));
+       _facility->SetValue (std_to_wx (dm.facility));
+       _package_type->SetValue (std_to_wx (dm.package_type));
+
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       overall_sizer->Add (table, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+       
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
+       if (buttons) {
+               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+       
+       SetSizer (overall_sizer);
+       overall_sizer->Layout ();
+       overall_sizer->SetSizeHints (this);
+}
+
+DCIMetadata
+DCIMetadataDialog::dci_metadata () const
+{
+       DCIMetadata dm;
+
+       dm.content_version = _content_version->GetValue ();
+       dm.audio_language = wx_to_std (_audio_language->GetValue ());
+       dm.subtitle_language = wx_to_std (_subtitle_language->GetValue ());
+       dm.territory = wx_to_std (_territory->GetValue ());
+       dm.rating = wx_to_std (_rating->GetValue ());
+       dm.studio = wx_to_std (_studio->GetValue ());
+       dm.facility = wx_to_std (_facility->GetValue ());
+       dm.package_type = wx_to_std (_package_type->GetValue ());
+
+       return dm;
+}
diff --git a/src/wx/dci_metadata_dialog.h b/src/wx/dci_metadata_dialog.h
new file mode 100644 (file)
index 0000000..240d553
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+    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 <wx/dialog.h>
+#include <wx/textctrl.h>
+#include <boost/shared_ptr.hpp>
+#include "lib/dci_metadata.h"
+
+class wxSpinCtrl;
+class Film;
+
+class DCIMetadataDialog : public wxDialog
+{
+public:
+       DCIMetadataDialog (wxWindow *, DCIMetadata);
+
+       DCIMetadata dci_metadata () const;
+
+private:
+       wxSpinCtrl* _content_version;
+       wxTextCtrl* _audio_language;
+       wxTextCtrl* _subtitle_language;
+       wxTextCtrl* _territory;
+       wxTextCtrl* _rating;
+       wxTextCtrl* _studio;
+       wxTextCtrl* _facility;
+       wxTextCtrl* _package_type;
+};
diff --git a/src/wx/dci_name_dialog.cc b/src/wx/dci_name_dialog.cc
deleted file mode 100644 (file)
index 6927d6c..0000000
+++ /dev/null
@@ -1,131 +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 <wx/sizer.h>
-#include "dci_name_dialog.h"
-#include "wx_util.h"
-#include "film.h"
-
-using boost::shared_ptr;
-
-DCINameDialog::DCINameDialog (wxWindow* parent, shared_ptr<Film> film)
-       : wxDialog (parent, wxID_ANY, _("DCI name"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
-       , _film (film)
-{
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
-       table->AddGrowableCol (1, 1);
-
-       add_label_to_sizer (table, this, "Audio Language (e.g. EN)");
-       _audio_language = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_audio_language, 1, wxEXPAND);
-
-       add_label_to_sizer (table, this, "Subtitle Language (e.g. FR)");
-       _subtitle_language = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_subtitle_language, 1, wxEXPAND);
-       
-       add_label_to_sizer (table, this, "Territory (e.g. UK)");
-       _territory = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_territory, 1, wxEXPAND);
-
-       add_label_to_sizer (table, this, "Rating (e.g. 15)");
-       _rating = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_rating, 1, wxEXPAND);
-
-       add_label_to_sizer (table, this, "Studio (e.g. TCF)");
-       _studio = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_studio, 1, wxEXPAND);
-
-       add_label_to_sizer (table, this, "Facility (e.g. DLA)");
-       _facility = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_facility, 1, wxEXPAND);
-
-       add_label_to_sizer (table, this, "Package Type (e.g. OV)");
-       _package_type = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_package_type, 1, wxEXPAND);
-
-       _audio_language->SetValue (std_to_wx (_film->audio_language ()));
-       _subtitle_language->SetValue (std_to_wx (_film->subtitle_language ()));
-       _territory->SetValue (std_to_wx (_film->territory ()));
-       _rating->SetValue (std_to_wx (_film->rating ()));
-       _studio->SetValue (std_to_wx (_film->studio ()));
-       _facility->SetValue (std_to_wx (_film->facility ()));
-       _package_type->SetValue (std_to_wx (_film->package_type ()));
-       
-       _audio_language->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (DCINameDialog::audio_language_changed), 0, this);
-       _subtitle_language->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (DCINameDialog::subtitle_language_changed), 0, this);
-       _territory->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (DCINameDialog::territory_changed), 0, this);
-       _rating->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (DCINameDialog::rating_changed), 0, this);
-       _studio->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (DCINameDialog::studio_changed), 0, this);
-       _facility->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (DCINameDialog::facility_changed), 0, this);
-       _package_type->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (DCINameDialog::package_type_changed), 0, this);
-
-       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
-       overall_sizer->Add (table, 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);
-}
-
-void
-DCINameDialog::audio_language_changed (wxCommandEvent &)
-{
-       _film->set_audio_language (wx_to_std (_audio_language->GetValue ()));
-}
-
-void
-DCINameDialog::subtitle_language_changed (wxCommandEvent &)
-{
-       _film->set_subtitle_language (wx_to_std (_subtitle_language->GetValue ()));
-}
-
-void
-DCINameDialog::territory_changed (wxCommandEvent &)
-{
-       _film->set_territory (wx_to_std (_territory->GetValue ()));
-}
-
-void
-DCINameDialog::rating_changed (wxCommandEvent &)
-{
-       _film->set_rating (wx_to_std (_rating->GetValue ()));
-}
-
-void
-DCINameDialog::studio_changed (wxCommandEvent &)
-{
-       _film->set_studio (wx_to_std (_studio->GetValue ()));
-}
-
-void
-DCINameDialog::facility_changed (wxCommandEvent &)
-{
-       _film->set_facility (wx_to_std (_facility->GetValue ()));
-}
-
-void
-DCINameDialog::package_type_changed (wxCommandEvent &)
-{
-       _film->set_package_type (wx_to_std (_package_type->GetValue ()));
-}
diff --git a/src/wx/dci_name_dialog.h b/src/wx/dci_name_dialog.h
deleted file mode 100644 (file)
index 1fd5436..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.
-
-*/
-
-#include <wx/dialog.h>
-#include <wx/textctrl.h>
-#include <boost/shared_ptr.hpp>
-
-class Film;
-
-class DCINameDialog : public wxDialog
-{
-public:
-       DCINameDialog (wxWindow *, boost::shared_ptr<Film>);
-
-private:
-       void audio_language_changed (wxCommandEvent &);
-       void subtitle_language_changed (wxCommandEvent &);
-       void territory_changed (wxCommandEvent &);
-       void rating_changed (wxCommandEvent &);
-       void studio_changed (wxCommandEvent &);
-       void facility_changed (wxCommandEvent &);
-       void package_type_changed (wxCommandEvent &);
-       
-       wxTextCtrl* _audio_language;
-       wxTextCtrl* _subtitle_language;
-       wxTextCtrl* _territory;
-       wxTextCtrl* _rating;
-       wxTextCtrl* _studio;
-       wxTextCtrl* _facility;
-       wxTextCtrl* _package_type;
-
-       boost::shared_ptr<Film> _film;
-};
index cb811fc1087a26839832a5b73f1bdb735d1d0ba3..47a546a8d8782223a5d519067c95fed7605fa13e 100644 (file)
@@ -19,6 +19,7 @@
 
 #include <wx/wx.h>
 #include <wx/stdpaths.h>
+#include <wx/filepicker.h>
 #include <boost/filesystem.hpp>
 #include "dir_picker_ctrl.h"
 #include "wx_util.h"
@@ -39,7 +40,7 @@ DirPickerCtrl::DirPickerCtrl (wxWindow* parent)
 
        SetSizerAndFit (_sizer);
 
-       _browse->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (DirPickerCtrl::browse_clicked), 0, this);
+       _browse->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DirPickerCtrl::browse_clicked, this));
 }
 
 void
@@ -50,12 +51,11 @@ DirPickerCtrl::SetPath (wxString p)
        if (_path == wxStandardPaths::Get().GetDocumentsDir()) {
                _folder->SetLabel (_("My Documents"));
        } else {
-#if BOOST_FILESYSTEM_VERSION == 3              
                _folder->SetLabel (std_to_wx (filesystem::path (wx_to_std (_path)).leaf().string()));
-#else
-               _folder->SetLabel (std_to_wx (filesystem::path (wx_to_std (_path)).leaf()));
-#endif         
        }
+
+       wxCommandEvent ev (wxEVT_COMMAND_DIRPICKER_CHANGED, wxID_ANY);
+       GetEventHandler()->ProcessEvent (ev);
 }
 
 wxString
@@ -65,10 +65,11 @@ DirPickerCtrl::GetPath () const
 }
 
 void
-DirPickerCtrl::browse_clicked (wxCommandEvent &)
+DirPickerCtrl::browse_clicked ()
 {
        wxDirDialog* d = new wxDirDialog (this);
-       d->ShowModal ();
-       SetPath (d->GetPath ());
+       if (d->ShowModal () == wxID_OK) {
+               SetPath (d->GetPath ());
+       }
        d->Destroy ();
 }
index df7b25f7a435d2be01f52041db49d3d7b15fced2..97c0d217a15541318bcb8a0bae5498a965dad93f 100644 (file)
@@ -28,7 +28,7 @@ public:
        void SetPath (wxString);
 
 private:
-       void browse_clicked (wxCommandEvent &);
+       void browse_clicked ();
        
        wxWindow* _parent;
        wxStaticText* _folder;
diff --git a/src/wx/editable_list.h b/src/wx/editable_list.h
new file mode 100644 (file)
index 0000000..98e7d0b
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+    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 <wx/wx.h>
+
+template<class T, class S>
+class EditableList : public wxPanel
+{
+public:
+       EditableList (
+               wxWindow* parent,
+               std::vector<std::string> columns,
+               boost::function<std::vector<T> ()> get,
+               boost::function<void (std::vector<T>)> set,
+               boost::function<std::string (T, int)> column
+               )
+               : wxPanel (parent)
+               , _get (get)
+               , _set (set)
+               , _columns (columns.size ())
+               , _column (column)
+       {
+               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+               SetSizer (s);
+
+               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               table->AddGrowableCol (0, 1);
+               s->Add (table, 1, wxALL | wxEXPAND, 8);
+
+               _list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (columns.size() * 200, 100), wxLC_REPORT | wxLC_SINGLE_SEL);
+
+               for (size_t i = 0; i < columns.size(); ++i) {
+                       wxListItem ip;
+                       ip.SetId (i);
+                       ip.SetText (std_to_wx (columns[i]));
+                       ip.SetWidth (200);
+                       _list->InsertColumn (i, ip);
+               }
+
+               table->Add (_list, 1, wxEXPAND | wxALL);
+
+               {
+                       wxSizer* s = new wxBoxSizer (wxVERTICAL);
+                       _add = new wxButton (this, wxID_ANY, _("Add..."));
+                       s->Add (_add, 0, wxTOP | wxBOTTOM, 2);
+                       _edit = new wxButton (this, wxID_ANY, _("Edit..."));
+                       s->Add (_edit, 0, wxTOP | wxBOTTOM, 2);
+                       _remove = new wxButton (this, wxID_ANY, _("Remove"));
+                       s->Add (_remove, 0, wxTOP | wxBOTTOM, 2);
+                       table->Add (s, 0);
+               }
+
+               std::vector<T> current = _get ();
+               for (typename std::vector<T>::iterator i = current.begin (); i != current.end(); ++i) {
+                       add_to_control (*i);
+               }
+
+               _add->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&EditableList::add_clicked, this));
+               _edit->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&EditableList::edit_clicked, this));
+               _remove->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&EditableList::remove_clicked, this));
+
+               _list->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&EditableList::selection_changed, this));
+               _list->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&EditableList::selection_changed, this));
+               _list->Bind (wxEVT_SIZE, boost::bind (&EditableList::resized, this, _1));
+               selection_changed ();
+
+       }
+
+private:       
+
+       void add_to_control (T item)
+       {
+               wxListItem list_item;
+               int const n = _list->GetItemCount ();
+               list_item.SetId (n);
+               _list->InsertItem (list_item);
+
+               for (int i = 0; i < _columns; ++i) {
+                       _list->SetItem (n, i, std_to_wx (_column (item, i)));
+               }
+       }
+
+       void selection_changed ()
+       {
+               int const i = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+               _edit->Enable (i >= 0);
+               _remove->Enable (i >= 0);
+       }
+
+       void add_clicked ()
+       {
+               T new_item;
+               S* dialog = new S (this);
+               dialog->set (new_item);
+               dialog->ShowModal ();
+
+               add_to_control (dialog->get ());
+               
+               std::vector<T> all = _get ();
+               all.push_back (dialog->get ());
+               _set (all);
+               
+               dialog->Destroy ();
+       }
+
+       void edit_clicked ()
+       {
+               int item = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+               if (item == -1) {
+                       return;
+               }
+
+               std::vector<T> all = _get ();
+               assert (item >= 0 && item < int (all.size ()));
+
+               S* dialog = new S (this);
+               dialog->set (all[item]);
+               dialog->ShowModal ();
+               all[item] = dialog->get ();
+               dialog->Destroy ();
+               
+               for (int i = 0; i < _columns; ++i) {
+                       _list->SetItem (item, i, std_to_wx (_column (all[item], i)));
+               }
+       }
+
+       void remove_clicked ()
+       {
+               int i = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+               if (i == -1) {
+                       return;
+               }
+               
+               _list->DeleteItem (i);
+               std::vector<T> all = _get ();
+               all.erase (all.begin() + i);
+               _set (all);
+
+               selection_changed ();
+       }
+
+       void resized (wxSizeEvent& ev)
+       {
+               int const w = GetSize().GetWidth() / _columns;
+               for (int i = 0; i < _columns; ++i) {
+                       _list->SetColumnWidth (i, w);
+               }
+               ev.Skip ();
+       }
+
+       boost::function <std::vector<T> ()> _get;
+       boost::function <void (std::vector<T>)> _set;
+       int _columns;
+       boost::function<std::string (T, int)> _column;
+
+       wxButton* _add;
+       wxButton* _edit;
+       wxButton* _remove;
+       wxListCtrl* _list;
+};
index 9326227a32b3c6baf333578bad5061d95549e4a2..56b6973757e0f7be59a52f892060a4223b4123a9 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 <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/external_audio_decoder.h"
-#include "filter_dialog.h"
+#include "lib/still_image_content.h"
+#include "lib/moving_image_content.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/sndfile_content.h"
+#include "lib/dcp_content_type.h"
+#include "lib/sound_processor.h"
+#include "lib/scaler.h"
+#include "timecode.h"
 #include "wx_util.h"
 #include "film_editor.h"
-#include "gain_calculator_dialog.h"
-#include "sound_processor.h"
-#include "dci_name_dialog.h"
-#include "scaler.h"
+#include "dci_metadata_dialog.h"
+#include "timeline_dialog.h"
+#include "timing_panel.h"
+#include "subtitle_panel.h"
+#include "audio_panel.h"
+#include "video_panel.h"
 
 using std::string;
 using std::cout;
@@ -54,474 +61,257 @@ 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)
+       , _menu (f, this)
        , _generally_sensitive (true)
+       , _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);
-
-       set_film (_film);
+
+       _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);
+       
+       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 wxFlexGridSizer (2, 4, 4);
-       wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL);
-       pad->Add (_film_sizer, 0, wxALL, 8);
-       _film_panel->SetSizer (pad);
-
-       add_label_to_sizer (_film_sizer, _film_panel, "Name");
-       _name = new wxTextCtrl (_film_panel, wxID_ANY);
-       _film_sizer->Add (_name, 1, wxEXPAND);
-
-       add_label_to_sizer (_film_sizer, _film_panel, "DCP Name");
-       _dcp_name = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
-       _film_sizer->Add (_dcp_name, 0, wxALIGN_CENTER_VERTICAL | wxSHRINK);
-
-       _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Use DCI name"));
-       _film_sizer->Add (_use_dci_name, 1, wxEXPAND);
-       _edit_dci_button = new wxButton (_film_panel, wxID_ANY, wxT ("Details..."));
-       _film_sizer->Add (_edit_dci_button, 0);
-
-       add_label_to_sizer (_film_sizer, _film_panel, "Content");
-       _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), wxT ("Select Content File"), wxT("*.*"));
-       _film_sizer->Add (_content, 1, wxEXPAND);
-
-       _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Trust content's header"));
-       video_control (_trust_content_header);
-       _film_sizer->Add (_trust_content_header, 1);
-       _film_sizer->AddSpacer (0);
-
-       add_label_to_sizer (_film_sizer, _film_panel, "Content Type");
-       _dcp_content_type = new wxComboBox (_film_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
-       _film_sizer->Add (_dcp_content_type);
-
-       video_control (add_label_to_sizer (_film_sizer, _film_panel, "Frames Per Second"));
-       _frames_per_second = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
-       _film_sizer->Add (video_control (_frames_per_second), 1, wxALIGN_CENTER_VERTICAL);
+       _dcp_panel = new wxPanel (_main_notebook);
+       _dcp_sizer = new wxBoxSizer (wxVERTICAL);
+       _dcp_panel->SetSizer (_dcp_sizer);
+
+       wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       _dcp_sizer->Add (grid, 0, wxEXPAND | wxALL, 8);
+
+       int r = 0;
        
-       video_control (add_label_to_sizer (_film_sizer, _film_panel, "Original Size"));
-       _original_size = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
-       _film_sizer->Add (video_control (_original_size), 1, wxALIGN_CENTER_VERTICAL);
+       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;
        
-       video_control (add_label_to_sizer (_film_sizer, _film_panel, "Length"));
-       _length = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
-       _film_sizer->Add (video_control (_length), 1, wxALIGN_CENTER_VERTICAL);
-
+       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;
+
+       int flags = wxALIGN_CENTER_VERTICAL;
+#ifdef __WXOSX__
+       flags |= wxALIGN_RIGHT;
+#endif 
+
+       _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 (_dcp_panel, wxID_ANY, _("Details..."));
+       grid->Add (_edit_dci_button, wxGBPosition (r, 1), wxDefaultSpan);
+       ++r;
+
+       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Container"), 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, _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_sizer (_film_sizer, _film_panel, "Trim frames"));
+               add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Frame Rate"), true, wxGBPosition (r, 0));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               video_control (add_label_to_sizer (s, _film_panel, "Start"));
-               _dcp_trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
-               s->Add (video_control (_dcp_trim_start));
-               video_control (add_label_to_sizer (s, _film_panel, "End"));
-               _dcp_trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
-               s->Add (video_control (_dcp_trim_end));
-
-               _film_sizer->Add (s);
+               _frame_rate = new wxChoice (_dcp_panel, wxID_ANY);
+               s->Add (_frame_rate, 1, wxALIGN_CENTER_VERTICAL);
+               _best_frame_rate = new wxButton (_dcp_panel, wxID_ANY, _("Use best"));
+               s->Add (_best_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND);
+               grid->Add (s, wxGBPosition (r, 1));
        }
+       ++r;
 
-       _encrypted = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Encrypted"));
-       _film_sizer->Add (_encrypted, 1);
-       _film_sizer->AddSpacer (0);
+       _encrypted = new wxCheckBox (_dcp_panel, wxID_ANY, wxT ("Encrypted"));
+       grid->Add (_encrypted, wxGBPosition (r, 0), wxGBSpan (1, 2));
+       ++r;
 
-       _multiple_reels = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Make multiple reels"));
-       _film_sizer->Add (_multiple_reels);
+       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Audio channels"), true, wxGBPosition (r, 0));
+       _audio_channels = new wxSpinCtrl (_dcp_panel, wxID_ANY);
+       grid->Add (_audio_channels, wxGBPosition (r, 1));
+       ++r;
 
-       {
-               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _reel_size = new wxSpinCtrl (_film_panel, wxID_ANY);
-               s->Add (_reel_size);
-               add_label_to_sizer (s, _film_panel, "Gb each");
-               _film_sizer->Add (s);
-       }
+       _three_d = new wxCheckBox (_dcp_panel, wxID_ANY, _("3D"));
+       grid->Add (_three_d, wxGBPosition (r, 0), wxGBSpan (1, 2));
+       ++r;
 
-       _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, wxT ("A/B"));
-       video_control (_dcp_ab);
-       _film_sizer->Add (_dcp_ab, 1);
-       _film_sizer->AddSpacer (0);
+       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Resolution"), true, wxGBPosition (r, 0));
+       _resolution = new wxChoice (_dcp_panel, wxID_ANY);
+       grid->Add (_resolution, wxGBPosition (r, 1));
+       ++r;
 
-       /* STILL-only stuff */
        {
-               still_control (add_label_to_sizer (_film_sizer, _film_panel, "Duration"));
+               add_label_to_grid_bag_sizer (grid, _dcp_panel, _("JPEG2000 bandwidth"), 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);
-               still_control (add_label_to_sizer (s, _film_panel, "s"));
-               _film_sizer->Add (s);
-       }
-
-       vector<DCPContentType const *> const ct = DCPContentType::all ();
-       for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
-               _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
-       }
-
-       _reel_size->SetRange(1, 1000);
-}
-
-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_COMBOBOX_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_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this);
-       _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
-       _dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this);
-       _encrypted->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::encrypted_toggled), 0, this);
-       _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this);
-       _dcp_trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_start_changed), 0, this);
-       _dcp_trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_end_changed), 0, this);
-       _multiple_reels->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::multiple_reels_toggled), 0, this);
-       _reel_size->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::reel_size_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_COMBOBOX_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_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
-       _audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::audio_stream_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
-               );
-       _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
-                       );
-       }
-}
-
-void
-FilmEditor::make_video_panel ()
-{
-       _video_panel = new wxPanel (_notebook);
-       _video_sizer = new wxFlexGridSizer (2, 4, 4);
-       wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL);
-       pad->Add (_video_sizer, 0, wxALL, 8);
-       _video_panel->SetSizer (pad);
-
-       add_label_to_sizer (_video_sizer, _video_panel, "Format");
-       _format = new wxComboBox (_video_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
-       _video_sizer->Add (_format);
-
-       {
-               add_label_to_sizer (_video_sizer, _video_panel, "Crop");
-               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-
-               add_label_to_sizer (s, _video_panel, "L");
-               _left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
-               s->Add (_left_crop, 0);
-               add_label_to_sizer (s, _video_panel, "R");
-               _right_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
-               s->Add (_right_crop, 0);
-               add_label_to_sizer (s, _video_panel, "T");
-               _top_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
-               s->Add (_top_crop, 0);
-               add_label_to_sizer (s, _video_panel, "B");
-               _bottom_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
-               s->Add (_bottom_crop, 0);
-
-               _video_sizer->Add (s);
+               _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-only stuff */
-       {
-               video_control (add_label_to_sizer (_video_sizer, _video_panel, "Filters"));
-               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _filters = new wxStaticText (_video_panel, wxID_ANY, wxT ("None"));
-               video_control (_filters);
-               s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
-               _filters_button = new wxButton (_video_panel, wxID_ANY, wxT ("Edit..."));
-               video_control (_filters_button);
-               s->Add (_filters_button, 0);
-               _video_sizer->Add (s, 1);
-       }
+       add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Standard"), true, wxGBPosition (r, 0));
+       _standard = new wxChoice (_dcp_panel, wxID_ANY);
+       grid->Add (_standard, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
+       ++r;
 
-       video_control (add_label_to_sizer (_video_sizer, _video_panel, "Scaler"));
-       _scaler = new wxComboBox (_video_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
-       _video_sizer->Add (video_control (_scaler), 1);
+       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;
 
        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_sizer (_video_sizer, _video_panel, "Colour look-up table");
-       _colour_lut = new wxComboBox (_video_panel, wxID_ANY);
-       for (int i = 0; i < 2; ++i) {
-               _colour_lut->Append (std_to_wx (colour_lut_index_to_name (i)));
-       }
-       _colour_lut->SetSelection (0);
-       _video_sizer->Add (_colour_lut, 1, wxEXPAND);
-
-       {
-               add_label_to_sizer (_video_sizer, _video_panel, "JPEG2000 bandwidth");
-               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");
-               _video_sizer->Add (s, 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 ()));
        }
 
-       _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);
-       _dcp_trim_start->SetRange (0, 100);
-       _dcp_trim_end->SetRange (0, 100);
-       _j2k_bandwidth->SetRange (50, 250);
-}
-
-void
-FilmEditor::make_audio_panel ()
-{
-       _audio_panel = new wxPanel (_notebook);
-       _audio_sizer = new wxFlexGridSizer (2, 4, 4);
-       wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL);
-       pad->Add (_audio_sizer, 0, wxALL, 8);
-       _audio_panel->SetSizer (pad);
-
-       {
-               video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Gain"));
-               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"));
-               _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);
-               _audio_sizer->Add (s);
+       vector<DCPContentType const *> const ct = DCPContentType::all ();
+       for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
+               _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
        }
 
-       {
-               video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Delay"));
-               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _audio_delay = new wxSpinCtrl (_audio_panel);
-               s->Add (video_control (_audio_delay), 1);
-               video_control (add_label_to_sizer (s, _audio_panel, "ms"));
-               _audio_sizer->Add (s);
+       list<int> const dfr = Config::instance()->allowed_dcp_frame_rates ();
+       for (list<int>::const_iterator i = dfr.begin(); i != dfr.end(); ++i) {
+               _frame_rate->Append (std_to_wx (boost::lexical_cast<string> (*i)));
        }
 
-       {
-               _use_content_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use content's audio"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
-               _audio_sizer->Add (video_control (_use_content_audio));
-               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _audio_stream = new wxComboBox (_audio_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
-               s->Add (video_control (_audio_stream), 1);
-               _audio = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
-               s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
-               _audio_sizer->Add (s, 1, wxEXPAND);
-       }
+       _audio_channels->SetRange (0, MAX_AUDIO_CHANNELS);
+       _j2k_bandwidth->SetRange (1, 250);
 
-       _use_external_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use external audio"));
-       _audio_sizer->Add (_use_external_audio);
-       _audio_sizer->AddSpacer (0);
-
-       assert (MAX_AUDIO_CHANNELS == 6);
-
-       char const * channels[] = {
-               "Left",
-               "Right",
-               "Centre",
-               "Lfe (sub)",
-               "Left surround",
-               "Right surround"
-       };
-
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               add_label_to_sizer (_audio_sizer, _audio_panel, channels[i]);
-               _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), wxT ("Select Audio File"), wxT ("*.wav"));
-               _audio_sizer->Add (_external_audio[i], 1, wxEXPAND);
-       }
+       _resolution->Append (_("2K"));
+       _resolution->Append (_("4K"));
 
-       _audio_gain->SetRange (-60, 60);
-       _audio_delay->SetRange (-1000, 1000);
+       _standard->Append (_("SMPTE"));
+       _standard->Append (_("Interop"));
 }
 
 void
-FilmEditor::make_subtitle_panel ()
+FilmEditor::connect_to_widgets ()
 {
-       _subtitle_panel = new wxPanel (_notebook);
-       _subtitle_sizer = new wxFlexGridSizer (2, 4, 4);
-       wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL);
-       pad->Add (_subtitle_sizer, 0, wxALL, 8);
-       _subtitle_panel->SetSizer (pad);
-
-       _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, wxT("With Subtitles"));
-       video_control (_with_subtitles);
-       _subtitle_sizer->Add (_with_subtitles, 1);
-       
-       _subtitle_stream = new wxComboBox (_subtitle_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
-       _subtitle_sizer->Add (video_control (_subtitle_stream));
-
-       video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Offset"));
-       _subtitle_offset = new wxSpinCtrl (_subtitle_panel);
-       _subtitle_sizer->Add (video_control (_subtitle_offset), 1);
+       _name->Bind             (wxEVT_COMMAND_TEXT_UPDATED,          boost::bind (&FilmEditor::name_changed, this));
+       _use_dci_name->Bind     (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::use_dci_name_toggled, this));
+       _edit_dci_button->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::edit_dci_button_clicked, this));
+       _container->Bind        (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::container_changed, this));
+       _content->Bind          (wxEVT_COMMAND_LIST_ITEM_SELECTED,    boost::bind (&FilmEditor::content_selection_changed, this));
+       _content->Bind          (wxEVT_COMMAND_LIST_ITEM_DESELECTED,  boost::bind (&FilmEditor::content_selection_changed, this));
+       _content->Bind          (wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, boost::bind (&FilmEditor::content_right_click, this, _1));
+       _content_add_file->Bind (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::content_add_file_clicked, this));
+       _content_add_folder->Bind (wxEVT_COMMAND_BUTTON_CLICKED,      boost::bind (&FilmEditor::content_add_folder_clicked, this));
+       _content_remove->Bind   (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::content_remove_clicked, this));
+       _content_timeline->Bind (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::content_timeline_clicked, this));
+       _scaler->Bind           (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::scaler_changed, this));
+       _dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::dcp_content_type_changed, this));
+       _frame_rate->Bind       (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::frame_rate_changed, this));
+       _best_frame_rate->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::best_frame_rate_clicked, this));
+       _audio_channels->Bind   (wxEVT_COMMAND_SPINCTRL_UPDATED,      boost::bind (&FilmEditor::audio_channels_changed, this));
+       _j2k_bandwidth->Bind    (wxEVT_COMMAND_SPINCTRL_UPDATED,      boost::bind (&FilmEditor::j2k_bandwidth_changed, this));
+       _resolution->Bind       (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::resolution_changed, this));
+       _sequence_video->Bind   (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::sequence_video_changed, this));
+       _three_d->Bind          (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::three_d_changed, this));
+       _standard->Bind         (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::standard_changed, this));
+}
+
+void
+FilmEditor::make_content_panel ()
+{
+       _content_panel = new wxPanel (_main_notebook);
+       _content_sizer = new wxBoxSizer (wxVERTICAL);
+       _content_panel->SetSizer (_content_sizer);
 
        {
-               video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Scale"));
                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, "%"));
-               _subtitle_sizer->Add (s);
-       }
-
-       _subtitle_offset->SetRange (-1024, 1024);
-       _subtitle_scale->SetRange (1, 1000);
-}
-
-/** Called when the left crop widget has been changed */
-void
-FilmEditor::left_crop_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
-
-       _film->set_left_crop (_left_crop->GetValue ());
-}
-
-/** Called when the right crop widget has been changed */
-void
-FilmEditor::right_crop_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
-
-       _film->set_right_crop (_right_crop->GetValue ());
-}
-
-/** Called when the top crop widget has been changed */
-void
-FilmEditor::top_crop_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
+               
+               _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);
 
-       _film->set_top_crop (_top_crop->GetValue ());
-}
+               _content->InsertColumn (0, wxT(""));
+               _content->SetColumnWidth (0, 512);
 
-/** Called when the bottom crop value has been changed */
-void
-FilmEditor::bottom_crop_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
+               wxBoxSizer* b = new wxBoxSizer (wxVERTICAL);
+               _content_add_file = new wxButton (_content_panel, wxID_ANY, _("Add file(s)..."));
+               b->Add (_content_add_file, 1, wxEXPAND | wxLEFT | wxRIGHT);
+               _content_add_folder = new wxButton (_content_panel, wxID_ANY, _("Add folder..."));
+               b->Add (_content_add_folder, 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);
 
-       _film->set_bottom_crop (_bottom_crop->GetValue ());
-}
+               s->Add (b, 0, wxALL, 4);
 
-/** Called when the content filename has been changed */
-void
-FilmEditor::content_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
+               _content_sizer->Add (s, 0.75, wxEXPAND | wxALL, 6);
        }
 
-       try {
-               _film->set_content (wx_to_std (_content->GetPath ()));
-       } catch (std::exception& e) {
-               _content->SetPath (std_to_wx (_film->directory ()));
-               error_dialog (this, String::compose ("Could not set content: %1", e.what ()));
-       }
-}
+       _sequence_video = new wxCheckBox (_content_panel, wxID_ANY, _("Keep video in sequence"));
+       _content_sizer->Add (_sequence_video);
 
-void
-FilmEditor::trust_content_header_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
+       _content_notebook = new wxNotebook (_content_panel, wxID_ANY);
+       _content_sizer->Add (_content_notebook, 1, wxEXPAND | wxTOP, 6);
 
-       _film->set_trust_content_header (_trust_content_header->GetValue ());
-}
-
-void
-FilmEditor::multiple_reels_toggled (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
-
-       if (_multiple_reels->GetValue()) {
-               _film->set_reel_size (_reel_size->GetValue() * 1e9);
-       } else {
-               _film->unset_reel_size ();
-       }
-
-       setup_reel_control_sensitivity ();
+       _video_panel = new VideoPanel (this);
+       _panels.push_back (_video_panel);
+       _audio_panel = new AudioPanel (this);
+       _panels.push_back (_audio_panel);
+       _subtitle_panel = new SubtitlePanel (this);
+       _panels.push_back (_subtitle_panel);
+       _timing_panel = new TimingPanel (this);
+       _panels.push_back (_timing_panel);
 }
 
+/** Called when the name widget has been changed */
 void
-FilmEditor::reel_size_changed (wxCommandEvent &)
+FilmEditor::name_changed ()
 {
        if (!_film) {
                return;
        }
 
-       _film->set_reel_size (static_cast<uint64_t> (_reel_size->GetValue()) * 1e9);
+       _film->set_name (string (_name->GetValue().mb_str()));
 }
 
-/** Called when the DCP A/B switch has been toggled */
 void
-FilmEditor::dcp_ab_toggled (wxCommandEvent &)
+FilmEditor::j2k_bandwidth_changed ()
 {
        if (!_film) {
                return;
        }
        
-       _film->set_dcp_ab (_dcp_ab->GetValue ());
+       _film->set_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1e6);
 }
 
 void
-FilmEditor::encrypted_toggled (wxCommandEvent &)
+FilmEditor::encrypted_toggled ()
 {
        if (!_film) {
                return;
@@ -532,55 +322,48 @@ FilmEditor::encrypted_toggled (wxCommandEvent &)
                               
 /** Called when the name widget has been changed */
 void
-FilmEditor::name_changed (wxCommandEvent &)
+FilmEditor::frame_rate_changed ()
 {
        if (!_film) {
                return;
        }
 
-       _film->set_name (string (_name->GetValue().mb_str()));
+       _film->set_video_frame_rate (
+               boost::lexical_cast<int> (
+                       wx_to_std (_frame_rate->GetString (_frame_rate->GetSelection ()))
+                       )
+               );
 }
 
 void
-FilmEditor::subtitle_offset_changed (wxCommandEvent &)
+FilmEditor::audio_channels_changed ()
 {
        if (!_film) {
                return;
        }
 
-       _film->set_subtitle_offset (_subtitle_offset->GetValue ());
+       _film->set_audio_channels (_audio_channels->GetValue ());
 }
 
 void
-FilmEditor::subtitle_scale_changed (wxCommandEvent &)
+FilmEditor::resolution_changed ()
 {
        if (!_film) {
                return;
        }
 
-       _film->set_subtitle_scale (_subtitle_scale->GetValue() / 100.0);
-}
-
-void
-FilmEditor::colour_lut_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
-       
-       _film->set_colour_lut (_colour_lut->GetSelection ());
+       _film->set_resolution (_resolution->GetSelection() == 0 ? RESOLUTION_2K : RESOLUTION_4K);
 }
 
 void
-FilmEditor::j2k_bandwidth_changed (wxCommandEvent &)
+FilmEditor::standard_changed ()
 {
        if (!_film) {
                return;
        }
-       
-       _film->set_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1e6);
-}      
 
+       _film->set_interop (_standard->GetSelection() == 1);
+}
 
 /** Called when the metadata stored in the Film object has changed;
  *  so that we can update the GUI.
@@ -596,201 +379,151 @@ FilmEditor::film_changed (Film::Property p)
        }
 
        stringstream s;
+
+       for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
+               (*i)->film_changed (p);
+       }
                
        switch (p) {
        case Film::NONE:
                break;
        case Film::CONTENT:
-               checked_set (_content, _film->content ());
-               setup_visibility ();
-               setup_formats ();
-               setup_subtitle_control_sensitivity ();
-               setup_streams ();
+               setup_content ();
                break;
-       case Film::TRUST_CONTENT_HEADER:
-               checked_set (_trust_content_header, _film->trust_content_header ());
+       case Film::CONTAINER:
+               setup_container ();
                break;
-       case Film::SUBTITLE_STREAMS:
-               setup_subtitle_control_sensitivity ();
-               setup_streams ();
-               break;
-       case Film::CONTENT_AUDIO_STREAMS:
-               setup_streams ();
-               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);
-               }
-               _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
-               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);
-               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 ();
-               break;
-       }
        case Film::NAME:
                checked_set (_name, _film->name());
-               _film->set_dci_date_today ();
-               _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
-               break;
-       case Film::FRAMES_PER_SECOND:
-               s << fixed << setprecision(2) << _film->frames_per_second();
-               _frames_per_second->SetLabel (std_to_wx (s.str ()));
+               setup_dcp_name ();
                break;
-       case Film::SIZE:
-               if (_film->size().width == 0 && _film->size().height == 0) {
-                       _original_size->SetLabel (wxT (""));
-               } else {
-                       s << _film->size().width << " x " << _film->size().height;
-                       _original_size->SetLabel (std_to_wx (s.str ()));
-               }
-               break;
-       case Film::LENGTH:
-               if (_film->frames_per_second() > 0 && _film->length()) {
-                       s << _film->length().get() << " frames; " << seconds_to_hms (_film->length().get() / _film->frames_per_second());
-               } else if (_film->length()) {
-                       s << _film->length().get() << " frames";
-               } 
-               _length->SetLabel (std_to_wx (s.str ()));
-               if (_film->length()) {
-                       _dcp_trim_start->SetRange (0, _film->length().get());
-                       _dcp_trim_end->SetRange (0, _film->length().get());
-               }
+       case Film::WITH_SUBTITLES:
+               setup_dcp_name ();
                break;
        case Film::DCP_CONTENT_TYPE:
                checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
-               _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
-               break;
-       case Film::DCP_AB:
-               checked_set (_dcp_ab, _film->dcp_ab ());
+               setup_dcp_name ();
                break;
        case Film::SCALER:
                checked_set (_scaler, Scaler::as_index (_film->scaler ()));
                break;
-       case Film::DCP_TRIM_START:
-               checked_set (_dcp_trim_start, _film->dcp_trim_start());
-               break;
-       case Film::DCP_TRIM_END:
-               checked_set (_dcp_trim_end, _film->dcp_trim_end());
-               break;
-       case Film::REEL_SIZE:
-               if (_film->reel_size()) {
-                       checked_set (_multiple_reels, true);
-                       checked_set (_reel_size, _film->reel_size().get() / 1e9);
-               } else {
-                       checked_set (_multiple_reels, false);
-               }
-               setup_reel_control_sensitivity ();
-               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 ();
-               _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
-               break;
-       case Film::SUBTITLE_OFFSET:
-               checked_set (_subtitle_offset, _film->subtitle_offset ());
-               break;
-       case Film::SUBTITLE_SCALE:
-               checked_set (_subtitle_scale, _film->subtitle_scale() * 100);
-               break;
        case Film::ENCRYPTED:
                checked_set (_encrypted, _film->encrypted ());
                break;
-       case Film::COLOUR_LUT:
-               checked_set (_colour_lut, _film->colour_lut ());
+       case Film::RESOLUTION:
+               checked_set (_resolution, _film->resolution() == RESOLUTION_2K ? 0 : 1);
+               setup_dcp_name ();
                break;
        case Film::J2K_BANDWIDTH:
                checked_set (_j2k_bandwidth, double (_film->j2k_bandwidth()) / 1e6);
                break;
        case Film::USE_DCI_NAME:
                checked_set (_use_dci_name, _film->use_dci_name ());
-               _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
+               setup_dcp_name ();
                break;
        case Film::DCI_METADATA:
-               _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
+               setup_dcp_name ();
                break;
-       case Film::CONTENT_AUDIO_STREAM:
-               if (_film->content_audio_stream()) {
-                       checked_set (_audio_stream, _film->content_audio_stream()->to_string());
+       case Film::VIDEO_FRAME_RATE:
+       {
+               bool done = false;
+               for (unsigned int i = 0; i < _frame_rate->GetCount(); ++i) {
+                       if (wx_to_std (_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->video_frame_rate())) {
+                               checked_set (_frame_rate, i);
+                               done = true;
+                               break;
+                       }
+               }
+
+               if (!done) {
+                       checked_set (_frame_rate, -1);
                }
-               _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
-               setup_audio_details ();
-               setup_audio_control_sensitivity ();
+
+               _best_frame_rate->Enable (_film->best_video_frame_rate () != _film->video_frame_rate ());
                break;
-       case Film::USE_CONTENT_AUDIO:
-               checked_set (_use_content_audio, _film->use_content_audio());
-               checked_set (_use_external_audio, !_film->use_content_audio());
-               _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
-               setup_audio_details ();
-               setup_audio_control_sensitivity ();
+       }
+       case Film::AUDIO_CHANNELS:
+               _audio_channels->SetValue (_film->audio_channels ());
+               setup_dcp_name ();
                break;
-       case Film::SUBTITLE_STREAM:
-               if (_film->subtitle_stream()) {
-                       checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
-               }
+       case Film::SEQUENCE_VIDEO:
+               checked_set (_sequence_video, _film->sequence_video ());
                break;
-       case Film::EXTERNAL_AUDIO:
-       {
-               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 ();
+       case Film::THREE_D:
+               checked_set (_three_d, _film->three_d ());
+               setup_dcp_name ();
+               break;
+       case Film::INTEROP:
+               checked_set (_standard, _film->interop() ? 1 : 0);
                break;
        }
+}
+
+void
+FilmEditor::film_content_changed (weak_ptr<Content> weak_content, int property)
+{
+       ensure_ui_thread ();
+       
+       if (!_film) {
+               /* We call this method ourselves (as well as using it as a signal handler)
+                  so _film can be 0.
+               */
+               return;
+       }
+
+       shared_ptr<Content> content = weak_content.lock ();
+       if (!content || content != selected_content ()) {
+               return;
+       }
+
+       for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
+               (*i)->film_content_changed (content, property);
+       }
+
+       if (property == FFmpegContentProperty::AUDIO_STREAM) {
+               setup_dcp_name ();
        }
 }
 
-/** 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 ();
+}      
+
+/** Called when the container widget has been changed */
+void
+FilmEditor::container_changed ()
 {
        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]);
        }
 }
 
 /** Called when the DCP content type widget has been changed */
 void
-FilmEditor::dcp_content_type_changed (wxCommandEvent &)
+FilmEditor::dcp_content_type_changed ()
 {
        if (!_film) {
                return;
@@ -806,12 +539,17 @@ FilmEditor::dcp_content_type_changed (wxCommandEvent &)
 void
 FilmEditor::set_film (shared_ptr<Film> f)
 {
-       _film = f;
+       set_general_sensitivity (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) {
@@ -819,93 +557,67 @@ FilmEditor::set_film (shared_ptr<Film> f)
        } else {
                FileChanged ("");
        }
-       
+
        film_changed (Film::NAME);
        film_changed (Film::USE_DCI_NAME);
        film_changed (Film::CONTENT);
-       film_changed (Film::TRUST_CONTENT_HEADER);
        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::RESOLUTION);
        film_changed (Film::SCALER);
-       film_changed (Film::DCP_TRIM_START);
-       film_changed (Film::DCP_TRIM_END);
-       film_changed (Film::REEL_SIZE);
-       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::ENCRYPTED);
-       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::FRAMES_PER_SECOND);
+       film_changed (Film::VIDEO_FRAME_RATE);
+       film_changed (Film::AUDIO_CHANNELS);
+       film_changed (Film::SEQUENCE_VIDEO);
+       film_changed (Film::THREE_D);
+       film_changed (Film::INTEROP);
+
+       if (!_film->content().empty ()) {
+               set_selection (_film->content().front ());
+       }
+
+       content_selection_changed ();
 }
 
-/** Updates the sensitivity of lots of widgets to a given value.
- *  @param s true to make sensitive, false to make insensitive.
- */
 void
-FilmEditor::set_things_sensitive (bool s)
+FilmEditor::set_general_sensitivity (bool s)
 {
        _generally_sensitive = s;
-       
+
+       /* Stuff in the Content / DCP tabs */
        _name->Enable (s);
        _use_dci_name->Enable (s);
        _edit_dci_button->Enable (s);
-       _format->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);
+       _content_add_file->Enable (s);
+       _content_add_folder->Enable (s);
+       _content_remove->Enable (s);
+       _content_timeline->Enable (s);
        _dcp_content_type->Enable (s);
-       _dcp_trim_start->Enable (s);
-       _dcp_trim_end->Enable (s);
-       _multiple_reels->Enable (s);
-       _reel_size->Enable (s);
-       _dcp_ab->Enable (s);
        _encrypted->Enable (s);
-       _colour_lut->Enable (s);
+       _frame_rate->Enable (s);
+       _audio_channels->Enable (s);
        _j2k_bandwidth->Enable (s);
-       _audio_gain->Enable (s);
-       _audio_gain_calculate_button->Enable (s);
-       _audio_delay->Enable (s);
-       _still_duration->Enable (s);
-
-       setup_subtitle_control_sensitivity ();
-       setup_audio_control_sensitivity ();
-       setup_reel_control_sensitivity ();
-}
+       _container->Enable (s);
+       _best_frame_rate->Enable (s && _film && _film->best_video_frame_rate () != _film->video_frame_rate ());
+       _sequence_video->Enable (s);
+       _resolution->Enable (s);
+       _scaler->Enable (s);
+       _three_d->Enable (s);
+       _standard->Enable (s);
 
-/** 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));
-       d->ShowModal ();
-       d->Destroy ();
+       /* Set the panels in the content notebook */
+       for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
+               (*i)->Enable (s);
+       }
 }
 
 /** Called when the scaler widget has been changed */
 void
-FilmEditor::scaler_changed (wxCommandEvent &)
+FilmEditor::scaler_changed ()
 {
        if (!_film) {
                return;
@@ -918,322 +630,289 @@ FilmEditor::scaler_changed (wxCommandEvent &)
 }
 
 void
-FilmEditor::audio_gain_changed (wxCommandEvent &)
+FilmEditor::use_dci_name_toggled ()
 {
        if (!_film) {
                return;
        }
 
-       _film->set_audio_gain (_audio_gain->GetValue ());
+       _film->set_use_dci_name (_use_dci_name->GetValue ());
 }
 
 void
-FilmEditor::audio_delay_changed (wxCommandEvent &)
+FilmEditor::edit_dci_button_clicked ()
 {
        if (!_film) {
                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;
+       DCIMetadataDialog* d = new DCIMetadataDialog (this, _film->dci_metadata ());
+       d->ShowModal ();
+       _film->set_dci_metadata (d->dci_metadata ());
+       d->Destroy ();
 }
 
 void
-FilmEditor::setup_visibility ()
+FilmEditor::active_jobs_changed (bool a)
 {
-       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);
-       }
-
-       _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 ();
-       Fit ();
+       set_general_sensitivity (!a);
 }
 
 void
-FilmEditor::still_duration_changed (wxCommandEvent &)
+FilmEditor::setup_dcp_name ()
 {
-       if (!_film) {
-               return;
+       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));
        }
-
-       _film->set_still_duration (_still_duration->GetValue ());
 }
 
 void
-FilmEditor::dcp_trim_start_changed (wxCommandEvent &)
+FilmEditor::best_frame_rate_clicked ()
 {
        if (!_film) {
                return;
        }
-
-       _film->set_dcp_trim_start (_dcp_trim_start->GetValue ());
+       
+       _film->set_video_frame_rate (_film->best_video_frame_rate ());
 }
 
 void
-FilmEditor::dcp_trim_end_changed (wxCommandEvent &)
+FilmEditor::setup_content ()
 {
-       if (!_film) {
-               return;
+       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 ();
+
+       ContentList content = _film->content ();
+       for (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);
+               }
        }
 
-       _film->set_dcp_trim_end (_dcp_trim_end->GetValue ());
+       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::audio_gain_calculate_button_clicked (wxCommandEvent &)
+FilmEditor::content_add_file_clicked ()
 {
-       GainCalculatorDialog* d = new GainCalculatorDialog (this);
-       d->ShowModal ();
+       wxFileDialog* d = new wxFileDialog (this, _("Choose a file or files"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE);
+       int const r = d->ShowModal ();
+       d->Destroy ();
 
-       if (d->wanted_fader() == 0 || d->actual_fader() == 0) {
-               d->Destroy ();
+       if (r != wxID_OK) {
                return;
        }
-       
-       _audio_gain->SetValue (
-               Config::instance()->sound_processor()->db_for_fader_change (
-                       d->wanted_fader (),
-                       d->actual_fader ()
-                       )
-               );
 
-       /* This appears to be necessary, as the change is not signalled,
-          I think.
-       */
-       wxCommandEvent dummy;
-       audio_gain_changed (dummy);
-       
-       d->Destroy ();
-}
+       wxArrayString paths;
+       d->GetPaths (paths);
 
-void
-FilmEditor::setup_formats ()
-{
-       ContentType c = VIDEO;
+       /* XXX: check for lots of files here and do something */
 
-       if (_film) {
-               c = _film->content_type ();
-       }
-       
-       _formats.clear ();
-
-       vector<Format const *> fmt = Format::all ();
-       for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
-               if (c == VIDEO && dynamic_cast<FixedFormat const *> (*i)) {
-                       _formats.push_back (*i);
-               } else if (c == STILL && dynamic_cast<VariableFormat const *> (*i)) {
-                       _formats.push_back (*i);
+       for (unsigned int i = 0; i < paths.GetCount(); ++i) {
+               boost::filesystem::path p (wx_to_std (paths[i]));
+
+               shared_ptr<Content> c;
+
+               if (valid_image_file (p)) {
+                       c.reset (new StillImageContent (_film, p));
+               } else if (SndfileContent::valid_file (p)) {
+                       c.reset (new SndfileContent (_film, p));
+               } else {
+                       c.reset (new FFmpegContent (_film, p));
                }
-       }
 
-       _format->Clear ();
-       for (vector<Format const *>::iterator i = _formats.begin(); i != _formats.end(); ++i) {
-               _format->Append (std_to_wx ((*i)->name ()));
+               _film->examine_and_add_content (c);
        }
-
-       _film_sizer->Layout ();
 }
 
 void
-FilmEditor::with_subtitles_toggled (wxCommandEvent &)
+FilmEditor::content_add_folder_clicked ()
 {
-       if (!_film) {
+       wxDirDialog* d = new wxDirDialog (this, _("Choose a folder"), wxT (""), wxDD_DIR_MUST_EXIST);
+       int const r = d->ShowModal ();
+       d->Destroy ();
+       
+       if (r != wxID_OK) {
                return;
        }
 
-       _film->set_with_subtitles (_with_subtitles->GetValue ());
+       _film->examine_and_add_content (
+               shared_ptr<MovingImageContent> (
+                       new MovingImageContent (_film, boost::filesystem::path (wx_to_std (d->GetPath ())))
+                       )
+               );
 }
 
 void
-FilmEditor::setup_subtitle_control_sensitivity ()
+FilmEditor::content_remove_clicked ()
 {
-       bool h = false;
-       if (_generally_sensitive && _film) {
-               h = !_film->subtitle_streams().empty();
+       shared_ptr<Content> c = selected_content ();
+       if (c) {
+               _film->remove_content (c);
        }
-       
-       _with_subtitles->Enable (h);
 
-       bool j = false;
-       if (_film) {
-               j = _film->with_subtitles ();
-       }
-       
-       _subtitle_stream->Enable (j);
-       _subtitle_offset->Enable (j);
-       _subtitle_scale->Enable (j);
+       content_selection_changed ();
 }
 
 void
-FilmEditor::setup_audio_control_sensitivity ()
+FilmEditor::content_selection_changed ()
 {
-       _use_content_audio->Enable (_generally_sensitive);
-       _use_external_audio->Enable (_generally_sensitive);
-       
-       bool const source = _generally_sensitive && _use_content_audio->GetValue();
-       bool const external = _generally_sensitive && _use_external_audio->GetValue();
+       setup_content_sensitivity ();
+       shared_ptr<Content> s = selected_content ();
 
-       _audio_stream->Enable (source);
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               _external_audio[i]->Enable (external);
-       }
+       /* All other sensitivity in content panels should be triggered by
+          one of these.
+       */
+       film_content_changed (s, ContentProperty::POSITION);
+       film_content_changed (s, ContentProperty::LENGTH);
+       film_content_changed (s, ContentProperty::TRIM_START);
+       film_content_changed (s, ContentProperty::TRIM_END);
+       film_content_changed (s, VideoContentProperty::VIDEO_CROP);
+       film_content_changed (s, VideoContentProperty::VIDEO_RATIO);
+       film_content_changed (s, VideoContentProperty::VIDEO_FRAME_TYPE);
+       film_content_changed (s, VideoContentProperty::COLOUR_CONVERSION);
+       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);
+       film_content_changed (s, FFmpegContentProperty::SUBTITLE_STREAMS);
+       film_content_changed (s, FFmpegContentProperty::FILTERS);
+       film_content_changed (s, SubtitleContentProperty::SUBTITLE_OFFSET);
+       film_content_changed (s, SubtitleContentProperty::SUBTITLE_SCALE);
 }
 
+/** Set up broad sensitivity based on the type of content that is selected */
 void
-FilmEditor::use_dci_name_toggled (wxCommandEvent &)
+FilmEditor::setup_content_sensitivity ()
 {
-       if (!_film) {
-               return;
-       }
+       _content_add_file->Enable (_generally_sensitive);
+       _content_add_folder->Enable (_generally_sensitive);
 
-       _film->set_use_dci_name (_use_dci_name->GetValue ());
+       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::edit_dci_button_clicked (wxCommandEvent &)
+shared_ptr<Content>
+FilmEditor::selected_content ()
 {
-       if (!_film) {
-               return;
+       int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+       if (s == -1) {
+               return shared_ptr<Content> ();
        }
 
-       DCINameDialog* d = new DCINameDialog (this, _film);
-       d->ShowModal ();
-       d->Destroy ();
+       ContentList c = _film->content ();
+       if (s < 0 || size_t (s) >= c.size ()) {
+               return shared_ptr<Content> ();
+       }
+       
+       return c[s];
 }
 
-void
-FilmEditor::setup_streams ()
+shared_ptr<VideoContent>
+FilmEditor::selected_video_content ()
 {
-       _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());
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return shared_ptr<VideoContent> ();
        }
 
-       _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());
-       } else {
-               _subtitle_stream->SetValue (wxT (""));
+       return dynamic_pointer_cast<VideoContent> (c);
+}
+
+shared_ptr<AudioContent>
+FilmEditor::selected_audio_content ()
+{
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return shared_ptr<AudioContent> ();
        }
+
+       return dynamic_pointer_cast<AudioContent> (c);
 }
 
-void
-FilmEditor::audio_stream_changed (wxCommandEvent &)
+shared_ptr<SubtitleContent>
+FilmEditor::selected_subtitle_content ()
 {
-       if (!_film) {
-               return;
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return shared_ptr<SubtitleContent> ();
        }
 
-       _film->set_content_audio_stream (
-               audio_stream_factory (
-                       string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ())),
-                       Film::state_version
-                       )
-               );
+       return dynamic_pointer_cast<SubtitleContent> (c);
 }
 
 void
-FilmEditor::subtitle_stream_changed (wxCommandEvent &)
+FilmEditor::content_timeline_clicked ()
 {
-       if (!_film) {
-               return;
+       if (_timeline_dialog) {
+               _timeline_dialog->Destroy ();
+               _timeline_dialog = 0;
        }
-
-       _film->set_subtitle_stream (
-               subtitle_stream_factory (
-                       string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ())),
-                       Film::state_version
-                       )
-               );
+       
+       _timeline_dialog = new TimelineDialog (this, _film);
+       _timeline_dialog->Show ();
 }
 
 void
-FilmEditor::setup_audio_details ()
+FilmEditor::set_selection (weak_ptr<Content> wc)
 {
-       if (!_film->audio_stream()) {
-               _audio->SetLabel (wxT (""));
-       } else {
-               stringstream s;
-               if (_film->audio_stream()->channels() == 1) {
-                       s << "1 channel";
+       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 {
-                       s << _film->audio_stream()->channels () << " channels";
+                       _content->SetItemState (i, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
                }
-               s << ", " << _film->audio_stream()->sample_rate() << "Hz";
-               _audio->SetLabel (std_to_wx (s.str ()));
        }
 }
 
 void
-FilmEditor::active_jobs_changed (bool a)
+FilmEditor::sequence_video_changed ()
 {
-       set_things_sensitive (!a);
+       if (!_film) {
+               return;
+       }
+       
+       _film->set_sequence_video (_sequence_video->GetValue ());
 }
 
 void
-FilmEditor::use_audio_changed (wxCommandEvent &)
+FilmEditor::content_right_click (wxListEvent& ev)
 {
-       _film->set_use_content_audio (_use_content_audio->GetValue());
+       ContentList cl;
+       cl.push_back (selected_content ());
+       _menu.popup (cl, ev.GetPoint ());
 }
 
 void
-FilmEditor::external_audio_changed (wxCommandEvent &)
+FilmEditor::three_d_changed ()
 {
-       vector<string> a;
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               a.push_back (wx_to_std (_external_audio[i]->GetPath()));
+       if (!_film) {
+               return;
        }
 
-       _film->set_external_audio (a);
-}
-
-void
-FilmEditor::setup_reel_control_sensitivity ()
-{
-       _reel_size->Enable (_multiple_reels->GetValue ());
+       _film->set_three_d (_three_d->GetValue ());
 }
index b990ec40db748ffdc202c01a398660f16a02907a..bb217211ca7719c0e58ba4a8cb22854726ec8961 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
@@ -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 <wx/collpane.h>
 #include <boost/signals2.hpp>
 #include "lib/film.h"
+#include "content_menu.h"
 
 class wxNotebook;
-
+class wxListCtrl;
+class wxListEvent;
 class Film;
+class TimelineDialog;
+class Ratio;
+class Timecode;
+class FilmEditorPanel;
+class SubtitleContent;
 
 /** @class FilmEditor
  *  @brief A wx widget to edit a film's metadata, and perform various functions.
@@ -41,146 +48,108 @@ 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;
 
+       /* Stuff for panels */
+       
+       wxNotebook* content_notebook () const {
+               return _content_notebook;
+       }
+
+       boost::shared_ptr<Film> film () const {
+               return _film;
+       }
+
+       boost::shared_ptr<Content> selected_content ();
+       boost::shared_ptr<VideoContent> selected_video_content ();
+       boost::shared_ptr<AudioContent> selected_audio_content ();
+       boost::shared_ptr<SubtitleContent> selected_subtitle_content ();
+       
 private:
-       void make_film_panel ();
-       void make_video_panel ();
-       void make_audio_panel ();
-       void make_subtitle_panel ();
+       void make_dcp_panel ();
+       void make_content_panel ();
        void connect_to_widgets ();
        
        /* Handle changes to the view */
-       void name_changed (wxCommandEvent &);
-       void use_dci_name_toggled (wxCommandEvent &);
-       void edit_dci_button_clicked (wxCommandEvent &);
-       void left_crop_changed (wxCommandEvent &);
-       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 dcp_trim_start_changed (wxCommandEvent &);
-       void dcp_trim_end_changed (wxCommandEvent &);
-       void multiple_reels_toggled (wxCommandEvent &);
-       void reel_size_changed (wxCommandEvent &);
-       void dcp_content_type_changed (wxCommandEvent &);
-       void encrypted_toggled (wxCommandEvent &);
-       void dcp_ab_toggled (wxCommandEvent &);
-       void scaler_changed (wxCommandEvent &);
-       void audio_gain_changed (wxCommandEvent &);
-       void audio_gain_calculate_button_clicked (wxCommandEvent &);
-       void audio_delay_changed (wxCommandEvent &);
-       void with_subtitles_toggled (wxCommandEvent &);
-       void subtitle_offset_changed (wxCommandEvent &);
-       void subtitle_scale_changed (wxCommandEvent &);
-       void colour_lut_changed (wxCommandEvent &);
-       void j2k_bandwidth_changed (wxCommandEvent &);
-       void still_duration_changed (wxCommandEvent &);
-       void audio_stream_changed (wxCommandEvent &);
-       void subtitle_stream_changed (wxCommandEvent &);
-       void use_audio_changed (wxCommandEvent &);
-       void external_audio_changed (wxCommandEvent &);
+       void name_changed ();
+       void use_dci_name_toggled ();
+       void edit_dci_button_clicked ();
+       void content_selection_changed ();
+       void content_add_file_clicked ();
+       void content_add_folder_clicked ();
+       void content_remove_clicked ();
+       void container_changed ();
+       void dcp_content_type_changed ();
+       void scaler_changed ();
+       void j2k_bandwidth_changed ();
+       void frame_rate_changed ();
+       void best_frame_rate_clicked ();
+       void content_timeline_clicked ();
+       void audio_channels_changed ();
+       void resolution_changed ();
+       void sequence_video_changed ();
+       void content_right_click (wxListEvent &);
+       void three_d_changed ();
+       void standard_changed ();
+       void encrypted_toggled ();
 
        /* Handle changes to the model */
        void film_changed (Film::Property);
+       void film_content_changed (boost::weak_ptr<Content>, int);
 
-       /* Button clicks */
-       void edit_filters_clicked (wxCommandEvent &);
-
-       void set_things_sensitive (bool);
-       void setup_formats ();
-       void setup_subtitle_control_sensitivity ();
-       void setup_audio_control_sensitivity ();
-       void setup_reel_control_sensitivity ();
-       void setup_streams ();
-       void setup_audio_details ();
+       void set_general_sensitivity (bool);
+       void setup_dcp_name ();
+       void setup_content ();
+       void setup_container ();
+       void setup_content_sensitivity ();
        
-       wxControl* video_control (wxControl *);
-       wxControl* still_control (wxControl *);
-
        void active_jobs_changed (bool);
 
-       wxNotebook* _notebook;
-       wxPanel* _film_panel;
-       wxSizer* _film_sizer;
-       wxPanel* _video_panel;
-       wxSizer* _video_sizer;
-       wxPanel* _audio_panel;
-       wxSizer* _audio_sizer;
-       wxPanel* _subtitle_panel;
-       wxSizer* _subtitle_sizer;
+       FilmEditorPanel* _video_panel;
+       FilmEditorPanel* _audio_panel;
+       FilmEditorPanel* _subtitle_panel;
+       FilmEditorPanel* _timing_panel;
+       std::list<FilmEditorPanel *> _panels;
+
+       wxNotebook* _main_notebook;
+       wxNotebook* _content_notebook;
+       wxPanel* _dcp_panel;
+       wxSizer* _dcp_sizer;
+       wxPanel* _content_panel;
+       wxSizer* _content_sizer;
 
        /** 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_file;
+       wxButton* _content_add_folder;
+       wxButton* _content_remove;
+       wxButton* _content_earlier;
+       wxButton* _content_later;
+       wxButton* _content_timeline;
+       wxCheckBox* _sequence_video;
        wxButton* _edit_dci_button;
-       /** The Film's format */
-       wxComboBox* _format;
-       /** 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 */
-       wxComboBox* _scaler;
-       wxRadioButton* _use_content_audio;
-       wxComboBox* _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;
-       /** The Film's audio delay */
-       wxSpinCtrl* _audio_delay;
-       wxCheckBox* _with_subtitles;
-       wxComboBox* _subtitle_stream;
-       wxSpinCtrl* _subtitle_offset;
-       wxSpinCtrl* _subtitle_scale;
-       wxComboBox* _colour_lut;
-       wxSpinCtrl* _j2k_bandwidth;
-       /** The Film's DCP content type */
-       wxComboBox* _dcp_content_type;
-       /** The Film's frames per second */
-       wxStaticText* _frames_per_second;
-       /** The Film's original size */
-       wxStaticText* _original_size;
-       /** The Film's length */
-       wxStaticText* _length;
-       /** The Film's audio details */
-       wxStaticText* _audio;
-       /** The Film's duration for still sources */
-       wxSpinCtrl* _still_duration;
-
-       wxSpinCtrl* _dcp_trim_start;
-       wxSpinCtrl* _dcp_trim_end;
+       wxChoice* _scaler;
+       wxSpinCtrl* _j2k_bandwidth;
+       wxChoice* _dcp_content_type;
+       wxChoice* _frame_rate;
+       wxSpinCtrl* _audio_channels;
+       wxButton* _best_frame_rate;
+       wxCheckBox* _three_d;
+       wxChoice* _resolution;
+       wxChoice* _standard;
        wxCheckBox* _encrypted;
-       wxCheckBox* _multiple_reels;
-       wxSpinCtrl* _reel_size;
-       /** Selector to generate an A/B comparison DCP */
-       wxCheckBox* _dcp_ab;
 
-       std::list<wxControl*> _video_controls;
-       std::list<wxControl*> _still_controls;
+       ContentMenu _menu;
 
-       std::vector<Format const *> _formats;
+       std::vector<Ratio const *> _ratios;
 
        bool _generally_sensitive;
+       TimelineDialog* _timeline_dialog;
 };
diff --git a/src/wx/film_editor_panel.cc b/src/wx/film_editor_panel.cc
new file mode 100644 (file)
index 0000000..a637df1
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+    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 <wx/notebook.h>
+#include "film_editor_panel.h"
+#include "film_editor.h"
+
+using boost::shared_ptr;
+
+FilmEditorPanel::FilmEditorPanel (FilmEditor* e, wxString name)
+       : wxPanel (e->content_notebook (), wxID_ANY)
+       , _editor (e)
+       , _sizer (new wxBoxSizer (wxVERTICAL))
+{
+       e->content_notebook()->AddPage (this, name, false);
+       SetSizer (_sizer);
+}
+
diff --git a/src/wx/film_editor_panel.h b/src/wx/film_editor_panel.h
new file mode 100644 (file)
index 0000000..8acb3ef
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+    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.
+
+*/
+
+#ifndef DCPOMATIC_FILM_EDITOR_PANEL_H
+#define DCPOMATIC_FILM_EDITOR_PANEL_H
+
+#include <boost/shared_ptr.hpp>
+#include <wx/wx.h>
+#include "lib/film.h"
+
+class FilmEditor;
+class Content;
+
+class FilmEditorPanel : public wxPanel
+{
+public:
+       FilmEditorPanel (FilmEditor *, wxString);
+
+       virtual void film_changed (Film::Property) {}
+       virtual void film_content_changed (boost::shared_ptr<Content>, int) = 0;
+
+protected:
+       FilmEditor* _editor;
+       wxSizer* _sizer;
+};
+
+#endif
diff --git a/src/wx/film_list.cc b/src/wx/film_list.cc
deleted file mode 100644 (file)
index 05d9734..0000000
+++ /dev/null
@@ -1,65 +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/filesystem.hpp>
-#include "lib/film.h"
-#include "film_list.h"
-
-using namespace std;
-using namespace boost;
-
-FilmList::FilmList (string d)
-       : _directory (d)
-       , _list (1)
-{
-       for (filesystem::directory_iterator i = filesystem::directory_iterator (_directory); i != filesystem::directory_iterator(); ++i) {
-               if (is_directory (*i)) {
-                       filesystem::path m = filesystem::path (*i) / filesystem::path ("metadata");
-                       if (is_regular_file (m)) {
-                               Film* f = new Film (i->path().string());
-                               _films.push_back (f);
-                       }
-               }
-       }
-
-       for (vector<Film const *>::iterator i = _films.begin(); i != _films.end(); ++i) {
-               _list.append_text ((*i)->name ());
-       }
-
-       _list.set_headers_visible (false);
-       _list.get_selection()->signal_changed().connect (bind (&FilmList::selection_changed, this));
-}
-
-Gtk::Widget&
-FilmList::widget ()
-{
-       return _list;
-}
-
-void
-FilmList::selection_changed ()
-{
-       Gtk::ListViewText::SelectionList s = _list.get_selected ();
-       if (s.empty ()) {
-               return;
-       }
-
-       assert (s[0] < int (_films.size ()));
-       SelectionChanged (_films[s[0]]);
-}
diff --git a/src/wx/film_list.h b/src/wx/film_list.h
deleted file mode 100644 (file)
index 5a4ac3c..0000000
+++ /dev/null
@@ -1,41 +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 <string>
-#include <vector>
-#include <gtkmm.h>
-
-class Film;
-
-class FilmList
-{
-public:
-       FilmList (std::string);
-       
-       Gtk::Widget& widget ();
-
-       sigc::signal<void, Film const *> SelectionChanged;
-
-private:
-       void selection_changed ();
-
-       std::string _directory;
-       std::vector<Film const *> _films;
-       Gtk::ListViewText _list;
-};
index 3d8198457db1a3c342290087fe938408d63a11c1..3ba7ee7ce8dfa20ffa05fd7ed6809365e6cc6f29 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/still_image_content.h"
+#include "lib/video_decoder.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 std::make_pair;
 using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::weak_ptr;
+using libdcp::Size;
 
 FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        : wxPanel (p)
        , _panel (new wxPanel (this))
        , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
-       , _play_button (new wxToggleButton (this, wxID_ANY, wxT ("Play")))
+       , _back_button (new wxButton (this, wxID_ANY, wxT("<")))
+       , _forward_button (new wxButton (this, wxID_ANY, wxT(">")))
+       , _frame_number (new wxStaticText (this, wxID_ANY, wxT("")))
+       , _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
+       , _play_button (new wxToggleButton (this, wxID_ANY, _("Play")))
        , _got_frame (false)
-       , _out_width (0)
-       , _out_height (0)
-       , _panel_width (0)
-       , _panel_height (0)
-       , _clear_required (false)
 {
+#ifndef __WXOSX__
        _panel->SetDoubleBuffered (true);
-#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9       
+#endif
+       
        _panel->SetBackgroundStyle (wxBG_STYLE_PAINT);
-#endif 
        
        _v_sizer = new wxBoxSizer (wxVERTICAL);
        SetSizer (_v_sizer);
@@ -68,18 +76,32 @@ FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        _v_sizer->Add (_panel, 1, wxEXPAND);
 
        wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
+
+       wxBoxSizer* time_sizer = new wxBoxSizer (wxVERTICAL);
+       time_sizer->Add (_frame_number, 0, wxEXPAND);
+       time_sizer->Add (_timecode, 0, wxEXPAND);
+
+       h_sizer->Add (_back_button, 0, wxALL, 2);
+       h_sizer->Add (time_sizer, 0, wxEXPAND);
+       h_sizer->Add (_forward_button, 0, wxALL, 2);
        h_sizer->Add (_play_button, 0, wxEXPAND);
        h_sizer->Add (_slider, 1, wxEXPAND);
 
-       _v_sizer->Add (h_sizer, 0, wxEXPAND);
+       _v_sizer->Add (h_sizer, 0, wxEXPAND | wxALL, 6);
+
+       _frame_number->SetMinSize (wxSize (84, -1));
+       _back_button->SetMinSize (wxSize (32, -1));
+       _forward_button->SetMinSize (wxSize (32, -1));
 
-       _panel->Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (FilmViewer::paint_panel), 0, this);
-       _panel->Connect (wxID_ANY, wxEVT_SIZE, wxSizeEventHandler (FilmViewer::panel_sized), 0, this);
-       _slider->Connect (wxID_ANY, wxEVT_SCROLL_THUMBTRACK, wxScrollEventHandler (FilmViewer::slider_moved), 0, this);
-       _slider->Connect (wxID_ANY, wxEVT_SCROLL_PAGEUP, wxScrollEventHandler (FilmViewer::slider_moved), 0, this);
-       _slider->Connect (wxID_ANY, wxEVT_SCROLL_PAGEDOWN, wxScrollEventHandler (FilmViewer::slider_moved), 0, this);
-       _play_button->Connect (wxID_ANY, wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler (FilmViewer::play_clicked), 0, this);
-       _timer.Connect (wxID_ANY, wxEVT_TIMER, wxTimerEventHandler (FilmViewer::timer), 0, this);
+       _panel->Bind          (wxEVT_PAINT,                        boost::bind (&FilmViewer::paint_panel,     this));
+       _panel->Bind          (wxEVT_SIZE,                         boost::bind (&FilmViewer::panel_sized,     this, _1));
+       _slider->Bind         (wxEVT_SCROLL_THUMBTRACK,            boost::bind (&FilmViewer::slider_moved,    this));
+       _slider->Bind         (wxEVT_SCROLL_PAGEUP,                boost::bind (&FilmViewer::slider_moved,    this));
+       _slider->Bind         (wxEVT_SCROLL_PAGEDOWN,              boost::bind (&FilmViewer::slider_moved,    this));
+       _play_button->Bind    (wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, boost::bind (&FilmViewer::play_clicked,    this));
+       _timer.Bind           (wxEVT_TIMER,                        boost::bind (&FilmViewer::timer,           this));
+       _back_button->Bind    (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&FilmViewer::back_clicked,    this));
+       _forward_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&FilmViewer::forward_clicked, this));
 
        set_film (f);
 
@@ -88,96 +110,67 @@ FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
                );
 }
 
-void
-FilmViewer::film_changed (Film::Property p)
-{
-       switch (p) {
-       case Film::FORMAT:
-               calculate_sizes ();
-               update_from_raw ();
-               break;
-       case Film::CONTENT:
-       {
-               shared_ptr<DecodeOptions> o (new DecodeOptions);
-               o->decode_audio = false;
-               o->decode_subtitles = true;
-               o->video_sync = false;
-               _decoders = decoder_factory (_film, o, 0);
-               _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3));
-               _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this));
-               _decoders.video->set_subtitle_stream (_film->subtitle_stream());
-               calculate_sizes ();
-               get_frame ();
-               _panel->Refresh ();
-               _slider->Show (_film->content_type() == VIDEO);
-               _play_button->Show (_film->content_type() == VIDEO);
-               _v_sizer->Layout ();
-               break;
-       }
-       case Film::WITH_SUBTITLES:
-       case Film::SUBTITLE_OFFSET:
-       case Film::SUBTITLE_SCALE:
-       case Film::SCALER:
-               update_from_raw ();
-               break;
-       case Film::SUBTITLE_STREAM:
-               _decoders.video->set_subtitle_stream (_film->subtitle_stream ());
-               break;
-       default:
-               break;
-       }
-}
-
 void
 FilmViewer::set_film (shared_ptr<Film> f)
 {
        if (_film == f) {
                return;
        }
-       
+
        _film = f;
 
+       _frame.reset ();
+       _queue.clear ();
+
+       _slider->SetValue (0);
+       set_position_text (0);
+       
        if (!_film) {
                return;
        }
 
-       _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
+       _player = f->make_player ();
+       _player->disable_audio ();
+       _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _2, _5));
+       _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1));
 
-       film_changed (Film::CONTENT);
-       film_changed (Film::CROP);
-       film_changed (Film::FORMAT);
-       film_changed (Film::WITH_SUBTITLES);
-       film_changed (Film::SUBTITLE_OFFSET);
-       film_changed (Film::SUBTITLE_SCALE);
-       film_changed (Film::SUBTITLE_STREAM);
+       calculate_sizes ();
+       fetch_current_frame_again ();
 }
 
 void
-FilmViewer::decoder_changed ()
+FilmViewer::fetch_current_frame_again ()
 {
-       if (_decoders.video->seek_to_last ()) {
+       if (!_player) {
                return;
        }
 
-       get_frame ();
-       _panel->Refresh ();
-       _panel->Update ();
+       /* Player::video_position is the time after the last frame that we received.
+          We want to see it again, so seek back one frame.
+       */
+
+       Time p = _player->video_position() - _film->video_frames_to_time (1);
+       if (p < 0) {
+               p = 0;
+       }
+
+       _player->seek (p, true);
+       fetch_next_frame ();
 }
 
 void
-FilmViewer::timer (wxTimerEvent &)
+FilmViewer::timer ()
 {
-       if (!_film) {
+       if (!_player) {
                return;
        }
        
-       _panel->Refresh ();
-       _panel->Update ();
+       fetch_next_frame ();
 
-       get_frame ();
+       Time const len = _film->length ();
 
-       if (_film->length()) {
-               int const new_slider_position = 4096 * _decoders.video->last_source_time() / (_film->length().get() / _film->frames_per_second());
+       if (len) {
+               int const new_slider_position = 4096 * _player->video_position() / len;
                if (new_slider_position != _slider->GetValue()) {
                        _slider->SetValue (new_slider_position);
                }
@@ -186,125 +179,88 @@ FilmViewer::timer (wxTimerEvent &)
 
 
 void
-FilmViewer::paint_panel (wxPaintEvent &)
+FilmViewer::paint_panel ()
 {
        wxPaintDC dc (_panel);
 
-       if (_clear_required) {
-               dc.Clear ();
-               _clear_required = false;
-       }
-
-       if (!_display_frame || !_film || !_out_width || !_out_height) {
+       if (!_frame || !_film || !_out_size.width || !_out_size.height) {
                dc.Clear ();
                return;
        }
 
-       wxImage frame (_out_width, _out_height, _display_frame->data()[0], true);
+       shared_ptr<Image> packed_frame (new Image (_frame, false));
+
+       wxImage frame (_out_size.width, _out_size.height, packed_frame->data()[0], true);
        wxBitmap frame_bitmap (frame);
        dc.DrawBitmap (frame_bitmap, 0, 0);
 
-       if (_film->with_subtitles() && _display_sub) {
-               wxImage sub (_display_sub->size().width, _display_sub->size().height, _display_sub->data()[0], _display_sub->alpha(), true);
-               wxBitmap sub_bitmap (sub);
-               dc.DrawBitmap (sub_bitmap, _display_sub_position.x, _display_sub_position.y);
+       if (_out_size.width < _panel_size.width) {
+               wxPen p (GetBackgroundColour ());
+               wxBrush b (GetBackgroundColour ());
+               dc.SetPen (p);
+               dc.SetBrush (b);
+               dc.DrawRectangle (_out_size.width, 0, _panel_size.width - _out_size.width, _panel_size.height);
        }
+
+       if (_out_size.height < _panel_size.height) {
+               wxPen p (GetBackgroundColour ());
+               wxBrush b (GetBackgroundColour ());
+               dc.SetPen (p);
+               dc.SetBrush (b);
+               dc.DrawRectangle (0, _out_size.height, _panel_size.width, _panel_size.height - _out_size.height);
+       }               
 }
 
 
 void
-FilmViewer::slider_moved (wxScrollEvent &)
+FilmViewer::slider_moved ()
 {
-       if (!_film || !_film->length()) {
-               return;
+       if (_film && _player) {
+               _player->seek (_slider->GetValue() * _film->length() / 4096, false);
+               fetch_next_frame ();
        }
-       
-       if (_decoders.video->seek (_slider->GetValue() * _film->length().get() / (4096 * _film->frames_per_second()))) {
-               return;
-       }
-       
-       get_frame ();
-       _panel->Refresh ();
-       _panel->Update ();
 }
 
 void
 FilmViewer::panel_sized (wxSizeEvent& ev)
 {
-       _panel_width = ev.GetSize().GetWidth();
-       _panel_height = ev.GetSize().GetHeight();
+       _panel_size.width = ev.GetSize().GetWidth();
+       _panel_size.height = ev.GetSize().GetHeight();
        calculate_sizes ();
-       update_from_raw ();
-}
-
-void
-FilmViewer::update_from_raw ()
-{
-       if (!_raw_frame) {
-               return;
-       }
-
-       raw_to_display ();
-       
-       _panel->Refresh ();
-       _panel->Update ();
+       fetch_current_frame_again ();
 }
 
-void
-FilmViewer::raw_to_display ()
-{
-       if (!_raw_frame || _out_width < 64 || _out_height < 64 || !_film) {
-               return;
-       }
-
-       Size old_size;
-       if (_display_frame) {
-               old_size = _display_frame->size();
-       }
-
-       /* Get a compacted image as we have to feed it to wxWidgets */
-       _display_frame = _raw_frame->scale_and_convert_to_rgb (Size (_out_width, _out_height), 0, _film->scaler(), false);
-
-       if (old_size != _display_frame->size()) {
-               _clear_required = true;
-       }
-
-       if (_raw_sub) {
-               Rect tx = subtitle_transformed_area (
-                       float (_out_width) / _film->size().width,
-                       float (_out_height) / _film->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();
-       } else {
-               _display_sub.reset ();
-       }
-}      
-
 void
 FilmViewer::calculate_sizes ()
 {
-       if (!_film) {
+       if (!_film || !_player) {
                return;
        }
+
+       Ratio const * container = _film->container ();
        
-       float const panel_ratio = static_cast<float> (_panel_width) / _panel_height;
-       float const film_ratio = _film->format() ? _film->format()->ratio_as_float(_film) : 1.78;
+       float const panel_ratio = static_cast<float> (_panel_size.width) / _panel_size.height;
+       float const film_ratio = container ? container->ratio () : 1.78;
+                       
        if (panel_ratio < film_ratio) {
                /* panel is less widscreen than the film; clamp width */
-               _out_width = _panel_width;
-               _out_height = _out_width / film_ratio;
+               _out_size.width = _panel_size.width;
+               _out_size.height = _out_size.width / film_ratio;
        } else {
-               /* panel is more widescreen than the film; clamp heignt */
-               _out_height = _panel_height;
-               _out_width = _out_height * film_ratio;
+               /* panel is more widescreen than the film; clamp height */
+               _out_size.height = _panel_size.height;
+               _out_size.width = _out_size.height * film_ratio;
        }
+
+       /* Catch silly values */
+       _out_size.width = max (64, _out_size.width);
+       _out_size.height = max (64, _out_size.height);
+
+       _player->set_video_container_size (_out_size);
 }
 
 void
-FilmViewer::play_clicked (wxCommandEvent &)
+FilmViewer::play_clicked ()
 {
        check_play_state ();
 }
@@ -312,48 +268,88 @@ FilmViewer::play_clicked (wxCommandEvent &)
 void
 FilmViewer::check_play_state ()
 {
-       if (!_film) {
+       if (!_film || _film->video_frame_rate() == 0) {
                return;
        }
        
        if (_play_button->GetValue()) {
-               _timer.Start (1000 / _film->frames_per_second());
+               _timer.Start (1000 / _film->video_frame_rate());
        } else {
                _timer.Stop ();
        }
 }
 
 void
-FilmViewer::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle> sub)
+FilmViewer::process_video (shared_ptr<const Image> image, Eyes eyes, Time t)
 {
-       _raw_frame = image;
-       _raw_sub = sub;
+       if (eyes == EYES_RIGHT) {
+               return;
+       }
+       
+       if (_got_frame) {
+               /* This is an additional frame emitted by a single pass.  Store it. */
+               _queue.push_front (make_pair (image, t));
+               return;
+       }
+       
+       _frame = image;
+       _got_frame = true;
 
-       raw_to_display ();
+       set_position_text (t);
+}
 
-       _got_frame = true;
+void
+FilmViewer::set_position_text (Time t)
+{
+       if (!_film) {
+               _frame_number->SetLabel ("0");
+               _timecode->SetLabel ("0:0:0.0");
+               return;
+       }
+               
+       double const fps = _film->video_frame_rate ();
+       /* Count frame number from 1 ... not sure if this is the best idea */
+       _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ)) + 1));
+       
+       double w = static_cast<double>(t) / TIME_HZ;
+       int const h = (w / 3600);
+       w -= h * 3600;
+       int const m = (w / 60);
+       w -= m * 60;
+       int const s = floor (w);
+       w -= s;
+       int const f = rint (w * fps);
+       _timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d.%02d"), h, m, s, f));
 }
 
+/** Ask the player to emit its next frame, then update our display */
 void
-FilmViewer::get_frame ()
+FilmViewer::fetch_next_frame ()
 {
-       /* Clear our raw frame in case we don't get a new one */
-       _raw_frame.reset ();
+       /* Clear our frame in case we don't get a new one */
+       _frame.reset ();
+
+       if (!_player) {
+               return;
+       }
+
+       _got_frame = false;
        
-       try {
-               _got_frame = false;
-               while (!_got_frame) {
-                       if (_decoders.video->pass ()) {
-                               /* We didn't get a frame before the decoder gave up,
-                                  so clear our display frame.
-                               */
-                               _display_frame.reset ();
-                               break;
-                       }
+       if (!_queue.empty ()) {
+               process_video (_queue.back().first, EYES_BOTH, _queue.back().second);
+               _queue.pop_back ();
+       } else {
+               try {
+                       while (!_got_frame && !_player->pass ()) {}
+               } catch (DecodeError& e) {
+                       _play_button->SetValue (false);
+                       check_play_state ();
+                       error_dialog (this, wxString::Format (_("Could not decode video for view (%s)"), std_to_wx(e.what()).data()));
                }
-       } catch (DecodeError& e) {
-               error_dialog (this, String::compose ("Could not decode video for view (%1)", e.what()));
        }
+
+       _panel->Refresh ();
+       _panel->Update ();
 }
 
 void
@@ -376,3 +372,43 @@ FilmViewer::active_jobs_changed (bool a)
        _play_button->Enable (!a);
 }
 
+void
+FilmViewer::back_clicked ()
+{
+       if (!_player) {
+               return;
+       }
+
+       /* Player::video_position is the time after the last frame that we received.
+          We want to see the one before it, so we need to go back 2.
+       */
+
+       Time p = _player->video_position() - _film->video_frames_to_time (2);
+       if (p < 0) {
+               p = 0;
+       }
+       
+       _player->seek (p, true);
+       fetch_next_frame ();
+}
+
+void
+FilmViewer::forward_clicked ()
+{
+       if (!_player) {
+               return;
+       }
+
+       fetch_next_frame ();
+}
+
+void
+FilmViewer::player_changed (bool frequent)
+{
+       if (frequent) {
+               return;
+       }
+       
+       calculate_sizes ();
+       fetch_current_frame_again ();
+}
index 6029c04f32a7c02769f715248ae4a25bce774bb6..7bda9617e9bea0aa92beae1e9d540f0f2f807610 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. fetch_next_frame() asks our _player to decode some data.  If it does, process_video()
+ *     will be called.
+ *
+ *  2. process_video() takes the image from the player (_frame).
+ *
+ *  3. fetch_next_frame() calls _panel->Refresh() and _panel->Update() which results in
+ *     paint_panel() being called; this creates frame_bitmap from _frame and blits it to the display.
+ *
+ * fetch_current_frame_again() asks the player to re-emit its current frame on the next pass(), and then
+ * starts from step #1.
  */
 class FilmViewer : public wxPanel
 {
@@ -42,41 +53,42 @@ public:
        void set_film (boost::shared_ptr<Film>);
 
 private:
-       void film_changed (Film::Property);
-       void paint_panel (wxPaintEvent &);
+       void paint_panel ();
        void panel_sized (wxSizeEvent &);
-       void slider_moved (wxScrollEvent &);
-       void play_clicked (wxCommandEvent &);
-       void timer (wxTimerEvent &);
-       void process_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>);
+       void slider_moved ();
+       void play_clicked ();
+       void timer ();
+       void process_video (boost::shared_ptr<const Image>, Eyes, Time);
        void calculate_sizes ();
        void check_play_state ();
-       void update_from_raw ();
-       void decoder_changed ();
-       void raw_to_display ();
-       void get_frame ();
+       void fetch_current_frame_again ();
+       void fetch_next_frame ();
        void active_jobs_changed (bool);
+       void back_clicked ();
+       void forward_clicked ();
+       void player_changed (bool);
+       void set_position_text (Time);
 
        boost::shared_ptr<Film> _film;
+       boost::shared_ptr<Player> _player;
 
-       wxBoxSizer* _v_sizer;
+       wxSizer* _v_sizer;
        wxPanel* _panel;
        wxSlider* _slider;
+       wxButton* _back_button;
+       wxButton* _forward_button;
+       wxStaticText* _frame_number;
+       wxStaticText* _timecode;
        wxToggleButton* _play_button;
        wxTimer _timer;
 
-       Decoders _decoders;
-       boost::shared_ptr<Image> _raw_frame;
-       boost::shared_ptr<Subtitle> _raw_sub;
-       boost::shared_ptr<Image> _display_frame;
-       boost::shared_ptr<RGBPlusAlphaImage> _display_sub;
-       Position _display_sub_position;
+       boost::shared_ptr<const Image> _frame;
        bool _got_frame;
 
-       int _out_width;
-       int _out_height;
-       int _panel_width;
-       int _panel_height;
+       /** Size of our output (including padding if we have any) */
+       libdcp::Size _out_size;
+       /** Size of the panel that we have available */
+       libdcp::Size _panel_size;
 
-       bool _clear_required;
+       std::list<std::pair<boost::shared_ptr<const Image>, Time> > _queue;
 };
index 2abe53026532aff7a42c54862132067898cdfccc..13907ae0cb9c468b63b88715b29d2f7ece4a6d46 100644 (file)
 
 #include "lib/film.h"
 #include "filter_dialog.h"
-#include "filter_view.h"
+#include "filter_editor.h"
 
 using namespace std;
 using boost::bind;
 
 FilterDialog::FilterDialog (wxWindow* parent, vector<Filter const *> const & f)
        : wxDialog (parent, wxID_ANY, wxString (_("Filters")))
-       , _filters (new FilterView (this, f))
+       , _filters (new FilterEditor (this, f))
 {
        wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
        sizer->Add (_filters, 1, wxEXPAND | wxALL, 6);
index e76f8536bfa3c17c52c6d012bf8ea2535973be2e..d54e6f2e452c6c047dfc2cfb970eb668690142b7 100644 (file)
@@ -25,7 +25,8 @@
 #include <boost/signals2.hpp>
 
 class Film;
-class FilterView;
+class FilterEditor;
+class Filter;
 
 /** @class FilterDialog
  *  @brief A dialog to select FFmpeg filters.
@@ -40,5 +41,5 @@ public:
 private:
        void active_changed ();
        
-       FilterView* _filters;
+       FilterEditor* _filters;
 };
diff --git a/src/wx/filter_editor.cc b/src/wx/filter_editor.cc
new file mode 100644 (file)
index 0000000..4dd1800
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+    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/filter_editor.cc
+ *  @brief A panel to select FFmpeg filters.
+ */
+
+#include <iostream>
+#include <algorithm>
+#include "lib/filter.h"
+#include "filter_editor.h"
+#include "wx_util.h"
+
+using namespace std;
+
+FilterEditor::FilterEditor (wxWindow* parent, vector<Filter const *> const & active)
+       : wxPanel (parent)
+{
+       wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+       SetSizer (sizer);
+       
+       vector<Filter const *> filters = Filter::all ();
+
+       typedef map<string, list<Filter const *> > CategoryMap;
+       CategoryMap categories;
+
+       for (vector<Filter const *>::iterator i = filters.begin(); i != filters.end(); ++i) {
+               CategoryMap::iterator j = categories.find ((*i)->category ());
+               if (j == categories.end ()) {
+                       list<Filter const *> c;
+                       c.push_back (*i);
+                       categories[(*i)->category()] = c;
+               } else {
+                       j->second.push_back (*i);
+               }
+       }
+
+       for (CategoryMap::iterator i = categories.begin(); i != categories.end(); ++i) {
+
+               wxStaticText* c = new wxStaticText (this, wxID_ANY, std_to_wx (i->first));
+               wxFont font = c->GetFont();
+               font.SetWeight(wxFONTWEIGHT_BOLD);
+               c->SetFont(font);
+               sizer->Add (c);
+
+               for (list<Filter const *>::iterator j = i->second.begin(); j != i->second.end(); ++j) {
+                       wxCheckBox* b = new wxCheckBox (this, wxID_ANY, std_to_wx ((*j)->name ()));
+                       bool const a = find (active.begin(), active.end(), *j) != active.end ();
+                       b->SetValue (a);
+                       _filters[*j] = b;
+                       b->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&FilterEditor::filter_toggled, this));
+                       sizer->Add (b);
+               }
+
+               sizer->AddSpacer (6);
+       }
+}
+
+void
+FilterEditor::filter_toggled ()
+{
+       ActiveChanged ();
+}
+
+vector<Filter const*>
+FilterEditor::active () const
+{
+       vector<Filter const *> active;
+       for (map<Filter const *, wxCheckBox*>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
+               if (i->second->IsChecked ()) {
+                       active.push_back (i->first);
+               }
+       }
+
+       return active;
+}
diff --git a/src/wx/filter_editor.h b/src/wx/filter_editor.h
new file mode 100644 (file)
index 0000000..4e1d682
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+    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/filter_editor.h
+ *  @brief A panel to select FFmpeg filters.
+ */
+
+#include <boost/signals2.hpp>
+#include <vector>
+#include <map>
+#include <wx/wx.h>
+
+class Filter;
+
+/** @class FilterEditor
+ *  @brief A panel to select FFmpeg filters.
+ */
+class FilterEditor : public wxPanel
+{
+public:
+       FilterEditor (wxWindow *, std::vector<Filter const *> const &);
+
+       std::vector<Filter const *> active () const;
+
+       boost::signals2::signal<void()> ActiveChanged;
+
+private:
+       void filter_toggled ();
+
+       std::map<Filter const *, wxCheckBox *> _filters;
+};
diff --git a/src/wx/filter_view.cc b/src/wx/filter_view.cc
deleted file mode 100644 (file)
index 8d9535d..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.
-
-*/
-
-/** @file  src/filter_view.cc
- *  @brief A panel to select FFmpeg filters.
- */
-
-#include <iostream>
-#include <algorithm>
-#include "lib/filter.h"
-#include "filter_view.h"
-#include "wx_util.h"
-
-using namespace std;
-
-FilterView::FilterView (wxWindow* parent, vector<Filter const *> const & active)
-       : wxPanel (parent)
-{
-       wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
-       SetSizer (sizer);
-       
-       vector<Filter const *> filters = Filter::all ();
-
-       for (vector<Filter const *>::iterator i = filters.begin(); i != filters.end(); ++i) {
-               wxCheckBox* b = new wxCheckBox (this, wxID_ANY, std_to_wx ((*i)->name ()));
-               bool const a = find (active.begin(), active.end(), *i) != active.end ();
-               b->SetValue (a);
-               _filters[*i] = b;
-               b->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilterView::filter_toggled), 0, this);
-               sizer->Add (b);
-       }
-}
-
-void
-FilterView::filter_toggled (wxCommandEvent &)
-{
-       ActiveChanged ();
-}
-
-vector<Filter const*>
-FilterView::active () const
-{
-       vector<Filter const *> active;
-       for (map<Filter const *, wxCheckBox*>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
-               if (i->second->IsChecked ()) {
-                       active.push_back (i->first);
-               }
-       }
-
-       return active;
-}
diff --git a/src/wx/filter_view.h b/src/wx/filter_view.h
deleted file mode 100644 (file)
index b8d5f64..0000000
+++ /dev/null
@@ -1,47 +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/filter_view.h
- *  @brief A panel to select FFmpeg filters.
- */
-
-#include <boost/signals2.hpp>
-#include <vector>
-#include <map>
-#include <wx/wx.h>
-
-class Filter;
-
-/** @class FilterView
- *  @brief A panel to select FFmpeg filters.
- */
-class FilterView : public wxPanel
-{
-public:
-       FilterView (wxWindow *, std::vector<Filter const *> const &);
-
-       std::vector<Filter const *> active () const;
-
-       boost::signals2::signal<void()> ActiveChanged;
-
-private:
-       void filter_toggled (wxCommandEvent &);
-
-       std::map<Filter const *, wxCheckBox *> _filters;
-};
index 3f07faf062e4239835d692f8466d30c438fb7822..f9880c0440d513c727deea6cf790b3818ceb1f67 100644 (file)
 using namespace boost;
 
 GainCalculatorDialog::GainCalculatorDialog (wxWindow* parent)
-       : wxDialog (parent, wxID_ANY, wxString (_("Gain Calculator")))
+       : wxDialog (parent, wxID_ANY, _("Gain Calculator"))
 {
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        table->AddGrowableCol (1, 1);
 
-       add_label_to_sizer (table, this, "I want to play this back at fader");
+       add_label_to_sizer (table, this, _("I want to play this back at fader"), true);
        _wanted = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, wxTextValidator (wxFILTER_NUMERIC));
        table->Add (_wanted, 1, wxEXPAND);
 
-       add_label_to_sizer (table, this, "But I have to use fader");
+       add_label_to_sizer (table, this, _("But I have to use fader"), true);
        _actual = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, wxTextValidator (wxFILTER_NUMERIC));
        table->Add (_actual, 1, wxEXPAND);
 
        wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
-       overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6);
+       overall_sizer->Add (table, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
 
        wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
        if (buttons) {
index 9c70405846cc1587baf8e3fcfd350a4bfd2267df..a95087b02ee27f139e2ff318fe56cfbba358ca23 100644 (file)
 
 using std::string;
 using std::list;
+using std::map;
+using std::cout;
 using boost::shared_ptr;
+using boost::weak_ptr;
+
+class JobRecord
+{
+public:
+       JobRecord (shared_ptr<Job> job, wxScrolledWindow* window, wxPanel* panel, wxFlexGridSizer* table, bool pause)
+               : _job (job)
+               , _window (window)
+               , _panel (panel)
+               , _table (table)
+       {
+               int n = 0;
+               
+               wxStaticText* m = new wxStaticText (panel, wxID_ANY, std_to_wx (_job->name ()));
+               table->Insert (n, m, 0, wxALIGN_CENTER_VERTICAL | wxALL, 6);
+               ++n;
+       
+               _gauge = new wxGauge (panel, wxID_ANY, 100);
+               /* This seems to be required to allow the gauge to shrink under OS X */
+               _gauge->SetMinSize (wxSize (0, -1));
+               table->Insert (n, _gauge, 1, wxEXPAND | wxLEFT | wxRIGHT);
+               ++n;
+               
+               _message = new wxStaticText (panel, wxID_ANY, std_to_wx (""));
+               table->Insert (n, _message, 1, wxALIGN_CENTER_VERTICAL | wxALL, 6);
+               ++n;
+       
+               _cancel = new wxButton (panel, wxID_ANY, _("Cancel"));
+               _cancel->Bind (wxEVT_COMMAND_BUTTON_CLICKED, &JobRecord::cancel_clicked, this);
+               table->Insert (n, _cancel, 1, wxALIGN_CENTER_VERTICAL | wxALL, 6);
+               ++n;
+       
+               if (pause) {
+                       _pause = new wxButton (_panel, wxID_ANY, _("Pause"));
+                       _pause->Bind (wxEVT_COMMAND_BUTTON_CLICKED, &JobRecord::pause_clicked, this);
+                       table->Insert (n, _pause, 1, wxALIGN_CENTER_VERTICAL | wxALL, 6);
+                       ++n;
+               }
+       
+               _details = new wxButton (_panel, wxID_ANY, _("Details..."));
+               _details->Bind (wxEVT_COMMAND_BUTTON_CLICKED, &JobRecord::details_clicked, this);
+               _details->Enable (false);
+               table->Insert (n, _details, 1, wxALIGN_CENTER_VERTICAL | wxALL, 6);
+               ++n;
+       
+               job->Progress.connect (boost::bind (&JobRecord::progress, this));
+               job->Finished.connect (boost::bind (&JobRecord::finished, this));
+       
+               table->Layout ();
+               panel->FitInside ();
+       }
+
+       void maybe_pulse ()
+       {
+               if (_job->running() && _job->progress_unknown ()) {
+                       _gauge->Pulse ();
+               }
+       }
+
+private:
+
+       void progress ()
+       {
+               float const p = _job->overall_progress ();
+               if (p >= 0) {
+                       checked_set (_message, _job->status ());
+                       _gauge->SetValue (p * 100);
+               }
+
+               _table->Layout ();
+               _window->FitInside ();
+       }
+
+       void finished ()
+       {
+               checked_set (_message, _job->status ());
+               if (!_job->finished_cancelled ()) {
+                       _gauge->SetValue (100);
+               }
+               
+               _cancel->Enable (false);
+               if (!_job->error_details().empty ()) {
+                       _details->Enable (true);
+               }
+               
+               _table->Layout ();
+               _window->FitInside ();
+       }
+
+       void details_clicked (wxCommandEvent &)
+       {
+               string s = _job->error_summary();
+               s[0] = toupper (s[0]);
+               error_dialog (_window, std_to_wx (String::compose ("%1.\n\n%2", s, _job->error_details())));
+       }
+       
+       void cancel_clicked (wxCommandEvent &)
+       {
+               _job->cancel ();
+       }
+
+       void pause_clicked (wxCommandEvent &)
+       {
+               if (_job->paused()) {
+                       _job->resume ();
+                       _pause->SetLabel (_("Pause"));
+               } else {
+                       _job->pause ();
+                       _pause->SetLabel (_("Resume"));
+               }
+       }
+       
+       boost::shared_ptr<Job> _job;
+       wxScrolledWindow* _window;
+       wxPanel* _panel;
+       wxFlexGridSizer* _table;
+       wxGauge* _gauge;
+       wxStaticText* _message;
+       wxButton* _cancel;
+       wxButton* _pause;
+       wxButton* _details;
+};
 
 /** Must be called in the GUI thread */
-JobManagerView::JobManagerView (wxWindow* parent)
+JobManagerView::JobManagerView (wxWindow* parent, Buttons buttons)
        : wxScrolledWindow (parent)
+       , _buttons (buttons)
 {
        _panel = new wxPanel (this);
        wxSizer* sizer = new wxBoxSizer (wxVERTICAL);
        sizer->Add (_panel, 1, wxEXPAND);
        SetSizer (sizer);
+
+       int N = 5;
+       if (buttons & PAUSE) {
+               ++N;
+       }
        
-       _table = new wxFlexGridSizer (3, 6, 6);
+       _table = new wxFlexGridSizer (N, 6, 6);
        _table->AddGrowableCol (1, 1);
        _panel->SetSizer (_table);
 
        SetScrollRate (0, 32);
 
-       Connect (wxID_ANY, wxEVT_TIMER, wxTimerEventHandler (JobManagerView::periodic), 0, this);
+       Bind (wxEVT_TIMER, boost::bind (&JobManagerView::periodic, this));
        _timer.reset (new wxTimer (this));
        _timer->Start (1000);
-
-       update ();
+       
+       JobManager::instance()->JobAdded.connect (bind (&JobManagerView::job_added, this, _1));
 }
 
 void
-JobManagerView::periodic (wxTimerEvent &)
+JobManagerView::job_added (weak_ptr<Job> j)
 {
-       update ();
+       shared_ptr<Job> job = j.lock ();
+       if (job) {
+               _job_records.push_back (shared_ptr<JobRecord> (new JobRecord (job, this, _panel, _table, _buttons & PAUSE)));
+       }
 }
 
-/** Update the view by examining the state of each job.
- *  Must be called in the GUI thread.
- */
 void
-JobManagerView::update ()
+JobManagerView::periodic ()
 {
-       list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
-
-       int index = 0;
-
-       for (list<shared_ptr<Job> >::iterator i = jobs.begin(); i != jobs.end(); ++i) {
-               
-               if (_job_records.find (*i) == _job_records.end ()) {
-                       wxStaticText* m = new wxStaticText (_panel, wxID_ANY, std_to_wx ((*i)->name ()));
-                       _table->Insert (index, m, 0, wxALIGN_CENTER_VERTICAL | wxALL, 6);
-                       
-                       JobRecord r;
-                       r.finalised = false;
-                       r.gauge = new wxGauge (_panel, wxID_ANY, 100);
-                       _table->Insert (index + 1, r.gauge, 1, wxEXPAND | wxLEFT | wxRIGHT);
-                       
-                       r.message = new wxStaticText (_panel, wxID_ANY, std_to_wx (""));
-                       _table->Insert (index + 2, r.message, 1, wxALIGN_CENTER_VERTICAL | wxALL, 6);
-                       
-                       _job_records[*i] = r;
-               }
-
-               string const st = (*i)->status ();
-
-               if (!(*i)->finished ()) {
-                       float const p = (*i)->overall_progress ();
-                       if (p >= 0) {
-                               _job_records[*i].message->SetLabel (std_to_wx (st));
-                               _job_records[*i].gauge->SetValue (p * 100);
-                       } else {
-                               _job_records[*i].message->SetLabel (wxT ("Running"));
-                               _job_records[*i].gauge->Pulse ();
-                       }
-               }
-               
-               if ((*i)->finished() && !_job_records[*i].finalised) {
-                       _job_records[*i].gauge->SetValue (100);
-                       _job_records[*i].message->SetLabel (std_to_wx (st));
-                       _job_records[*i].finalised = true;
-               }
-
-               index += 3;
+       for (list<shared_ptr<JobRecord> >::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
+               (*i)->maybe_pulse ();
        }
-
-       _table->Layout ();
-       FitInside ();
 }
index 5c10890ef01391966c9131af3de312e25eb9bceb..c4bb1e2189b6e32850a0694c52e65121c957a316 100644 (file)
@@ -26,6 +26,7 @@
 #include <wx/wx.h>
 
 class Job;
+class JobRecord;
 
 /** @class JobManagerView
  *  @brief Class which is a wxPanel for showing the progress of jobs.
@@ -33,21 +34,20 @@ class Job;
 class JobManagerView : public wxScrolledWindow
 {
 public:
-       JobManagerView (wxWindow *);
-
-       void update ();
+       enum Buttons {
+               PAUSE = 0x1,
+       };
+               
+       JobManagerView (wxWindow *, Buttons);
 
 private:
-       void periodic (wxTimerEvent &);
+       void job_added (boost::weak_ptr<Job>);
+       void periodic ();
 
-       boost::shared_ptr<wxTimer> _timer;
        wxPanel* _panel;
        wxFlexGridSizer* _table;
-       struct JobRecord {
-               wxGauge* gauge;
-               wxStaticText* message;
-               bool finalised;
-       };
+       boost::shared_ptr<wxTimer> _timer;
                
-       std::map<boost::shared_ptr<Job>, JobRecord> _job_records;
+       std::list<boost::shared_ptr<JobRecord> > _job_records;
+       Buttons _buttons;
 };
index f2056cf498abacd429c92464d9176082e3e7c5c0..df4aa7d2e78a740c786caffb1250a798c065a864 100644 (file)
 using boost::shared_ptr;
 
 void
-JobWrapper::make_dcp (wxWindow* parent, shared_ptr<Film> film, bool transcode)
+JobWrapper::make_dcp (wxWindow* parent, shared_ptr<Film> film)
 {
        if (!film) {
                return;
        }
 
        try {
-               film->make_dcp (transcode);
+               film->make_dcp ();
        } catch (BadSettingError& e) {
-               error_dialog (parent, String::compose ("Bad setting for %1 (%2)", e.setting(), e.what ()));
+               error_dialog (parent, wxString::Format (_("Bad setting for %s (%s)"), std_to_wx(e.setting()).data(), std_to_wx(e.what()).data()));
        } catch (std::exception& e) {
-               error_dialog (parent, String::compose ("Could not make DCP: %1", e.what ()));
+               error_dialog (parent, wxString::Format (_("Could not make DCP: %s"), std_to_wx(e.what()).data()));
        }
 }
index 7120e9f106ec0acc8acb0ad397b1e19d7baf514a..b0a4693dd07359d77777425f2b9823a601885e37 100644 (file)
@@ -24,6 +24,6 @@ class Film;
 namespace JobWrapper
 {
 
-void make_dcp (wxWindow *, boost::shared_ptr<Film>, bool);
+void make_dcp (wxWindow *, boost::shared_ptr<Film>);
        
 }
index d94c130575ab270a478d54971640f01bf328d37b..a9f63cffce971e06dd65a8d1c9d6d2f0cbab2a29 100644 (file)
@@ -43,9 +43,6 @@ KDMDialog::KDMDialog (wxWindow* parent)
        : wxDialog (parent, wxID_ANY, _("Make KDMs"))
 {
        wxBoxSizer* vertical = new wxBoxSizer (wxVERTICAL);
-
-       add_label_to_sizer (vertical, this, "Make KDMs for");
-
        wxBoxSizer* targets = new wxBoxSizer (wxHORIZONTAL);
        
        _targets = new wxTreeCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_HIDE_ROOT | wxTR_MULTIPLE | wxTR_HAS_BUTTONS);
@@ -63,37 +60,37 @@ KDMDialog::KDMDialog (wxWindow* parent)
        wxBoxSizer* target_buttons = new wxBoxSizer (wxVERTICAL);
 
        _add_cinema = new wxButton (this, wxID_ANY, _("Add Cinema..."));
-       target_buttons->Add (_add_cinema, 1, 0, 6);
+       target_buttons->Add (_add_cinema, 1, wxEXPAND, 6);
        _edit_cinema = new wxButton (this, wxID_ANY, _("Edit Cinema..."));
-       target_buttons->Add (_edit_cinema, 1, 0, 6);
+       target_buttons->Add (_edit_cinema, 1, wxEXPAND, 6);
        _remove_cinema = new wxButton (this, wxID_ANY, _("Remove Cinema"));
-       target_buttons->Add (_remove_cinema, 1, 0, 6);
+       target_buttons->Add (_remove_cinema, 1, wxEXPAND, 6);
        
        _add_screen = new wxButton (this, wxID_ANY, _("Add Screen..."));
-       target_buttons->Add (_add_screen, 1, 0, 6);
+       target_buttons->Add (_add_screen, 1, wxEXPAND, 6);
        _edit_screen = new wxButton (this, wxID_ANY, _("Edit Screen..."));
-       target_buttons->Add (_edit_screen, 1, 0, 6);
+       target_buttons->Add (_edit_screen, 1, wxEXPAND, 6);
        _remove_screen = new wxButton (this, wxID_ANY, _("Remove Screen"));
-       target_buttons->Add (_remove_screen, 1, 0, 6);
+       target_buttons->Add (_remove_screen, 1, wxEXPAND, 6);
 
        targets->Add (target_buttons, 0, 0, 6);
 
        vertical->Add (targets, 1, wxEXPAND | wxALL, 6);
 
        wxFlexGridSizer* table = new wxFlexGridSizer (3, 2, 6);
-       add_label_to_sizer (table, this, "From");
+       add_label_to_sizer (table, this, "From", true);
        _from_date = new wxDatePickerCtrl (this, wxID_ANY);
        table->Add (_from_date, 1, wxEXPAND);
        _from_time = new wxTimePickerCtrl (this, wxID_ANY);
        table->Add (_from_time, 1, wxEXPAND);
        
-       add_label_to_sizer (table, this, "Until");
+       add_label_to_sizer (table, this, "Until", true);
        _until_date = new wxDatePickerCtrl (this, wxID_ANY);
        table->Add (_until_date, 1, wxEXPAND);
        _until_time = new wxTimePickerCtrl (this, wxID_ANY);
        table->Add (_until_time, 1, wxEXPAND);
 
-       add_label_to_sizer (table, this, "Write to");
+       add_label_to_sizer (table, this, "Write to", true);
 
 #ifdef __WXMSW__
        _folder = new DirPickerCtrl (this);
@@ -105,7 +102,7 @@ KDMDialog::KDMDialog (wxWindow* parent)
        
        vertical->Add (table, 0, wxEXPAND | wxALL, 6);
 
-       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL);
        if (buttons) {
                vertical->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
        }
index eb6f2849b5cdd5ebc910b794bbff3ddcbb5bb6b9..6a8935232234add7736bc7dfc263d5da060465a2 100644 (file)
 #include <wx/stdpaths.h>
 #include "lib/config.h"
 #include "new_film_dialog.h"
-#ifdef __WXMSW__
+#include "wx_util.h"
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
 #include "dir_picker_ctrl.h"
 #endif
-#include "wx_util.h"
 
 using namespace std;
 using namespace boost;
 
+boost::optional<string> NewFilmDialog::_directory;
+
 NewFilmDialog::NewFilmDialog (wxWindow* parent)
-       : wxDialog (parent, wxID_ANY, wxString (_("New Film")))
+       : wxDialog (parent, wxID_ANY, _("New Film"))
 {
        wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
        SetSizer (overall_sizer);
        
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
+       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);
+       overall_sizer->Add (table, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
 
-       add_label_to_sizer (table, this, "Film name");
+       add_label_to_sizer (table, this, _("Film name"), true);
        _name = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_name, 1, wxEXPAND);
+       table->Add (_name, 0, wxEXPAND);
 
-       add_label_to_sizer (table, this, "Create in folder");
-#ifdef __WXMSW__
-       _folder = new DirPickerCtrl (this);
+       add_label_to_sizer (table, this, _("Create in folder"), true);
+
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
+       _folder = new DirPickerCtrl (this); 
 #else  
-       _folder = new wxDirPickerCtrl (this, wxDD_DIR_MUST_EXIST);
+       _folder = new wxDirPickerCtrl (this, wxID_ANY);
 #endif
-       _folder->SetPath (std_to_wx (Config::instance()->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir()))));
+
+       if (!_directory) {
+               _directory = Config::instance()->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir()));
+       }
+       
+       _folder->SetPath (std_to_wx (_directory.get()));
        table->Add (_folder, 1, wxEXPAND);
 
        wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL);
@@ -61,6 +69,11 @@ NewFilmDialog::NewFilmDialog (wxWindow* parent)
        overall_sizer->SetSizeHints (this);
 }
 
+NewFilmDialog::~NewFilmDialog ()
+{
+       _directory = wx_to_std (_folder->GetPath ());
+}
+
 string
 NewFilmDialog::get_path () const
 {
index 3d1253ecc7d3cbae849afc61ca20ad3195aca67a..f8f3aa08dd1ca5f360afaa495299afd1d8470346 100644 (file)
@@ -19,6 +19,7 @@
 
 #include <wx/wx.h>
 #include <wx/filepicker.h>
+#include "wx_util.h"
 
 class DirPickerCtrl;
 
@@ -26,14 +27,16 @@ class NewFilmDialog : public wxDialog
 {
 public:
        NewFilmDialog (wxWindow *);
+       ~NewFilmDialog ();
 
        std::string get_path () const;
 
 private:
        wxTextCtrl* _name;
-#ifdef __WXMSW__       
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
        DirPickerCtrl* _folder;
-#else
+#else  
        wxDirPickerCtrl* _folder;
 #endif 
+       static boost::optional<std::string> _directory;
 };
diff --git a/src/wx/po/es_ES.po b/src/wx/po/es_ES.po
new file mode 100644 (file)
index 0000000..08a1fde
--- /dev/null
@@ -0,0 +1,673 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: libdcpomatic-wx\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-04-02 19:08-0500\n"
+"Last-Translator: Manuel AC <manuel.acevedo@civantos.>\n"
+"Language-Team: Manuel AC <manuel.acevedo@civantos.com>\n"
+"Language: es-ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.5\n"
+
+#: src/wx/film_editor.cc:426 src/wx/film_editor.cc:435
+msgid "%"
+msgstr "%"
+
+#: src/wx/about_dialog.cc:77
+msgid ""
+"(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen"
+msgstr ""
+
+#: src/wx/config_dialog.cc:96
+msgid "(restart DCP-o-matic to see language changes)"
+msgstr ""
+
+#: src/wx/film_editor.cc:1423
+msgid "1 channel"
+msgstr "1 canal"
+
+#: src/wx/about_dialog.cc:30
+#, fuzzy
+msgid "About DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/wx/config_dialog.cc:299
+msgid "Add"
+msgstr "Añadir"
+
+#: src/wx/film_editor.cc:317
+msgid "Add..."
+msgstr ""
+
+#: src/wx/audio_dialog.cc:32 src/wx/film_editor.cc:343
+msgid "Audio"
+msgstr "Audio"
+
+#: src/wx/film_editor.cc:379
+msgid "Audio Delay"
+msgstr "Retardo del audio"
+
+#: src/wx/film_editor.cc:367
+msgid "Audio Gain"
+msgstr "Ganancia del audio"
+
+#: src/wx/dci_metadata_dialog.cc:33
+msgid "Audio Language (e.g. EN)"
+msgstr "Idioma del audio (ej. ES)"
+
+#: src/wx/film_editor.cc:391
+#, fuzzy
+msgid "Audio Stream"
+msgstr "Retardo del audio"
+
+#: src/wx/job_wrapper.cc:38
+#, c-format
+msgid "Bad setting for %s (%s)"
+msgstr "Configuración erronea para %s (%s)"
+
+#: src/wx/film_editor.cc:264
+msgid "Bottom crop"
+msgstr "Recortar abajo"
+
+#: src/wx/dir_picker_ctrl.cc:38
+msgid "Browse..."
+msgstr "Explorar..."
+
+#: src/wx/gain_calculator_dialog.cc:36
+msgid "But I have to use fader"
+msgstr "pero tengo que usar el fader a"
+
+#: src/wx/audio_mapping_view.cc:192
+msgid "C"
+msgstr ""
+
+#: src/wx/film_editor.cc:376
+msgid "Calculate..."
+msgstr "Calcular..."
+
+#: src/wx/job_manager_view.cc:98
+msgid "Cancel"
+msgstr ""
+
+#: src/wx/audio_dialog.cc:43
+msgid "Channels"
+msgstr "Canales"
+
+#: src/wx/film_editor.cc:1163
+msgid "Choose a file or files"
+msgstr ""
+
+#: src/wx/film_editor.cc:131
+#, fuzzy
+msgid "Container"
+msgstr "Contenido"
+
+#: src/wx/film_editor.cc:82
+msgid "Content"
+msgstr "Contenido"
+
+#: src/wx/film_editor.cc:136
+msgid "Content Type"
+msgstr "Tipo de contenido"
+
+#: src/wx/audio_mapping_view.cc:181
+#, fuzzy
+msgid "Content channel"
+msgstr "1 canal"
+
+#: src/wx/film_viewer.cc:326
+#, c-format
+msgid "Could not decode video for view (%s)"
+msgstr "No se pudo decodificar el vídeo para mostrarlo (%s)"
+
+#: src/wx/job_wrapper.cc:40
+#, c-format
+msgid "Could not make DCP: %s"
+msgstr "No se pudo crear el DCP: %s"
+
+#: src/wx/new_film_dialog.cc:48
+msgid "Create in folder"
+msgstr "Crear en carpeta"
+
+#: src/wx/config_dialog.cc:260
+#, fuzzy
+msgid "Creator"
+msgstr "Crear en carpeta"
+
+#: src/wx/film_editor.cc:1322
+#, c-format
+msgid "Cropped to %dx%d (%.2f:1)\n"
+msgstr ""
+
+#: src/wx/dci_metadata_dialog.cc:28
+msgid "DCI name"
+msgstr "Nombre DCI"
+
+#: src/wx/film_editor.cc:84
+msgid "DCP"
+msgstr ""
+
+#: src/wx/film_editor.cc:142
+msgid "DCP Frame Rate"
+msgstr "Velocidad DCP"
+
+#: src/wx/film_editor.cc:115
+msgid "DCP Name"
+msgstr "Nombre DCP"
+
+#: src/wx/film_editor.cc:152
+#, fuzzy
+msgid "DCP audio channels"
+msgstr "canales"
+
+#: src/wx/about_dialog.cc:44 src/wx/wx_util.cc:87 src/wx/wx_util.cc:95
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/wx/config_dialog.cc:46
+msgid "DCP-o-matic Preferences"
+msgstr "Preferencias DCP-o-matic"
+
+#: src/wx/audio_dialog.cc:97
+#, fuzzy, c-format
+msgid "DCP-o-matic audio - %s"
+msgstr "Audio DCP-o-matic - %1"
+
+#: src/wx/config_dialog.cc:123
+msgid "Default DCI name details"
+msgstr "Detalles por defecto del nombre DCI"
+
+#: src/wx/config_dialog.cc:138
+#, fuzzy
+msgid "Default JPEG2000 bandwidth"
+msgstr "Ancho de banda JPEG2000"
+
+#: src/wx/config_dialog.cc:128
+#, fuzzy
+msgid "Default container"
+msgstr "Tipo de contenido"
+
+#: src/wx/config_dialog.cc:133
+#, fuzzy
+msgid "Default content type"
+msgstr "Tipo de contenido"
+
+#: src/wx/config_dialog.cc:114
+msgid "Default directory for new films"
+msgstr "Carpeta por defecto para nuevas películas"
+
+#: src/wx/config_dialog.cc:109
+#, fuzzy
+msgid "Default duration of still images"
+msgstr "Carpeta por defecto para nuevas películas"
+
+#: src/wx/film_editor.cc:127 src/wx/job_manager_view.cc:110
+msgid "Details..."
+msgstr "Detalles..."
+
+#: src/wx/properties_dialog.cc:45
+msgid "Disk space required"
+msgstr "Espacio requerido en disco"
+
+#: src/wx/imagemagick_content_dialog.cc:36
+msgid "Duration"
+msgstr "Duración"
+
+#: src/wx/config_dialog.cc:301
+msgid "Edit"
+msgstr "Editar"
+
+#: src/wx/config_dialog.cc:124 src/wx/film_editor.cc:288
+msgid "Edit..."
+msgstr "Editar..."
+
+#: src/wx/config_dialog.cc:55
+#, fuzzy
+msgid "Encoding servers"
+msgstr "Servidores de codificación"
+
+#: src/wx/dci_metadata_dialog.cc:53
+msgid "Facility (e.g. DLA)"
+msgstr "Compañía (ej. DLA)"
+
+#: src/wx/properties_dialog.cc:36
+msgid "Film Properties"
+msgstr "Propiedades de la película"
+
+#: src/wx/new_film_dialog.cc:44
+msgid "Film name"
+msgstr "Nombre de la película"
+
+#: src/wx/film_editor.cc:284 src/wx/filter_dialog.cc:32
+msgid "Filters"
+msgstr "Filtros"
+
+#: src/wx/properties_dialog.cc:41
+msgid "Frames"
+msgstr "Fotogramas"
+
+#: src/wx/properties_dialog.cc:49
+msgid "Frames already encoded"
+msgstr "Fotogramas ya codificados"
+
+#: src/wx/about_dialog.cc:60
+msgid "Free, open-source DCP generation from almost anything."
+msgstr ""
+
+#: src/wx/gain_calculator_dialog.cc:27
+msgid "Gain Calculator"
+msgstr "Calculadora de ganancia"
+
+#: src/wx/properties_dialog.cc:56
+msgid "Gb"
+msgstr "Gb"
+
+#: src/wx/server_dialog.cc:36
+msgid "Host name or IP address"
+msgstr "Nombre o dirección IP"
+
+#: src/wx/film_editor.cc:1427
+msgid "Hz"
+msgstr "Hz"
+
+#: src/wx/gain_calculator_dialog.cc:32
+msgid "I want to play this back at fader"
+msgstr "Quiero reproducir con el fader a"
+
+#: src/wx/config_dialog.cc:217 src/wx/config_dialog.cc:288
+msgid "IP address"
+msgstr "Dirección IP"
+
+#: src/wx/imagemagick_content_dialog.cc:29
+msgid "Image"
+msgstr ""
+
+#: src/wx/config_dialog.cc:256
+msgid "Issuer"
+msgstr ""
+
+#: src/wx/film_editor.cc:158
+msgid "JPEG2000 bandwidth"
+msgstr "Ancho de banda JPEG2000"
+
+#: src/wx/audio_mapping_view.cc:184
+msgid "L"
+msgstr ""
+
+#: src/wx/film_editor.cc:249
+msgid "Left crop"
+msgstr "Recorte izquierda"
+
+#: src/wx/film_editor.cc:460
+msgid "Length"
+msgstr "Longitud"
+
+#: src/wx/audio_mapping_view.cc:196
+msgid "Lfe"
+msgstr ""
+
+#: src/wx/film_editor.cc:330
+msgid "Loop everything"
+msgstr ""
+
+#: src/wx/audio_mapping_view.cc:200
+#, fuzzy
+msgid "Ls"
+msgstr "s"
+
+#: src/wx/config_dialog.cc:141 src/wx/film_editor.cc:162
+msgid "MBps"
+msgstr "MBps"
+
+#: src/wx/config_dialog.cc:57
+msgid "Metadata"
+msgstr ""
+
+#: src/wx/config_dialog.cc:53
+msgid "Miscellaneous"
+msgstr ""
+
+#: src/wx/dir_picker_ctrl.cc:52
+msgid "My Documents"
+msgstr "Mis documentos"
+
+#: src/wx/film_editor.cc:110
+msgid "Name"
+msgstr "Nombre"
+
+#: src/wx/new_film_dialog.cc:35
+msgid "New Film"
+msgstr "Nueva película"
+
+#: src/wx/film_editor.cc:286 src/wx/film_editor.cc:769
+msgid "None"
+msgstr "Ninguno"
+
+#: src/wx/film_editor.cc:1309
+#, c-format
+msgid "Original video is %dx%d (%.2f:1)\n"
+msgstr ""
+
+#: src/wx/dci_metadata_dialog.cc:57
+msgid "Package Type (e.g. OV)"
+msgstr "Tipo de paquete (ej. OV)"
+
+#: src/wx/film_editor.cc:1343
+#, c-format
+msgid "Padded with black to %dx%d (%.2f:1)\n"
+msgstr ""
+
+#: src/wx/config_dialog.cc:229
+#, fuzzy
+msgid "Password"
+msgstr "Clave del TMS"
+
+#: src/wx/job_manager_view.cc:104 src/wx/job_manager_view.cc:206
+msgid "Pause"
+msgstr ""
+
+#: src/wx/audio_dialog.cc:59
+msgid "Peak"
+msgstr "Pico"
+
+#: src/wx/film_viewer.cc:64
+msgid "Play"
+msgstr "Reproducir"
+
+#: src/wx/audio_plot.cc:110
+msgid "Please wait; audio is being analysed..."
+msgstr "Por favor espere, el audio está siendo analizado..."
+
+#: src/wx/audio_mapping_view.cc:188
+msgid "R"
+msgstr ""
+
+#: src/wx/audio_dialog.cc:60
+msgid "RMS"
+msgstr "RMS"
+
+#: src/wx/dci_metadata_dialog.cc:45
+msgid "Rating (e.g. 15)"
+msgstr "Clasificación (ej. 16)"
+
+#: src/wx/config_dialog.cc:303 src/wx/film_editor.cc:319
+msgid "Remove"
+msgstr "Quitar"
+
+#: src/wx/job_manager_view.cc:209
+msgid "Resume"
+msgstr ""
+
+#: src/wx/film_editor.cc:254
+msgid "Right crop"
+msgstr "Recorte derecha"
+
+#: src/wx/audio_mapping_view.cc:204
+#, fuzzy
+msgid "Rs"
+msgstr "s"
+
+#: src/wx/job_manager_view.cc:128
+msgid "Running"
+msgstr "Ejecutando"
+
+#: src/wx/film_editor.cc:269
+#, fuzzy
+msgid "Scale to"
+msgstr "Escalador"
+
+#: src/wx/film_editor.cc:1335
+#, c-format
+msgid "Scaled to %dx%d (%.2f:1)\n"
+msgstr ""
+
+#: src/wx/film_editor.cc:167
+msgid "Scaler"
+msgstr "Escalador"
+
+#: src/wx/server_dialog.cc:25
+msgid "Server"
+msgstr "Servidor"
+
+#: src/wx/config_dialog.cc:85
+msgid "Set language"
+msgstr ""
+
+#: src/wx/film_editor.cc:362
+msgid "Show Audio..."
+msgstr "Mostrar audio..."
+
+#: src/wx/audio_dialog.cc:70
+msgid "Smoothing"
+msgstr "Suavizado"
+
+#: src/wx/film_editor.cc:457
+#, fuzzy
+msgid "Start time"
+msgstr "Inicio"
+
+#: src/wx/dci_metadata_dialog.cc:49
+msgid "Studio (e.g. TCF)"
+msgstr "Estudio (ej. TCF)"
+
+#: src/wx/dci_metadata_dialog.cc:37
+msgid "Subtitle Language (e.g. FR)"
+msgstr "Idioma del subtítulo (ej. EN)"
+
+#: src/wx/film_editor.cc:422
+msgid "Subtitle Offset"
+msgstr "Desplazamiento del subtítulo"
+
+#: src/wx/film_editor.cc:431
+msgid "Subtitle Scale"
+msgstr "Escala del subtítulo"
+
+#: src/wx/film_editor.cc:439
+#, fuzzy
+msgid "Subtitle Stream"
+msgstr "Escala del subtítulo"
+
+#: src/wx/film_editor.cc:345
+msgid "Subtitles"
+msgstr "Subtítulos"
+
+#: src/wx/about_dialog.cc:120
+msgid "Supported by"
+msgstr ""
+
+#: src/wx/config_dialog.cc:59
+#, fuzzy
+msgid "TMS"
+msgstr "RMS"
+
+#: src/wx/config_dialog.cc:221
+#, fuzzy
+msgid "Target path"
+msgstr "Ruta en el TMS"
+
+#: src/wx/dci_metadata_dialog.cc:41
+msgid "Territory (e.g. UK)"
+msgstr "Territorio (ej. ES)"
+
+#: src/wx/config_dialog.cc:292
+msgid "Threads"
+msgstr "Hilos"
+
+#: src/wx/server_dialog.cc:40
+msgid "Threads to use"
+msgstr "Hilos a utilizar"
+
+#: src/wx/config_dialog.cc:104
+msgid "Threads to use for encoding on this host"
+msgstr "Hilos a utilizar para la codificación en esta máquina"
+
+#: src/wx/audio_plot.cc:140
+msgid "Time"
+msgstr "Tiempo"
+
+#: src/wx/timeline_dialog.cc:32
+#, fuzzy
+msgid "Timeline"
+msgstr "Tiempo"
+
+#: src/wx/film_editor.cc:321
+msgid "Timeline..."
+msgstr ""
+
+#: src/wx/film_editor.cc:347
+msgid "Timing"
+msgstr ""
+
+#: src/wx/film_editor.cc:259
+msgid "Top crop"
+msgstr "Recortar arriba"
+
+#: src/wx/about_dialog.cc:99
+msgid "Translated by"
+msgstr ""
+
+#: src/wx/audio_dialog.cc:54
+msgid "Type"
+msgstr "Tipo"
+
+#: src/wx/film_editor.cc:125
+msgid "Use DCI name"
+msgstr "Usar el nombre DCI"
+
+#: src/wx/film_editor.cc:146
+msgid "Use best"
+msgstr "Usar la mejor"
+
+#: src/wx/config_dialog.cc:225
+#, fuzzy
+msgid "User name"
+msgstr "Usar el nombre DCI"
+
+#: src/wx/film_editor.cc:341
+msgid "Video"
+msgstr "Vídeo"
+
+#: src/wx/film_editor.cc:417
+msgid "With Subtitles"
+msgstr "Con subtítulos"
+
+#: src/wx/about_dialog.cc:90
+msgid "Written by"
+msgstr ""
+
+#: src/wx/timeline.cc:200
+#, fuzzy
+msgid "audio"
+msgstr "Audio"
+
+#: src/wx/film_editor.cc:1425
+msgid "channels"
+msgstr "canales"
+
+#: src/wx/properties_dialog.cc:50
+msgid "counting..."
+msgstr "contando..."
+
+#: src/wx/film_editor.cc:372
+msgid "dB"
+msgstr "dB"
+
+#. / TRANSLATORS: this is an abbreviation for milliseconds, the unit of time
+#: src/wx/film_editor.cc:385
+msgid "ms"
+msgstr "ms"
+
+#. / TRANSLATORS: this is an abbreviation for seconds, the unit of time
+#: src/wx/config_dialog.cc:112 src/wx/imagemagick_content_dialog.cc:41
+msgid "s"
+msgstr "s"
+
+#: src/wx/film_editor.cc:334
+msgid "times"
+msgstr ""
+
+#: src/wx/timeline.cc:220
+#, fuzzy
+msgid "video"
+msgstr "Vídeo"
+
+#~ msgid "A/B"
+#~ msgstr "A/B"
+
+#~ msgid "Colour look-up table"
+#~ msgstr "Tabla de referencia de colores"
+
+#~ msgid "Could not open content file (%s)"
+#~ msgstr "No se pudo abrir el fichero (%s)"
+
+#~ msgid "Could not set content: %s"
+#~ msgstr "No se pudo establecer el contenido: %s"
+
+#, fuzzy
+#~ msgid "DVD-o-matic Preferences"
+#~ msgstr "Preferencias DVD-o-matic"
+
+#~ msgid "End"
+#~ msgstr "Fin"
+
+#~ msgid "Film"
+#~ msgstr "Película"
+
+#~ msgid "Format"
+#~ msgstr "Formato"
+
+#~ msgid "Original Frame Rate"
+#~ msgstr "Velocidad original"
+
+#, fuzzy
+#~ msgid "Reference filters"
+#~ msgstr "Filtros de referencia para A/B"
+
+#, fuzzy
+#~ msgid "Reference scaler"
+#~ msgstr "Escalador de referencia para A/B"
+
+#~ msgid "Select Audio File"
+#~ msgstr "Seleccionar fichero de audio"
+
+#~ msgid "Select Content File"
+#~ msgstr "Seleccionar fichero de contenido"
+
+#~ msgid "Trim frames"
+#~ msgstr "Recortar fotogramas"
+
+#, fuzzy
+#~ msgid "Trim method"
+#~ msgstr "Recortar fotogramas"
+
+#~ msgid "Trust content's header"
+#~ msgstr "Confiar en la cabecera del contenido"
+
+#~ msgid "Use content's audio"
+#~ msgstr "Usar el audio del contenido"
+
+#~ msgid "Use external audio"
+#~ msgstr "Usar audio externo"
+
+#~ msgid "frames"
+#~ msgstr "fotogramas"
+
+#~ msgid "unknown"
+#~ msgstr "desconocido"
+
+#~ msgid "TMS IP address"
+#~ msgstr "Dirección IP del TMS"
+
+#~ msgid "TMS user name"
+#~ msgstr "Usuario del TMS"
+
+#~ msgid "Original Size"
+#~ msgstr "Tamaño original"
diff --git a/src/wx/po/fr_FR.po b/src/wx/po/fr_FR.po
new file mode 100644 (file)
index 0000000..1c0a02d
--- /dev/null
@@ -0,0 +1,676 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: DCP-o-matic FRENCH\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-07-16 23:21+0100\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/wx/film_editor.cc:426
+#: src/wx/film_editor.cc:435
+msgid "%"
+msgstr "%"
+
+#: src/wx/about_dialog.cc:77
+msgid "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen"
+msgstr "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen"
+
+#: src/wx/config_dialog.cc:96
+msgid "(restart DCP-o-matic to see language changes)"
+msgstr "(redémarrez DCP-o-matic pour que les changements de langue prennent effet)"
+
+#: src/wx/film_editor.cc:1423
+msgid "1 channel"
+msgstr "1 canal"
+
+#: src/wx/about_dialog.cc:30
+msgid "About DCP-o-matic"
+msgstr "À propos de DCP-o-matic"
+
+#: src/wx/config_dialog.cc:299
+msgid "Add"
+msgstr "Ajouter"
+
+#: src/wx/film_editor.cc:317
+msgid "Add..."
+msgstr "Ajouter..."
+
+#: src/wx/audio_dialog.cc:32
+#: src/wx/film_editor.cc:343
+msgid "Audio"
+msgstr "Audio"
+
+#: src/wx/film_editor.cc:379
+msgid "Audio Delay"
+msgstr "Délai audio"
+
+#: src/wx/film_editor.cc:367
+msgid "Audio Gain"
+msgstr "Gain audio"
+
+#: src/wx/dci_metadata_dialog.cc:33
+msgid "Audio Language (e.g. EN)"
+msgstr "Langue audio (ex. FR)"
+
+#: src/wx/film_editor.cc:391
+msgid "Audio Stream"
+msgstr "Flux audio"
+
+#: src/wx/job_wrapper.cc:38
+#, c-format
+msgid "Bad setting for %s (%s)"
+msgstr "Mauvais paramètre pour %s (%s)"
+
+#: src/wx/film_editor.cc:264
+msgid "Bottom crop"
+msgstr "Découpe bas"
+
+#: src/wx/dir_picker_ctrl.cc:38
+msgid "Browse..."
+msgstr "Parcourir..."
+
+#: src/wx/gain_calculator_dialog.cc:36
+msgid "But I have to use fader"
+msgstr "Je souhaite utiliser ce volume"
+
+#: src/wx/audio_mapping_view.cc:192
+msgid "C"
+msgstr "C"
+
+#: src/wx/film_editor.cc:376
+msgid "Calculate..."
+msgstr "Calcul..."
+
+#: src/wx/job_manager_view.cc:98
+msgid "Cancel"
+msgstr "Annuler"
+
+#: src/wx/audio_dialog.cc:43
+msgid "Channels"
+msgstr "Canaux"
+
+#: src/wx/film_editor.cc:1163
+msgid "Choose a file or files"
+msgstr "Choisissez un ou plusieurs fichiers"
+
+#: src/wx/film_editor.cc:131
+msgid "Container"
+msgstr "Contenu"
+
+#: src/wx/film_editor.cc:82
+msgid "Content"
+msgstr "Contenu"
+
+#: src/wx/film_editor.cc:136
+msgid "Content Type"
+msgstr "Type de Contenu"
+
+#: src/wx/audio_mapping_view.cc:181
+msgid "Content channel"
+msgstr "Contenu audio"
+
+#: src/wx/film_viewer.cc:326
+#, c-format
+msgid "Could not decode video for view (%s)"
+msgstr "Décodage de la vidéo pour visualisation impossible (%s)"
+
+#: src/wx/job_wrapper.cc:40
+#, c-format
+msgid "Could not make DCP: %s"
+msgstr "Impossible de créer le DCP : %s"
+
+#: src/wx/new_film_dialog.cc:48
+msgid "Create in folder"
+msgstr "Créer dans le dossier"
+
+#: src/wx/config_dialog.cc:260
+msgid "Creator"
+msgstr "Créateur"
+
+#: src/wx/film_editor.cc:1322
+#, c-format
+msgid "Cropped to %dx%d (%.2f:1)\n"
+msgstr "Découpe de %dx%d (%.2f:1)\n"
+
+#: src/wx/dci_metadata_dialog.cc:28
+msgid "DCI name"
+msgstr "Nom DCI"
+
+#: src/wx/film_editor.cc:84
+msgid "DCP"
+msgstr "DCP"
+
+#: src/wx/film_editor.cc:142
+msgid "DCP Frame Rate"
+msgstr "Cadence image du DCP"
+
+#: src/wx/film_editor.cc:115
+msgid "DCP Name"
+msgstr "Nom du DCP"
+
+#: src/wx/film_editor.cc:152
+msgid "DCP audio channels"
+msgstr "canaux audios du DCP"
+
+#: src/wx/about_dialog.cc:44
+#: src/wx/wx_util.cc:87
+#: src/wx/wx_util.cc:95
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/wx/config_dialog.cc:46
+msgid "DCP-o-matic Preferences"
+msgstr "Préférences DCP-o-matic"
+
+#: src/wx/audio_dialog.cc:97
+#, c-format
+msgid "DCP-o-matic audio - %s"
+msgstr "Son DCP-o-matic  - %s"
+
+#: src/wx/config_dialog.cc:123
+msgid "Default DCI name details"
+msgstr "Détails du nom DCI par défaut"
+
+#: src/wx/config_dialog.cc:138
+msgid "Default JPEG2000 bandwidth"
+msgstr "Qualité JPEG2000 par défaut"
+
+#: src/wx/config_dialog.cc:128
+msgid "Default container"
+msgstr "Type de contenu par défaut"
+
+#: src/wx/config_dialog.cc:133
+msgid "Default content type"
+msgstr "Type de contenu par défaut"
+
+#: src/wx/config_dialog.cc:114
+msgid "Default directory for new films"
+msgstr "Dossier par défaut des nouveaux films"
+
+#: src/wx/config_dialog.cc:109
+msgid "Default duration of still images"
+msgstr "Durée par défaut des images fixes"
+
+#: src/wx/film_editor.cc:127
+#: src/wx/job_manager_view.cc:110
+msgid "Details..."
+msgstr "Détails..."
+
+#: src/wx/properties_dialog.cc:45
+msgid "Disk space required"
+msgstr "Espace disque requis"
+
+#: src/wx/imagemagick_content_dialog.cc:36
+msgid "Duration"
+msgstr "Durée"
+
+#: src/wx/config_dialog.cc:301
+msgid "Edit"
+msgstr "Édition"
+
+#: src/wx/config_dialog.cc:124
+#: src/wx/film_editor.cc:288
+msgid "Edit..."
+msgstr "Éditer..."
+
+#: src/wx/config_dialog.cc:55
+msgid "Encoding servers"
+msgstr "Serveurs d'encodage"
+
+#: src/wx/dci_metadata_dialog.cc:53
+msgid "Facility (e.g. DLA)"
+msgstr "Laboratoire (ex. DLA)"
+
+#: src/wx/properties_dialog.cc:36
+msgid "Film Properties"
+msgstr "Propriétés du film"
+
+#: src/wx/new_film_dialog.cc:44
+msgid "Film name"
+msgstr "Nom du Film"
+
+#: src/wx/film_editor.cc:284
+#: src/wx/filter_dialog.cc:32
+msgid "Filters"
+msgstr "Filtres"
+
+#: src/wx/properties_dialog.cc:41
+msgid "Frames"
+msgstr "Images"
+
+#: src/wx/properties_dialog.cc:49
+msgid "Frames already encoded"
+msgstr "Images déjà encodées"
+
+#: src/wx/about_dialog.cc:60
+msgid "Free, open-source DCP generation from almost anything."
+msgstr "Création de DCP libre et gratuit depuis presque tout."
+
+#: src/wx/gain_calculator_dialog.cc:27
+msgid "Gain Calculator"
+msgstr "Calculateur de gain"
+
+#: src/wx/properties_dialog.cc:56
+msgid "Gb"
+msgstr "Gb"
+
+#: src/wx/server_dialog.cc:36
+msgid "Host name or IP address"
+msgstr "Nom de l'hôte ou adresse IP"
+
+#: src/wx/film_editor.cc:1427
+msgid "Hz"
+msgstr "Hz"
+
+#: src/wx/gain_calculator_dialog.cc:32
+msgid "I want to play this back at fader"
+msgstr "Je veux le jouer à ce volume"
+
+#: src/wx/config_dialog.cc:217
+#: src/wx/config_dialog.cc:288
+msgid "IP address"
+msgstr "Adresse IP"
+
+#: src/wx/imagemagick_content_dialog.cc:29
+msgid "Image"
+msgstr "Image"
+
+#: src/wx/config_dialog.cc:256
+msgid "Issuer"
+msgstr "Emetteur"
+
+#: src/wx/film_editor.cc:158
+msgid "JPEG2000 bandwidth"
+msgstr "Qualité JPEG2000"
+
+#: src/wx/audio_mapping_view.cc:184
+msgid "L"
+msgstr "L"
+
+#: src/wx/film_editor.cc:249
+msgid "Left crop"
+msgstr "Découpe gauche"
+
+#: src/wx/film_editor.cc:460
+msgid "Length"
+msgstr "Longueur / durée"
+
+#: src/wx/audio_mapping_view.cc:196
+msgid "Lfe"
+msgstr "Lfe"
+
+#: src/wx/film_editor.cc:330
+msgid "Loop everything"
+msgstr "Tout mettre en boucle"
+
+#: src/wx/audio_mapping_view.cc:200
+msgid "Ls"
+msgstr "Ls"
+
+#: src/wx/config_dialog.cc:141
+#: src/wx/film_editor.cc:162
+msgid "MBps"
+msgstr "MBps"
+
+#: src/wx/config_dialog.cc:57
+msgid "Metadata"
+msgstr "Métadonnées"
+
+#: src/wx/config_dialog.cc:53
+msgid "Miscellaneous"
+msgstr "Divers"
+
+#: src/wx/dir_picker_ctrl.cc:52
+msgid "My Documents"
+msgstr "Mes Documents"
+
+#: src/wx/film_editor.cc:110
+msgid "Name"
+msgstr "Nom"
+
+#: src/wx/new_film_dialog.cc:35
+msgid "New Film"
+msgstr "Nouveau Film"
+
+#: src/wx/film_editor.cc:286
+#: src/wx/film_editor.cc:769
+msgid "None"
+msgstr "Aucun"
+
+#: src/wx/film_editor.cc:1309
+#, c-format
+msgid "Original video is %dx%d (%.2f:1)\n"
+msgstr "La vidéo originale est %dx%d (%.2f:1)\n"
+
+#: src/wx/dci_metadata_dialog.cc:57
+msgid "Package Type (e.g. OV)"
+msgstr "Type de paquet (ex. OV)"
+
+#: src/wx/film_editor.cc:1343
+#, c-format
+msgid "Padded with black to %dx%d (%.2f:1)\n"
+msgstr "Enveloppe noire de %dx%d (%.2f:1)\n"
+
+#: src/wx/config_dialog.cc:229
+msgid "Password"
+msgstr "Mot de passe"
+
+#: src/wx/job_manager_view.cc:104
+#: src/wx/job_manager_view.cc:206
+msgid "Pause"
+msgstr "Pause"
+
+#: src/wx/audio_dialog.cc:59
+msgid "Peak"
+msgstr "Crête"
+
+#: src/wx/film_viewer.cc:64
+msgid "Play"
+msgstr "Lecture"
+
+#: src/wx/audio_plot.cc:110
+msgid "Please wait; audio is being analysed..."
+msgstr "Merci de patienter ; analyse de la piste son..."
+
+#: src/wx/audio_mapping_view.cc:188
+msgid "R"
+msgstr "R"
+
+#: src/wx/audio_dialog.cc:60
+msgid "RMS"
+msgstr "RMS"
+
+#: src/wx/dci_metadata_dialog.cc:45
+msgid "Rating (e.g. 15)"
+msgstr "Rating (ex. 15)"
+
+#: src/wx/config_dialog.cc:303
+#: src/wx/film_editor.cc:319
+msgid "Remove"
+msgstr "Supprimer"
+
+#: src/wx/job_manager_view.cc:209
+msgid "Resume"
+msgstr "Reprendre"
+
+#: src/wx/film_editor.cc:254
+msgid "Right crop"
+msgstr "Découpe droite"
+
+#: src/wx/audio_mapping_view.cc:204
+msgid "Rs"
+msgstr "Rs"
+
+#: src/wx/job_manager_view.cc:128
+msgid "Running"
+msgstr "Progression"
+
+#: src/wx/film_editor.cc:269
+msgid "Scale to"
+msgstr "Mise à l'échelle"
+
+#: src/wx/film_editor.cc:1335
+#, c-format
+msgid "Scaled to %dx%d (%.2f:1)\n"
+msgstr "Mis à l'échelle de %dx%d (%.2f:1)\n"
+
+#: src/wx/film_editor.cc:167
+msgid "Scaler"
+msgstr "Mise à l'échelle"
+
+#: src/wx/server_dialog.cc:25
+msgid "Server"
+msgstr "Serveur"
+
+#: src/wx/config_dialog.cc:85
+msgid "Set language"
+msgstr "Selectionnez la langue"
+
+#: src/wx/film_editor.cc:362
+msgid "Show Audio..."
+msgstr "Analyser le son..."
+
+#: src/wx/audio_dialog.cc:70
+msgid "Smoothing"
+msgstr "Lissage"
+
+#: src/wx/film_editor.cc:457
+msgid "Start time"
+msgstr "Début"
+
+#: src/wx/dci_metadata_dialog.cc:49
+msgid "Studio (e.g. TCF)"
+msgstr "Studio (ex. TCF)"
+
+#: src/wx/dci_metadata_dialog.cc:37
+msgid "Subtitle Language (e.g. FR)"
+msgstr "Langue de sous-titres (ex. FR)"
+
+#: src/wx/film_editor.cc:422
+msgid "Subtitle Offset"
+msgstr "Décalage du sous-titre"
+
+#: src/wx/film_editor.cc:431
+msgid "Subtitle Scale"
+msgstr "Taille du sous-titre"
+
+#: src/wx/film_editor.cc:439
+msgid "Subtitle Stream"
+msgstr "Flux de sous-titre"
+
+#: src/wx/film_editor.cc:345
+msgid "Subtitles"
+msgstr "Sous-titres"
+
+#: src/wx/about_dialog.cc:120
+msgid "Supported by"
+msgstr "Soutenu par"
+
+#: src/wx/config_dialog.cc:59
+msgid "TMS"
+msgstr "TMS"
+
+#: src/wx/config_dialog.cc:221
+msgid "Target path"
+msgstr "Chemin d'accès"
+
+#: src/wx/dci_metadata_dialog.cc:41
+msgid "Territory (e.g. UK)"
+msgstr "Territoire (ex. FR)"
+
+#: src/wx/config_dialog.cc:292
+msgid "Threads"
+msgstr "Processus"
+
+#: src/wx/server_dialog.cc:40
+msgid "Threads to use"
+msgstr "Nombre de processus à utiliser"
+
+#: src/wx/config_dialog.cc:104
+msgid "Threads to use for encoding on this host"
+msgstr "Nombre de processus à utiliser sur cet hôte"
+
+#: src/wx/audio_plot.cc:140
+msgid "Time"
+msgstr "Durée"
+
+#: src/wx/timeline_dialog.cc:32
+msgid "Timeline"
+msgstr "Timeline"
+
+#: src/wx/film_editor.cc:321
+msgid "Timeline..."
+msgstr "Timeline..."
+
+#: src/wx/film_editor.cc:347
+msgid "Timing"
+msgstr ""
+
+#: src/wx/film_editor.cc:259
+msgid "Top crop"
+msgstr "Découpe haut"
+
+#: src/wx/about_dialog.cc:99
+msgid "Translated by"
+msgstr "Traduit par"
+
+#: src/wx/audio_dialog.cc:54
+msgid "Type"
+msgstr "Type"
+
+#: src/wx/film_editor.cc:125
+msgid "Use DCI name"
+msgstr "Utiliser le nom DCI"
+
+#: src/wx/film_editor.cc:146
+msgid "Use best"
+msgstr "Automatique"
+
+#: src/wx/config_dialog.cc:225
+msgid "User name"
+msgstr "Nom d'utilisateur"
+
+#: src/wx/film_editor.cc:341
+msgid "Video"
+msgstr "Vidéo"
+
+#: src/wx/film_editor.cc:417
+msgid "With Subtitles"
+msgstr "Avec sous-titres"
+
+#: src/wx/about_dialog.cc:90
+msgid "Written by"
+msgstr "Développé par"
+
+#: src/wx/timeline.cc:200
+msgid "audio"
+msgstr "audio"
+
+#: src/wx/film_editor.cc:1425
+msgid "channels"
+msgstr "canaux"
+
+#: src/wx/properties_dialog.cc:50
+msgid "counting..."
+msgstr "calcul..."
+
+#: src/wx/film_editor.cc:372
+msgid "dB"
+msgstr "dB"
+
+#. / TRANSLATORS: this is an abbreviation for milliseconds, the unit of time
+#: src/wx/film_editor.cc:385
+msgid "ms"
+msgstr "ms"
+
+#. / TRANSLATORS: this is an abbreviation for seconds, the unit of time
+#: src/wx/config_dialog.cc:112
+#: src/wx/imagemagick_content_dialog.cc:41
+msgid "s"
+msgstr "s"
+
+#: src/wx/film_editor.cc:334
+msgid "times"
+msgstr ""
+
+#: src/wx/timeline.cc:220
+msgid "video"
+msgstr "vidéo"
+
+#~ msgid "A/B"
+#~ msgstr "A/B"
+
+#~ msgid "A/B mode"
+#~ msgstr "A/B mode"
+
+#~ msgid "Audio will be resampled from %dHz to %dHz\n"
+#~ msgstr "L'audio sera rééchantillonné de %dHz à %dHz\n"
+
+#~ msgid "Colour look-up table"
+#~ msgstr "Espace colorimétrique"
+
+#~ msgid "Could not open content file (%s)"
+#~ msgstr "Ouverture du contenu impossible (%s)"
+
+#~ msgid "Could not set content: %s"
+#~ msgstr "Sélectionner du contenu impossible : %s"
+
+#, fuzzy
+#~ msgid "DVD-o-matic Preferences"
+#~ msgstr "Préférences de DCP-o-matic"
+
+#~ msgid "Default format"
+#~ msgstr "Format par défaut"
+
+#~ msgid "End"
+#~ msgstr "Fin"
+
+#~ msgid "Film"
+#~ msgstr "Film"
+
+#~ msgid "Format"
+#~ msgstr "Format"
+
+#~ msgid "Original Frame Rate"
+#~ msgstr "Cadence d'images originale"
+
+#~ msgid "Reference filters"
+#~ msgstr "Filtres de référence"
+
+#~ msgid "Reference scaler"
+#~ msgstr "Échelle de référence"
+
+#~ msgid "Select Audio File"
+#~ msgstr "Sélectionner le fichier son"
+
+#~ msgid "Select Content File"
+#~ msgstr "Sélectionner le fichier vidéo"
+
+#~ msgid "Trim frames"
+#~ msgstr "Images coupées"
+
+#~ msgid "Trim method"
+#~ msgstr "Méthod de découpage"
+
+#~ msgid "Trust content's header"
+#~ msgstr "Faire confiance à l'en-tête"
+
+#~ msgid "Use content's audio"
+#~ msgstr "Utiliser le son intégré"
+
+#~ msgid "Use external audio"
+#~ msgstr "Utiliser une source audio externe"
+
+#~ msgid "encode all frames and play the subset"
+#~ msgstr "encoder toutes les images mais lire seulement la sélection"
+
+#~ msgid "encode only the subset"
+#~ msgstr "encoder seulement la sélection"
+
+#~ msgid "frames"
+#~ msgstr "images"
+
+#~ msgid "pixels"
+#~ msgstr "pixels"
+
+#~ msgid "unknown"
+#~ msgstr "inconnu"
+
+#~ msgid "TMS IP address"
+#~ msgstr "Adresse IP du TMS"
+
+#~ msgid "TMS user name"
+#~ msgstr "Nom d'utilisateur du TMS"
+
+#~ msgid "Original Size"
+#~ msgstr "Taille Originale"
diff --git a/src/wx/po/it_IT.po b/src/wx/po/it_IT.po
new file mode 100644 (file)
index 0000000..fd5b650
--- /dev/null
@@ -0,0 +1,684 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: IT VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-04-28 10:27+0100\n"
+"Last-Translator: Maci <macibro@gmail.com>\n"
+"Language-Team: \n"
+"Language: Italiano\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.5\n"
+
+#: src/wx/film_editor.cc:426 src/wx/film_editor.cc:435
+msgid "%"
+msgstr "%"
+
+#: src/wx/about_dialog.cc:77
+msgid ""
+"(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen"
+msgstr ""
+
+#: src/wx/config_dialog.cc:96
+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:1423
+msgid "1 channel"
+msgstr "1 canale"
+
+#: src/wx/about_dialog.cc:30
+#, fuzzy
+msgid "About DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/wx/config_dialog.cc:299
+msgid "Add"
+msgstr "Aggiungi"
+
+#: src/wx/film_editor.cc:317
+msgid "Add..."
+msgstr ""
+
+#: src/wx/audio_dialog.cc:32 src/wx/film_editor.cc:343
+msgid "Audio"
+msgstr "Audio"
+
+#: src/wx/film_editor.cc:379
+msgid "Audio Delay"
+msgstr "Ritardo dell'audio"
+
+#: src/wx/film_editor.cc:367
+msgid "Audio Gain"
+msgstr "Guadagno dell'audio"
+
+#: src/wx/dci_metadata_dialog.cc:33
+msgid "Audio Language (e.g. EN)"
+msgstr "Lingua dell'audio (es. EN)"
+
+#: src/wx/film_editor.cc:391
+#, fuzzy
+msgid "Audio Stream"
+msgstr "Ritardo dell'audio"
+
+#: src/wx/job_wrapper.cc:38
+#, c-format
+msgid "Bad setting for %s (%s)"
+msgstr "Valore sbagliato per %s (%s)"
+
+#: src/wx/film_editor.cc:264
+msgid "Bottom crop"
+msgstr "Taglio in basso"
+
+#: src/wx/dir_picker_ctrl.cc:38
+msgid "Browse..."
+msgstr "Sfoglia..."
+
+#: src/wx/gain_calculator_dialog.cc:36
+msgid "But I have to use fader"
+msgstr "Ma dovrò riprodurre con il fader a"
+
+#: src/wx/audio_mapping_view.cc:192
+msgid "C"
+msgstr ""
+
+#: src/wx/film_editor.cc:376
+msgid "Calculate..."
+msgstr "Calcola..."
+
+#: src/wx/job_manager_view.cc:98
+msgid "Cancel"
+msgstr "Annulla"
+
+#: src/wx/audio_dialog.cc:43
+msgid "Channels"
+msgstr "Canali"
+
+#: src/wx/film_editor.cc:1163
+msgid "Choose a file or files"
+msgstr ""
+
+#: src/wx/film_editor.cc:131
+#, fuzzy
+msgid "Container"
+msgstr "Contenuto"
+
+#: src/wx/film_editor.cc:82
+msgid "Content"
+msgstr "Contenuto"
+
+#: src/wx/film_editor.cc:136
+msgid "Content Type"
+msgstr "Tipo di contenuto"
+
+#: src/wx/audio_mapping_view.cc:181
+#, fuzzy
+msgid "Content channel"
+msgstr "1 canale"
+
+#: src/wx/film_viewer.cc:326
+#, c-format
+msgid "Could not decode video for view (%s)"
+msgstr "Non posso decodificare il video per guardarlo (%s)"
+
+#: src/wx/job_wrapper.cc:40
+#, c-format
+msgid "Could not make DCP: %s"
+msgstr "Non posso creare il DCP: %s"
+
+#: src/wx/new_film_dialog.cc:48
+msgid "Create in folder"
+msgstr "Crea nella cartella"
+
+#: src/wx/config_dialog.cc:260
+#, fuzzy
+msgid "Creator"
+msgstr "Crea nella cartella"
+
+#: src/wx/film_editor.cc:1322
+#, c-format
+msgid "Cropped to %dx%d (%.2f:1)\n"
+msgstr "Tagliato da %dx%d (%.2f:1)\n"
+
+#: src/wx/dci_metadata_dialog.cc:28
+msgid "DCI name"
+msgstr "Nome del DCP"
+
+#: src/wx/film_editor.cc:84
+msgid "DCP"
+msgstr ""
+
+#: src/wx/film_editor.cc:142
+msgid "DCP Frame Rate"
+msgstr "Frequenza fotogrammi del DCP"
+
+#: src/wx/film_editor.cc:115
+msgid "DCP Name"
+msgstr "Nome del DCP"
+
+#: src/wx/film_editor.cc:152
+#, fuzzy
+msgid "DCP audio channels"
+msgstr "canali"
+
+#: src/wx/about_dialog.cc:44 src/wx/wx_util.cc:87 src/wx/wx_util.cc:95
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/wx/config_dialog.cc:46
+msgid "DCP-o-matic Preferences"
+msgstr "Preferenze DCP-o-matic"
+
+#: src/wx/audio_dialog.cc:97
+#, c-format
+msgid "DCP-o-matic audio - %s"
+msgstr "Audio DCP-o-matic - %s"
+
+#: src/wx/config_dialog.cc:123
+msgid "Default DCI name details"
+msgstr "Dettagli del nome di default DCI"
+
+#: src/wx/config_dialog.cc:138
+#, fuzzy
+msgid "Default JPEG2000 bandwidth"
+msgstr "Banda passante JPEG2000"
+
+#: src/wx/config_dialog.cc:128
+#, fuzzy
+msgid "Default container"
+msgstr "Tipo di contenuto"
+
+#: src/wx/config_dialog.cc:133
+#, fuzzy
+msgid "Default content type"
+msgstr "Tipo di contenuto"
+
+#: src/wx/config_dialog.cc:114
+msgid "Default directory for new films"
+msgstr "Directory di default per i nuovi films"
+
+#: src/wx/config_dialog.cc:109
+#, fuzzy
+msgid "Default duration of still images"
+msgstr "Directory di default per i nuovi films"
+
+#: src/wx/film_editor.cc:127 src/wx/job_manager_view.cc:110
+msgid "Details..."
+msgstr "Dettagli"
+
+#: src/wx/properties_dialog.cc:45
+msgid "Disk space required"
+msgstr "Spazio su disco rischiesto"
+
+#: src/wx/imagemagick_content_dialog.cc:36
+msgid "Duration"
+msgstr "Durata"
+
+#: src/wx/config_dialog.cc:301
+msgid "Edit"
+msgstr "Modifica"
+
+#: src/wx/config_dialog.cc:124 src/wx/film_editor.cc:288
+msgid "Edit..."
+msgstr "Modifica..."
+
+#: src/wx/config_dialog.cc:55
+#, fuzzy
+msgid "Encoding servers"
+msgstr "Servers di codifica"
+
+#: src/wx/dci_metadata_dialog.cc:53
+msgid "Facility (e.g. DLA)"
+msgstr "Facility (es. DLA)"
+
+#: src/wx/properties_dialog.cc:36
+msgid "Film Properties"
+msgstr "Proprietà del film"
+
+#: src/wx/new_film_dialog.cc:44
+msgid "Film name"
+msgstr "Nome del film"
+
+#: src/wx/film_editor.cc:284 src/wx/filter_dialog.cc:32
+msgid "Filters"
+msgstr "Filtri"
+
+#: src/wx/properties_dialog.cc:41
+msgid "Frames"
+msgstr "Fotogrammi"
+
+#: src/wx/properties_dialog.cc:49
+msgid "Frames already encoded"
+msgstr "Fotogrammi già codificati"
+
+#: src/wx/about_dialog.cc:60
+msgid "Free, open-source DCP generation from almost anything."
+msgstr ""
+
+#: src/wx/gain_calculator_dialog.cc:27
+msgid "Gain Calculator"
+msgstr "Calcolatore del guadagno audio"
+
+#: src/wx/properties_dialog.cc:56
+msgid "Gb"
+msgstr "Gb"
+
+#: src/wx/server_dialog.cc:36
+msgid "Host name or IP address"
+msgstr "Nome dell'Host o indirizzo IP"
+
+#: src/wx/film_editor.cc:1427
+msgid "Hz"
+msgstr "Hz"
+
+#: src/wx/gain_calculator_dialog.cc:32
+msgid "I want to play this back at fader"
+msgstr "Sto usando il fader a"
+
+#: src/wx/config_dialog.cc:217 src/wx/config_dialog.cc:288
+msgid "IP address"
+msgstr "Indirizzo IP"
+
+#: src/wx/imagemagick_content_dialog.cc:29
+msgid "Image"
+msgstr ""
+
+#: src/wx/config_dialog.cc:256
+msgid "Issuer"
+msgstr ""
+
+#: src/wx/film_editor.cc:158
+msgid "JPEG2000 bandwidth"
+msgstr "Banda passante JPEG2000"
+
+#: src/wx/audio_mapping_view.cc:184
+msgid "L"
+msgstr ""
+
+#: src/wx/film_editor.cc:249
+msgid "Left crop"
+msgstr "Taglio a sinistra"
+
+#: src/wx/film_editor.cc:460
+msgid "Length"
+msgstr "Lunghezza"
+
+#: src/wx/audio_mapping_view.cc:196
+msgid "Lfe"
+msgstr ""
+
+#: src/wx/film_editor.cc:330
+msgid "Loop everything"
+msgstr ""
+
+#: src/wx/audio_mapping_view.cc:200
+#, fuzzy
+msgid "Ls"
+msgstr "s"
+
+#: src/wx/config_dialog.cc:141 src/wx/film_editor.cc:162
+msgid "MBps"
+msgstr "MBps"
+
+#: src/wx/config_dialog.cc:57
+msgid "Metadata"
+msgstr ""
+
+#: src/wx/config_dialog.cc:53
+msgid "Miscellaneous"
+msgstr ""
+
+#: src/wx/dir_picker_ctrl.cc:52
+msgid "My Documents"
+msgstr "Documenti"
+
+#: src/wx/film_editor.cc:110
+msgid "Name"
+msgstr "Nome"
+
+#: src/wx/new_film_dialog.cc:35
+msgid "New Film"
+msgstr "Nuovo Film"
+
+#: src/wx/film_editor.cc:286 src/wx/film_editor.cc:769
+msgid "None"
+msgstr "Nessuno"
+
+#: src/wx/film_editor.cc:1309
+#, c-format
+msgid "Original video is %dx%d (%.2f:1)\n"
+msgstr "Il video originale è %dx%d (%.2f:1)\n"
+
+#: src/wx/dci_metadata_dialog.cc:57
+msgid "Package Type (e.g. OV)"
+msgstr "Tipo di Package (es. OV)"
+
+#: src/wx/film_editor.cc:1343
+#, c-format
+msgid "Padded with black to %dx%d (%.2f:1)\n"
+msgstr "Riempito con nero a %dx%d (%.2f:1)\n"
+
+#: src/wx/config_dialog.cc:229
+#, fuzzy
+msgid "Password"
+msgstr "Password del TMS"
+
+#: src/wx/job_manager_view.cc:104 src/wx/job_manager_view.cc:206
+msgid "Pause"
+msgstr ""
+
+#: src/wx/audio_dialog.cc:59
+msgid "Peak"
+msgstr "Picco"
+
+#: src/wx/film_viewer.cc:64
+msgid "Play"
+msgstr "Riproduci"
+
+#: src/wx/audio_plot.cc:110
+msgid "Please wait; audio is being analysed..."
+msgstr "Attendere prego; sto analizzando l'audio..."
+
+#: src/wx/audio_mapping_view.cc:188
+msgid "R"
+msgstr ""
+
+#: src/wx/audio_dialog.cc:60
+msgid "RMS"
+msgstr "RMS"
+
+#: src/wx/dci_metadata_dialog.cc:45
+msgid "Rating (e.g. 15)"
+msgstr "Classificazione (es. 15)"
+
+#: src/wx/config_dialog.cc:303 src/wx/film_editor.cc:319
+msgid "Remove"
+msgstr "Rimuovi"
+
+#: src/wx/job_manager_view.cc:209
+msgid "Resume"
+msgstr ""
+
+#: src/wx/film_editor.cc:254
+msgid "Right crop"
+msgstr "Taglio a destra"
+
+#: src/wx/audio_mapping_view.cc:204
+#, fuzzy
+msgid "Rs"
+msgstr "s"
+
+#: src/wx/job_manager_view.cc:128
+msgid "Running"
+msgstr "In corso"
+
+#: src/wx/film_editor.cc:269
+#, fuzzy
+msgid "Scale to"
+msgstr "Scaler"
+
+#: src/wx/film_editor.cc:1335
+#, c-format
+msgid "Scaled to %dx%d (%.2f:1)\n"
+msgstr "Scalato a %dx%d (%.2f:1)\n"
+
+#: src/wx/film_editor.cc:167
+msgid "Scaler"
+msgstr "Scaler"
+
+#: src/wx/server_dialog.cc:25
+msgid "Server"
+msgstr "Server"
+
+#: src/wx/config_dialog.cc:85
+msgid "Set language"
+msgstr "Seleziona la lingua"
+
+#: src/wx/film_editor.cc:362
+msgid "Show Audio..."
+msgstr "Mostra Audio..."
+
+#: src/wx/audio_dialog.cc:70
+msgid "Smoothing"
+msgstr "Levigatura"
+
+#: src/wx/film_editor.cc:457
+#, fuzzy
+msgid "Start time"
+msgstr "Inizio"
+
+#: src/wx/dci_metadata_dialog.cc:49
+msgid "Studio (e.g. TCF)"
+msgstr "Studio (es. TCF)"
+
+#: src/wx/dci_metadata_dialog.cc:37
+msgid "Subtitle Language (e.g. FR)"
+msgstr "Lingua dei Sottotitoli (es. FR)"
+
+#: src/wx/film_editor.cc:422
+msgid "Subtitle Offset"
+msgstr "Sfalsamento dei Sottotitoli"
+
+#: src/wx/film_editor.cc:431
+msgid "Subtitle Scale"
+msgstr "Scala dei Sottotitoli"
+
+#: src/wx/film_editor.cc:439
+#, fuzzy
+msgid "Subtitle Stream"
+msgstr "Scala dei Sottotitoli"
+
+#: src/wx/film_editor.cc:345
+msgid "Subtitles"
+msgstr "Sottotitoli"
+
+#: src/wx/about_dialog.cc:120
+msgid "Supported by"
+msgstr ""
+
+#: src/wx/config_dialog.cc:59
+#, fuzzy
+msgid "TMS"
+msgstr "RMS"
+
+#: src/wx/config_dialog.cc:221
+#, fuzzy
+msgid "Target path"
+msgstr "Percorso di destinazione del TMS"
+
+#: src/wx/dci_metadata_dialog.cc:41
+msgid "Territory (e.g. UK)"
+msgstr "Nazione (es. UK)"
+
+#: src/wx/config_dialog.cc:292
+msgid "Threads"
+msgstr "Threads"
+
+#: src/wx/server_dialog.cc:40
+msgid "Threads to use"
+msgstr "Threads da usare"
+
+#: src/wx/config_dialog.cc:104
+msgid "Threads to use for encoding on this host"
+msgstr "Threads da usare per codificare su questo host"
+
+#: src/wx/audio_plot.cc:140
+msgid "Time"
+msgstr "Tempo"
+
+#: src/wx/timeline_dialog.cc:32
+#, fuzzy
+msgid "Timeline"
+msgstr "Tempo"
+
+#: src/wx/film_editor.cc:321
+msgid "Timeline..."
+msgstr ""
+
+#: src/wx/film_editor.cc:347
+msgid "Timing"
+msgstr ""
+
+#: src/wx/film_editor.cc:259
+msgid "Top crop"
+msgstr "Taglio in alto"
+
+#: src/wx/about_dialog.cc:99
+msgid "Translated by"
+msgstr ""
+
+#: src/wx/audio_dialog.cc:54
+msgid "Type"
+msgstr "Tipo"
+
+#: src/wx/film_editor.cc:125
+msgid "Use DCI name"
+msgstr "Usa nome DCI"
+
+#: src/wx/film_editor.cc:146
+msgid "Use best"
+msgstr "Usa la migliore"
+
+#: src/wx/config_dialog.cc:225
+#, fuzzy
+msgid "User name"
+msgstr "Usa nome DCI"
+
+#: src/wx/film_editor.cc:341
+msgid "Video"
+msgstr "Video"
+
+#: src/wx/film_editor.cc:417
+msgid "With Subtitles"
+msgstr "Con sottotitoli"
+
+#: src/wx/about_dialog.cc:90
+msgid "Written by"
+msgstr ""
+
+#: src/wx/timeline.cc:200
+#, fuzzy
+msgid "audio"
+msgstr "Audio"
+
+#: src/wx/film_editor.cc:1425
+msgid "channels"
+msgstr "canali"
+
+#: src/wx/properties_dialog.cc:50
+msgid "counting..."
+msgstr "conteggio..."
+
+#: src/wx/film_editor.cc:372
+msgid "dB"
+msgstr "dB"
+
+#. / TRANSLATORS: this is an abbreviation for milliseconds, the unit of time
+#: src/wx/film_editor.cc:385
+msgid "ms"
+msgstr "ms"
+
+#. / TRANSLATORS: this is an abbreviation for seconds, the unit of time
+#: src/wx/config_dialog.cc:112 src/wx/imagemagick_content_dialog.cc:41
+msgid "s"
+msgstr "s"
+
+#: src/wx/film_editor.cc:334
+msgid "times"
+msgstr ""
+
+#: src/wx/timeline.cc:220
+#, fuzzy
+msgid "video"
+msgstr "Video"
+
+#~ msgid "A/B"
+#~ msgstr "A/B"
+
+#~ msgid "Audio will be resampled from %dHz to %dHz\n"
+#~ msgstr "L'Audio sarà ricampionato da %dHz a %dHz\n"
+
+#~ msgid "Colour look-up table"
+#~ msgstr "Tabella per ricerca del colore"
+
+#~ msgid "Could not open content file (%s)"
+#~ msgstr "Non posso aprire il file del contenuto (%s)"
+
+#~ msgid "Could not set content: %s"
+#~ msgstr "Non posso regolare il contenuto: %s"
+
+#, fuzzy
+#~ msgid "DVD-o-matic Preferences"
+#~ msgstr "Preferenze DVD-o-matic"
+
+#~ msgid "End"
+#~ msgstr "Fine"
+
+#~ msgid "Film"
+#~ msgstr "Film"
+
+#~ msgid "Format"
+#~ msgstr "Formato"
+
+#~ msgid "Original Frame Rate"
+#~ msgstr "Frequenza fotogrammi originale"
+
+#, fuzzy
+#~ msgid "Reference filters"
+#~ msgstr "Filtri di riferimento A/B"
+
+#, fuzzy
+#~ msgid "Reference scaler"
+#~ msgstr "Scalatura di riferimento A/B"
+
+#~ msgid "Select Audio File"
+#~ msgstr "Seleziona file audio"
+
+#~ msgid "Select Content File"
+#~ msgstr "Seleziona il file con il contenuto"
+
+#~ msgid "Trim frames"
+#~ msgstr "Taglia fotogrammi"
+
+#~ msgid "Trim method"
+#~ msgstr "Metodo di taglio"
+
+#~ msgid "Trust content's header"
+#~ msgstr "Conferma l'intestazione del contenuto"
+
+#~ msgid "Use content's audio"
+#~ msgstr "Usa l'audio del contenuto"
+
+#~ msgid "Use external audio"
+#~ msgstr "Usa l'audio esterno"
+
+#~ msgid "encode all frames and play the subset"
+#~ msgstr "Codifica tutti i fotogrammi e riproduci la selezione"
+
+#~ msgid "encode only the subset"
+#~ msgstr "codifica solo la selezione"
+
+#~ msgid "frames"
+#~ msgstr "fotogrammi"
+
+#~ msgid "pixels"
+#~ msgstr "pizels"
+
+#~ msgid "unknown"
+#~ msgstr "sconosciuto"
+
+#~ msgid "TMS IP address"
+#~ msgstr "Indirizzo IP del TMS"
+
+#~ msgid "TMS user name"
+#~ msgstr "Nome utente del TMS"
+
+#~ msgid "Original Size"
+#~ msgstr "Dimensione Originale"
diff --git a/src/wx/po/sv_SE.po b/src/wx/po/sv_SE.po
new file mode 100644 (file)
index 0000000..c86fcc8
--- /dev/null
@@ -0,0 +1,679 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: DCP-o-matic\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-04-09 10:13+0100\n"
+"Last-Translator: Adam Klotblixt <adam.klotblixt@gmail.com>\n"
+"Language-Team: \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.5\n"
+
+#: src/wx/film_editor.cc:426 src/wx/film_editor.cc:435
+msgid "%"
+msgstr "%"
+
+#: src/wx/about_dialog.cc:77
+msgid ""
+"(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen"
+msgstr ""
+
+#: src/wx/config_dialog.cc:96
+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:1423
+msgid "1 channel"
+msgstr "1 kanal"
+
+#: src/wx/about_dialog.cc:30
+#, fuzzy
+msgid "About DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/wx/config_dialog.cc:299
+msgid "Add"
+msgstr "Lägg till"
+
+#: src/wx/film_editor.cc:317
+msgid "Add..."
+msgstr ""
+
+#: src/wx/audio_dialog.cc:32 src/wx/film_editor.cc:343
+msgid "Audio"
+msgstr "Audio"
+
+#: src/wx/film_editor.cc:379
+msgid "Audio Delay"
+msgstr "Audio Fördröjning"
+
+#: src/wx/film_editor.cc:367
+msgid "Audio Gain"
+msgstr "Audio Förstärkning"
+
+#: src/wx/dci_metadata_dialog.cc:33
+msgid "Audio Language (e.g. EN)"
+msgstr "Audio Språk (ex. SV)"
+
+#: src/wx/film_editor.cc:391
+#, fuzzy
+msgid "Audio Stream"
+msgstr "Audio Fördröjning"
+
+#: src/wx/job_wrapper.cc:38
+#, c-format
+msgid "Bad setting for %s (%s)"
+msgstr "Felaktig inställning för %s (%s)"
+
+#: src/wx/film_editor.cc:264
+msgid "Bottom crop"
+msgstr "Nedre beskärning"
+
+#: src/wx/dir_picker_ctrl.cc:38
+msgid "Browse..."
+msgstr "Bläddra..."
+
+#: src/wx/gain_calculator_dialog.cc:36
+msgid "But I have to use fader"
+msgstr "Men jag måste använda mixervolym"
+
+#: src/wx/audio_mapping_view.cc:192
+msgid "C"
+msgstr ""
+
+#: src/wx/film_editor.cc:376
+msgid "Calculate..."
+msgstr "Beräkna..."
+
+#: src/wx/job_manager_view.cc:98
+msgid "Cancel"
+msgstr "Avbryt"
+
+#: src/wx/audio_dialog.cc:43
+msgid "Channels"
+msgstr "Kanaler"
+
+#: src/wx/film_editor.cc:1163
+msgid "Choose a file or files"
+msgstr ""
+
+#: src/wx/film_editor.cc:131
+#, fuzzy
+msgid "Container"
+msgstr "Innehåll"
+
+#: src/wx/film_editor.cc:82
+msgid "Content"
+msgstr "Innehåll"
+
+#: src/wx/film_editor.cc:136
+msgid "Content Type"
+msgstr "Innehållstyp"
+
+#: src/wx/audio_mapping_view.cc:181
+#, fuzzy
+msgid "Content channel"
+msgstr "1 kanal"
+
+#: src/wx/film_viewer.cc:326
+#, c-format
+msgid "Could not decode video for view (%s)"
+msgstr "Kunde inte avkoda video för visning (%s)"
+
+#: src/wx/job_wrapper.cc:40
+#, c-format
+msgid "Could not make DCP: %s"
+msgstr "Kunde inte skapa DCP: %s"
+
+#: src/wx/new_film_dialog.cc:48
+msgid "Create in folder"
+msgstr "Skapa i katalog"
+
+#: src/wx/config_dialog.cc:260
+#, fuzzy
+msgid "Creator"
+msgstr "Skapa i katalog"
+
+#: src/wx/film_editor.cc:1322
+#, c-format
+msgid "Cropped to %dx%d (%.2f:1)\n"
+msgstr "Beskuren till %dx%d (%.2f:1)\n"
+
+#: src/wx/dci_metadata_dialog.cc:28
+msgid "DCI name"
+msgstr "DCI namn"
+
+#: src/wx/film_editor.cc:84
+msgid "DCP"
+msgstr ""
+
+#: src/wx/film_editor.cc:142
+msgid "DCP Frame Rate"
+msgstr "DCP bildhastighet"
+
+#: src/wx/film_editor.cc:115
+msgid "DCP Name"
+msgstr "DCP Namn"
+
+#: src/wx/film_editor.cc:152
+#, fuzzy
+msgid "DCP audio channels"
+msgstr "kanaler"
+
+#: src/wx/about_dialog.cc:44 src/wx/wx_util.cc:87 src/wx/wx_util.cc:95
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/wx/config_dialog.cc:46
+msgid "DCP-o-matic Preferences"
+msgstr "DCP-o-matic Inställningar"
+
+#: src/wx/audio_dialog.cc:97
+#, c-format
+msgid "DCP-o-matic audio - %s"
+msgstr "DCP-o-matic audio - %s"
+
+#: src/wx/config_dialog.cc:123
+msgid "Default DCI name details"
+msgstr "Detaljer om förvalda DCI-namn"
+
+#: src/wx/config_dialog.cc:138
+#, fuzzy
+msgid "Default JPEG2000 bandwidth"
+msgstr "JPEG2000 bandbredd"
+
+#: src/wx/config_dialog.cc:128
+#, fuzzy
+msgid "Default container"
+msgstr "Innehållstyp"
+
+#: src/wx/config_dialog.cc:133
+#, fuzzy
+msgid "Default content type"
+msgstr "Innehållstyp"
+
+#: src/wx/config_dialog.cc:114
+msgid "Default directory for new films"
+msgstr "Förvald katalog för nya filmer"
+
+#: src/wx/config_dialog.cc:109
+#, fuzzy
+msgid "Default duration of still images"
+msgstr "Förvald katalog för nya filmer"
+
+#: src/wx/film_editor.cc:127 src/wx/job_manager_view.cc:110
+msgid "Details..."
+msgstr "Detaljer..."
+
+#: src/wx/properties_dialog.cc:45
+msgid "Disk space required"
+msgstr "Diskutrymme som krävs"
+
+#: src/wx/imagemagick_content_dialog.cc:36
+msgid "Duration"
+msgstr "Längd"
+
+#: src/wx/config_dialog.cc:301
+msgid "Edit"
+msgstr "Redigera"
+
+#: src/wx/config_dialog.cc:124 src/wx/film_editor.cc:288
+msgid "Edit..."
+msgstr "Redigera..."
+
+#: src/wx/config_dialog.cc:55
+#, fuzzy
+msgid "Encoding servers"
+msgstr "Kodningsservrar"
+
+#: src/wx/dci_metadata_dialog.cc:53
+msgid "Facility (e.g. DLA)"
+msgstr "Företag (ex. DLA)"
+
+#: src/wx/properties_dialog.cc:36
+msgid "Film Properties"
+msgstr "Film Egenskaper"
+
+#: src/wx/new_film_dialog.cc:44
+msgid "Film name"
+msgstr "film namn"
+
+#: src/wx/film_editor.cc:284 src/wx/filter_dialog.cc:32
+msgid "Filters"
+msgstr "Filter"
+
+#: src/wx/properties_dialog.cc:41
+msgid "Frames"
+msgstr "Bildrutor"
+
+#: src/wx/properties_dialog.cc:49
+msgid "Frames already encoded"
+msgstr "Bildrutor redan kodade"
+
+#: src/wx/about_dialog.cc:60
+msgid "Free, open-source DCP generation from almost anything."
+msgstr ""
+
+#: src/wx/gain_calculator_dialog.cc:27
+msgid "Gain Calculator"
+msgstr "Volym Kalkylator"
+
+#: src/wx/properties_dialog.cc:56
+msgid "Gb"
+msgstr "Gb"
+
+#: src/wx/server_dialog.cc:36
+msgid "Host name or IP address"
+msgstr "Värd-namn eller IP-adress"
+
+#: src/wx/film_editor.cc:1427
+msgid "Hz"
+msgstr "Hz"
+
+#: src/wx/gain_calculator_dialog.cc:32
+msgid "I want to play this back at fader"
+msgstr "Jag vill spela upp detta med mixervolym"
+
+#: src/wx/config_dialog.cc:217 src/wx/config_dialog.cc:288
+msgid "IP address"
+msgstr "IP-adress"
+
+#: src/wx/imagemagick_content_dialog.cc:29
+msgid "Image"
+msgstr ""
+
+#: src/wx/config_dialog.cc:256
+msgid "Issuer"
+msgstr ""
+
+#: src/wx/film_editor.cc:158
+msgid "JPEG2000 bandwidth"
+msgstr "JPEG2000 bandbredd"
+
+#: src/wx/audio_mapping_view.cc:184
+msgid "L"
+msgstr ""
+
+#: src/wx/film_editor.cc:249
+msgid "Left crop"
+msgstr "Vänster beskärning"
+
+#: src/wx/film_editor.cc:460
+msgid "Length"
+msgstr "Längd"
+
+#: src/wx/audio_mapping_view.cc:196
+msgid "Lfe"
+msgstr ""
+
+#: src/wx/film_editor.cc:330
+msgid "Loop everything"
+msgstr ""
+
+#: src/wx/audio_mapping_view.cc:200
+#, fuzzy
+msgid "Ls"
+msgstr "s"
+
+#: src/wx/config_dialog.cc:141 src/wx/film_editor.cc:162
+msgid "MBps"
+msgstr "MBps"
+
+#: src/wx/config_dialog.cc:57
+msgid "Metadata"
+msgstr ""
+
+#: src/wx/config_dialog.cc:53
+msgid "Miscellaneous"
+msgstr ""
+
+#: src/wx/dir_picker_ctrl.cc:52
+msgid "My Documents"
+msgstr "Mina Dokument"
+
+#: src/wx/film_editor.cc:110
+msgid "Name"
+msgstr "Namn"
+
+#: src/wx/new_film_dialog.cc:35
+msgid "New Film"
+msgstr "Ny Film"
+
+#: src/wx/film_editor.cc:286 src/wx/film_editor.cc:769
+msgid "None"
+msgstr "Inget"
+
+#: src/wx/film_editor.cc:1309
+#, c-format
+msgid "Original video is %dx%d (%.2f:1)\n"
+msgstr "Original-videon är %dx%d (%.2f:1)\n"
+
+#: src/wx/dci_metadata_dialog.cc:57
+msgid "Package Type (e.g. OV)"
+msgstr "Förpackningstyp (ex. OV)"
+
+#: src/wx/film_editor.cc:1343
+#, c-format
+msgid "Padded with black to %dx%d (%.2f:1)\n"
+msgstr "Svarta kanter tillagda för %dx%d (%.2f:1)\n"
+
+#: src/wx/config_dialog.cc:229
+#, fuzzy
+msgid "Password"
+msgstr "TMS lösenord"
+
+#: src/wx/job_manager_view.cc:104 src/wx/job_manager_view.cc:206
+msgid "Pause"
+msgstr ""
+
+#: src/wx/audio_dialog.cc:59
+msgid "Peak"
+msgstr "Topp"
+
+#: src/wx/film_viewer.cc:64
+msgid "Play"
+msgstr "Spela"
+
+#: src/wx/audio_plot.cc:110
+msgid "Please wait; audio is being analysed..."
+msgstr "Vänligen vänta; audio analyseras..."
+
+#: src/wx/audio_mapping_view.cc:188
+msgid "R"
+msgstr ""
+
+#: src/wx/audio_dialog.cc:60
+msgid "RMS"
+msgstr "RMS"
+
+#: src/wx/dci_metadata_dialog.cc:45
+msgid "Rating (e.g. 15)"
+msgstr "Klassificering (ex. 15)"
+
+#: src/wx/config_dialog.cc:303 src/wx/film_editor.cc:319
+msgid "Remove"
+msgstr "Ta bort"
+
+#: src/wx/job_manager_view.cc:209
+msgid "Resume"
+msgstr ""
+
+#: src/wx/film_editor.cc:254
+msgid "Right crop"
+msgstr "Höger beskärning"
+
+#: src/wx/audio_mapping_view.cc:204
+#, fuzzy
+msgid "Rs"
+msgstr "s"
+
+#: src/wx/job_manager_view.cc:128
+msgid "Running"
+msgstr "Körs"
+
+#: src/wx/film_editor.cc:269
+#, fuzzy
+msgid "Scale to"
+msgstr "Omskalare"
+
+#: src/wx/film_editor.cc:1335
+#, c-format
+msgid "Scaled to %dx%d (%.2f:1)\n"
+msgstr "Skalad till %dx%d (%.2f:1)\n"
+
+#: src/wx/film_editor.cc:167
+msgid "Scaler"
+msgstr "Omskalare"
+
+#: src/wx/server_dialog.cc:25
+msgid "Server"
+msgstr "Server"
+
+#: src/wx/config_dialog.cc:85
+msgid "Set language"
+msgstr "Välj språk"
+
+#: src/wx/film_editor.cc:362
+msgid "Show Audio..."
+msgstr "Visa Audio..."
+
+#: src/wx/audio_dialog.cc:70
+msgid "Smoothing"
+msgstr "Utjämning"
+
+#: src/wx/film_editor.cc:457
+#, fuzzy
+msgid "Start time"
+msgstr "Start"
+
+#: src/wx/dci_metadata_dialog.cc:49
+msgid "Studio (e.g. TCF)"
+msgstr "Studio (ex. TCF)"
+
+#: src/wx/dci_metadata_dialog.cc:37
+msgid "Subtitle Language (e.g. FR)"
+msgstr "Undertextspråk (ex. SV)"
+
+#: src/wx/film_editor.cc:422
+msgid "Subtitle Offset"
+msgstr "Undertext Förskjutning"
+
+#: src/wx/film_editor.cc:431
+msgid "Subtitle Scale"
+msgstr "Undertext Skalning"
+
+#: src/wx/film_editor.cc:439
+#, fuzzy
+msgid "Subtitle Stream"
+msgstr "Undertext Skalning"
+
+#: src/wx/film_editor.cc:345
+msgid "Subtitles"
+msgstr "Undertexter"
+
+#: src/wx/about_dialog.cc:120
+msgid "Supported by"
+msgstr ""
+
+#: src/wx/config_dialog.cc:59
+#, fuzzy
+msgid "TMS"
+msgstr "RMS"
+
+#: src/wx/config_dialog.cc:221
+#, fuzzy
+msgid "Target path"
+msgstr "TMS målsökväg"
+
+#: src/wx/dci_metadata_dialog.cc:41
+msgid "Territory (e.g. UK)"
+msgstr "Område (ex. SV)"
+
+#: src/wx/config_dialog.cc:292
+msgid "Threads"
+msgstr "Trådar"
+
+#: src/wx/server_dialog.cc:40
+msgid "Threads to use"
+msgstr "Antal trådar att använda"
+
+#: src/wx/config_dialog.cc:104
+msgid "Threads to use for encoding on this host"
+msgstr "Antal trådar att använda vid kodning på denna maskin"
+
+#: src/wx/audio_plot.cc:140
+msgid "Time"
+msgstr "Tid"
+
+#: src/wx/timeline_dialog.cc:32
+#, fuzzy
+msgid "Timeline"
+msgstr "Tid"
+
+#: src/wx/film_editor.cc:321
+msgid "Timeline..."
+msgstr ""
+
+#: src/wx/film_editor.cc:347
+msgid "Timing"
+msgstr ""
+
+#: src/wx/film_editor.cc:259
+msgid "Top crop"
+msgstr "Övre beskärning"
+
+#: src/wx/about_dialog.cc:99
+msgid "Translated by"
+msgstr ""
+
+#: src/wx/audio_dialog.cc:54
+msgid "Type"
+msgstr "Typ"
+
+#: src/wx/film_editor.cc:125
+msgid "Use DCI name"
+msgstr "Använd DCI-namnet"
+
+#: src/wx/film_editor.cc:146
+msgid "Use best"
+msgstr "Använd bästa"
+
+#: src/wx/config_dialog.cc:225
+#, fuzzy
+msgid "User name"
+msgstr "Använd DCI-namnet"
+
+#: src/wx/film_editor.cc:341
+msgid "Video"
+msgstr "Video"
+
+#: src/wx/film_editor.cc:417
+msgid "With Subtitles"
+msgstr "Med Undertexter"
+
+#: src/wx/about_dialog.cc:90
+msgid "Written by"
+msgstr ""
+
+#: src/wx/timeline.cc:200
+#, fuzzy
+msgid "audio"
+msgstr "Audio"
+
+#: src/wx/film_editor.cc:1425
+msgid "channels"
+msgstr "kanaler"
+
+#: src/wx/properties_dialog.cc:50
+msgid "counting..."
+msgstr "räknar..."
+
+#: src/wx/film_editor.cc:372
+msgid "dB"
+msgstr "dB"
+
+#. / TRANSLATORS: this is an abbreviation for milliseconds, the unit of time
+#: src/wx/film_editor.cc:385
+msgid "ms"
+msgstr "ms"
+
+#. / TRANSLATORS: this is an abbreviation for seconds, the unit of time
+#: src/wx/config_dialog.cc:112 src/wx/imagemagick_content_dialog.cc:41
+msgid "s"
+msgstr "s"
+
+#: src/wx/film_editor.cc:334
+msgid "times"
+msgstr ""
+
+#: src/wx/timeline.cc:220
+#, fuzzy
+msgid "video"
+msgstr "Video"
+
+#~ msgid "A/B"
+#~ msgstr "A/B"
+
+#~ msgid "Audio will be resampled from %dHz to %dHz\n"
+#~ msgstr "Audio kommer att samplas om från %dHz till %dHz\n"
+
+#~ msgid "Colour look-up table"
+#~ msgstr "Färguppslagningstabell"
+
+#~ msgid "Could not open content file (%s)"
+#~ msgstr "Kunde inte öppna innehållsfilen (%s)"
+
+#~ msgid "Could not set content: %s"
+#~ msgstr "Kunde inte fastställa innehåll: %s"
+
+#, fuzzy
+#~ msgid "DVD-o-matic Preferences"
+#~ msgstr "DVD-o-matic Inställningar"
+
+#~ msgid "End"
+#~ msgstr "Slut"
+
+#~ msgid "Film"
+#~ msgstr "Film"
+
+#~ msgid "Format"
+#~ msgstr "Format"
+
+#~ msgid "Original Frame Rate"
+#~ msgstr "Ursprunglig bildhastighet"
+
+#, fuzzy
+#~ msgid "Reference filters"
+#~ msgstr "Referensfilter för A/B"
+
+#, fuzzy
+#~ msgid "Reference scaler"
+#~ msgstr "Referensomskalare för A/B"
+
+#~ msgid "Select Audio File"
+#~ msgstr "Välj audiofil"
+
+#~ msgid "Select Content File"
+#~ msgstr "Välj innehållsfil"
+
+#~ msgid "Trim frames"
+#~ msgstr "Skippa bilder"
+
+#, fuzzy
+#~ msgid "Trim method"
+#~ msgstr "Skippa bilder"
+
+#~ msgid "Trust content's header"
+#~ msgstr "Lita på källans information"
+
+#~ msgid "Use content's audio"
+#~ msgstr "Använd innehållets audio"
+
+#~ msgid "Use external audio"
+#~ msgstr "Använd extern audio"
+
+#~ msgid "frames"
+#~ msgstr "bilder"
+
+#~ msgid "pixels"
+#~ msgstr "pixlar"
+
+#~ msgid "unknown"
+#~ msgstr "okänt"
+
+#~ msgid "TMS IP address"
+#~ msgstr "TMS IP-adress"
+
+#~ msgid "TMS user name"
+#~ msgstr "TMS användarnamn"
+
+#~ msgid "Original Size"
+#~ msgstr "Ursprunglig Storlek"
diff --git a/src/wx/preset_colour_conversion_dialog.cc b/src/wx/preset_colour_conversion_dialog.cc
new file mode 100644 (file)
index 0000000..ce6897e
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+    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/statline.h>
+#include "lib/colour_conversion.h"
+#include "wx_util.h"
+#include "preset_colour_conversion_dialog.h"
+#include "colour_conversion_editor.h"
+
+using std::string;
+using std::cout;
+
+PresetColourConversionDialog::PresetColourConversionDialog (wxWindow* parent)
+       : wxDialog (parent, wxID_ANY, _("Colour conversion"))
+       , _editor (new ColourConversionEditor (this))
+{
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       SetSizer (overall_sizer);
+
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       table->AddGrowableCol (1, 1);
+       add_label_to_sizer (table, this, _("Name"), true);
+       _name = new wxTextCtrl (this, wxID_ANY, wxT (""));
+       table->Add (_name, 1, wxEXPAND | wxALL);
+
+       overall_sizer->Add (table, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+       overall_sizer->Add (new wxStaticLine (this, wxID_ANY), 0, wxEXPAND);
+       overall_sizer->Add (_editor);
+
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
+       if (buttons) {
+               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+
+       overall_sizer->Layout ();
+       overall_sizer->SetSizeHints (this);
+}
+
+PresetColourConversion
+PresetColourConversionDialog::get () const
+{
+       PresetColourConversion pc;
+       pc.name = wx_to_std (_name->GetValue ());
+       pc.conversion = _editor->get ();
+       return pc;
+}
+
+void
+PresetColourConversionDialog::set (PresetColourConversion c)
+{
+       _name->SetValue (std_to_wx (c.name));
+       _editor->set (c.conversion);
+}
diff --git a/src/wx/preset_colour_conversion_dialog.h b/src/wx/preset_colour_conversion_dialog.h
new file mode 100644 (file)
index 0000000..4e61239
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+    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 ColourConversionEditor;
+
+class PresetColourConversionDialog : public wxDialog
+{
+public:
+       PresetColourConversionDialog (wxWindow *);
+
+       void set (PresetColourConversion);
+       PresetColourConversion get () const;
+
+private:
+       wxTextCtrl* _name;
+       ColourConversionEditor* _editor;
+};
index b03c6b32c54d95e3b23cda98d63e121ee5338b29..a1ba81b3b5ae07838a23adb2f7d4a8d9477e169a 100644 (file)
@@ -36,41 +36,28 @@ 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, 3, 6);
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
 
-       add_label_to_sizer (table, this, "Frames");
-       _frames = new wxStaticText (this, wxID_ANY, std_to_wx (""));
+       add_label_to_sizer (table, this, _("Frames"), true);
+       _frames = new wxStaticText (this, wxID_ANY, wxT (""));
        table->Add (_frames, 1, wxALIGN_CENTER_VERTICAL);
 
-       add_label_to_sizer (table, this, "Disk space required for frames");
-       _disk_for_frames = new wxStaticText (this, wxID_ANY, std_to_wx (""));
-       table->Add (_disk_for_frames, 1, wxALIGN_CENTER_VERTICAL);
-       
-       add_label_to_sizer (table, this, "Total disk space required");
-       _total_disk = new wxStaticText (this, wxID_ANY, std_to_wx (""));
-       table->Add (_total_disk, 1, wxALIGN_CENTER_VERTICAL);
+       add_label_to_sizer (table, this, _("Disk space required"), true);
+       _disk = new wxStaticText (this, wxID_ANY, wxT (""));
+       table->Add (_disk, 1, wxALIGN_CENTER_VERTICAL);
 
-       add_label_to_sizer (table, this, "Frames already encoded");
-       _encoded = new ThreadedStaticText (this, "counting...", boost::bind (&PropertiesDialog::frames_already_encoded, this));
+       add_label_to_sizer (table, this, _("Frames already encoded"), true);
+       _encoded = new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this));
        table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL);
 
-       if (_film->length()) {
-               _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().get())));
-               double const disk = ((double) _film->j2k_bandwidth() / 8) * _film->length().get() / (_film->frames_per_second () * 1073741824);
-               stringstream s;
-               s << fixed << setprecision (1) << disk << "Gb";
-               _disk_for_frames->SetLabel (std_to_wx (s.str ()));
-               stringstream t;
-               t << fixed << setprecision (1) << (disk * 2) << "Gb";
-               _total_disk->SetLabel (std_to_wx (t.str ()));
-       } else {
-               _frames->SetLabel (_("unknown"));
-               _disk_for_frames->SetLabel (_("unknown"));
-               _total_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);
+       overall_sizer->Add (table, 0, wxALL, DCPOMATIC_DIALOG_BORDER);
        
        wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
        if (buttons) {
@@ -91,9 +78,9 @@ PropertiesDialog::frames_already_encoded () const
                return "";
        }
        
-       if (_film->dcp_length()) {
+       if (_film->length()) {
                /* XXX: encoded_frames() should check which frames have been encoded */
-               u << " (" << ((_film->encoded_frames() - _film->dcp_trim_start()) * 100 / _film->dcp_length().get()) << "%)";
+               u << " (" << (_film->encoded_frames() * 100 / _film->time_to_video_frames (_film->length())) << "%)";
        }
        return u.str ();
 }
index 308c0f7b3f5bdced158de82e7362bbdae7711006..cae929e1894e19235dc84741a84ab6eab3adfda8 100644 (file)
@@ -32,8 +32,7 @@ private:
 
        boost::shared_ptr<Film> _film;
        wxStaticText* _frames;
-       wxStaticText* _disk_for_frames;
-       wxStaticText* _total_disk;
+       wxStaticText* _disk;
        ThreadedStaticText* _encoded;
 };
 
diff --git a/src/wx/repeat_dialog.cc b/src/wx/repeat_dialog.cc
new file mode 100644 (file)
index 0000000..3721c61
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+    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 "repeat_dialog.h"
+#include "wx_util.h"
+
+RepeatDialog::RepeatDialog (wxWindow* parent)
+       : wxDialog (parent, wxID_ANY, _("Repeat Content"))
+{
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       SetSizer (overall_sizer);
+       
+       wxFlexGridSizer* table = new wxFlexGridSizer (3, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       table->AddGrowableCol (1, 1);
+       overall_sizer->Add (table, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+
+       add_label_to_sizer (table, this, _("Repeat"), true);
+       _number = new wxSpinCtrl (this, wxID_ANY);
+       table->Add (_number, 1);
+
+       add_label_to_sizer (table, this, _("times"), false);
+
+       _number->SetRange (1, 1024);
+
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL);
+       if (buttons) {
+               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+
+       overall_sizer->Layout ();
+       overall_sizer->SetSizeHints (this);
+}
+
+int
+RepeatDialog::number () const
+{
+       return _number->GetValue ();
+}
diff --git a/src/wx/repeat_dialog.h b/src/wx/repeat_dialog.h
new file mode 100644 (file)
index 0000000..cbcc6bb
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+    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/spinctrl.h>
+
+class RepeatDialog : public wxDialog
+{
+public:
+       RepeatDialog (wxWindow *);
+
+       int number () const;
+
+private:
+       wxSpinCtrl* _number;
+};
index 910dece9d0dcc9a05c48b7055c5bb99f79e7b9dc..7ff5197137cd27e6d06c25e3d0326fb684d34bf0 100644 (file)
@@ -35,11 +35,11 @@ ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_
        wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
        table->AddGrowableCol (1, 1);
 
-       add_label_to_sizer (table, this, "Name");
+       add_label_to_sizer (table, this, "Name", true);
        _name = new wxTextCtrl (this, wxID_ANY, std_to_wx (name), wxDefaultPosition, wxSize (320, -1));
        table->Add (_name, 1, wxEXPAND);
 
-       add_label_to_sizer (table, this, "Certificate");
+       add_label_to_sizer (table, this, "Certificate", true);
        _certificate_load = new wxButton (this, wxID_ANY, wxT ("Load from file..."));
        table->Add (_certificate_load, 1, wxEXPAND);
 
index 7b394a484addd3a75114cd7fdb61c25b54a75285..c1dbc4bca18e588eeb20e0ce137e9bb26a97a8f8 100644 (file)
 #include "server_dialog.h"
 #include "wx_util.h"
 
-ServerDialog::ServerDialog (wxWindow* parent, ServerDescription* server)
-       : wxDialog (parent, wxID_ANY, wxString (_("Server")))
+using boost::shared_ptr;
+
+ServerDialog::ServerDialog (wxWindow* parent)
+       : wxDialog (parent, wxID_ANY, _("Server"))
 {
-       if (server) {
-               _server = server;
-       } else {
-               _server = new ServerDescription ("localhost", 1);
-       }
-               
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 4, 4);
+       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");
-       _host = new wxTextCtrl (this, wxID_ANY);
-       table->Add (_host, 1, wxEXPAND);
+        wxClientDC dc (parent);
+       /* XXX: bit of a mystery why we need such a long string here */
+        wxSize size = dc.GetTextExtent (wxT ("255.255.255.255.255.255.255.255"));
+        size.SetHeight (-1);
+
+        wxTextValidator validator (wxFILTER_INCLUDE_CHAR_LIST);
+        wxArrayString list;
 
-       add_label_to_sizer (table, this, "Threads to use");
+       add_label_to_sizer (table, this, _("Host name or IP address"), true);
+       _host = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, size);
+       table->Add (_host, 1, wxEXPAND | wxALL);
+
+       add_label_to_sizer (table, this, _("Threads to use"), true);
        _threads = new wxSpinCtrl (this, wxID_ANY);
        table->Add (_threads, 1, wxEXPAND);
 
-       _host->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ServerDialog::host_changed), 0, this);
        _threads->SetRange (0, 256);
-       _threads->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ServerDialog::threads_changed), 0, this);
-
-       _host->SetValue (std_to_wx (_server->host_name ()));
-       _threads->SetValue (_server->threads ());
 
        wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
-       overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6);
+       overall_sizer->Add (table, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
 
        wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
        if (buttons) {
@@ -62,20 +61,18 @@ ServerDialog::ServerDialog (wxWindow* parent, ServerDescription* server)
 }
 
 void
-ServerDialog::host_changed (wxCommandEvent &)
-{
-       _server->set_host_name (wx_to_std (_host->GetValue ()));
-}
-
-void
-ServerDialog::threads_changed (wxCommandEvent &)
+ServerDialog::set (ServerDescription server)
 {
-       _server->set_threads (_threads->GetValue ());
+       _host->SetValue (std_to_wx (server.host_name ()));
+       _threads->SetValue (server.threads ());
 }
 
-ServerDescription *
-ServerDialog::server () const
+ServerDescription
+ServerDialog::get () const
 {
-       return _server;
+       ServerDescription server;
+       server.set_host_name (wx_to_std (_host->GetValue ()));
+       server.set_threads (_threads->GetValue ());
+       return server;
 }
 
index 0912fd60f19291a866b57cc5456320d9f7e24e00..a6f48fe7b71a5e82d296bff1ad3ed55ae329680b 100644 (file)
@@ -25,15 +25,12 @@ class ServerDescription;
 class ServerDialog : public wxDialog
 {
 public:
-       ServerDialog (wxWindow *, ServerDescription *);
+       ServerDialog (wxWindow *);
 
-       ServerDescription* server () const;
+       void set (ServerDescription);
+       ServerDescription get () const;
 
 private:
-       void host_changed (wxCommandEvent &);
-       void threads_changed (wxCommandEvent &);
-
-       ServerDescription* _server;
        wxTextCtrl* _host;
        wxSpinCtrl* _threads;
 };
diff --git a/src/wx/subtitle_panel.cc b/src/wx/subtitle_panel.cc
new file mode 100644 (file)
index 0000000..8f2b08a
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+    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/lexical_cast.hpp>
+#include <wx/spinctrl.h>
+#include "lib/ffmpeg_content.h"
+#include "subtitle_panel.h"
+#include "film_editor.h"
+#include "wx_util.h"
+
+using std::vector;
+using std::string;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::dynamic_pointer_cast;
+
+SubtitlePanel::SubtitlePanel (FilmEditor* e)
+       : FilmEditorPanel (e, _("Subtitles"))
+{
+       wxFlexGridSizer* grid = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       _sizer->Add (grid, 0, wxALL, 8);
+
+       _with_subtitles = new wxCheckBox (this, wxID_ANY, _("With Subtitles"));
+       grid->Add (_with_subtitles, 1);
+       grid->AddSpacer (0);
+       
+       {
+               add_label_to_sizer (grid, this, _("Subtitle Offset"), true);
+               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _offset = new wxSpinCtrl (this);
+               s->Add (_offset);
+               add_label_to_sizer (s, this, _("%"), false);
+               grid->Add (s);
+       }
+
+       {
+               add_label_to_sizer (grid, this, _("Subtitle Scale"), true);
+               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _scale = new wxSpinCtrl (this);
+               s->Add (_scale);
+               add_label_to_sizer (s, this, _("%"), false);
+               grid->Add (s);
+       }
+
+       add_label_to_sizer (grid, this, _("Subtitle Stream"), true);
+       _stream = new wxChoice (this, wxID_ANY);
+       grid->Add (_stream, 1, wxEXPAND);
+       
+       _offset->SetRange (-100, 100);
+       _scale->SetRange (1, 1000);
+       _scale->SetValue (100);
+
+       _with_subtitles->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&SubtitlePanel::with_subtitles_toggled, this));
+       _offset->Bind         (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::offset_changed, this));
+       _scale->Bind          (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::scale_changed, this));
+       _stream->Bind         (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&SubtitlePanel::stream_changed, this));
+}
+
+void
+SubtitlePanel::film_changed (Film::Property property)
+{
+       switch (property) {
+       case Film::CONTENT:
+               setup_sensitivity ();
+               break;
+       case Film::WITH_SUBTITLES:
+               checked_set (_with_subtitles, _editor->film()->with_subtitles ());
+               setup_sensitivity ();
+               break;
+       default:
+               break;
+       }
+}
+
+void
+SubtitlePanel::film_content_changed (shared_ptr<Content> c, int property)
+{
+       shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (c);
+       shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
+       
+       if (property == FFmpegContentProperty::SUBTITLE_STREAMS) {
+               _stream->Clear ();
+               if (fc) {
+                       vector<shared_ptr<FFmpegSubtitleStream> > s = fc->subtitle_streams ();
+                       for (vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
+                               _stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
+                       }
+                       
+                       if (fc->subtitle_stream()) {
+                               checked_set (_stream, lexical_cast<string> (fc->subtitle_stream()->id));
+                       } else {
+                               _stream->SetSelection (wxNOT_FOUND);
+                       }
+               }
+               setup_sensitivity ();
+       } else if (property == SubtitleContentProperty::SUBTITLE_OFFSET) {
+               checked_set (_offset, sc ? (sc->subtitle_offset() * 100) : 0);
+       } else if (property == SubtitleContentProperty::SUBTITLE_SCALE) {
+               checked_set (_scale, sc ? (sc->subtitle_scale() * 100) : 100);
+       }
+
+}
+
+void
+SubtitlePanel::with_subtitles_toggled ()
+{
+       if (!_editor->film()) {
+               return;
+       }
+
+       _editor->film()->set_with_subtitles (_with_subtitles->GetValue ());
+}
+
+void
+SubtitlePanel::setup_sensitivity ()
+{
+       bool h = false;
+       bool j = false;
+       if (_editor->film()) {
+               h = _editor->film()->has_subtitles ();
+               j = _editor->film()->with_subtitles ();
+       }
+       
+       _with_subtitles->Enable (h);
+       _offset->Enable (j);
+       _scale->Enable (j);
+       _stream->Enable (j);
+}
+
+void
+SubtitlePanel::stream_changed ()
+{
+       shared_ptr<Content> c = _editor->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 (_stream->GetClientObject (_stream->GetSelection ()));
+       while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
+               ++i;
+       }
+
+       if (i != a.end ()) {
+               fc->set_subtitle_stream (*i);
+       }
+}
+
+void
+SubtitlePanel::offset_changed ()
+{
+       shared_ptr<SubtitleContent> c = _editor->selected_subtitle_content ();
+       if (!c) {
+               return;
+       }
+
+       c->set_subtitle_offset (_offset->GetValue() / 100.0);
+}
+
+void
+SubtitlePanel::scale_changed ()
+{
+       shared_ptr<SubtitleContent> c = _editor->selected_subtitle_content ();
+       if (!c) {
+               return;
+       }
+
+       c->set_subtitle_scale (_scale->GetValue() / 100.0);
+}
+
diff --git a/src/wx/subtitle_panel.h b/src/wx/subtitle_panel.h
new file mode 100644 (file)
index 0000000..3f79518
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+    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 "film_editor_panel.h"
+
+class wxCheckBox;
+class wxSpinCtrl;
+
+class SubtitlePanel : public FilmEditorPanel
+{
+public:
+       SubtitlePanel (FilmEditor *);
+
+       void film_changed (Film::Property);
+       void film_content_changed (boost::shared_ptr<Content>, int);
+
+       
+private:
+       void with_subtitles_toggled ();
+       void offset_changed ();
+       void scale_changed ();
+       void stream_changed ();
+
+       void setup_sensitivity ();
+       
+       wxCheckBox* _with_subtitles;
+       wxSpinCtrl* _offset;
+       wxSpinCtrl* _scale;
+       wxChoice* _stream;
+};
diff --git a/src/wx/timecode.cc b/src/wx/timecode.cc
new file mode 100644 (file)
index 0000000..033bd2b
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+    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 "lib/util.h"
+#include "timecode.h"
+#include "wx_util.h"
+
+using std::string;
+using std::cout;
+using boost::lexical_cast;
+
+Timecode::Timecode (wxWindow* parent)
+       : wxPanel (parent)
+{
+       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);
+
+       _sizer = new wxBoxSizer (wxHORIZONTAL);
+       
+       _editable = new wxPanel (this);
+       wxSizer* editable_sizer = new wxBoxSizer (wxHORIZONTAL);
+       _hours = new wxTextCtrl (_editable, wxID_ANY, wxT(""), wxDefaultPosition, size, 0, validator);
+       _hours->SetMaxLength (2);
+       editable_sizer->Add (_hours);
+       add_label_to_sizer (editable_sizer, _editable, wxT (":"), false);
+       _minutes = new wxTextCtrl (_editable, wxID_ANY, wxT(""), wxDefaultPosition, size, 0, validator);
+       _minutes->SetMaxLength (2);
+       editable_sizer->Add (_minutes);
+       add_label_to_sizer (editable_sizer, _editable, wxT (":"), false);
+       _seconds = new wxTextCtrl (_editable, wxID_ANY, wxT(""), wxDefaultPosition, size, 0, validator);
+       _seconds->SetMaxLength (2);
+       editable_sizer->Add (_seconds);
+       add_label_to_sizer (editable_sizer, _editable, wxT ("."), false);
+       _frames = new wxTextCtrl (_editable, wxID_ANY, wxT(""), wxDefaultPosition, size, 0, validator);
+       _frames->SetMaxLength (2);
+       editable_sizer->Add (_frames);
+       _set_button = new wxButton (_editable, wxID_ANY, _("Set"));
+       editable_sizer->Add (_set_button, 0, wxLEFT | wxRIGHT, 8);
+       _editable->SetSizerAndFit (editable_sizer);
+       _sizer->Add (_editable);
+
+       _fixed = add_label_to_sizer (_sizer, this, wxT ("42"), false);
+       
+       _hours->Bind      (wxEVT_COMMAND_TEXT_UPDATED,   boost::bind (&Timecode::changed, this));
+       _minutes->Bind    (wxEVT_COMMAND_TEXT_UPDATED,   boost::bind (&Timecode::changed, this));
+       _seconds->Bind    (wxEVT_COMMAND_TEXT_UPDATED,   boost::bind (&Timecode::changed, this));
+       _frames->Bind     (wxEVT_COMMAND_TEXT_UPDATED,   boost::bind (&Timecode::changed, this));
+       _set_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&Timecode::set_clicked, this));
+
+       _set_button->Enable (false);
+
+       set_editable (true);
+
+       SetSizerAndFit (_sizer);
+}
+
+void
+Timecode::set (Time t, int fps)
+{
+       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;
+
+       checked_set (_hours, lexical_cast<string> (h));
+       checked_set (_minutes, lexical_cast<string> (m));
+       checked_set (_seconds, lexical_cast<string> (s));
+       checked_set (_frames, lexical_cast<string> (f));
+
+       _fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02d", h, m, s, f));
+}
+
+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 ()
+{
+       _set_button->Enable (true);
+}
+
+void
+Timecode::set_clicked ()
+{
+       Changed ();
+       _set_button->Enable (false);
+}
+
+void
+Timecode::set_editable (bool e)
+{
+       _editable->Show (e);
+       _fixed->Show (!e);
+       _sizer->Layout ();
+}
diff --git a/src/wx/timecode.h b/src/wx/timecode.h
new file mode 100644 (file)
index 0000000..5b094e3
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+    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;
+
+       void set_editable (bool);
+
+       boost::signals2::signal<void ()> Changed;
+
+private:
+       void changed ();
+       void set_clicked ();
+       
+       wxSizer* _sizer;
+       wxPanel* _editable;
+       wxTextCtrl* _hours;
+       wxStaticText* _hours_label;
+       wxTextCtrl* _minutes;
+       wxTextCtrl* _seconds;
+       wxTextCtrl* _frames;
+       wxButton* _set_button;
+       wxStaticText* _fixed;
+};
+
diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc
new file mode 100644 (file)
index 0000000..87070b3
--- /dev/null
@@ -0,0 +1,652 @@
+/*
+    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 "lib/film.h"
+#include "lib/playlist.h"
+#include "film_editor.h"
+#include "timeline.h"
+#include "wx_util.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 boost::noncopyable
+{
+public:
+       View (Timeline& t)
+               : _timeline (t)
+       {
+
+       }
+
+       virtual ~View () {}
+               
+       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<int> 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<int> _last_paint_bbox;
+};
+
+class ContentView : public View
+{
+public:
+       ContentView (Timeline& tl, shared_ptr<Content> c)
+               : View (tl)
+               , _content (c)
+               , _track (0)
+               , _selected (false)
+       {
+               _content_connection = c->Changed.connect (bind (&ContentView::content_changed, this, _2, _3));
+       }
+
+       dcpomatic::Rect<int> bbox () const
+       {
+               shared_ptr<const Film> film = _timeline.film ();
+               shared_ptr<const Content> content = _content.lock ();
+               if (!film || !content) {
+                       return dcpomatic::Rect<int> ();
+               }
+               
+               return dcpomatic::Rect<int> (
+                       time_x (content->position ()) - 8,
+                       y_pos (_track) - 8,
+                       content->length_after_trim () * _timeline.pixels_per_time_unit() + 16,
+                       _timeline.track_height() + 16
+                       );
+       }
+
+       void set_selected (bool s) {
+               _selected = s;
+               force_redraw ();
+       }
+       
+       bool selected () const {
+               return _selected;
+       }
+
+       shared_ptr<Content> content () const {
+               return _content.lock ();
+       }
+
+       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> cont = content ();
+               if (!film || !cont) {
+                       return;
+               }
+
+               Time const position = cont->position ();
+               Time const len = cont->length_after_trim ();
+
+               wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2);
+
+               gc->SetPen (*wxBLACK_PEN);
+               
+               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));
+               }
+
+               wxGraphicsPath path = gc->CreatePath ();
+               path.MoveToPoint    (time_x (position),       y_pos (_track) + 4);
+               path.AddLineToPoint (time_x (position + len), y_pos (_track) + 4);
+               path.AddLineToPoint (time_x (position + len), y_pos (_track + 1) - 4);
+               path.AddLineToPoint (time_x (position),       y_pos (_track + 1) - 4);
+               path.AddLineToPoint (time_x (position),       y_pos (_track) + 4);
+               gc->StrokePath (path);
+               gc->FillPath (path);
+
+               wxString name = wxString::Format (wxT ("%s [%s]"), std_to_wx (cont->path().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 (position), y_pos (_track), len * _timeline.pixels_per_time_unit(), _timeline.track_height()));
+               gc->DrawText (name, time_x (position) + 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, bool frequent)
+       {
+               ensure_ui_thread ();
+               
+               if (p == ContentProperty::POSITION || p == ContentProperty::LENGTH) {
+                       force_redraw ();
+               }
+
+               if (!frequent) {
+                       _timeline.setup_pixels_per_time_unit ();
+                       _timeline.Refresh ();
+               }
+       }
+
+       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)
+               : ContentView (tl, c)
+       {}
+       
+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)
+               : ContentView (tl, c)
+       {}
+
+private:       
+
+       wxString type () const
+       {
+               if (dynamic_pointer_cast<FFmpegContent> (content ())) {
+                       return _("video");
+               } else {
+                       return _("still");
+               }
+       }
+
+       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<int> bbox () const
+       {
+               return dcpomatic::Rect<int> (0, _y - 4, _timeline.width(), 24);
+       }
+
+       void set_y (int y)
+       {
+               _y = y;
+               force_redraw ();
+       }
+
+private:       
+
+       void do_paint (wxGraphicsContext* gc)
+       {
+               gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID));
+               
+               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_position (0)
+       , _first_move (false)
+       , _menu (film, this)
+{
+#ifndef __WXOSX__
+       SetDoubleBuffered (true);
+#endif 
+
+       Bind (wxEVT_PAINT,      boost::bind (&Timeline::paint,       this));
+       Bind (wxEVT_LEFT_DOWN,  boost::bind (&Timeline::left_down,   this, _1));
+       Bind (wxEVT_LEFT_UP,    boost::bind (&Timeline::left_up,     this, _1));
+       Bind (wxEVT_RIGHT_DOWN, boost::bind (&Timeline::right_down,  this, _1));
+       Bind (wxEVT_MOTION,     boost::bind (&Timeline::mouse_moved, this, _1));
+       Bind (wxEVT_SIZE,       boost::bind (&Timeline::resized,     this));
+
+       playlist_changed ();
+
+       SetMinSize (wxSize (640, tracks() * track_height() + 96));
+
+       _playlist_connection = film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this));
+}
+
+void
+Timeline::paint ()
+{
+       wxPaintDC dc (this);
+
+       wxGraphicsContext* gc = wxGraphicsContext::Create (dc);
+       if (!gc) {
+               return;
+       }
+
+       gc->SetFont (gc->CreateFont (*wxNORMAL_FONT));
+
+       for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
+               (*i)->paint (gc);
+       }
+
+       delete gc;
+}
+
+void
+Timeline::playlist_changed ()
+{
+       ensure_ui_thread ();
+       
+       shared_ptr<const Film> fl = _film.lock ();
+       if (!fl) {
+               return;
+       }
+
+       _views.clear ();
+       _views.push_back (_time_axis_view);
+
+       ContentList content = fl->playlist()->content ();
+
+       for (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)));
+               }
+               if (dynamic_pointer_cast<AudioContent> (*i)) {
+                       _views.push_back (shared_ptr<View> (new AudioContentView (*this, *i)));
+               }
+       }
+
+       assign_tracks ();
+       setup_pixels_per_time_unit ();
+       Refresh ();
+}
+
+void
+Timeline::assign_tracks ()
+{
+       for (ViewList::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 (ViewList::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();
+
+               int t = 1;
+               while (1) {
+                       ViewList::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();
+                                       
+                               if (test && test->track() == t) {
+                                       bool const no_overlap =
+                                               (acv_content->position() < test_content->position() && acv_content->end() < test_content->position()) ||
+                                               (acv_content->position() > test_content->end()      && acv_content->end() > test_content->end());
+                                       
+                                       if (!no_overlap) {
+                                               /* 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 ();
+}
+
+shared_ptr<View>
+Timeline::event_to_view (wxMouseEvent& ev)
+{
+       ViewList::iterator i = _views.begin();
+       Position<int> const p (ev.GetX(), ev.GetY());
+       while (i != _views.end() && !(*i)->bbox().contains (p)) {
+               ++i;
+       }
+
+       if (i == _views.end ()) {
+               return shared_ptr<View> ();
+       }
+
+       return *i;
+}
+
+void
+Timeline::left_down (wxMouseEvent& ev)
+{
+       shared_ptr<View> view = event_to_view (ev);
+       shared_ptr<ContentView> content_view = dynamic_pointer_cast<ContentView> (view);
+
+       _down_view.reset ();
+
+       if (content_view) {
+               _down_view = content_view;
+               _down_view_position = content_view->content()->position ();
+       }
+
+       for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
+               shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
+               if (!cv) {
+                       continue;
+               }
+               
+               if (!ev.ShiftDown ()) {
+                       cv->set_selected (view == *i);
+               }
+               
+               if (view == *i) {
+                       _film_editor->set_selection (cv->content ());
+               }
+       }
+
+       if (content_view && ev.ShiftDown ()) {
+               content_view->set_selected (!content_view->selected ());
+       }
+
+       _left_down = true;
+       _down_point = ev.GetPosition ();
+       _first_move = false;
+
+       if (_down_view) {
+               _down_view->content()->set_change_signals_frequent (true);
+       }
+}
+
+void
+Timeline::left_up (wxMouseEvent& ev)
+{
+       _left_down = false;
+
+       if (_down_view) {
+               _down_view->content()->set_change_signals_frequent (false);
+       }
+
+       set_position_from_event (ev);
+}
+
+void
+Timeline::mouse_moved (wxMouseEvent& ev)
+{
+       if (!_left_down) {
+               return;
+       }
+
+       set_position_from_event (ev);
+}
+
+void
+Timeline::right_down (wxMouseEvent& ev)
+{
+       shared_ptr<View> view = event_to_view (ev);
+       shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (view);
+       if (!cv) {
+               return;
+       }
+
+       if (!cv->selected ()) {
+               clear_selection ();
+               cv->set_selected (true);
+       }
+
+       _menu.popup (selected_content (), ev.GetPosition ());
+}
+
+void
+Timeline::set_position_from_event (wxMouseEvent& ev)
+{
+       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) {
+               _down_view->content()->set_position (max (static_cast<Time> (0), _down_view_position + time_diff));
+
+               shared_ptr<Film> film = _film.lock ();
+               assert (film);
+               film->set_sequence_video (false);
+       }
+}
+
+void
+Timeline::force_redraw (dcpomatic::Rect<int> 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 ()
+{
+       setup_pixels_per_time_unit ();
+}
+
+void
+Timeline::clear_selection ()
+{
+       for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
+               shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
+               if (cv) {
+                       cv->set_selected (false);
+               }
+       }
+}
+
+Timeline::ContentViewList
+Timeline::selected_views () const
+{
+       ContentViewList sel;
+       
+       for (ViewList::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+               shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
+               if (cv && cv->selected()) {
+                       sel.push_back (cv);
+               }
+       }
+
+       return sel;
+}
+
+ContentList
+Timeline::selected_content () const
+{
+       ContentList sel;
+       ContentViewList views = selected_views ();
+       
+       for (ContentViewList::const_iterator i = views.begin(); i != views.end(); ++i) {
+               sel.push_back ((*i)->content ());
+       }
+
+       return sel;
+}
diff --git a/src/wx/timeline.h b/src/wx/timeline.h
new file mode 100644 (file)
index 0000000..0217373
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+    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 "lib/util.h"
+#include "lib/rect.h"
+#include "content_menu.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<int> 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<int> tracks_position () const {
+               return Position<int> (8, 8);
+       }
+
+       int tracks () const;
+
+       void setup_pixels_per_time_unit ();
+
+private:
+       void paint ();
+       void left_down (wxMouseEvent &);
+       void left_up (wxMouseEvent &);
+       void right_down (wxMouseEvent &);
+       void mouse_moved (wxMouseEvent &);
+       void playlist_changed ();
+       void resized ();
+       void assign_tracks ();
+       void set_position_from_event (wxMouseEvent &);
+       void clear_selection ();
+
+       typedef std::vector<boost::shared_ptr<View> > ViewList;
+       typedef std::vector<boost::shared_ptr<ContentView> > ContentViewList;
+
+       boost::shared_ptr<View> event_to_view (wxMouseEvent &);
+       ContentViewList selected_views () const;
+       ContentList selected_content () const;
+
+       FilmEditor* _film_editor;
+       boost::weak_ptr<Film> _film;
+       ViewList _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_position;
+       bool _first_move;
+       ContentMenu _menu;
+
+       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..9493d0a
--- /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 "lib/playlist.h"
+#include "film_editor.h"
+#include "timeline_dialog.h"
+#include "wx_util.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;
+};
diff --git a/src/wx/timing_panel.cc b/src/wx/timing_panel.cc
new file mode 100644 (file)
index 0000000..ba645cf
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+    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 "lib/content.h"
+#include "lib/still_image_content.h"
+#include "timing_panel.h"
+#include "wx_util.h"
+#include "timecode.h"
+#include "film_editor.h"
+
+using std::cout;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+
+TimingPanel::TimingPanel (FilmEditor* e)
+       : FilmEditorPanel (e, _("Timing"))
+{
+       wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
+       _sizer->Add (grid, 0, wxALL, 8);
+
+       add_label_to_sizer (grid, this, _("Position"), true);
+       _position = new Timecode (this);
+       grid->Add (_position);
+       add_label_to_sizer (grid, this, _("Length"), true);
+       _length = new Timecode (this);
+       grid->Add (_length);
+       add_label_to_sizer (grid, this, _("Trim from start"), true);
+       _trim_start = new Timecode (this);
+       grid->Add (_trim_start);
+       add_label_to_sizer (grid, this, _("Trim from end"), true);
+       _trim_end = new Timecode (this);
+       grid->Add (_trim_end);
+
+       _position->Changed.connect   (boost::bind (&TimingPanel::position_changed, this));
+       _length->Changed.connect     (boost::bind (&TimingPanel::length_changed, this));
+       _trim_start->Changed.connect (boost::bind (&TimingPanel::trim_start_changed, this));
+       _trim_end->Changed.connect   (boost::bind (&TimingPanel::trim_end_changed, this));
+}
+
+void
+TimingPanel::film_content_changed (shared_ptr<Content> content, int property)
+{
+       if (property == ContentProperty::POSITION) {
+               if (content) {
+                       _position->set (content->position (), _editor->film()->video_frame_rate ());
+               } else {
+                       _position->set (0, 24);
+               }
+       } else if (property == ContentProperty::LENGTH) {
+               if (content) {
+                       _length->set (content->full_length (), _editor->film()->video_frame_rate ());
+               } else {
+                       _length->set (0, 24);
+               }
+       } else if (property == ContentProperty::TRIM_START) {
+               if (content) {
+                       _trim_start->set (content->trim_start (), _editor->film()->video_frame_rate ());
+               } else {
+                       _trim_start->set (0, 24);
+               }
+       } else if (property == ContentProperty::TRIM_END) {
+               if (content) {
+                       _trim_end->set (content->trim_end (), _editor->film()->video_frame_rate ());
+               } else {
+                       _trim_end->set (0, 24);
+               }
+       }       
+
+       _length->set_editable (dynamic_pointer_cast<StillImageContent> (content));
+}
+
+void
+TimingPanel::position_changed ()
+{
+       shared_ptr<Content> c = _editor->selected_content ();
+       if (!c) {
+               return;
+       }
+
+       c->set_position (_position->get (_editor->film()->video_frame_rate ()));
+}
+
+void
+TimingPanel::length_changed ()
+{
+       shared_ptr<Content> c = _editor->selected_content ();
+       if (!c) {
+               return;
+       }
+
+       shared_ptr<StillImageContent> ic = dynamic_pointer_cast<StillImageContent> (c);
+       if (ic) {
+               ic->set_video_length (rint (_length->get (_editor->film()->video_frame_rate()) * ic->video_frame_rate() / TIME_HZ));
+       }
+}
+
+void
+TimingPanel::trim_start_changed ()
+{
+       shared_ptr<Content> c = _editor->selected_content ();
+       if (!c) {
+               return;
+       }
+
+       c->set_trim_start (_trim_start->get (_editor->film()->video_frame_rate ()));
+}
+
+
+void
+TimingPanel::trim_end_changed ()
+{
+       shared_ptr<Content> c = _editor->selected_content ();
+       if (!c) {
+               return;
+       }
+
+       c->set_trim_end (_trim_end->get (_editor->film()->video_frame_rate ()));
+}
diff --git a/src/wx/timing_panel.h b/src/wx/timing_panel.h
new file mode 100644 (file)
index 0000000..b84ea52
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+    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 "film_editor_panel.h"
+
+class Timecode;
+
+class TimingPanel : public FilmEditorPanel
+{
+public:
+       TimingPanel (FilmEditor *);
+
+       void film_content_changed (boost::shared_ptr<Content>, int);
+       
+private:
+       void position_changed ();
+       void length_changed ();
+       void trim_start_changed ();
+       void trim_end_changed ();
+       
+       Timecode* _position;
+       Timecode* _length;
+       Timecode* _trim_start;
+       Timecode* _trim_end;
+};
diff --git a/src/wx/video_panel.cc b/src/wx/video_panel.cc
new file mode 100644 (file)
index 0000000..bb8476d
--- /dev/null
@@ -0,0 +1,393 @@
+/*
+    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 <wx/spinctrl.h>
+#include "lib/ratio.h"
+#include "lib/filter.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/colour_conversion.h"
+#include "lib/config.h"
+#include "filter_dialog.h"
+#include "video_panel.h"
+#include "wx_util.h"
+#include "film_editor.h"
+#include "content_colour_conversion_dialog.h"
+
+using std::vector;
+using std::string;
+using std::pair;
+using std::cout;
+using std::list;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::bind;
+using boost::optional;
+
+VideoPanel::VideoPanel (FilmEditor* e)
+       : FilmEditorPanel (e, _("Video"))
+{
+       wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       _sizer->Add (grid, 0, wxALL, 8);
+
+       int r = 0;
+
+       add_label_to_grid_bag_sizer (grid, this, _("Type"), true, wxGBPosition (r, 0));
+       _frame_type = new wxChoice (this, wxID_ANY);
+       grid->Add (_frame_type, wxGBPosition (r, 1));
+       ++r;
+       
+       add_label_to_grid_bag_sizer (grid, this, _("Left crop"), true, wxGBPosition (r, 0));
+       _left_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+       grid->Add (_left_crop, wxGBPosition (r, 1));
+       ++r;
+
+       add_label_to_grid_bag_sizer (grid, this, _("Right crop"), true, wxGBPosition (r, 0));
+       _right_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+       grid->Add (_right_crop, wxGBPosition (r, 1));
+       ++r;
+       
+       add_label_to_grid_bag_sizer (grid, this, _("Top crop"), true, wxGBPosition (r, 0));
+       _top_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+       grid->Add (_top_crop, wxGBPosition (r, 1));
+       ++r;
+       
+       add_label_to_grid_bag_sizer (grid, this, _("Bottom crop"), true, wxGBPosition (r, 0));
+       _bottom_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+       grid->Add (_bottom_crop, wxGBPosition (r, 1));
+       ++r;
+
+       add_label_to_grid_bag_sizer (grid, this, _("Scale to"), true, wxGBPosition (r, 0));
+       _ratio = new wxChoice (this, wxID_ANY);
+       grid->Add (_ratio, wxGBPosition (r, 1));
+       ++r;
+
+       {
+               add_label_to_grid_bag_sizer (grid, this, _("Filters"), true, wxGBPosition (r, 0));
+               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+
+               wxClientDC dc (this);
+               wxSize size = dc.GetTextExtent (wxT ("A quite long name"));
+               size.SetHeight (-1);
+               
+               _filters = new wxStaticText (this, wxID_ANY, _("None"), wxDefaultPosition, size);
+               s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
+               _filters_button = new wxButton (this, wxID_ANY, _("Edit..."));
+               s->Add (_filters_button, 0, wxALIGN_CENTER_VERTICAL);
+               grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
+       }
+       ++r;
+       
+       {
+               add_label_to_grid_bag_sizer (grid, this, _("Colour conversion"), true, wxGBPosition (r, 0));
+               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+
+               wxClientDC dc (this);
+               wxSize size = dc.GetTextExtent (wxT ("A quite long name"));
+               size.SetHeight (-1);
+               
+               _colour_conversion = new wxStaticText (this, wxID_ANY, wxT (""), wxDefaultPosition, size);
+
+               s->Add (_colour_conversion, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
+               _colour_conversion_button = new wxButton (this, wxID_ANY, _("Edit..."));
+               s->Add (_colour_conversion_button, 0, wxALIGN_CENTER_VERTICAL);
+               grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
+       }
+       ++r;
+
+       _description = new wxStaticText (this, wxID_ANY, wxT ("\n \n \n \n \n"), wxDefaultPosition, wxDefaultSize);
+       grid->Add (_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
+       wxFont font = _description->GetFont();
+       font.SetStyle(wxFONTSTYLE_ITALIC);
+       font.SetPointSize(font.GetPointSize() - 1);
+       _description->SetFont(font);
+       ++r;
+
+       _left_crop->SetRange (0, 1024);
+       _top_crop->SetRange (0, 1024);
+       _right_crop->SetRange (0, 1024);
+       _bottom_crop->SetRange (0, 1024);
+
+       vector<Ratio const *> ratios = Ratio::all ();
+       _ratio->Clear ();
+       for (vector<Ratio const *>::iterator i = ratios.begin(); i != ratios.end(); ++i) {
+               _ratio->Append (std_to_wx ((*i)->nickname ()));
+       }
+
+       _frame_type->Append (_("2D"));
+       _frame_type->Append (_("3D left/right"));
+
+       _frame_type->Bind               (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&VideoPanel::frame_type_changed, this));
+       _left_crop->Bind                (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&VideoPanel::left_crop_changed, this));
+       _right_crop->Bind               (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&VideoPanel::right_crop_changed, this));
+       _top_crop->Bind                 (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&VideoPanel::top_crop_changed, this));
+       _bottom_crop->Bind              (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&VideoPanel::bottom_crop_changed, this));
+       _ratio->Bind                    (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&VideoPanel::ratio_changed, this));
+       _filters_button->Bind           (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&VideoPanel::edit_filters_clicked, this));
+       _colour_conversion_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&VideoPanel::edit_colour_conversion_clicked, this));
+}
+
+
+/** Called when the left crop widget has been changed */
+void
+VideoPanel::left_crop_changed ()
+{
+       shared_ptr<VideoContent> c = _editor->selected_video_content ();
+       if (!c) {
+               return;
+       }
+
+       c->set_left_crop (_left_crop->GetValue ());
+}
+
+/** Called when the right crop widget has been changed */
+void
+VideoPanel::right_crop_changed ()
+{
+       shared_ptr<VideoContent> c = _editor->selected_video_content ();
+       if (!c) {
+               return;
+       }
+
+       c->set_right_crop (_right_crop->GetValue ());
+}
+
+/** Called when the top crop widget has been changed */
+void
+VideoPanel::top_crop_changed ()
+{
+       shared_ptr<VideoContent> c = _editor->selected_video_content ();
+       if (!c) {
+               return;
+       }
+
+       c->set_top_crop (_top_crop->GetValue ());
+}
+
+/** Called when the bottom crop value has been changed */
+void
+VideoPanel::bottom_crop_changed ()
+{
+       shared_ptr<VideoContent> c = _editor->selected_video_content ();
+       if (!c) {
+               return;
+       }
+
+       c->set_bottom_crop (_bottom_crop->GetValue ());
+}
+
+void
+VideoPanel::film_changed (Film::Property property)
+{
+       switch (property) {
+       case Film::CONTAINER:
+       case Film::VIDEO_FRAME_RATE:
+               setup_description ();
+               break;
+       default:
+               break;
+       }
+}
+
+void
+VideoPanel::film_content_changed (shared_ptr<Content> c, int property)
+{
+       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c);
+       shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
+
+       if (property == VideoContentProperty::VIDEO_FRAME_TYPE) {
+               checked_set (_frame_type, vc ? vc->video_frame_type () : VIDEO_FRAME_TYPE_2D);
+               setup_description ();
+       } else if (property == VideoContentProperty::VIDEO_CROP) {
+               checked_set (_left_crop,   vc ? vc->crop().left : 0);
+               checked_set (_right_crop,  vc ? vc->crop().right        : 0);
+               checked_set (_top_crop,    vc ? vc->crop().top  : 0);
+               checked_set (_bottom_crop, vc ? vc->crop().bottom : 0);
+               setup_description ();
+       } else if (property == VideoContentProperty::VIDEO_RATIO) {
+               if (vc) {
+                       int n = 0;
+                       vector<Ratio const *> ratios = Ratio::all ();
+                       vector<Ratio const *>::iterator i = ratios.begin ();
+                       while (i != ratios.end() && *i != vc->ratio()) {
+                               ++i;
+                               ++n;
+                       }
+
+                       if (i == ratios.end()) {
+                               checked_set (_ratio, -1);
+                       } else {
+                               checked_set (_ratio, n);
+                       }
+               } else {
+                       checked_set (_ratio, -1);
+               }
+               setup_description ();
+       } else if (property == VideoContentProperty::VIDEO_FRAME_RATE) {
+               setup_description ();
+       } else if (property == VideoContentProperty::COLOUR_CONVERSION) {
+               optional<size_t> preset = vc ? vc->colour_conversion().preset () : optional<size_t> ();
+               vector<PresetColourConversion> cc = Config::instance()->colour_conversions ();
+               _colour_conversion->SetLabel (preset ? std_to_wx (cc[preset.get()].name) : _("Custom"));
+       } else if (property == FFmpegContentProperty::FILTERS) {
+               if (fc) {
+                       pair<string, string> p = Filter::ffmpeg_strings (fc->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));
+                       }
+               }
+       }
+}
+
+/** Called when the `Edit filters' button has been clicked */
+void
+VideoPanel::edit_filters_clicked ()
+{
+       shared_ptr<Content> c = _editor->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 ();
+}
+
+void
+VideoPanel::setup_description ()
+{
+       shared_ptr<VideoContent> vc = _editor->selected_video_content ();
+       if (!vc) {
+               _description->SetLabel ("");
+               return;
+       }
+
+       wxString d;
+
+       int lines = 0;
+
+       if (vc->video_size().width && vc->video_size().height) {
+               d << wxString::Format (
+                       _("Content video is %dx%d (%.2f:1)\n"),
+                       vc->video_size_after_3d_split().width, vc->video_size_after_3d_split().height,
+                       float (vc->video_size_after_3d_split().width) / vc->video_size_after_3d_split().height
+                       );
+               ++lines;
+       }
+
+       Crop const crop = vc->crop ();
+       if ((crop.left || crop.right || crop.top || crop.bottom) && vc->video_size() != libdcp::Size (0, 0)) {
+               libdcp::Size cropped = vc->video_size_after_3d_split ();
+               cropped.width -= crop.left + crop.right;
+               cropped.height -= crop.top + crop.bottom;
+               d << wxString::Format (
+                       _("Cropped to %dx%d (%.2f:1)\n"),
+                       cropped.width, cropped.height,
+                       float (cropped.width) / cropped.height
+                       );
+               ++lines;
+       }
+
+       Ratio const * ratio = vc->ratio ();
+       if (ratio) {
+               libdcp::Size container_size = _editor->film()->container()->size (_editor->film()->full_frame ());
+               
+               libdcp::Size const scaled = ratio->size (container_size);
+               d << wxString::Format (
+                       _("Scaled to %dx%d (%.2f:1)\n"),
+                       scaled.width, scaled.height,
+                       float (scaled.width) / scaled.height
+                       );
+               ++lines;
+
+               if (scaled != container_size) {
+                       d << wxString::Format (
+                               _("Padded with black to %dx%d (%.2f:1)\n"),
+                               container_size.width, container_size.height,
+                               float (container_size.width) / container_size.height
+                               );
+                       ++lines;
+               }
+       }
+
+       d << wxString::Format (_("Content frame rate %.4f\n"), vc->video_frame_rate ());
+       ++lines;
+       FrameRateConversion frc (vc->video_frame_rate(), _editor->film()->video_frame_rate ());
+       d << frc.description << "\n";
+       ++lines;
+
+       for (int i = lines; i < 6; ++i) {
+               d << wxT ("\n ");
+       }
+
+       _description->SetLabel (d);
+       _sizer->Layout ();
+}
+
+
+void
+VideoPanel::ratio_changed ()
+{
+       if (!_editor->film ()) {
+               return;
+       }
+
+       shared_ptr<VideoContent> vc = _editor->selected_video_content ();
+       
+       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
+VideoPanel::frame_type_changed ()
+{
+       shared_ptr<VideoContent> vc = _editor->selected_video_content ();
+       if (vc) {
+               vc->set_video_frame_type (static_cast<VideoFrameType> (_frame_type->GetSelection ()));
+       }
+}
+
+void
+VideoPanel::edit_colour_conversion_clicked ()
+{
+       shared_ptr<VideoContent> vc = _editor->selected_video_content ();
+       if (!vc) {
+               return;
+       }
+
+       ColourConversion conversion = vc->colour_conversion ();
+       ContentColourConversionDialog* d = new ContentColourConversionDialog (this);
+       d->set (conversion);
+       d->ShowModal ();
+
+       vc->set_colour_conversion (d->get ());
+       d->Destroy ();
+}
diff --git a/src/wx/video_panel.h b/src/wx/video_panel.h
new file mode 100644 (file)
index 0000000..2ecf3c8
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+    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 "lib/film.h"
+#include "film_editor_panel.h"
+
+class wxChoice;
+class wxStaticText;
+class wxSpinCtrl;
+class wxButton;
+
+class VideoPanel : public FilmEditorPanel
+{
+public:
+       VideoPanel (FilmEditor *);
+
+       void film_changed (Film::Property);
+       void film_content_changed (boost::shared_ptr<Content>, int);
+
+private:
+       void left_crop_changed ();
+       void right_crop_changed ();
+       void top_crop_changed ();
+       void bottom_crop_changed ();
+       void edit_filters_clicked ();
+       void ratio_changed ();
+       void frame_type_changed ();
+       void edit_colour_conversion_clicked ();
+
+       void setup_description ();
+
+       wxChoice* _frame_type;
+       wxSpinCtrl* _left_crop;
+       wxSpinCtrl* _right_crop;
+       wxSpinCtrl* _top_crop;
+       wxSpinCtrl* _bottom_crop;
+       wxChoice* _ratio;
+       wxStaticText* _ratio_description;
+       wxStaticText* _description;
+       wxStaticText* _filters;
+       wxButton* _filters_button;
+       wxStaticText* _colour_conversion;
+       wxButton* _colour_conversion_button;
+};
index 82d9d3738a7cb4282c5637033f64e35b9412004f..8f35e2facea49aa784b761ef4dbb3cc341f3b537 100644 (file)
@@ -1,5 +1,66 @@
+import os
+import glob
+from waflib import Logs
+import i18n
+
+sources = """
+          about_dialog.cc
+          audio_dialog.cc
+          audio_mapping_view.cc
+          audio_panel.cc
+          audio_plot.cc
+          cinema_dialog.cc
+          colour_conversion_editor.cc
+          config_dialog.cc
+          content_colour_conversion_dialog.cc
+          content_menu.cc
+          dci_metadata_dialog.cc
+          dir_picker_ctrl.cc
+          film_editor.cc
+          film_editor_panel.cc
+          film_viewer.cc
+          filter_dialog.cc
+          filter_editor.cc
+          gain_calculator_dialog.cc
+          job_manager_view.cc
+          job_wrapper.cc
+          kdm_dialog.cc
+          new_film_dialog.cc
+          preset_colour_conversion_dialog.cc
+          properties_dialog.cc
+          repeat_dialog.cc
+          screen_dialog.cc
+          server_dialog.cc
+          subtitle_panel.cc
+          timecode.cc
+          timeline.cc
+          timeline_dialog.cc
+          timing_panel.cc
+          video_panel.cc
+          wx_util.cc
+          wx_ui_signaller.cc
+          """
+
 def configure(conf):
-    conf.check_cfg(package = '', path = 'wx-config', args = '--cppflags --cxxflags --libs', uselib_store = 'WXWIDGETS', mandatory = True)
+    args = '--cppflags --cxxflags'
+    if not conf.env.STATIC:
+        args += ' --libs'
+
+    conf.check_cfg(msg='Checking for wxWidgets', package='', path=conf.options.wx_config, args=args,
+                   uselib_store='WXWIDGETS', mandatory=True)
+
+    if conf.env.STATIC:
+       # wx-config returns its static libraries as full paths, without -l prefixes, which confuses
+        # check_cfg(), so just hard-code it all.
+        conf.env.STLIB_WXWIDGETS = ['wx_gtk2u_xrc-2.9', 'wx_gtk2u_qa-2.9', 'wx_baseu_net-2.9', 'wx_gtk2u_html-2.9',
+                                    'wx_gtk2u_adv-2.9', 'wx_gtk2u_core-2.9', 'wx_baseu_xml-2.9', 'wx_baseu-2.9']
+        conf.env.LIB_WXWIDGETS = ['tiff', 'SM', 'dl', 'jpeg', 'png', 'X11']
+    conf.in_msg = 1
+    wx_version = conf.check_cfg(package='', path=conf.options.wx_config, args='--version').strip()
+    conf.im_msg = 0
+    if wx_version != '2.9.4' and wx_version != '2.9.5':
+        conf.fatal('wxwidgets version 2.9.4 or 2.9.5 is required; %s found' % wx_version)
 
 def build(bld):
     if bld.env.STATIC:
@@ -7,30 +68,20 @@ def build(bld):
     else:
         obj = bld(features = 'cxx cxxshlib')
 
-    obj.name   = 'libdvdomatic-wx'
-    obj.includes = [ '..' ]
-    obj.export_includes = ['.']
+    obj.name   = 'libdcpomatic-wx'
+#    obj.includes = [ '..' ]
+    obj.export_includes = ['..']
     obj.uselib = 'WXWIDGETS'
-    obj.use = 'libdvdomatic'
-    obj.source = """
-                 config_dialog.cc
-                 dci_name_dialog.cc
-                 dir_picker_ctrl.cc
-                 film_editor.cc
-                 film_viewer.cc
-                 filter_dialog.cc
-                 filter_view.cc
-                 gain_calculator_dialog.cc
-                 job_manager_view.cc
-                 job_wrapper.cc
-                 kdm_dialog.cc
-                 cinema_dialog.cc
-                 new_film_dialog.cc
-                 screen_dialog.cc
-                 properties_dialog.cc
-                 server_dialog.cc
-                 wx_util.cc
-                 wx_ui_signaller.cc
-                 """
-
-    obj.target = 'dvdomatic-wx'
+    if bld.env.TARGET_LINUX:
+        obj.uselib += ' GTK'
+    obj.use = 'libdcpomatic'
+    obj.source = sources
+    obj.target = 'dcpomatic-wx'
+
+    i18n.po_to_mo(os.path.join('src', 'wx'), 'libdcpomatic-wx', bld)
+
+def pot(bld):
+    i18n.pot(os.path.join('src', 'wx'), sources, 'libdcpomatic-wx')
+
+def pot_merge(bld):
+    i18n.pot_merge(os.path.join('src', 'wx'), 'libdcpomatic-wx')
index 2e926edc6af82dd2794281e436710a6dbc80422e..f306319608a0e194d5128b8f490d600179df4bfc 100644 (file)
@@ -29,6 +29,6 @@ wxUISignaller::wxUISignaller (wxEvtHandler* h)
 void
 wxUISignaller::wake_ui ()
 {
-        wxCommandEvent event (-1, -1);
-        _handler->AddPendingEvent (event);
+       wxCommandEvent event (-1, -1);
+       _handler->AddPendingEvent (event);
 }
index d134d2b6d5322cc7bd1dfe77486848ea34ee915f..f7df6fca48a9044f2df8e91f6b4582637cce5687 100644 (file)
@@ -17,7 +17,7 @@
 
 */
 
-#include "ui_signaller.h"
+#include "lib/ui_signaller.h"
 
 class wxEvtHandler;
 
index bc444e4bc2a6370ed54d100cb51f864f17345229..20fd2df755f30240e93995ac9639386085c67d34 100644 (file)
@@ -24,6 +24,8 @@
 #include <boost/thread.hpp>
 #include <wx/filepicker.h>
 #include <wx/spinctrl.h>
+#include "lib/config.h"
+#include "lib/util.h"
 #include "wx_util.h"
 
 using namespace std;
@@ -33,13 +35,45 @@ using namespace boost;
  *  @param s Sizer to add to.
  *  @param p Parent window for the wxStaticText.
  *  @param t Text for the wxStaticText.
+ *  @param left true if this label is a `left label'; ie the sort
+ *  of label which should be right-aligned on OS X.
  *  @param prop Proportion to pass when calling Add() on the wxSizer.
  */
 wxStaticText *
-add_label_to_sizer (wxSizer* s, wxWindow* p, string t, int prop)
+#ifdef __WXOSX__
+add_label_to_sizer (wxSizer* s, wxWindow* p, wxString t, bool left, int prop)
+#else
+add_label_to_sizer (wxSizer* s, wxWindow* p, wxString t, bool, int prop)
+#endif
 {
-       wxStaticText* m = new wxStaticText (p, wxID_ANY, std_to_wx (t));
-       s->Add (m, prop, wxALIGN_CENTER_VERTICAL | wxALL, 6);
+       int flags = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT;
+#ifdef __WXOSX__
+       if (left) {
+               flags |= wxALIGN_RIGHT;
+               t += wxT (":");
+       }
+#endif 
+       wxStaticText* m = new wxStaticText (p, wxID_ANY, t);
+       s->Add (m, prop, flags, 6);
+       return m;
+}
+
+wxStaticText *
+#ifdef __WXOSX__
+add_label_to_grid_bag_sizer (wxGridBagSizer* s, wxWindow* p, wxString t, bool left, wxGBPosition pos, wxGBSpan span)
+#else
+add_label_to_grid_bag_sizer (wxGridBagSizer* s, wxWindow* p, wxString t, bool, wxGBPosition pos, wxGBSpan span)
+#endif
+{
+       int flags = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT;
+#ifdef __WXOSX__
+       if (left) {
+               flags |= wxALIGN_RIGHT;
+               t += wxT (":");
+       }
+#endif 
+       wxStaticText* m = new wxStaticText (p, wxID_ANY, t);
+       s->Add (m, pos, span, flags);
        return m;
 }
 
@@ -48,13 +82,23 @@ add_label_to_sizer (wxSizer* s, wxWindow* p, string t, int prop)
  *  @param m Message.
  */
 void
-error_dialog (wxWindow* parent, string m)
+error_dialog (wxWindow* parent, wxString m)
 {
-       wxMessageDialog* d = new wxMessageDialog (parent, std_to_wx (m), wxT ("DVD-o-matic"), wxOK);
+       wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxOK);
        d->ShowModal ();
        d->Destroy ();
 }
 
+bool
+confirm_dialog (wxWindow* parent, wxString m)
+{
+       wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxYES_NO | wxICON_QUESTION);
+       int const r = d->ShowModal ();
+       d->Destroy ();
+       return r == wxID_YES;
+}
+       
+
 /** @param s wxWidgets string.
  *  @return Corresponding STL string.
  */
@@ -79,10 +123,10 @@ int const ThreadedStaticText::_update_event_id = 10000;
  *  @param initial Initial text for the wxStaticText while the computation is being run.
  *  @param fn Function which works out what the wxStaticText content should be and returns it.
  */
-ThreadedStaticText::ThreadedStaticText (wxWindow* parent, string initial, function<string ()> fn)
-       : wxStaticText (parent, wxID_ANY, std_to_wx (initial))
+ThreadedStaticText::ThreadedStaticText (wxWindow* parent, wxString initial, function<string ()> fn)
+       : wxStaticText (parent, wxID_ANY, initial)
 {
-       Connect (_update_event_id, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ThreadedStaticText::thread_finished), 0, this);
+       Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ThreadedStaticText::thread_finished, this, _1), _update_event_id);
        _thread = new thread (bind (&ThreadedStaticText::run, this, fn));
 }
 
@@ -138,22 +182,15 @@ checked_set (wxSpinCtrl* widget, int value)
 }
 
 void
-checked_set (wxComboBox* widget, int value)
+checked_set (wxChoice* widget, int value)
 {
        if (widget->GetSelection() != value) {
-               if (value == wxNOT_FOUND) {
-                       /* Work around an apparent wxWidgets bug; SetSelection (wxNOT_FOUND)
-                          appears not to work sometimes.
-                       */
-                       widget->SetValue (wxT (""));
-               } else {
-                       widget->SetSelection (value);
-               }
+               widget->SetSelection (value);
        }
 }
 
 void
-checked_set (wxComboBox* widget, string value)
+checked_set (wxChoice* widget, string value)
 {
        wxClientData* o = 0;
        if (widget->GetSelection() != -1) {
@@ -177,6 +214,14 @@ checked_set (wxTextCtrl* widget, string value)
        }
 }
 
+void
+checked_set (wxStaticText* widget, string value)
+{
+       if (widget->GetLabel() != std_to_wx (value)) {
+               widget->SetLabel (std_to_wx (value));
+       }
+}
+
 void
 checked_set (wxCheckBox* widget, bool value)
 {
@@ -192,3 +237,42 @@ checked_set (wxRadioButton* widget, bool value)
                widget->SetValue (value);
        }
 }
+
+void
+dcpomatic_setup_i18n ()
+{
+       int language = wxLANGUAGE_DEFAULT;
+
+       boost::optional<string> config_lang = Config::instance()->language ();
+       if (config_lang && !config_lang->empty ()) {
+               wxLanguageInfo const * li = wxLocale::FindLanguageInfo (std_to_wx (config_lang.get ()));
+               if (li) {
+                       language = li->Language;
+               }
+       }
+
+       wxLocale* locale = 0;
+       if (wxLocale::IsAvailable (language)) {
+               locale = new wxLocale (language, wxLOCALE_LOAD_DEFAULT);
+
+#ifdef DCPOMATIC_WINDOWS
+               locale->AddCatalogLookupPathPrefix (std_to_wx (mo_path().string()));
+#endif         
+
+#ifdef DCPOMATIC_POSIX
+               locale->AddCatalogLookupPathPrefix (POSIX_LOCALE_PREFIX);
+#endif
+
+               locale->AddCatalog (wxT ("libdcpomatic-wx"));
+               locale->AddCatalog (wxT ("dcpomatic"));
+               
+               if (!locale->IsOk()) {
+                       delete locale;
+                       locale = new wxLocale (wxLANGUAGE_ENGLISH);
+               }
+       }
+
+       if (locale) {
+               dcpomatic_setup_gettext_i18n (wx_to_std (locale->GetCanonicalName ()));
+       }
+}
index 6cb7fd00290e8abf553ceade48d651818e440716..d942d8fa83f22f7e07de20e0997c6b5c0eb2bae7 100644 (file)
 
 */
 
+#ifndef DCPOMATIC_WX_UTIL_H
+#define DCPOMATIC_WX_UTIL_H
+
 #include <wx/wx.h>
+#include <wx/gbsizer.h>
 #include <boost/function.hpp>
 #include <boost/thread.hpp>
+#ifdef __WXGTK__
+#include <gtk/gtk.h>
+#endif
 
 class wxFilePickerCtrl;
 class wxSpinCtrl;
+class wxGridBagSizer;
+
+#define DCPOMATIC_SIZER_X_GAP 8
+#define DCPOMATIC_SIZER_Y_GAP 8
+#define DCPOMATIC_DIALOG_BORDER 12
 
 /** @file src/wx/wx_util.h
  *  @brief Some utility functions and classes.
  */
 
-extern void error_dialog (wxWindow *, std::string);
-extern wxStaticText* add_label_to_sizer (wxSizer *, wxWindow *, std::string, int prop = 0);
+extern void error_dialog (wxWindow *, wxString);
+extern bool confirm_dialog (wxWindow *, wxString);
+extern wxStaticText* add_label_to_sizer (wxSizer *, wxWindow *, wxString, bool left, int prop = 0);
+extern wxStaticText* add_label_to_grid_bag_sizer (wxGridBagSizer *, wxWindow *, wxString, bool, wxGBPosition, wxGBSpan span = wxDefaultSpan);
 extern std::string wx_to_std (wxString);
 extern wxString std_to_wx (std::string);
+extern void dcpomatic_setup_i18n ();
 
 /** @class ThreadedStaticText
  *
@@ -41,7 +56,7 @@ extern wxString std_to_wx (std::string);
 class ThreadedStaticText : public wxStaticText
 {
 public:
-       ThreadedStaticText (wxWindow* parent, std::string initial, boost::function<std::string ()> fn);
+       ThreadedStaticText (wxWindow* parent, wxString initial, boost::function<std::string ()> fn);
        ~ThreadedStaticText ();
 
 private:
@@ -58,8 +73,18 @@ extern std::string string_client_data (wxClientData* o);
 
 extern void checked_set (wxFilePickerCtrl* widget, std::string value);
 extern void checked_set (wxSpinCtrl* widget, int value);
-extern void checked_set (wxComboBox* widget, int value);
-extern void checked_set (wxComboBox* widget, std::string value);
+extern void checked_set (wxChoice* widget, int value);
+extern void checked_set (wxChoice* widget, std::string value);
 extern void checked_set (wxTextCtrl* widget, std::string value);
 extern void checked_set (wxCheckBox* widget, bool value);
 extern void checked_set (wxRadioButton* widget, bool value);
+extern void checked_set (wxStaticText* widget, std::string value);
+
+/* GTK 2.24.17 has a buggy GtkFileChooserButton and it was put in Ubuntu 13.04.
+   Use our own dir picker as this is the least bad option I can think of.
+*/
+#if defined(__WXMSW__) || (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION == 24 && GTK_MICRO_VERSION == 17)
+#define DCPOMATIC_USE_OWN_DIR_PICKER
+#endif
+
+#endif
diff --git a/test/4k_test.cc b/test/4k_test.cc
new file mode 100644 (file)
index 0000000..ee9ac0d
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+    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/test/unit_test.hpp>
+#include "lib/film.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ratio.h"
+#include "test.h"
+
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (fourk_test)
+{
+       shared_ptr<Film> film = new_test_film ("4k_test");
+       film->set_name ("4k_test");
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/test.mp4"));
+       c->set_ratio (Ratio::from_id ("185"));
+       film->set_resolution (RESOLUTION_4K);
+       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_container (Ratio::from_id ("185"));
+       film->examine_and_add_content (c);
+       wait_for_jobs ();
+
+       film->make_dcp ();
+       wait_for_jobs ();
+
+       boost::filesystem::path p (test_film_dir ("4k_test"));
+       p /= film->dcp_name ();
+
+       check_dcp ("test/data/4k_test", p.string ());
+}
diff --git a/test/audio_delay_test.cc b/test/audio_delay_test.cc
new file mode 100644 (file)
index 0000000..77243ea
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+    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/test/unit_test.hpp>
+#include <libdcp/sound_frame.h>
+#include <libdcp/cpl.h>
+#include <libdcp/reel.h>
+#include <libdcp/sound_asset.h>
+#include "lib/sndfile_content.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ratio.h"
+#include "lib/film.h"
+#include "test.h"
+
+using std::string;
+using std::cout;
+using boost::lexical_cast;
+using boost::shared_ptr;
+
+static
+void test_audio_delay (int delay_in_ms)
+{
+       string const film_name = "audio_delay_test_" + lexical_cast<string> (delay_in_ms);
+       shared_ptr<Film> film = new_test_film (film_name);
+       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_container (Ratio::from_id ("185"));
+       film->set_name (film_name);
+
+       shared_ptr<SndfileContent> content (new SndfileContent (film, "test/data/staircase.wav"));
+       content->set_audio_delay (delay_in_ms);
+       film->examine_and_add_content (content);
+       wait_for_jobs ();
+
+       film->make_dcp ();
+       wait_for_jobs ();
+
+       boost::filesystem::path path = "build/test";
+       path /= film_name;
+       path /= film->dcp_name ();
+       libdcp::DCP check (path.string ());
+       check.read ();
+
+       shared_ptr<const libdcp::SoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
+       BOOST_CHECK (sound_asset);
+
+       /* Sample index in the DCP */
+       int n = 0;
+       /* DCP sound asset frame */
+       int frame = 0;
+       /* Delay in frames */
+       int const delay_in_frames = delay_in_ms * 48000 / 1000;
+
+       while (n < sound_asset->intrinsic_duration()) {
+               shared_ptr<const libdcp::SoundFrame> sound_frame = sound_asset->get_frame (frame++);
+               uint8_t const * d = sound_frame->data ();
+               
+               for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->channels())) {
+
+                       /* Mono input so it will appear on centre */
+                       int const sample = d[i + 7] | (d[i + 8] << 8);
+
+                       int delayed = n - delay_in_frames;
+                       if (delayed < 0 || delayed >= 4800) {
+                               delayed = 0;
+                       }
+
+                       BOOST_CHECK_EQUAL (sample, delayed);
+                       ++n;
+               }
+       }
+}
+
+
+/* Test audio delay when specified in a piece of audio content */
+BOOST_AUTO_TEST_CASE (audio_delay_test)
+{
+       test_audio_delay (0);
+       test_audio_delay (42);
+       test_audio_delay (-66);
+}
diff --git a/test/audio_merger_test.cc b/test/audio_merger_test.cc
new file mode 100644 (file)
index 0000000..31d055a
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+    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/test/unit_test.hpp>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/signals2.hpp>
+#include "lib/audio_merger.h"
+#include "lib/audio_buffers.h"
+
+using boost::shared_ptr;
+using boost::bind;
+
+static shared_ptr<const AudioBuffers> last_audio;
+
+static int
+pass_through (int x)
+{
+       return x;
+}
+
+BOOST_AUTO_TEST_CASE (audio_merger_test1)
+{
+       AudioMerger<int, int> merger (1, bind (&pass_through, _1), boost::bind (&pass_through, _1));
+
+       /* Push 64 samples, 0 -> 63 at time 0 */
+       shared_ptr<AudioBuffers> buffers (new AudioBuffers (1, 64));
+       for (int i = 0; i < 64; ++i) {
+               buffers->data()[0][i] = i;
+       }
+       merger.push (buffers, 0);
+
+       /* Push 64 samples, 0 -> 63 at time 22 */
+       merger.push (buffers, 22);
+
+       TimedAudioBuffers<int> tb = merger.pull (22);
+       BOOST_CHECK (tb.audio != shared_ptr<const AudioBuffers> ());
+       BOOST_CHECK_EQUAL (tb.audio->frames(), 22);
+       BOOST_CHECK_EQUAL (tb.time, 0);
+
+       /* And they should be a staircase */
+       for (int i = 0; i < 22; ++i) {
+               BOOST_CHECK_EQUAL (tb.audio->data()[0][i], i);
+       }
+
+       tb = merger.flush ();
+
+       /* That flush should give us 64 samples at 22 */
+       BOOST_CHECK_EQUAL (tb.audio->frames(), 64);
+       BOOST_CHECK_EQUAL (tb.time, 22);
+
+       /* Check the sample values */
+       for (int i = 0; i < 64; ++i) {
+               int correct = i;
+               if (i < (64 - 22)) {
+                       correct += i + 22;
+               }
+               BOOST_CHECK_EQUAL (tb.audio->data()[0][i], correct);
+       }
+}
+
+BOOST_AUTO_TEST_CASE (audio_merger_test2)
+{
+       AudioMerger<int, int> merger (1, bind (&pass_through, _1), boost::bind (&pass_through, _1));
+
+       /* Push 64 samples, 0 -> 63 at time 9 */
+       shared_ptr<AudioBuffers> buffers (new AudioBuffers (1, 64));
+       for (int i = 0; i < 64; ++i) {
+               buffers->data()[0][i] = i;
+       }
+       merger.push (buffers, 9);
+
+       TimedAudioBuffers<int> tb = merger.pull (9);
+       BOOST_CHECK_EQUAL (tb.audio->frames(), 9);
+       BOOST_CHECK_EQUAL (tb.time, 0);
+       
+       for (int i = 0; i < 9; ++i) {
+               BOOST_CHECK_EQUAL (tb.audio->data()[0][i], 0);
+       }
+       
+       tb = merger.flush ();
+
+       /* That flush should give us 64 samples at 9 */
+       BOOST_CHECK_EQUAL (tb.audio->frames(), 64);
+       BOOST_CHECK_EQUAL (tb.time, 9);
+       
+       /* Check the sample values */
+       for (int i = 0; i < 64; ++i) {
+               BOOST_CHECK_EQUAL (tb.audio->data()[0][i], i);
+       }
+}
diff --git a/test/black_fill_test.cc b/test/black_fill_test.cc
new file mode 100644 (file)
index 0000000..2c239e7
--- /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 <boost/test/unit_test.hpp>
+#include "lib/still_image_content.h"
+#include "lib/dcp_content_type.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "test.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<StillImageContent> contentA (new StillImageContent (film, "test/data/simple_testcard_640x480.png"));
+       contentA->set_ratio (Ratio::from_id ("185"));
+       shared_ptr<StillImageContent> contentB (new StillImageContent (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);
+       wait_for_jobs ();
+
+       contentA->set_video_length (3);
+       contentA->set_position (film->video_frames_to_time (2));
+       contentB->set_video_length (1);
+       contentB->set_position (film->video_frames_to_time (7));
+
+       film->make_dcp ();
+
+       wait_for_jobs ();
+
+       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());
+}
+
diff --git a/test/client_server_test.cc b/test/client_server_test.cc
new file mode 100644 (file)
index 0000000..8662f54
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+    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/test/unit_test.hpp>
+#include <boost/thread.hpp>
+#include "lib/server.h"
+#include "lib/image.h"
+#include "lib/cross.h"
+#include "lib/dcp_video_frame.h"
+
+using std::list;
+using boost::shared_ptr;
+using boost::thread;
+
+void
+do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription description, shared_ptr<EncodedData> locally_encoded)
+{
+       shared_ptr<EncodedData> remotely_encoded;
+       BOOST_CHECK_NO_THROW (remotely_encoded = frame->encode_remotely (description));
+       BOOST_CHECK (remotely_encoded);
+       
+       BOOST_CHECK_EQUAL (locally_encoded->size(), remotely_encoded->size());
+       BOOST_CHECK (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()) == 0);
+}
+
+BOOST_AUTO_TEST_CASE (client_server_test)
+{
+       shared_ptr<Image> image (new Image (PIX_FMT_RGB24, libdcp::Size (1998, 1080), true));
+       uint8_t* p = image->data()[0];
+       
+       for (int y = 0; y < 1080; ++y) {
+               uint8_t* q = p;
+               for (int x = 0; x < 1998; ++x) {
+                       *q++ = x % 256;
+                       *q++ = y % 256;
+                       *q++ = (x + y) % 256;
+               }
+               p += image->stride()[0];
+       }
+
+       shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, libdcp::Size (100, 200), true));
+       p = sub_image->data()[0];
+       for (int y = 0; y < 200; ++y) {
+               uint8_t* q = p;
+               for (int x = 0; x < 100; ++x) {
+                       *q++ = y % 256;
+                       *q++ = x % 256;
+                       *q++ = (x + y) % 256;
+                       *q++ = 1;
+               }
+               p += sub_image->stride()[0];
+       }
+
+//     shared_ptr<Subtitle> subtitle (new Subtitle (Position<int> (50, 60), sub_image));
+
+       shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test.log"));
+
+       shared_ptr<DCPVideoFrame> frame (
+               new DCPVideoFrame (
+                       image,
+                       0,
+                       EYES_BOTH,
+                       ColourConversion (),
+                       24,
+                       200000000,
+                       log
+                       )
+               );
+
+       shared_ptr<EncodedData> locally_encoded = frame->encode_locally ();
+       BOOST_ASSERT (locally_encoded);
+       
+       Server* server = new Server (log);
+
+       new thread (boost::bind (&Server::run, server, 2));
+
+       /* Let the server get itself ready */
+       dcpomatic_sleep (1);
+
+       ServerDescription description ("localhost", 2);
+
+       list<thread*> threads;
+       for (int i = 0; i < 8; ++i) {
+               threads.push_back (new thread (boost::bind (do_remote_encode, frame, description, locally_encoded)));
+       }
+
+       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
+               (*i)->join ();
+       }
+
+       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
+               delete *i;
+       }
+}
+
diff --git a/test/colour_conversion_test.cc b/test/colour_conversion_test.cc
new file mode 100644 (file)
index 0000000..3e90d54
--- /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/test/unit_test.hpp>
+#include <libdcp/colour_matrix.h>
+#include "lib/colour_conversion.h"
+
+using std::cout;
+
+/* Basic test of identifier() for ColourConversion (i.e. a hash of the numbers) */
+BOOST_AUTO_TEST_CASE (colour_conversion_test)
+{
+       ColourConversion A (2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6);
+       ColourConversion B (2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6);
+
+       BOOST_CHECK_EQUAL (A.identifier(), "246ff9b7dc32c0488948a32a713924b3");
+       BOOST_CHECK_EQUAL (B.identifier(), "a8d1da30f96a121d8db06a03409758b3");
+}
diff --git a/test/ffmpeg_audio_test.cc b/test/ffmpeg_audio_test.cc
new file mode 100644 (file)
index 0000000..65c2932
--- /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/test/unit_test.hpp>
+#include <libdcp/cpl.h>
+#include <libdcp/dcp.h>
+#include <libdcp/sound_asset.h>
+#include <libdcp/sound_frame.h>
+#include <libdcp/reel.h>
+#include "lib/sndfile_content.h"
+#include "lib/film.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ratio.h"
+#include "lib/ffmpeg_content.h"
+#include "test.h"
+
+using std::string;
+using boost::lexical_cast;
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (ffmpeg_audio_test)
+{
+       shared_ptr<Film> film = new_test_film ("ffmpeg_audio_test");
+       film->set_name ("ffmpeg_audio_test");
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/staircase.mov"));
+       c->set_ratio (Ratio::from_id ("185"));
+       film->examine_and_add_content (c);
+
+       wait_for_jobs ();
+
+       film->set_container (Ratio::from_id ("185"));
+       film->set_audio_channels (6);
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       film->make_dcp ();
+       film->write_metadata ();
+
+       wait_for_jobs ();
+
+       boost::filesystem::path path = "build/test";
+       path /= "ffmpeg_audio_test";
+       path /= film->dcp_name ();
+       libdcp::DCP check (path.string ());
+       check.read ();
+
+       shared_ptr<const libdcp::SoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
+       BOOST_CHECK (sound_asset);
+       BOOST_CHECK (sound_asset->channels () == 6);
+
+       /* Sample index in the DCP */
+       int n = 0;
+       /* DCP sound asset frame */
+       int frame = 0;
+
+       while (n < sound_asset->intrinsic_duration()) {
+               shared_ptr<const libdcp::SoundFrame> sound_frame = sound_asset->get_frame (frame++);
+               uint8_t const * d = sound_frame->data ();
+               
+               for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->channels())) {
+
+                       if (sound_asset->channels() > 0) {
+                               /* L should be silent */
+                               int const sample = d[i + 0] | (d[i + 1] << 8);
+                               BOOST_CHECK_EQUAL (sample, 0);
+                       }
+
+                       if (sound_asset->channels() > 1) {
+                               /* R should be silent */
+                               int const sample = d[i + 2] | (d[i + 3] << 8);
+                               BOOST_CHECK_EQUAL (sample, 0);
+                       }
+                       
+                       if (sound_asset->channels() > 2) {
+                               /* Mono input so it will appear on centre */
+                               int const sample = d[i + 7] | (d[i + 8] << 8);
+                               BOOST_CHECK_EQUAL (sample, n);
+                       }
+
+                       if (sound_asset->channels() > 3) {
+                               /* Lfe should be silent */
+                               int const sample = d[i + 9] | (d[i + 10] << 8);
+                               BOOST_CHECK_EQUAL (sample, 0);
+                       }
+
+                       if (sound_asset->channels() > 4) {
+                               /* Ls should be silent */
+                               int const sample = d[i + 11] | (d[i + 12] << 8);
+                               BOOST_CHECK_EQUAL (sample, 0);
+                       }
+
+
+                       if (sound_asset->channels() > 5) {
+                               /* Rs should be silent */
+                               int const sample = d[i + 13] | (d[i + 14] << 8);
+                               BOOST_CHECK_EQUAL (sample, 0);
+                       }
+
+                       ++n;
+               }
+       }
+}
diff --git a/test/ffmpeg_dcp_test.cc b/test/ffmpeg_dcp_test.cc
new file mode 100644 (file)
index 0000000..ef10e60
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+    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/test/unit_test.hpp>
+#include <boost/filesystem.hpp>
+#include "lib/film.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/ratio.h"
+#include "lib/dcp_content_type.h"
+#include "test.h"
+
+using boost::shared_ptr;
+
+/** @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_jobs ();
+       
+       film->set_container (Ratio::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       film->make_dcp ();
+       film->write_metadata ();
+
+       wait_for_jobs ();
+}
+
+/** 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->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..24c9188
--- /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 <boost/test/unit_test.hpp>
+#include "lib/ffmpeg_examiner.h"
+#include "lib/ffmpeg_content.h"
+#include "test.h"
+
+using boost::shared_ptr;
+
+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..c6e9c03
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+    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/test/unit_test.hpp>
+#include "lib/film.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/ffmpeg_content.h"
+#include "test.h"
+
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
+{
+       shared_ptr<Film> film = new_test_film ("ffmpeg_pts_offset_test");
+       shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/test.mp4"));
+       content->_audio_stream.reset (new FFmpegAudioStream);
+       content->_video_frame_rate = 24;
+
+       {
+               /* Sound == video so no offset required */
+               content->_first_video = 0;
+               content->_audio_stream->first_audio = 0;
+               FFmpegDecoder decoder (film, content, true, true);
+               BOOST_CHECK_EQUAL (decoder._video_pts_offset, 0);
+               BOOST_CHECK_EQUAL (decoder._audio_pts_offset, 0);
+       }
+
+       {
+               /* Common offset should be removed */
+               content->_first_video = 600;
+               content->_audio_stream->first_audio = 600;
+               FFmpegDecoder decoder (film, content, true, true);
+               BOOST_CHECK_EQUAL (decoder._video_pts_offset, -600);
+               BOOST_CHECK_EQUAL (decoder._audio_pts_offset, -600);
+       }
+
+       {
+               /* Video is on a frame boundary */
+               content->_first_video = 1.0 / 24.0;
+               content->_audio_stream->first_audio = 0;
+               FFmpegDecoder decoder (film, content, true, true);
+               BOOST_CHECK_EQUAL (decoder._video_pts_offset, 0);
+               BOOST_CHECK_EQUAL (decoder._audio_pts_offset, 0);
+       }
+
+       {
+               /* Video is off a frame boundary */
+               double const frame = 1.0 / 24.0;
+               content->_first_video = frame + 0.0215;
+               content->_audio_stream->first_audio = 0;
+               FFmpegDecoder decoder (film, content, true, true);
+               BOOST_CHECK_EQUAL (decoder._video_pts_offset, (frame - 0.0215));
+               BOOST_CHECK_EQUAL (decoder._audio_pts_offset, (frame - 0.0215));
+       }
+
+       {
+               /* Video is off a frame boundary and both have a common offset */
+               double const frame = 1.0 / 24.0;
+               content->_first_video = frame + 0.0215 + 4.1;
+               content->_audio_stream->first_audio = 4.1;
+               FFmpegDecoder decoder (film, content, true, true);
+               BOOST_CHECK_EQUAL (decoder._video_pts_offset, (frame - 0.0215) - 4.1);
+               BOOST_CHECK_EQUAL (decoder._audio_pts_offset, (frame - 0.0215) - 4.1);
+       }
+}
diff --git a/test/film_metadata_test.cc b/test/film_metadata_test.cc
new file mode 100644 (file)
index 0000000..324787f
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+    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 <sstream>
+#include <boost/test/unit_test.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/date_time.hpp>
+#include "lib/film.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ratio.h"
+
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (film_metadata_test)
+{
+       string const test_film = "build/test/film_metadata_test";
+       
+       if (boost::filesystem::exists (test_film)) {
+               boost::filesystem::remove_all (test_film);
+       }
+
+       shared_ptr<Film> f (new Film (test_film));
+       f->_dci_date = boost::gregorian::from_undelimited_string ("20130211");
+       BOOST_CHECK (f->container() == 0);
+       BOOST_CHECK (f->dcp_content_type() == 0);
+
+       f->set_name ("fred");
+       f->set_dcp_content_type (DCPContentType::from_pretty_name ("Short"));
+       f->set_container (Ratio::from_id ("185"));
+       f->set_j2k_bandwidth (200000000);
+       f->write_metadata ();
+
+       stringstream s;
+       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));
+       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->container(), Ratio::from_id ("185"));
+       
+       g->write_metadata ();
+       BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0);
+}
diff --git a/test/frame_rate_test.cc b/test/frame_rate_test.cc
new file mode 100644 (file)
index 0000000..1cae59e
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+    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/test/unit_test.hpp>
+#include "lib/film.h"
+#include "lib/config.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/playlist.h"
+#include "test.h"
+
+using boost::shared_ptr;
+
+/* Test Playlist::best_dcp_frame_rate and FrameRateConversion
+   with a single piece of content.
+*/
+BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
+{
+       shared_ptr<Film> film = new_test_film ("best_dcp_frame_rate_test_single");
+       /* Get any piece of content, it doesn't matter what */
+       shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/test.mp4"));
+       film->add_content (content);
+       wait_for_jobs ();
+       
+       /* Run some tests with a limited range of allowed rates */
+       
+       std::list<int> afr;
+       afr.push_back (24);
+       afr.push_back (25);
+       afr.push_back (30);
+       Config::instance()->set_allowed_dcp_frame_rates (afr);
+
+       content->_video_frame_rate = 60;
+       int best = film->playlist()->best_dcp_frame_rate ();
+       FrameRateConversion frc = FrameRateConversion (60, best);
+       BOOST_CHECK_EQUAL (best, 30);
+       BOOST_CHECK_EQUAL (frc.skip, true);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+       
+       content->_video_frame_rate = 50;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (50, best);
+       BOOST_CHECK_EQUAL (best, 25);
+       BOOST_CHECK_EQUAL (frc.skip, true);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+
+       content->_video_frame_rate = 48;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (48, best);
+       BOOST_CHECK_EQUAL (best, 24);
+       BOOST_CHECK_EQUAL (frc.skip, true);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+
+       content->_video_frame_rate = 30;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (30, best);
+       BOOST_CHECK_EQUAL (best, 30);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+
+       content->_video_frame_rate = 29.97;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (29.97, best);
+       BOOST_CHECK_EQUAL (best, 30);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, true);
+       
+       content->_video_frame_rate = 25;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (25, best);
+       BOOST_CHECK_EQUAL (best, 25);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+
+       content->_video_frame_rate = 24;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (24, best);
+       BOOST_CHECK_EQUAL (best, 24);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+
+       content->_video_frame_rate = 14.5;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (14.5, best);
+       BOOST_CHECK_EQUAL (best, 30);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, true);
+       BOOST_CHECK_EQUAL (frc.change_speed, true);
+
+       content->_video_frame_rate = 12.6;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (12.6, best);
+       BOOST_CHECK_EQUAL (best, 25);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, true);
+       BOOST_CHECK_EQUAL (frc.change_speed, true);
+
+       content->_video_frame_rate = 12.4;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (12.4, best);
+       BOOST_CHECK_EQUAL (best, 25);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, true);
+       BOOST_CHECK_EQUAL (frc.change_speed, true);
+
+       content->_video_frame_rate = 12;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (12, best);
+       BOOST_CHECK_EQUAL (best, 24);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, true);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+
+       /* Now add some more rates and see if it will use them
+          in preference to skip/repeat.
+       */
+
+       afr.push_back (48);
+       afr.push_back (50);
+       afr.push_back (60);
+       Config::instance()->set_allowed_dcp_frame_rates (afr);
+
+       content->_video_frame_rate = 60;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (60, best);
+       BOOST_CHECK_EQUAL (best, 60);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+       
+       content->_video_frame_rate = 50;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (50, best);
+       BOOST_CHECK_EQUAL (best, 50);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+
+       content->_video_frame_rate = 48;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (48, best);
+       BOOST_CHECK_EQUAL (best, 48);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, false);
+
+       /* Check some out-there conversions (not the best) */
+       
+       frc = FrameRateConversion (14.99, 24);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, true);
+       BOOST_CHECK_EQUAL (frc.change_speed, true);
+
+       /* Check some conversions with limited DCP targets */
+
+       afr.clear ();
+       afr.push_back (24);
+       Config::instance()->set_allowed_dcp_frame_rates (afr);
+
+       content->_video_frame_rate = 25;
+       best = film->playlist()->best_dcp_frame_rate ();
+       frc = FrameRateConversion (25, best);
+       BOOST_CHECK_EQUAL (best, 24);
+       BOOST_CHECK_EQUAL (frc.skip, false);
+       BOOST_CHECK_EQUAL (frc.repeat, false);
+       BOOST_CHECK_EQUAL (frc.change_speed, true);
+}
+
+/* Test Playlist::best_dcp_frame_rate and FrameRateConversion
+   with two pieces of content.
+*/
+BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_double)
+{
+       shared_ptr<Film> film = new_test_film ("best_dcp_frame_rate_test_double");
+       /* Get any old content, it doesn't matter what */
+       shared_ptr<FFmpegContent> A (new FFmpegContent (film, "test/data/test.mp4"));
+       film->add_content (A);
+       shared_ptr<FFmpegContent> B (new FFmpegContent (film, "test/data/test.mp4"));
+       film->add_content (B);
+       wait_for_jobs ();
+
+       /* Run some tests with a limited range of allowed rates */
+       
+       std::list<int> afr;
+       afr.push_back (24);
+       afr.push_back (25);
+       afr.push_back (30);
+       Config::instance()->set_allowed_dcp_frame_rates (afr);
+
+       A->_video_frame_rate = 30;
+       B->_video_frame_rate = 24;
+       BOOST_CHECK_EQUAL (film->playlist()->best_dcp_frame_rate(), 25);
+}
+
+
+BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
+{
+       shared_ptr<Film> film = new_test_film ("audio_sampling_rate_test");
+       /* Get any piece of content, it doesn't matter what */
+       shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/test.mp4"));
+       film->add_content (content);
+       wait_for_jobs ();
+       
+       std::list<int> afr;
+       afr.push_back (24);
+       afr.push_back (25);
+       afr.push_back (30);
+       Config::instance()->set_allowed_dcp_frame_rates (afr);
+
+       content->_video_frame_rate = 24;
+       film->set_video_frame_rate (24);
+       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+       BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 48000);
+
+       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
+       BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 48000);
+
+       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0)));
+       BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 96000);
+
+       content->_video_frame_rate = 23.976;
+       film->set_video_frame_rate (24);
+       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+       BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 47952);
+
+       content->_video_frame_rate = 29.97;
+       film->set_video_frame_rate (30);
+       BOOST_CHECK_EQUAL (film->video_frame_rate (), 30);
+       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+       BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 47952);
+
+       content->_video_frame_rate = 25;
+       film->set_video_frame_rate (24);
+       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+       BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 50000);
+
+       content->_video_frame_rate = 25;
+       film->set_video_frame_rate (24);
+       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
+       BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 50000);
+
+       /* Check some out-there conversions (not the best) */
+       
+       content->_video_frame_rate = 14.99;
+       film->set_video_frame_rate (25);
+       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
+       /* The FrameRateConversion within output_audio_frame_rate should choose to double-up
+          the 14.99 fps video to 30 and then run it slow at 25.
+       */
+       BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), rint (48000 * 2 * 14.99 / 25));
+}
+
diff --git a/test/guessdcp.py b/test/guessdcp.py
new file mode 100644 (file)
index 0000000..d4c41af
--- /dev/null
@@ -0,0 +1,15 @@
+import os
+
+def path(f):
+    for d in os.listdir(f):
+        try:
+            for s in os.listdir(os.path.join(f, d)):
+                if s.endswith('.xml'):
+                    return os.path.join(f, d)
+        except:
+            pass
+
+    return None
+
+if __name__ == '__main__':
+    print path('/home/carl/Unsafe/DCP/Boon')
diff --git a/test/image_test.cc b/test/image_test.cc
new file mode 100644 (file)
index 0000000..82ebe0b
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+    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/test/unit_test.hpp>
+#include "lib/image.h"
+#include "lib/scaler.h"
+
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (aligned_image_test)
+{
+       Image* s = new Image (PIX_FMT_RGB24, libdcp::Size (50, 50), true);
+       BOOST_CHECK_EQUAL (s->components(), 1);
+       /* 160 is 150 aligned to the nearest 32 bytes */
+       BOOST_CHECK_EQUAL (s->stride()[0], 160);
+       BOOST_CHECK_EQUAL (s->line_size()[0], 150);
+       BOOST_CHECK (s->data()[0]);
+       BOOST_CHECK (!s->data()[1]);
+       BOOST_CHECK (!s->data()[2]);
+       BOOST_CHECK (!s->data()[3]);
+
+       /* copy constructor */
+       Image* t = new Image (*s);
+       BOOST_CHECK_EQUAL (t->components(), 1);
+       BOOST_CHECK_EQUAL (t->stride()[0], 160);
+       BOOST_CHECK_EQUAL (t->line_size()[0], 150);
+       BOOST_CHECK (t->data()[0]);
+       BOOST_CHECK (!t->data()[1]);
+       BOOST_CHECK (!t->data()[2]);
+       BOOST_CHECK (!t->data()[3]);
+       BOOST_CHECK (t->data() != s->data());
+       BOOST_CHECK (t->data()[0] != s->data()[0]);
+       BOOST_CHECK (t->line_size() != s->line_size());
+       BOOST_CHECK (t->line_size()[0] == s->line_size()[0]);
+       BOOST_CHECK (t->stride() != s->stride());
+       BOOST_CHECK (t->stride()[0] == s->stride()[0]);
+
+       /* assignment operator */
+       Image* u = new Image (PIX_FMT_YUV422P, libdcp::Size (150, 150), false);
+       *u = *s;
+       BOOST_CHECK_EQUAL (u->components(), 1);
+       BOOST_CHECK_EQUAL (u->stride()[0], 160);
+       BOOST_CHECK_EQUAL (u->line_size()[0], 150);
+       BOOST_CHECK (u->data()[0]);
+       BOOST_CHECK (!u->data()[1]);
+       BOOST_CHECK (!u->data()[2]);
+       BOOST_CHECK (!u->data()[3]);
+       BOOST_CHECK (u->data() != s->data());
+       BOOST_CHECK (u->data()[0] != s->data()[0]);
+       BOOST_CHECK (u->line_size() != s->line_size());
+       BOOST_CHECK (u->line_size()[0] == s->line_size()[0]);
+       BOOST_CHECK (u->stride() != s->stride());
+       BOOST_CHECK (u->stride()[0] == s->stride()[0]);
+
+       delete s;
+       delete t;
+       delete u;
+}
+
+BOOST_AUTO_TEST_CASE (compact_image_test)
+{
+       Image* s = new Image (PIX_FMT_RGB24, libdcp::Size (50, 50), false);
+       BOOST_CHECK_EQUAL (s->components(), 1);
+       BOOST_CHECK_EQUAL (s->stride()[0], 50 * 3);
+       BOOST_CHECK_EQUAL (s->line_size()[0], 50 * 3);
+       BOOST_CHECK (s->data()[0]);
+       BOOST_CHECK (!s->data()[1]);
+       BOOST_CHECK (!s->data()[2]);
+       BOOST_CHECK (!s->data()[3]);
+
+       /* copy constructor */
+       Image* t = new Image (*s);
+       BOOST_CHECK_EQUAL (t->components(), 1);
+       BOOST_CHECK_EQUAL (t->stride()[0], 50 * 3);
+       BOOST_CHECK_EQUAL (t->line_size()[0], 50 * 3);
+       BOOST_CHECK (t->data()[0]);
+       BOOST_CHECK (!t->data()[1]);
+       BOOST_CHECK (!t->data()[2]);
+       BOOST_CHECK (!t->data()[3]);
+       BOOST_CHECK (t->data() != s->data());
+       BOOST_CHECK (t->data()[0] != s->data()[0]);
+       BOOST_CHECK (t->line_size() != s->line_size());
+       BOOST_CHECK (t->line_size()[0] == s->line_size()[0]);
+       BOOST_CHECK (t->stride() != s->stride());
+       BOOST_CHECK (t->stride()[0] == s->stride()[0]);
+
+       /* assignment operator */
+       Image* u = new Image (PIX_FMT_YUV422P, libdcp::Size (150, 150), true);
+       *u = *s;
+       BOOST_CHECK_EQUAL (u->components(), 1);
+       BOOST_CHECK_EQUAL (u->stride()[0], 50 * 3);
+       BOOST_CHECK_EQUAL (u->line_size()[0], 50 * 3);
+       BOOST_CHECK (u->data()[0]);
+       BOOST_CHECK (!u->data()[1]);
+       BOOST_CHECK (!u->data()[2]);
+       BOOST_CHECK (!u->data()[3]);
+       BOOST_CHECK (u->data() != s->data());
+       BOOST_CHECK (u->data()[0] != s->data()[0]);
+       BOOST_CHECK (u->line_size() != s->line_size());
+       BOOST_CHECK (u->line_size()[0] == s->line_size()[0]);
+       BOOST_CHECK (u->stride() != s->stride());
+       BOOST_CHECK (u->stride()[0] == s->stride()[0]);
+
+       delete s;
+       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<Image> image (new Image (PIX_FMT_YUV420P, libdcp::Size (16, 16), true));
+       image->make_black ();
+       Crop crop;
+       crop.top = 3;
+       image->crop (crop, false);
+}
+
+/* Test cropping of a YUV 4:2:0 image by 1 pixel, which used to fail because
+   the U/V copying was not rounded up to the next sample.
+*/
+BOOST_AUTO_TEST_CASE (crop_image_test2)
+{
+       /* Here's a 1998 x 1080 image which is black */
+       shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, libdcp::Size (1998, 1080), true));
+       image->make_black ();
+
+       /* Crop it by 1 pixel */
+       Crop crop;
+       crop.left = 1;
+       image = image->crop (crop, true);
+
+       /* Convert it back to RGB to make comparison to black easier */
+       image = image->scale (image->size(), Scaler::from_id ("bicubic"), PIX_FMT_RGB24, true);
+
+       /* Check that its still black after the crop */
+       uint8_t* p = image->data()[0];
+       for (int y = 0; y < image->size().height; ++y) {
+               uint8_t* q = p;
+               for (int x = 0; x < image->size().width; ++x) {
+                       BOOST_CHECK_EQUAL (*q++, 0);
+                       BOOST_CHECK_EQUAL (*q++, 0);
+                       BOOST_CHECK_EQUAL (*q++, 0);
+               }
+               p += image->stride()[0];
+       }
+}
diff --git a/test/job_test.cc b/test/job_test.cc
new file mode 100644 (file)
index 0000000..a3ad1bb
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+    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/test/unit_test.hpp>
+#include "lib/job.h"
+#include "lib/job_manager.h"
+#include "lib/cross.h"
+
+using std::string;
+using boost::shared_ptr;
+
+class TestJob : public Job
+{
+public:
+       TestJob (shared_ptr<Film> f)
+               : Job (f)
+       {
+
+       }
+
+       void set_finished_ok () {
+               set_state (FINISHED_OK);
+       }
+
+       void set_finished_error () {
+               set_state (FINISHED_ERROR);
+       }
+
+       void run ()
+       {
+               while (1) {
+                       if (finished ()) {
+                               return;
+                       }
+               }
+       }
+
+       string name () const {
+               return "";
+       }
+};
+
+BOOST_AUTO_TEST_CASE (job_manager_test)
+{
+       shared_ptr<Film> f;
+
+       /* Single job */
+       shared_ptr<TestJob> a (new TestJob (f));
+
+       JobManager::instance()->add (a);
+       dcpomatic_sleep (1);
+       BOOST_CHECK_EQUAL (a->running (), true);
+       a->set_finished_ok ();
+       dcpomatic_sleep (2);
+       BOOST_CHECK_EQUAL (a->finished_ok(), true);
+}
diff --git a/test/make_black_test.cc b/test/make_black_test.cc
new file mode 100644 (file)
index 0000000..17c78d2
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+    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/test/unit_test.hpp>
+#include <libdcp/util.h>
+extern "C" {
+#include <libavutil/pixfmt.h>
+}
+#include "lib/image.h"
+#include "lib/scaler.h"
+
+using std::list;
+
+/* Check that Image::make_black works, and doesn't use values which crash
+   sws_scale().
+*/
+BOOST_AUTO_TEST_CASE (make_black_test)
+{
+       libdcp::Size in_size (512, 512);
+       libdcp::Size out_size (1024, 1024);
+
+       list<AVPixelFormat> pix_fmts;
+       pix_fmts.push_back (AV_PIX_FMT_RGB24);
+       pix_fmts.push_back (AV_PIX_FMT_YUV420P);
+       pix_fmts.push_back (AV_PIX_FMT_YUV422P10LE);
+       pix_fmts.push_back (AV_PIX_FMT_YUV422P16LE);
+       pix_fmts.push_back (AV_PIX_FMT_YUV444P9LE);
+       pix_fmts.push_back (AV_PIX_FMT_YUV444P9BE);
+       pix_fmts.push_back (AV_PIX_FMT_YUV444P10LE);
+       pix_fmts.push_back (AV_PIX_FMT_YUV444P10BE);
+       pix_fmts.push_back (AV_PIX_FMT_UYVY422);
+       pix_fmts.push_back (AV_PIX_FMT_YUVJ420P);
+       pix_fmts.push_back (AV_PIX_FMT_YUVJ422P);
+       pix_fmts.push_back (AV_PIX_FMT_YUVJ444P);
+
+       int N = 0;
+       for (list<AVPixelFormat>::const_iterator i = pix_fmts.begin(); i != pix_fmts.end(); ++i) {
+               boost::shared_ptr<Image> foo (new Image (*i, in_size, true));
+               foo->make_black ();
+               boost::shared_ptr<Image> bar = foo->scale (out_size, Scaler::from_id ("bicubic"), PIX_FMT_RGB24, true);
+               
+               uint8_t* p = bar->data()[0];
+               for (int y = 0; y < bar->size().height; ++y) {
+                       uint8_t* q = p;
+                       for (int x = 0; x < bar->line_size()[0]; ++x) {
+                               if (*q != 0) {
+                                       std::cerr << "x=" << x << ", (x%3)=" << (x%3) << "\n";
+                               }
+                               BOOST_CHECK_EQUAL (*q++, 0);
+                       }
+                       p += bar->stride()[0];
+               }
+
+               ++N;
+       }
+}
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 ab5e01e..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-version 1
-name fred
-use_dci_name 1
-content 
-trust_content_header 1
-dcp_content_type Short
-format 185
-left_crop 1
-right_crop 2
-top_crop 3
-bottom_crop 4
-filter pphb
-filter unsharp
-scaler bicubic
-dcp_trim_start 42
-dcp_trim_end 99
-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
-audio_language 
-subtitle_language 
-territory 
-rating 
-studio 
-facility 
-package_type 
-width 0
-height 0
-length 0
-content_digest 
-external_audio_stream external 0 0
-frames_per_second 0
diff --git a/test/pixel_formats_test.cc b/test/pixel_formats_test.cc
new file mode 100644 (file)
index 0000000..1b720d9
--- /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.
+
+*/
+
+#include <boost/test/unit_test.hpp>
+#include <list>
+extern "C" {
+#include <libavutil/pixfmt.h>
+#include <libavcodec/avcodec.h>
+}
+#include "lib/image.h"
+
+using std::list;
+using std::cout;
+
+struct Case
+{
+       Case (AVPixelFormat f, int c, int l0, int l1, int l2, float b0, float b1, float b2)
+               : format(f)
+               , components(c)
+       {
+               lines[0] = l0;
+               lines[1] = l1;
+               lines[2] = l2;
+               bpp[0] = b0;
+               bpp[1] = b1;
+               bpp[2] = b2;
+       }
+       
+       AVPixelFormat format;
+       int components;
+       int lines[3];
+       float bpp[3];
+};
+
+
+BOOST_AUTO_TEST_CASE (pixel_formats_test)
+{
+       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  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV420P,     3, 480, 240, 240, 1, 0.5, 0.5));
+       cases.push_back(Case(AV_PIX_FMT_YUV422P,     3, 480, 480, 480, 1, 0.5, 0.5));
+       cases.push_back(Case(AV_PIX_FMT_YUV422P10LE, 3, 480, 480, 480, 2, 1,   1  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV422P16LE, 3, 480, 480, 480, 2, 1,   1  ));
+       cases.push_back(Case(AV_PIX_FMT_UYVY422,     1, 480, 480, 480, 2, 0,   0  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV444P,     3, 480, 480, 480, 1, 1,   1  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV444P9BE,  3, 480, 480, 480, 2, 2,   2  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV444P9LE,  3, 480, 480, 480, 2, 2,   2  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV444P10BE, 3, 480, 480, 480, 2, 2,   2  ));
+       cases.push_back(Case(AV_PIX_FMT_YUV444P10LE, 3, 480, 480, 480, 2, 2,   2  ));
+
+       for (list<Case>::iterator i = cases.begin(); i != cases.end(); ++i) {
+               AVFrame* f = av_frame_alloc ();
+               f->width = 640;
+               f->height = 480;
+               f->format = static_cast<int> (i->format);
+               av_frame_get_buffer (f, true);
+               Image t (f);
+               BOOST_CHECK_EQUAL(t.components(), i->components);
+               BOOST_CHECK_EQUAL(t.lines(0), i->lines[0]);
+               BOOST_CHECK_EQUAL(t.lines(1), i->lines[1]);
+               BOOST_CHECK_EQUAL(t.lines(2), i->lines[2]);
+               BOOST_CHECK_EQUAL(t.bytes_per_pixel(0), i->bpp[0]);
+               BOOST_CHECK_EQUAL(t.bytes_per_pixel(1), i->bpp[1]);
+               BOOST_CHECK_EQUAL(t.bytes_per_pixel(2), i->bpp[2]);
+       }
+}
diff --git a/test/play_test.cc b/test/play_test.cc
new file mode 100644 (file)
index 0000000..cb5b6cb
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+    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/test/unit_test.hpp>
+#include "lib/player.h"
+#include "lib/ratio.h"
+#include "lib/dcp_content_type.h"
+#include "test.h"
+
+/* This test needs stuff in Player that is only included in debug mode */
+#ifdef DCPOMATIC_DEBUG
+
+using std::cout;
+using boost::optional;
+using boost::shared_ptr;
+
+struct Video
+{
+       boost::shared_ptr<Content> content;
+       boost::shared_ptr<const Image> image;
+       Time time;
+};
+
+class PlayerWrapper
+{
+public:
+       PlayerWrapper (shared_ptr<Player> p)
+               : _player (p)
+       {
+               _player->Video.connect (bind (&PlayerWrapper::process_video, this, _1, _2, _5));
+       }
+
+       void process_video (shared_ptr<const Image> i, bool, Time t)
+       {
+               Video v;
+               v.content = _player->_last_video;
+               v.image = i;
+               v.time = t;
+               _queue.push_front (v);
+       }
+
+       optional<Video> get_video ()
+       {
+               while (_queue.empty() && !_player->pass ()) {}
+               if (_queue.empty ()) {
+                       return optional<Video> ();
+               }
+               
+               Video v = _queue.back ();
+               _queue.pop_back ();
+               return v;
+       }
+
+       void seek (Time t, bool ac)
+       {
+               _player->seek (t, ac);
+               _queue.clear ();
+       }
+
+private:
+       shared_ptr<Player> _player;
+       std::list<Video> _queue;
+};
+
+BOOST_AUTO_TEST_CASE (play_test)
+{
+       shared_ptr<Film> film = new_test_film ("play_test");
+       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_container (Ratio::from_id ("185"));
+       film->set_name ("play_test");
+
+       shared_ptr<FFmpegContent> A (new FFmpegContent (film, "test/data/red_24.mp4"));
+       film->examine_and_add_content (A);
+       wait_for_jobs ();
+
+       BOOST_CHECK_EQUAL (A->video_length(), 16);
+
+       shared_ptr<FFmpegContent> B (new FFmpegContent (film, "test/data/red_30.mp4"));
+       film->examine_and_add_content (B);
+       wait_for_jobs ();
+
+       BOOST_CHECK_EQUAL (B->video_length(), 16);
+       
+       /* Film should have been set to 25fps */
+       BOOST_CHECK_EQUAL (film->video_frame_rate(), 25);
+
+       BOOST_CHECK_EQUAL (A->position(), 0);
+       /* A is 16 frames long at 25 fps */
+       BOOST_CHECK_EQUAL (B->position(), 16 * TIME_HZ / 25);
+
+       shared_ptr<Player> player = film->make_player ();
+       PlayerWrapper wrap (player);
+       /* Seek and audio don't get on at the moment */
+       player->disable_audio ();
+
+       for (int i = 0; i < 32; ++i) {
+               optional<Video> v = wrap.get_video ();
+               BOOST_CHECK (v);
+               if (i < 16) {
+                       BOOST_CHECK (v.get().content == A);
+               } else {
+                       BOOST_CHECK (v.get().content == B);
+               }
+       }
+
+       player->seek (10 * TIME_HZ / 25, true);
+       optional<Video> v = wrap.get_video ();
+       BOOST_CHECK (v);
+       BOOST_CHECK_EQUAL (v.get().time, 10 * TIME_HZ / 25);
+}
+
+#endif
diff --git a/test/ratio_test.cc b/test/ratio_test.cc
new file mode 100644 (file)
index 0000000..11517ed
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+    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/test/unit_test.hpp>
+#include <libdcp/util.h>
+#include "lib/ratio.h"
+
+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/resampler_test.cc b/test/resampler_test.cc
new file mode 100644 (file)
index 0000000..1ef69b0
--- /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/test/unit_test.hpp>
+#include "lib/audio_buffers.h"
+#include "lib/resampler.h"
+
+using std::pair;
+using std::cout;
+using boost::shared_ptr;
+
+static void
+resampler_test_one (int from, int to)
+{
+       Resampler resamp (from, to, 1);
+
+       int total_out = 0;
+
+       /* 3 hours */
+       int64_t const N = from * 60 * 60 * 3;
+       
+       for (int64_t i = 0; i < N; i += 1000) {
+               shared_ptr<AudioBuffers> a (new AudioBuffers (1, 1000));
+               a->make_silent ();
+               pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> r = resamp.run (a, i);
+               BOOST_CHECK_EQUAL (r.second, total_out);
+               total_out += r.first->frames ();
+       }
+}      
+               
+/** Check that the timings that come back from the resampler correspond
+    to the number of samples it generates.
+*/
+BOOST_AUTO_TEST_CASE (resampler_test)
+{
+       resampler_test_one (44100, 48000);
+       resampler_test_one (44100, 46080);
+       resampler_test_one (44100, 50000);
+}
diff --git a/test/scaling_test.cc b/test/scaling_test.cc
new file mode 100644 (file)
index 0000000..8d00be7
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+    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/test/unit_test.hpp>
+#include "lib/still_image_content.h"
+#include "lib/ratio.h"
+#include "lib/film.h"
+#include "lib/dcp_content_type.h"
+#include "test.h"
+
+/** @file test/scaling_test.cc
+ *  @brief Test scaling and black-padding of images from a still-image source.
+ */
+
+using std::string;
+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 ();
+
+       wait_for_jobs ();
+
+       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<StillImageContent> imc (new StillImageContent (film, "test/data/simple_testcard_640x480.png"));
+
+       film->examine_and_add_content (imc);
+
+       wait_for_jobs ();
+       
+       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");
+}
+
diff --git a/test/silence_padding_test.cc b/test/silence_padding_test.cc
new file mode 100644 (file)
index 0000000..82cbad0
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+    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/test/unit_test.hpp>
+#include <libdcp/cpl.h>
+#include <libdcp/dcp.h>
+#include <libdcp/sound_asset.h>
+#include <libdcp/sound_frame.h>
+#include <libdcp/reel.h>
+#include "lib/sndfile_content.h"
+#include "lib/film.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ratio.h"
+#include "test.h"
+
+using std::string;
+using boost::lexical_cast;
+using boost::shared_ptr;
+
+static void test_silence_padding (int channels)
+{
+       string const film_name = "silence_padding_test_" + lexical_cast<string> (channels);
+       shared_ptr<Film> film = new_test_film (film_name);
+       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_container (Ratio::from_id ("185"));
+       film->set_name (film_name);
+
+       shared_ptr<SndfileContent> content (new SndfileContent (film, "test/data/staircase.wav"));
+       film->examine_and_add_content (content);
+       wait_for_jobs ();
+
+       film->set_audio_channels (channels);
+       film->make_dcp ();
+       wait_for_jobs ();
+
+       boost::filesystem::path path = "build/test";
+       path /= film_name;
+       path /= film->dcp_name ();
+       libdcp::DCP check (path.string ());
+       check.read ();
+
+       shared_ptr<const libdcp::SoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
+       BOOST_CHECK (sound_asset);
+       BOOST_CHECK (sound_asset->channels () == channels);
+
+       /* Sample index in the DCP */
+       int n = 0;
+       /* DCP sound asset frame */
+       int frame = 0;
+
+       while (n < sound_asset->intrinsic_duration()) {
+               shared_ptr<const libdcp::SoundFrame> sound_frame = sound_asset->get_frame (frame++);
+               uint8_t const * d = sound_frame->data ();
+               
+               for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->channels())) {
+
+                       if (sound_asset->channels() > 0) {
+                               /* L should be silent */
+                               int const sample = d[i + 0] | (d[i + 1] << 8);
+                               BOOST_CHECK_EQUAL (sample, 0);
+                       }
+
+                       if (sound_asset->channels() > 1) {
+                               /* R should be silent */
+                               int const sample = d[i + 2] | (d[i + 3] << 8);
+                               BOOST_CHECK_EQUAL (sample, 0);
+                       }
+                       
+                       if (sound_asset->channels() > 2) {
+                               /* Mono input so it will appear on centre */
+                               int const sample = d[i + 7] | (d[i + 8] << 8);
+                               BOOST_CHECK_EQUAL (sample, n);
+                       }
+
+                       if (sound_asset->channels() > 3) {
+                               /* Lfe should be silent */
+                               int const sample = d[i + 9] | (d[i + 10] << 8);
+                               BOOST_CHECK_EQUAL (sample, 0);
+                       }
+
+                       if (sound_asset->channels() > 4) {
+                               /* Ls should be silent */
+                               int const sample = d[i + 11] | (d[i + 12] << 8);
+                               BOOST_CHECK_EQUAL (sample, 0);
+                       }
+
+
+                       if (sound_asset->channels() > 5) {
+                               /* Rs should be silent */
+                               int const sample = d[i + 13] | (d[i + 14] << 8);
+                               BOOST_CHECK_EQUAL (sample, 0);
+                       }
+
+                       ++n;
+               }
+       }
+       
+}
+
+BOOST_AUTO_TEST_CASE (silence_padding_test)
+{
+       for (int i = 1; i < MAX_AUDIO_CHANNELS; ++i) {
+               test_silence_padding (i);
+       }
+}
diff --git a/test/stream_test.cc b/test/stream_test.cc
new file mode 100644 (file)
index 0000000..b56f133
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+    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/test/unit_test.hpp>
+#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
+#include "lib/ffmpeg_content.h"
+
+using std::pair;
+using std::list;
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (stream_test)
+{
+       xmlpp::Document doc;
+       xmlpp::Element* root = doc.create_root_node ("FFmpegAudioStream");
+       root->add_child("Name")->add_child_text ("hello there world");
+       root->add_child("Id")->add_child_text ("4");
+       root->add_child("FrameRate")->add_child_text ("44100");
+       root->add_child("Channels")->add_child_text ("2");
+       xmlpp::Element* mapping = root->add_child("Mapping");
+       mapping->add_child("ContentChannels")->add_child_text ("2");
+       {
+               /* L -> L */
+               xmlpp::Element* map = mapping->add_child ("Map");
+               map->add_child("ContentIndex")->add_child_text ("0");
+               map->add_child("DCP")->add_child_text ("0");
+       }
+       {
+               /* L -> C */
+               xmlpp::Element* map = mapping->add_child ("Map");
+               map->add_child("ContentIndex")->add_child_text ("0");
+               map->add_child("DCP")->add_child_text ("2");
+       }
+       {
+               /* R -> R */
+               xmlpp::Element* map = mapping->add_child ("Map");
+               map->add_child("ContentIndex")->add_child_text ("1");
+               map->add_child("DCP")->add_child_text ("1");
+       }
+       {
+               /* R -> C */
+               xmlpp::Element* map = mapping->add_child ("Map");
+               map->add_child("ContentIndex")->add_child_text ("1");
+               map->add_child("DCP")->add_child_text ("2");
+       }
+               
+       FFmpegAudioStream a (shared_ptr<cxml::Node> (new cxml::Node (root)));
+
+       BOOST_CHECK_EQUAL (a.id, 4);
+       BOOST_CHECK_EQUAL (a.frame_rate, 44100);
+       BOOST_CHECK_EQUAL (a.channels, 2);
+       BOOST_CHECK_EQUAL (a.name, "hello there world");
+       BOOST_CHECK_EQUAL (a.mapping.content_channels(), 2);
+       BOOST_CHECK_EQUAL (a.mapping.content_to_dcp().size(), 4);
+
+       list<pair<int, libdcp::Channel> > m = a.mapping.content_to_dcp ();
+       list<pair<int, libdcp::Channel> >::iterator i = m.begin();
+
+       BOOST_CHECK_EQUAL (i->first, 0);
+       BOOST_CHECK_EQUAL (i->second, libdcp::LEFT);
+       ++i;
+       
+       BOOST_CHECK_EQUAL (i->first, 0);
+       BOOST_CHECK_EQUAL (i->second, libdcp::CENTRE);
+       ++i;
+       
+       BOOST_CHECK_EQUAL (i->first, 1);
+       BOOST_CHECK_EQUAL (i->second, libdcp::RIGHT);
+       ++i;
+
+       BOOST_CHECK_EQUAL (i->first, 1);
+       BOOST_CHECK_EQUAL (i->second, libdcp::CENTRE);
+       ++i;
+}
+
index c393aac5e4d485c5a01e59e6410335db9ef7c013..154510738e3200bd4ebc1b15cb52ce58763a42b0 100644 (file)
 
 */
 
-#include <fstream>
-#include <iostream>
-#include <boost/filesystem.hpp>
-#include <boost/algorithm/string/predicate.hpp>
-#include "format.h"
-#include "film.h"
-#include "filter.h"
-#include "job_manager.h"
-#include "util.h"
-#include "exceptions.h"
-#include "delay_line.h"
-#include "image.h"
-#include "log.h"
-#include "dcp_video_frame.h"
-#include "config.h"
-#include "server.h"
-#include "cross.h"
-#include "job.h"
-#include "subtitle.h"
-#include "scaler.h"
-#include "ffmpeg_decoder.h"
-#include "external_audio_decoder.h"
+#include <vector>
+#include <list>
+#include <libdcp/dcp.h>
+#include "lib/config.h"
+#include "lib/util.h"
+#include "lib/ui_signaller.h"
+#include "lib/film.h"
+#include "lib/job_manager.h"
+#include "lib/job.h"
+#include "lib/cross.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 std::cerr;
+using std::list;
 using boost::shared_ptr;
-using boost::thread;
-using boost::dynamic_pointer_cast;
-
-void
-setup_test_config ()
-{
-       Config::instance()->set_num_local_encoding_threads (1);
-       Config::instance()->set_servers (vector<ServerDescription*> ());
-       Config::instance()->set_server_port (61920);
-}
 
-shared_ptr<Film>
-new_test_film (string name)
-{
-       string const d = String::compose ("build/test/%1", name);
-       if (boost::filesystem::exists (d)) {
-               boost::filesystem::remove_all (d);
-       }
-       return shared_ptr<Film> (new Film (d, false));
-}
-
-BOOST_AUTO_TEST_CASE (film_metadata_test)
-{
-       dvdomatic_setup ();
-       setup_test_config ();
-
-       string const test_film = "build/test/film_metadata_test";
-       
-       if (boost::filesystem::exists (test_film)) {
-               boost::filesystem::remove_all (test_film);
-       }
-
-       BOOST_CHECK_THROW (new Film (test_film, true), OpenFileError);
-       
-       shared_ptr<Film> f (new Film (test_film, false));
-       BOOST_CHECK (f->format() == 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_dcp_trim_start (42);
-       f->set_dcp_trim_end (99);
-       f->set_dcp_ab (true);
-       f->write_metadata ();
-
-       stringstream s;
-       s << "diff -u test/metadata.ref " << test_film << "/metadata";
-       BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0);
-
-       shared_ptr<Film> g (new Film (test_film, true));
-
-       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->dcp_trim_start(), 42);
-       BOOST_CHECK_EQUAL (g->dcp_trim_end(), 99);
-       BOOST_CHECK_EQUAL (g->dcp_ab(), true);
-       
-       g->write_metadata ();
-       BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0);
-}
-
-BOOST_AUTO_TEST_CASE (stream_test)
-{
-       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);
-       BOOST_CHECK_EQUAL (a.channel_layout(), 1);
-       BOOST_CHECK_EQUAL (a.name(), "hello there world");
-       BOOST_CHECK_EQUAL (a.to_string(), "ffmpeg 4 44100 1 hello there world");
-
-       ExternalAudioStream e ("external 44100 1", boost::optional<int> (1));
-       BOOST_CHECK_EQUAL (e.sample_rate(), 44100);
-       BOOST_CHECK_EQUAL (e.channel_layout(), 1);
-       BOOST_CHECK_EQUAL (e.to_string(), "external 44100 1");
-
-       SubtitleStream s ("5 a b c", boost::optional<int> (1));
-       BOOST_CHECK_EQUAL (s.id(), 5);
-       BOOST_CHECK_EQUAL (s.name(), "a b c");
-
-       shared_ptr<AudioStream> ff = audio_stream_factory ("ffmpeg 4 44100 1 hello there world", boost::optional<int> (1));
-       shared_ptr<FFmpegAudioStream> cff = dynamic_pointer_cast<FFmpegAudioStream> (ff);
-       BOOST_CHECK (cff);
-       BOOST_CHECK_EQUAL (cff->id(), 4);
-       BOOST_CHECK_EQUAL (cff->sample_rate(), 44100);
-       BOOST_CHECK_EQUAL (cff->channel_layout(), 1);
-       BOOST_CHECK_EQUAL (cff->name(), "hello there world");
-       BOOST_CHECK_EQUAL (cff->to_string(), "ffmpeg 4 44100 1 hello there world");
-
-       shared_ptr<AudioStream> fe = audio_stream_factory ("external 44100 1", boost::optional<int> (1));
-       BOOST_CHECK_EQUAL (fe->sample_rate(), 44100);
-       BOOST_CHECK_EQUAL (fe->channel_layout(), 1);
-       BOOST_CHECK_EQUAL (fe->to_string(), "external 44100 1");
-}
-
-BOOST_AUTO_TEST_CASE (format_test)
-{
-       Format::setup_formats ();
-       
-       Format const * f = Format::from_nickname ("Flat");
-       BOOST_CHECK (f);
-       BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 185);
-       
-       f = Format::from_nickname ("Scope");
-       BOOST_CHECK (f);
-       BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 239);
-}
-
-BOOST_AUTO_TEST_CASE (util_test)
-{
-       string t = "Hello this is a string \"with quotes\" and indeed without them";
-       vector<string> b = split_at_spaces_considering_quotes (t);
-       vector<string>::iterator i = b.begin ();
-       BOOST_CHECK_EQUAL (*i++, "Hello");
-       BOOST_CHECK_EQUAL (*i++, "this");
-       BOOST_CHECK_EQUAL (*i++, "is");
-       BOOST_CHECK_EQUAL (*i++, "a");
-       BOOST_CHECK_EQUAL (*i++, "string");
-       BOOST_CHECK_EQUAL (*i++, "with quotes");
-       BOOST_CHECK_EQUAL (*i++, "and");
-       BOOST_CHECK_EQUAL (*i++, "indeed");
-       BOOST_CHECK_EQUAL (*i++, "without");
-       BOOST_CHECK_EQUAL (*i++, "them");
-}
-
-class NullLog : public Log
+class TestUISignaller : public UISignaller
 {
 public:
-       void do_log (string) {}
-};
-
-void
-do_positive_delay_line_test (int delay_length, int data_length)
-{
-       NullLog log;
-       
-       DelayLine d (&log, 6, delay_length);
-       shared_ptr<AudioBuffers> data (new AudioBuffers (6, data_length));
-
-       int in = 0;
-       int out = 0;
-       int returned = 0;
-       int zeros = 0;
-       
-       for (int i = 0; i < 64; ++i) {
-               for (int j = 0; j < data_length; ++j) {
-                       for (int c = 0; c < 6; ++c ) {
-                               data->data(c)[j] = in;
-                               ++in;
-                       }
-               }
-
-               /* This only works because the delay line modifies the parameter */
-               d.process_audio (data);
-               returned += data->frames ();
+       /* No wakes in tests: we call ui_idle ourselves */
+       void wake_ui ()
+       {
 
-               for (int j = 0; j < data->frames(); ++j) {
-                       if (zeros < delay_length) {
-                               for (int c = 0; c < 6; ++c) {
-                                       BOOST_CHECK_EQUAL (data->data(c)[j], 0);
-                               }
-                               ++zeros;
-                       } else {
-                               for (int c = 0; c < 6; ++c) {
-                                       BOOST_CHECK_EQUAL (data->data(c)[j], out);
-                                       ++out;
-                               }
-                       }
-               }
        }
+};
 
-       BOOST_CHECK_EQUAL (returned, 64 * data_length);
-}
-
-void
-do_negative_delay_line_test (int delay_length, int data_length)
+struct TestConfig
 {
-       NullLog log;
-
-       DelayLine d (&log, 6, delay_length);
-       shared_ptr<AudioBuffers> data (new AudioBuffers (6, data_length));
-
-       int in = 0;
-       int out = -delay_length * 6;
-       int returned = 0;
-       
-       for (int i = 0; i < 256; ++i) {
-               data->set_frames (data_length);
-               for (int j = 0; j < data_length; ++j) {
-                       for (int c = 0; c < 6; ++c) {
-                               data->data(c)[j] = in;
-                               ++in;
-                       }
-               }
+       TestConfig()
+       {
+               dcpomatic_setup();
 
-               /* This only works because the delay line modifies the parameter */
-               d.process_audio (data);
-               returned += data->frames ();
+               Config::instance()->set_num_local_encoding_threads (1);
+               Config::instance()->set_servers (vector<ServerDescription> ());
+               Config::instance()->set_server_port (61920);
+               Config::instance()->set_default_dci_metadata (DCIMetadata ());
+               Config::instance()->set_default_container (static_cast<Ratio*> (0));
+               Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
 
-               for (int j = 0; j < data->frames(); ++j) {
-                       for (int c = 0; c < 6; ++c) {
-                               BOOST_CHECK_EQUAL (data->data(c)[j], out);
-                               ++out;
-                       }
-               }
+               ui_signaller = new TestUISignaller ();
        }
+};
 
-       returned += -delay_length;
-       BOOST_CHECK_EQUAL (returned, 256 * data_length);
-}
-
-BOOST_AUTO_TEST_CASE (delay_line_test)
-{
-       do_positive_delay_line_test (64, 128);
-       do_positive_delay_line_test (128, 64);
-       do_positive_delay_line_test (3, 512);
-       do_positive_delay_line_test (512, 3);
-
-       do_positive_delay_line_test (0, 64);
-
-       do_negative_delay_line_test (-64, 128);
-       do_negative_delay_line_test (-128, 64);
-       do_negative_delay_line_test (-3, 512);
-       do_negative_delay_line_test (-512, 3);
-}
-
-BOOST_AUTO_TEST_CASE (md5_digest_test)
-{
-       string const t = md5_digest ("test/md5.test");
-       BOOST_CHECK_EQUAL (t, "15058685ba99decdc4398c7634796eb0");
+BOOST_GLOBAL_FIXTURE (TestConfig);
 
-       BOOST_CHECK_THROW (md5_digest ("foobar"), OpenFileError);
-}
-
-BOOST_AUTO_TEST_CASE (paths_test)
+boost::filesystem::path
+test_film_dir (string name)
 {
-       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");
+       boost::filesystem::path p;
+       p /= "build";
+       p /= "test";
+       p /= name;
+       return p;
 }
 
-void
-do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* description, shared_ptr<EncodedData> locally_encoded)
+shared_ptr<Film>
+new_test_film (string name)
 {
-       shared_ptr<EncodedData> remotely_encoded;
-       BOOST_CHECK_NO_THROW (remotely_encoded = frame->encode_remotely (description));
-       BOOST_CHECK (remotely_encoded);
+       boost::filesystem::path p = test_film_dir (name);
+       if (boost::filesystem::exists (p)) {
+               boost::filesystem::remove_all (p);
+       }
        
-       BOOST_CHECK_EQUAL (locally_encoded->size(), remotely_encoded->size());
-       BOOST_CHECK (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()) == 0);
+       shared_ptr<Film> f = shared_ptr<Film> (new Film (p.string()));
+       f->write_metadata ();
+       return f;
 }
 
-BOOST_AUTO_TEST_CASE (client_server_test)
+static void
+check_file (string ref, string check)
 {
-       shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, Size (1998, 1080), false));
-       uint8_t* p = image->data()[0];
+       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);
        
-       for (int y = 0; y < 1080; ++y) {
-               for (int x = 0; x < 1998; ++x) {
-                       *p++ = x % 256;
-                       *p++ = y % 256;
-                       *p++ = (x + y) % 256;
-               }
-       }
-
-       shared_ptr<Image> sub_image (new SimpleImage (PIX_FMT_RGBA, Size (100, 200), false));
-       p = sub_image->data()[0];
-       for (int y = 0; y < 200; ++y) {
-               for (int x = 0; x < 100; ++x) {
-                       *p++ = y % 256;
-                       *p++ = x % 256;
-                       *p++ = (x + y) % 256;
-                       *p++ = 1;
-               }
-       }
+       int const buffer_size = 65536;
+       uint8_t* ref_buffer = new uint8_t[buffer_size];
+       uint8_t* check_buffer = new uint8_t[buffer_size];
 
-       shared_ptr<Subtitle> subtitle (new Subtitle (Position (50, 60), sub_image));
-
-       FileLog log ("build/test/client_server_test.log");
-
-       shared_ptr<DCPVideoFrame> frame (
-               new DCPVideoFrame (
-                       image,
-                       subtitle,
-                       Size (1998, 1080),
-                       0,
-                       0,
-                       1,
-                       Scaler::from_id ("bicubic"),
-                       0,
-                       24,
-                       "",
-                       0,
-                       200000000,
-                       &log
-                       )
-               );
-
-       shared_ptr<EncodedData> locally_encoded = frame->encode_locally ();
-       BOOST_ASSERT (locally_encoded);
-       
-       Server* server = new Server (&log);
+       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);
 
-       new thread (boost::bind (&Server::run, server, 2));
-
-       /* Let the server get itself ready */
-       dvdomatic_sleep (1);
-
-       ServerDescription description ("localhost", 2);
-
-       list<thread*> threads;
-       for (int i = 0; i < 8; ++i) {
-               threads.push_back (new thread (boost::bind (do_remote_encode, frame, &description, locally_encoded)));
-       }
-
-       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
-               (*i)->join ();
+               BOOST_CHECK_EQUAL (memcmp (ref_buffer, check_buffer, this_time), 0);
+               N -= this_time;
        }
 
-       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
-               delete *i;
-       }
-}
-
-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 (true);
+       delete[] ref_buffer;
+       delete[] check_buffer;
 
-       while (JobManager::instance()->work_to_do ()) {
-               dvdomatic_sleep (1);
-       }
-       
-       BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
+       fclose (ref_file);
+       fclose (check_file);
 }
 
-BOOST_AUTO_TEST_CASE (make_dcp_with_range_test)
+static void
+note (libdcp::NoteType, string n)
 {
-       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_dcp_trim_end (42);
-       film->make_dcp (true);
-
-       while (JobManager::instance()->work_to_do() && !JobManager::instance()->errors()) {
-               dvdomatic_sleep (1);
-       }
-
-       BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
+       cout << n << "\n";
 }
 
-BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
-{
-       shared_ptr<Film> f = new_test_film ("audio_sampling_rate_test");
-       f->set_frames_per_second (24);
-
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
-       BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000);
-
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
-       BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000);
-
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0)));
-       BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 96000);
-
-       f->set_frames_per_second (23.976);
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
-       BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952);
-
-       f->set_frames_per_second (29.97);
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
-       BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952);
+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)));
 }
 
-class TestJob : public Job
+void
+wait_for_jobs ()
 {
-public:
-       TestJob (shared_ptr<Film> f, shared_ptr<Job> req)
-               : Job (f, req)
-       {
-
-       }
-
-       void set_finished_ok () {
-               set_state (FINISHED_OK);
-       }
-
-       void set_finished_error () {
-               set_state (FINISHED_ERROR);
+       JobManager* jm = JobManager::instance ();
+       while (jm->work_to_do ()) {
+               ui_signaller->ui_idle ();
        }
-
-       void run ()
-       {
-               while (1) {
-                       if (finished ()) {
-                               return;
+       if (jm->errors ()) {
+               for (list<shared_ptr<Job> >::iterator i = jm->_jobs.begin(); i != jm->_jobs.end(); ++i) {
+                       if ((*i)->finished_in_error ()) {
+                               cerr << (*i)->error_summary () << "\n"
+                                    << (*i)->error_details () << "\n";
                        }
                }
        }
+               
+       BOOST_CHECK (!jm->errors());
 
-       string name () const {
-               return "";
-       }
-};
-
-BOOST_AUTO_TEST_CASE (job_manager_test)
-{
-       shared_ptr<Film> f;
-
-       /* Single job, no dependency */
-       shared_ptr<TestJob> a (new TestJob (f, shared_ptr<Job> ()));
-
-       JobManager::instance()->add (a);
-       dvdomatic_sleep (1);
-       BOOST_CHECK_EQUAL (a->running (), true);
-       a->set_finished_ok ();
-       dvdomatic_sleep (2);
-       BOOST_CHECK_EQUAL (a->finished_ok(), true);
-
-       /* Two jobs, dependency */
-       a.reset (new TestJob (f, shared_ptr<Job> ()));
-       shared_ptr<TestJob> b (new TestJob (f, a));
-
-       JobManager::instance()->add (a);
-       JobManager::instance()->add (b);
-       dvdomatic_sleep (2);
-       BOOST_CHECK_EQUAL (a->running(), true);
-       BOOST_CHECK_EQUAL (b->running(), false);
-       a->set_finished_ok ();
-       dvdomatic_sleep (2);
-       BOOST_CHECK_EQUAL (a->finished_ok(), true);
-       BOOST_CHECK_EQUAL (b->running(), true);
-       b->set_finished_ok ();
-       dvdomatic_sleep (2);
-       BOOST_CHECK_EQUAL (b->finished_ok(), true);
-
-       /* Two jobs, dependency, first fails */
-       a.reset (new TestJob (f, shared_ptr<Job> ()));
-       b.reset (new TestJob (f, a));
-
-       JobManager::instance()->add (a);
-       JobManager::instance()->add (b);
-       dvdomatic_sleep (2);
-       BOOST_CHECK_EQUAL (a->running(), true);
-       BOOST_CHECK_EQUAL (b->running(), false);
-       a->set_finished_error ();
-       dvdomatic_sleep (2);
-       BOOST_CHECK_EQUAL (a->finished_in_error(), true);
-       BOOST_CHECK_EQUAL (b->running(), false);
-}
-
-BOOST_AUTO_TEST_CASE (compact_image_test)
-{
-       SimpleImage* s = new SimpleImage (PIX_FMT_RGB24, Size (50, 50), false);
-       BOOST_CHECK_EQUAL (s->components(), 1);
-       BOOST_CHECK_EQUAL (s->stride()[0], 50 * 3);
-       BOOST_CHECK_EQUAL (s->line_size()[0], 50 * 3);
-       BOOST_CHECK (s->data()[0]);
-       BOOST_CHECK (!s->data()[1]);
-       BOOST_CHECK (!s->data()[2]);
-       BOOST_CHECK (!s->data()[3]);
-
-       /* copy constructor */
-       SimpleImage* t = new SimpleImage (*s);
-       BOOST_CHECK_EQUAL (t->components(), 1);
-       BOOST_CHECK_EQUAL (t->stride()[0], 50 * 3);
-       BOOST_CHECK_EQUAL (t->line_size()[0], 50 * 3);
-       BOOST_CHECK (t->data()[0]);
-       BOOST_CHECK (!t->data()[1]);
-       BOOST_CHECK (!t->data()[2]);
-       BOOST_CHECK (!t->data()[3]);
-       BOOST_CHECK (t->data() != s->data());
-       BOOST_CHECK (t->data()[0] != s->data()[0]);
-       BOOST_CHECK (t->line_size() != s->line_size());
-       BOOST_CHECK (t->line_size()[0] == s->line_size()[0]);
-       BOOST_CHECK (t->stride() != s->stride());
-       BOOST_CHECK (t->stride()[0] == s->stride()[0]);
-
-       /* assignment operator */
-       SimpleImage* u = new SimpleImage (PIX_FMT_YUV422P, Size (150, 150), true);
-       *u = *s;
-       BOOST_CHECK_EQUAL (u->components(), 1);
-       BOOST_CHECK_EQUAL (u->stride()[0], 50 * 3);
-       BOOST_CHECK_EQUAL (u->line_size()[0], 50 * 3);
-       BOOST_CHECK (u->data()[0]);
-       BOOST_CHECK (!u->data()[1]);
-       BOOST_CHECK (!u->data()[2]);
-       BOOST_CHECK (!u->data()[3]);
-       BOOST_CHECK (u->data() != s->data());
-       BOOST_CHECK (u->data()[0] != s->data()[0]);
-       BOOST_CHECK (u->line_size() != s->line_size());
-       BOOST_CHECK (u->line_size()[0] == s->line_size()[0]);
-       BOOST_CHECK (u->stride() != s->stride());
-       BOOST_CHECK (u->stride()[0] == s->stride()[0]);
-
-       delete s;
-       delete t;
-       delete u;
-}
-
-BOOST_AUTO_TEST_CASE (aligned_image_test)
-{
-       SimpleImage* s = new SimpleImage (PIX_FMT_RGB24, Size (50, 50), true);
-       BOOST_CHECK_EQUAL (s->components(), 1);
-       /* 160 is 150 aligned to the nearest 32 bytes */
-       BOOST_CHECK_EQUAL (s->stride()[0], 160);
-       BOOST_CHECK_EQUAL (s->line_size()[0], 150);
-       BOOST_CHECK (s->data()[0]);
-       BOOST_CHECK (!s->data()[1]);
-       BOOST_CHECK (!s->data()[2]);
-       BOOST_CHECK (!s->data()[3]);
-
-       /* copy constructor */
-       SimpleImage* t = new SimpleImage (*s);
-       BOOST_CHECK_EQUAL (t->components(), 1);
-       BOOST_CHECK_EQUAL (t->stride()[0], 160);
-       BOOST_CHECK_EQUAL (t->line_size()[0], 150);
-       BOOST_CHECK (t->data()[0]);
-       BOOST_CHECK (!t->data()[1]);
-       BOOST_CHECK (!t->data()[2]);
-       BOOST_CHECK (!t->data()[3]);
-       BOOST_CHECK (t->data() != s->data());
-       BOOST_CHECK (t->data()[0] != s->data()[0]);
-       BOOST_CHECK (t->line_size() != s->line_size());
-       BOOST_CHECK (t->line_size()[0] == s->line_size()[0]);
-       BOOST_CHECK (t->stride() != s->stride());
-       BOOST_CHECK (t->stride()[0] == s->stride()[0]);
-
-       /* assignment operator */
-       SimpleImage* u = new SimpleImage (PIX_FMT_YUV422P, Size (150, 150), false);
-       *u = *s;
-       BOOST_CHECK_EQUAL (u->components(), 1);
-       BOOST_CHECK_EQUAL (u->stride()[0], 160);
-       BOOST_CHECK_EQUAL (u->line_size()[0], 150);
-       BOOST_CHECK (u->data()[0]);
-       BOOST_CHECK (!u->data()[1]);
-       BOOST_CHECK (!u->data()[2]);
-       BOOST_CHECK (!u->data()[3]);
-       BOOST_CHECK (u->data() != s->data());
-       BOOST_CHECK (u->data()[0] != s->data()[0]);
-       BOOST_CHECK (u->line_size() != s->line_size());
-       BOOST_CHECK (u->line_size()[0] == s->line_size()[0]);
-       BOOST_CHECK (u->stride() != s->stride());
-       BOOST_CHECK (u->stride()[0] == s->stride()[0]);
-
-       delete s;
-       delete t;
-       delete u;
+       ui_signaller->ui_idle ();
 }
diff --git a/test/test.h b/test/test.h
new file mode 100644 (file)
index 0000000..5c37f82
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+    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/filesystem.hpp>
+
+class Film;
+
+extern void wait_for_jobs ();
+extern boost::shared_ptr<Film> new_test_film (std::string);
+extern void check_dcp (std::string, std::string);
+extern boost::filesystem::path test_film_dir (std::string);
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
diff --git a/test/threed_test.cc b/test/threed_test.cc
new file mode 100644 (file)
index 0000000..31a95e3
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+    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/test/unit_test.hpp>
+#include "test.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ffmpeg_content.h"
+
+using std::cout;
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (threed_test)
+{
+       shared_ptr<Film> film = new_test_film ("threed_test");
+       film->set_name ("test_film2");
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/test.mp4"));
+       c->set_ratio (Ratio::from_id ("185"));
+       c->set_video_frame_type (VIDEO_FRAME_TYPE_3D_LEFT_RIGHT);
+       film->examine_and_add_content (c);
+
+       wait_for_jobs ();
+       
+       film->set_container (Ratio::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       film->set_three_d (true);
+       film->make_dcp ();
+       film->write_metadata ();
+
+       wait_for_jobs ();
+}
diff --git a/test/torture_partial.py b/test/torture_partial.py
new file mode 100644 (file)
index 0000000..ced8db6
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/python
+
+import sys
+import os
+import shutil
+import random
+
+import guessdcp
+
+if len(sys.argv) < 2:
+    print 'Syntax: %s <film>' % sys.argv[0]
+    sys.exit(1)
+
+film = sys.argv[1]
+
+print 'Creating reference Film'
+os.system('makedcp -n %s' % film)
+
+videos = os.listdir(os.path.join(film, 'video'))
+assert(len(videos) == 1)
+
+full_size = os.path.getsize(os.path.join(film, 'video', videos[0]))
+print 'Video MXF is %d bytes long' % full_size
+
+while 1:
+    film_copy = '%s-copy' % film
+
+    try:
+        shutil.rmtree(film_copy)
+    except:
+        pass
+
+    print 'Copying %s to %s' % (film, film_copy)
+    shutil.copytree(film, film_copy)
+    old_dcp = guessdcp.path(film_copy)
+    print 'Removing %s and log' % old_dcp
+    shutil.rmtree(old_dcp)
+    os.remove(os.path.join(film_copy, 'log'))
+
+    truncated_size = random.randint(1, full_size)
+    print 'Truncating video MXF to %d' % truncated_size
+    videos = os.listdir(os.path.join(film_copy, 'video'))
+    assert(len(videos) == 1)
+    os.system('ls -l %s' % os.path.join(film_copy, 'video'))
+    os.system('truncate %s --size %d' % (os.path.join(film_copy, 'video', videos[0]), truncated_size))
+    os.system('ls -l %s' % os.path.join(film_copy, 'video'))
+
+    print 'Rebuilding'
+    os.system('makedcp -n %s' % film_copy)
+
+    print 'Checking'
+    r = os.system('dcpdiff %s %s' % (guessdcp.path(film), guessdcp.path(film_copy)))
+    if r != 0:
+        print 'FAIL'
+        sys.exit(1)
+
+    print 'OK'
+    print
+
+    print 'Deleting copy'
+    shutil.rmtree(film_copy)
+    
diff --git a/test/util_test.cc b/test/util_test.cc
new file mode 100644 (file)
index 0000000..a5e671f
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+    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/test/unit_test.hpp>
+#include "lib/util.h"
+#include "lib/exceptions.h"
+
+using std::string;
+using std::vector;
+
+BOOST_AUTO_TEST_CASE (util_test)
+{
+       string t = "Hello this is a string \"with quotes\" and indeed without them";
+       vector<string> b = split_at_spaces_considering_quotes (t);
+       vector<string>::iterator i = b.begin ();
+       BOOST_CHECK_EQUAL (*i++, "Hello");
+       BOOST_CHECK_EQUAL (*i++, "this");
+       BOOST_CHECK_EQUAL (*i++, "is");
+       BOOST_CHECK_EQUAL (*i++, "a");
+       BOOST_CHECK_EQUAL (*i++, "string");
+       BOOST_CHECK_EQUAL (*i++, "with quotes");
+       BOOST_CHECK_EQUAL (*i++, "and");
+       BOOST_CHECK_EQUAL (*i++, "indeed");
+       BOOST_CHECK_EQUAL (*i++, "without");
+       BOOST_CHECK_EQUAL (*i++, "them");
+}
+
+BOOST_AUTO_TEST_CASE (md5_digest_test)
+{
+       string const t = md5_digest ("test/data/md5.test");
+       BOOST_CHECK_EQUAL (t, "15058685ba99decdc4398c7634796eb0");
+
+       BOOST_CHECK_THROW (md5_digest ("foobar"), OpenFileError);
+}
index 15d5410b3edbcc37f71172638c304295ce85a783..7c7aee733bd51488f791a57887ea25fa7dcf3260 100644 (file)
@@ -1,15 +1,46 @@
 def configure(conf):
+    boost_test_suffix=''
+    if conf.env.TARGET_WINDOWS:
+        boost_test_suffix='-mt'
+
     conf.check_cxx(fragment = """
                               #define BOOST_TEST_MODULE Config test\n
                              #include <boost/test/unit_test.hpp>\n
                               int main() {}
-                              """, msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework-mt', uselib_store = 'BOOST_TEST')
+                              """, msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework%s' % boost_test_suffix, uselib_store = 'BOOST_TEST')
 
 def build(bld):
     obj = bld(features = 'cxx cxxprogram')
     obj.name   = 'unit-tests'
-    obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC'
-    obj.use    = 'libdvdomatic'
-    obj.source = 'test.cc'
+    obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
+    obj.use    = 'libdcpomatic'
+    obj.source = """
+                 test.cc
+                 colour_conversion_test.cc
+                 audio_delay_test.cc
+                 silence_padding_test.cc
+                 audio_merger_test.cc
+                 resampler_test.cc
+                 ffmpeg_audio_test.cc
+                 threed_test.cc
+                 play_test.cc
+                 frame_rate_test.cc
+                 ffmpeg_pts_offset.cc
+                 ffmpeg_examiner_test.cc
+                 black_fill_test.cc
+                 scaling_test.cc
+                 ratio_test.cc
+                 pixel_formats_test.cc
+                 make_black_test.cc
+                 film_metadata_test.cc
+                 stream_test.cc
+                 util_test.cc
+                 ffmpeg_dcp_test.cc
+                 job_test.cc
+                 client_server_test.cc
+                 image_test.cc
+                 4k_test.cc
+                 """
+
     obj.target = 'unit-tests'
     obj.install_path = ''
diff --git a/version-test.py b/version-test.py
deleted file mode 100644 (file)
index 47b4159..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/bin/python
-
-import version
-
-a = version.Version("0.51")
-assert(a.major == 0)
-assert(a.minor == 51)
-assert(a.pre == False)
-assert(a.beta == None)
-assert(str(a) == "0.51")
-
-a.bump_and_to_pre()
-assert(a.major == 0)
-assert(a.minor == 52)
-assert(a.pre == True)
-assert(a.beta == None)
-assert(str(a) == "0.52pre")
-
-a.bump()
-assert(a.major == 0)
-assert(a.minor == 53)
-assert(a.pre == False)
-assert(a.beta == None)
-assert(str(a) == "0.53")
-
-a.to_pre()
-a.bump_beta()
-assert(a.major == 0)
-assert(a.minor == 53)
-assert(a.pre == False)
-assert(a.beta == 1)
-assert(str(a) == "0.53beta1")
-
-a.bump_beta()
-assert(a.major == 0)
-assert(a.minor == 53)
-assert(a.pre == False)
-assert(a.beta == 2)
-assert(str(a) == "0.53beta2")
-
-a.to_release()
-assert(a.major == 0)
-assert(a.minor == 53)
-assert(a.pre == False)
-assert(a.beta == None)
-assert(str(a) == "0.53")
-
-b = version.Version("1.42beta1")
-assert(b.major == 1)
-assert(b.minor == 42)
-assert(b.pre == False)
-assert(b.beta == 1)
diff --git a/version.py b/version.py
deleted file mode 100644 (file)
index 5b16352..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/usr/bin/python
-
-import os
-import sys
-import datetime
-import shutil
-import copy
-
-class Version:
-    def __init__(self, s):
-        self.pre = False
-        self.beta = None
-
-        if s.startswith("'"):
-            s = s[1:]
-        if s.endswith("'"):
-            s = s[0:-1]
-        
-        if s.endswith('pre'):
-            s = s[0:-3]
-            self.pre = True
-
-        b = s.find("beta")
-        if b != -1:
-            self.beta = int(s[b+4:])
-            s = s[0:b]
-
-        p = s.split('.')
-        self.major = int(p[0])
-        self.minor = int(p[1])
-
-    def bump(self):
-        self.minor += 1
-        self.pre = False
-        self.beta = None
-
-    def to_pre(self):
-        self.pre = True
-        self.beta = None
-
-    def bump_and_to_pre(self):
-        self.bump()
-        self.pre = True
-        self.beta = None
-
-    def to_release(self):
-        self.pre = False
-        self.beta = None
-
-    def bump_beta(self):
-        if self.pre:
-            self.pre = False
-            self.beta = 1
-        elif self.beta is not None:
-            self.beta += 1
-        elif self.beta is None:
-            self.beta = 1
-
-    def __str__(self):
-        s = '%d.%02d' % (self.major, self.minor)
-        if self.beta is not None:
-            s += 'beta%d' % self.beta
-        elif self.pre:
-            s += 'pre'
-
-        return s
-        
-def rewrite_wscript(method):
-    f = open('wscript', 'rw')
-    o = open('wscript.tmp', 'w')
-    version = None
-    while 1:
-        l = f.readline()
-        if l == '':
-            break
-
-        s = l.split()
-        if len(s) == 3 and s[0] == "VERSION":
-            version = Version(s[2])
-            method(version)
-            print "Writing %s" % version
-            print >>o,"VERSION = '%s'" % version
-        else:
-            print >>o,l,
-    f.close()
-    o.close()
-
-    os.rename('wscript.tmp', 'wscript')
-    return version
-
-def append_to_changelog(version):
-    f = open('ChangeLog', 'r')
-    c = f.read()
-    f.close()
-
-    f = open('ChangeLog', 'w')
-    now = datetime.datetime.now()
-    f.write('%d-%02d-%02d  Carl Hetherington  <cth@carlh.net>\n\n\t* Version %s released.\n\n' % (now.year, now.month, now.day, version))
-    f.write(c)
diff --git a/windows/.gtkrc-2.0 b/windows/.gtkrc-2.0
deleted file mode 100755 (executable)
index 0ea1d69..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-gtk-theme-name = "MS-Windows"
-style "user-font"
-{
-       font_name="Tahoma 8"
-}
-widget_class "*" style "user-font"
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/windows/dvdomatic.bmp b/windows/dvdomatic.bmp
deleted file mode 100644 (file)
index 0a196f7..0000000
Binary files a/windows/dvdomatic.bmp and /dev/null differ
diff --git a/windows/dvdomatic.ico b/windows/dvdomatic.ico
deleted file mode 100644 (file)
index 225008c..0000000
Binary files a/windows/dvdomatic.ico and /dev/null differ
diff --git a/windows/dvdomatic.rc b/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/windows/dvdomatic_taskbar.ico b/windows/dvdomatic_taskbar.ico
deleted file mode 100644 (file)
index f4489fa..0000000
Binary files a/windows/dvdomatic_taskbar.ico and /dev/null differ
diff --git a/windows/installer.nsi.32.in b/windows/installer.nsi.32.in
deleted file mode 100644 (file)
index 7b34dfe..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-!include "MUI2.nsh"
-Name "DVD-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"
-
-InstallDir "$PROGRAMFILES\DVD-o-matic"
-
-!insertmacro MUI_PAGE_WELCOME
-!insertmacro MUI_PAGE_LICENSE "../../COPYING"
-!insertmacro MUI_PAGE_DIRECTORY
-!insertmacro MUI_PAGE_INSTFILES
-!insertmacro MUI_PAGE_FINISH
-
-!insertmacro MUI_UNPAGE_WELCOME
-!insertmacro MUI_UNPAGE_CONFIRM
-!insertmacro MUI_UNPAGE_INSTFILES
-!insertmacro MUI_UNPAGE_FINISH
-!insertmacro MUI_LANGUAGE "English"
-
-Section "install" "Installation info"
-SetOutPath "$INSTDIR\bin"
-
-File "%deps%/bin/asdcp-libdcp.dll"
-File "%deps%/bin/avcodec-54.dll"
-File "%deps%/bin/avfilter-3.dll"
-File "%deps%/bin/avformat-54.dll"
-File "%deps%/bin/avutil-51.dll"
-File "%deps%/bin/dcp.dll"
-File "%deps%/bin/libintl-8.dll"
-File "%deps%/bin/kumu-libdcp.dll"
-File "%deps%/bin/libboost_chrono-mt.dll"
-File "%deps%/bin/libboost_filesystem-mt.dll"
-File "%deps%/bin/libboost_system-mt.dll"
-File "%deps%/bin/libboost_thread_win32-mt.dll"
-File "%deps%/bin/libboost_date_time-mt.dll"
-File "%deps%/bin/libeay32.dll"
-File "%deps%/bin/libgcc_s_sjlj-1.dll"
-File "%deps%/bin/libgio-2.0-0.dll"
-File "%deps%/bin/libglib-2.0-0.dll"
-File "%deps%/bin/libgobject-2.0-0.dll"
-File "%deps%/bin/libiconv-2.dll"
-File "%deps%/bin/libjpeg-8.dll"
-File "%deps%/bin/libMagick++-5.dll"
-File "%deps%/bin/libMagickCore-5.dll"
-File "%deps%/bin/libMagickWand-5.dll"
-File "%deps%/bin/libopenjpeg-1.dll"
-File "%deps%/bin/libpng15-15.dll"
-File "%deps%/bin/libsigc-2.0-0.dll"
-File "%deps%/bin/libsndfile-1.dll"
-File "%deps%/bin/libssh.dll"
-File "%deps%/bin/libstdc++-6.dll"
-File "%deps%/bin/postproc-52.dll"
-File "%deps%/bin/swresample-0.dll"
-File "%deps%/bin/swscale-2.dll"
-File "%deps%/bin/zlib1.dll"
-File "%deps%/bin/libjpeg-8.dll"
-File "%deps%/bin/wxbase294u_gcc_custom.dll"
-File "%deps%/bin/wxmsw294u_core_gcc_custom.dll"
-File "%deps%/bin/wxmsw294u_adv_gcc_custom.dll"
-File "%deps%/bin/libcairo-2.dll"
-File "%deps%/bin/libfreetype-6.dll"
-File "%deps%/bin/libgthread-2.0-0.dll"
-File "%deps%/bin/libpango-1.0-0.dll"
-File "%deps%/bin/libgmodule-2.0-0.dll"
-File "%deps%/bin/libpangocairo-1.0-0.dll"
-File "%deps%/bin/libpangowin32-1.0-0.dll"
-File "%deps%/bin/libtiff-5.dll"
-File "%deps%/bin/libglibmm-2.4-1.dll"
-File "%deps%/bin/libxml++-2.6-2.dll"
-File "%deps%/bin/libxml2-2.dll"
-
-File "%binaries%/src/wx/dvdomatic-wx.dll"
-File "%binaries%/src/lib/dvdomatic.dll"
-File "%binaries%/src/tools/dvdomatic.exe"
-File "%binaries%/src/tools/servomatic_cli.exe"
-File "%binaries%/src/tools/servomatic_gui.exe"
-
-# I don't know why, but sometimes it seems that 
-# delegates.xml must be in with the binaries, and
-# sometimes in the $PROFILE.  Meh.
-File "%deps%/etc/ImageMagick/delegates.xml"
-SetOutPath "$PROFILE\.magick"
-File "%deps%/etc/ImageMagick/delegates.xml"
-
-CreateShortCut "$DESKTOP\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" ""
-CreateShortCut "$DESKTOP\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" ""
-CreateDirectory "$SMPROGRAMS\DVD-o-matic"
-CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall.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 encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" "" "$INSTDIR\bin\servomatic_gui.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"
-WriteUninstaller "$INSTDIR\Uninstall.exe"
-SectionEnd
-Section "Uninstall"
-RMDir /r "$INSTDIR\*.*"    
-RMDir "$INSTDIR"
-Delete "$DESKTOP\DVD-o-matic.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"
-SectionEnd
diff --git a/windows/installer.nsi.64.in b/windows/installer.nsi.64.in
deleted file mode 100644 (file)
index d4fa996..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-!include "MUI2.nsh"
-!include "x64.nsh"
-
-Name "DVD-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"
-
-InstallDir "$PROGRAMFILES\DVD-o-matic"
-
-!insertmacro MUI_PAGE_WELCOME
-!insertmacro MUI_PAGE_LICENSE "../../COPYING"
-!insertmacro MUI_PAGE_DIRECTORY
-!insertmacro MUI_PAGE_INSTFILES
-!insertmacro MUI_PAGE_FINISH
-
-!insertmacro MUI_UNPAGE_WELCOME
-!insertmacro MUI_UNPAGE_CONFIRM
-!insertmacro MUI_UNPAGE_INSTFILES
-!insertmacro MUI_UNPAGE_FINISH
-!insertmacro MUI_LANGUAGE "English"
-
-Section "install" "Installation info"
-
-${If} ${RunningX64}
-   DetailPrint "Installer running on 64-bit host"
-   ; disable registry redirection (enable access to 64-bit portion of registry)
-   SetRegView 64
-   ; change install dir
-   StrCpy $INSTDIR "$PROGRAMFILES64\DVD-o-matic"
-${EndIf}
-
-SetOutPath "$INSTDIR\bin"
-
-File "%deps%/bin/asdcp-libdcp.dll"
-File "%deps%/bin/avcodec-54.dll"
-File "%deps%/bin/avfilter-3.dll"
-File "%deps%/bin/avformat-54.dll"
-File "%deps%/bin/avutil-51.dll"
-File "%deps%/bin/dcp.dll"
-File "%deps%/bin/libintl-8.dll"
-File "%deps%/bin/kumu-libdcp.dll"
-File "%deps%/bin/libboost_chrono-mt.dll"
-File "%deps%/bin/libboost_filesystem-mt.dll"
-File "%deps%/bin/libboost_system-mt.dll"
-File "%deps%/bin/libboost_thread_win32-mt.dll"
-File "%deps%/bin/libboost_date_time-mt.dll"
-File "%deps%/bin/libeay32.dll"
-File "%deps%/bin/libgcc_s_sjlj-1.dll"
-File "%deps%/bin/libgio-2.0-0.dll"
-File "%deps%/bin/libglib-2.0-0.dll"
-File "%deps%/bin/libgobject-2.0-0.dll"
-File "%deps%/bin/libiconv-2.dll"
-File "%deps%/bin/libjpeg-8.dll"
-File "%deps%/bin/libMagick++-5.dll"
-File "%deps%/bin/libMagickCore-5.dll"
-File "%deps%/bin/libMagickWand-5.dll"
-File "%deps%/bin/libopenjpeg-1.dll"
-File "%deps%/bin/libpng15-15.dll"
-File "%deps%/bin/libsigc-2.0-0.dll"
-File "%deps%/bin/libsndfile-1.dll"
-File "%deps%/bin/libssh.dll"
-File "%deps%/bin/libstdc++-6.dll"
-File "%deps%/bin/postproc-52.dll"
-File "%deps%/bin/swresample-0.dll"
-File "%deps%/bin/swscale-2.dll"
-File "%deps%/bin/zlib1.dll"
-File "%deps%/bin/libjpeg-8.dll"
-File "%deps%/bin/wxbase294u_gcc_custom.dll"
-File "%deps%/bin/wxmsw294u_core_gcc_custom.dll"
-File "%deps%/bin/wxmsw294u_adv_gcc_custom.dll"
-File "%deps%/bin/libcairo-2.dll"
-File "%deps%/bin/libfreetype-6.dll"
-File "%deps%/bin/libgthread-2.0-0.dll"
-File "%deps%/bin/libpango-1.0-0.dll"
-File "%deps%/bin/libgmodule-2.0-0.dll"
-File "%deps%/bin/libpangocairo-1.0-0.dll"
-File "%deps%/bin/libpangowin32-1.0-0.dll"
-File "%deps%/bin/libtiff-5.dll"
-File "%deps%/bin/libglibmm-2.4-1.dll"
-File "%deps%/bin/libxml++-2.6-2.dll"
-File "%deps%/bin/libxml2-2.dll"
-
-File "%binaries%/src/wx/dvdomatic-wx.dll"
-File "%binaries%/src/lib/dvdomatic.dll"
-File "%binaries%/src/tools/dvdomatic.exe"
-File "%binaries%/src/tools/servomatic_cli.exe"
-File "%binaries%/src/tools/servomatic_gui.exe"
-
-# I don't know why, but sometimes it seems that 
-# delegates.xml must be in with the binaries, and
-# sometimes in the $PROFILE.  Meh.
-File "%deps%/etc/ImageMagick/delegates.xml"
-SetOutPath "$PROFILE\.magick"
-File "%deps%/etc/ImageMagick/delegates.xml"
-
-CreateShortCut "$DESKTOP\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" ""
-CreateShortCut "$DESKTOP\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" ""
-CreateDirectory "$SMPROGRAMS\DVD-o-matic"
-CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall.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 encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" "" "$INSTDIR\bin\servomatic_gui.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"
-WriteUninstaller "$INSTDIR\Uninstall.exe"
-SectionEnd
-Section "Uninstall"
-RMDir /r "$INSTDIR\*.*"    
-RMDir "$INSTDIR"
-Delete "$DESKTOP\DVD-o-matic.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"
-SectionEnd
diff --git a/windows/wscript b/windows/wscript
deleted file mode 100644 (file)
index 585cebe..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-def build(bld):
-    bld.new_task_gen(features = 'subst', source = 'installer.nsi.32.in', target = 'installer.32.nsi', version = bld.env.VERSION)
-    bld.new_task_gen(features = 'subst', source = 'installer.nsi.64.in', target = 'installer.64.nsi', version = bld.env.VERSION)
-    
diff --git a/wscript b/wscript
index 321a15a82aa9479bfc9e8e87d8c5592d4ab6cc39..481c9e8cf2e6f6cbf812c33107fa547a1804e611 100644 (file)
--- a/wscript
+++ b/wscript
@@ -2,225 +2,264 @@ import subprocess
 import os
 import sys
 
-APPNAME = 'dvdomatic'
-VERSION = '0.71beta2'
+APPNAME = 'dcpomatic'
+VERSION = '1.04pre'
 
 def options(opt):
     opt.load('compiler_cxx')
     opt.load('winres')
 
-    opt.add_option('--enable-debug', action='store_true', default = False, help = 'build with debugging information and without optimisation')
-    opt.add_option('--disable-gui', action='store_true', default = False, help = 'disable building of GUI tools')
-    opt.add_option('--target-windows', action='store_true', default = False, help = 'set up to do a cross-compile to Windows')
-    opt.add_option('--static', action='store_true', default = False, help = 'build statically, and link statically to libdcp and FFmpeg')
+    opt.add_option('--enable-debug', action='store_true', default=False, help='build with debugging information and without optimisation')
+    opt.add_option('--disable-gui', action='store_true', default=False, help='disable building of GUI tools')
+    opt.add_option('--target-windows', action='store_true', default=False, help='set up to do a cross-compile to Windows')
+    opt.add_option('--static', action='store_true', default=False, help='build statically, and link statically to libdcp and FFmpeg')
+    opt.add_option('--magickpp-config', action='store', default='Magick++-config', help='path to Magick++-config')
+    opt.add_option('--wx-config', action='store', default='wx-config', help='path to wx-config')
+
+def pkg_config_args(conf):
+    if conf.env.STATIC:
+        return '--cflags'
+    else:
+        return '--cflags --libs'
 
 def configure(conf):
     conf.load('compiler_cxx')
     if conf.options.target_windows:
         conf.load('winres')
 
-    conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS', '-msse', '-mfpmath=sse', '-ffast-math', '-fno-strict-aliasing', '-Wall', '-Wno-attributes', '-Wextra'])
+    conf.env.TARGET_WINDOWS = conf.options.target_windows
+    conf.env.DISABLE_GUI = conf.options.disable_gui
+    conf.env.STATIC = conf.options.static
+    conf.env.VERSION = VERSION
+    conf.env.TARGET_OSX = sys.platform == 'darwin'
+    conf.env.TARGET_LINUX = not conf.env.TARGET_WINDOWS and not conf.env.TARGET_OSX
+
+    # Common CXXFLAGS
+    conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS', '-D__STDC_LIMIT_MACROS', '-msse', '-mfpmath=sse', '-ffast-math', '-fno-strict-aliasing',
+                                       '-Wall', '-Wno-attributes', '-Wextra', '-D_FILE_OFFSET_BITS=64'])
 
-    if conf.options.target_windows:
-        conf.env.append_value('CXXFLAGS', ['-DDVDOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE'])
+    if conf.options.enable_debug:
+        conf.env.append_value('CXXFLAGS', ['-g', '-DDCPOMATIC_DEBUG'])
+    else:
+        conf.env.append_value('CXXFLAGS', '-O2')
+
+    # Windows-specific
+    if conf.env.TARGET_WINDOWS:
+        conf.env.append_value('CXXFLAGS', ['-DDCPOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE', '-DBOOST_THREAD_PROVIDES_GENERIC_SHARED_MUTEX_ON_WIN'])
         wxrc = os.popen('wx-config --rescomp').read().split()[1:]
         conf.env.append_value('WINRCFLAGS', wxrc)
         if conf.options.enable_debug:
             conf.env.append_value('CXXFLAGS', ['-mconsole'])
             conf.env.append_value('LINKFLAGS', ['-mconsole'])
         conf.check(lib = 'ws2_32', uselib_store = 'WINSOCK2', msg = "Checking for library winsock2")
+        conf.check(lib = 'bfd', uselib_store = 'BFD', msg = "Checking for library bfd")
+        conf.check(lib = 'dbghelp', uselib_store = 'DBGHELP', msg = "Checking for library dbghelp")
+        conf.check(lib = 'iberty', uselib_store = 'IBERTY', msg = "Checking for library iberty")
+        conf.check(lib = 'shlwapi', uselib_store = 'SHLWAPI', msg = "Checking for library shlwapi")
         boost_lib_suffix = '-mt'
         boost_thread = 'boost_thread_win32-mt'
-    else:
-        conf.env.append_value('CXXFLAGS', '-DDVDOMATIC_POSIX')
+
+    # POSIX-specific
+    if conf.env.TARGET_LINUX or conf.env.TARGET_OSX:
+        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/dcpomatic"' % conf.env['PREFIX'])
         boost_lib_suffix = ''
         boost_thread = 'boost_thread'
         conf.env.append_value('LINKFLAGS', '-pthread')
-        # libxml2 seems to be linked against this on Ubuntu, but it doesn't mention it in its .pc file
-        conf.env.append_value('LIB', 'lzma')
-
-    conf.env.TARGET_WINDOWS = conf.options.target_windows
-    conf.env.DISABLE_GUI = conf.options.disable_gui
-    conf.env.STATIC = conf.options.static
-    conf.env.VERSION = VERSION
-
-    if conf.options.enable_debug:
-        conf.env.append_value('CXXFLAGS', ['-g', '-DDVDOMATIC_DEBUG'])
-    else:
-        conf.env.append_value('CXXFLAGS', '-O2')
 
-    if not conf.options.static:
-        conf.check_cfg(package = 'libdcp', atleast_version = '0.36', args = '--cflags --libs', uselib_store = 'DCP', 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 = 'libpostproc', args = '--cflags --libs', uselib_store = 'POSTPROC', mandatory = True)
-    else:
+    # Linux-specific
+    if conf.env.TARGET_LINUX:
+        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_LINUX')
+        # libxml2 seems to be linked against this on Ubuntu but it doesn't mention it in its .pc file
+        conf.check_cfg(package='liblzma', args='--cflags --libs', uselib_store='LZMA', mandatory=True)
+        if conf.env.STATIC:
+            conf.check_cfg(package='gtk+-2.0', args='--cflags --libs', uselib_store='GTK', mandatory=True)
+        else:
+            # On Linux we need to be able to include <gtk/gtk.h> to check GTK's version
+            conf.check_cfg(package='gtk+-2.0', args='--cflags', uselib_store='GTK', mandatory=True)
+
+    # OSX-specific
+    if conf.env.TARGET_OSX:
+        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_OSX')
+        conf.env.append_value('LINKFLAGS', '-headerpad_max_install_names')
+
+    # Dependencies which are dynamically linked everywhere except --static
+    # Get libs only when we are dynamically linking
+    conf.check_cfg(package='libdcp',        atleast_version='0.78', args=pkg_config_args(conf), uselib_store='DCP',  mandatory=True)
+    # Remove erroneous escaping of quotes from xmlsec1 defines
+    conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
+    conf.check_cfg(package='libcxml',       atleast_version='0.01', args=pkg_config_args(conf), uselib_store='CXML', mandatory=True)
+    conf.check_cfg(package='libavformat',   args=pkg_config_args(conf), uselib_store='AVFORMAT',   mandatory=True)
+    conf.check_cfg(package='libavfilter',   args=pkg_config_args(conf), uselib_store='AVFILTER',   mandatory=True)
+    conf.check_cfg(package='libavcodec',    args=pkg_config_args(conf), uselib_store='AVCODEC',    mandatory=True)
+    conf.check_cfg(package='libavutil',     args=pkg_config_args(conf), uselib_store='AVUTIL',     mandatory=True)
+    conf.check_cfg(package='libswscale',    args=pkg_config_args(conf), uselib_store='SWSCALE',    mandatory=True)
+    conf.check_cfg(package='libswresample', args=pkg_config_args(conf), uselib_store='SWRESAMPLE', mandatory=True)
+    conf.check_cfg(package='libpostproc',   args=pkg_config_args(conf), uselib_store='POSTPROC',   mandatory=True)
+    conf.check_cfg(package='libopenjpeg',   args=pkg_config_args(conf), atleast_version='1.5.0', uselib_store='OPENJPEG', mandatory=True)
+    conf.check_cfg(package='libopenjpeg',   args=pkg_config_args(conf), max_version='1.5.1', mandatory=True)
+
+    if conf.env.STATIC:
         # This is hackio grotesquio for static builds (ie for .deb packages).  We need to link some things
         # statically and some dynamically, or things get horribly confused and the dynamic linker (I think)
-        # crashes horribly.  These calls do what the check_cfg calls would have done, but specify the
+        # crashes.  These calls do what the check_cfg calls would have done, but specify the
         # different bits as static or dynamic as required.  It'll break if you look at it funny, but
         # I think anyone else who builds would do so dynamically.
-        conf.env.HAVE_DCP = 1
-        conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
-        conf.env.LIB_DCP = ['glibmm-2.4', 'xml++-2.6', 'ssl', 'crypto', 'bz2']
-        conf.env.HAVE_AVFORMAT = 1
-        conf.env.STLIB_AVFORMAT = ['avformat']
-        conf.env.HAVE_AVFILTER = 1
-        conf.env.STLIB_AVFILTER = ['avfilter', 'swresample']
-        conf.env.HAVE_AVCODEC = 1
-        conf.env.STLIB_AVCODEC = ['avcodec']
-        conf.env.LIB_AVCODEC = ['x264', 'z']
-        conf.env.HAVE_AVUTIL = 1
-        conf.env.STLIB_AVUTIL = ['avutil']
-        conf.env.HAVE_SWSCALE = 1
-        conf.env.STLIB_SWSCALE = ['swscale']
-        conf.env.HAVE_SWRESAMPLE = 1
+        conf.env.STLIB_CXML       = ['cxml']
+        conf.env.STLIB_DCP        = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
+        conf.env.LIB_DCP          = ['glibmm-2.4', 'xml++-2.6', 'ssl', 'crypto', 'bz2', 'xmlsec1', 'xmlsec1-openssl', 'xslt']
+        conf.env.STLIB_CXML       = ['cxml']
+        conf.env.STLIB_AVFORMAT   = ['avformat']
+        conf.env.STLIB_AVFILTER   = ['avfilter', 'swresample']
+        conf.env.STLIB_AVCODEC    = ['avcodec']
+        conf.env.LIB_AVCODEC      = ['z']
+        conf.env.STLIB_AVUTIL     = ['avutil']
+        conf.env.STLIB_SWSCALE    = ['swscale']
+        conf.env.STLIB_POSTPROC   = ['postproc']
         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.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)
-    conf.check_cfg(package = '', path = 'Magick++-config', args = '--cppflags --cxxflags --libs', uselib_store = 'MAGICK', mandatory = True)
-
-    openjpeg_fragment = """
-                       #include <stdio.h>\n
-                       #include <openjpeg.h>\n
-                       int main () {\n
-                       void* p = (void *) opj_image_create;\n
-                       return 0;\n
-                       }
-                       """
-
-    if conf.options.static:
-        conf.check_cc(fragment = openjpeg_fragment, msg = 'Checking for library openjpeg', stlib = 'openjpeg', uselib_store = 'OPENJPEG')
-    else:
-        conf.check_cc(fragment = openjpeg_fragment, msg = 'Checking for library openjpeg', lib = 'openjpeg', uselib_store = 'OPENJPEG')
-
-    conf.check_cc(fragment  = """
-                              #include <libssh/libssh.h>\n
-                              int main () {\n
-                              ssh_session s = ssh_new ();\n
-                              return 0;\n
-                              }
-                              """, msg = 'Checking for library libssh', mandatory = False, lib = 'ssh', uselib_store = 'SSH')
-
-    conf.check_cxx(fragment = """
-                             #include <boost/thread.hpp>\n
-                             int main() { boost::thread t (); }\n
-                             """, msg = 'Checking for boost threading library',
-                              lib = [boost_thread, 'boost_system%s' % boost_lib_suffix],
-                              uselib_store = 'BOOST_THREAD')
-
-    conf.check_cxx(fragment = """
-                             #include <boost/filesystem.hpp>\n
-                             int main() { boost::filesystem::copy_file ("a", "b"); }\n
-                             """, msg = 'Checking for boost filesystem library',
-                              libpath = '/usr/local/lib',
-                              lib = ['boost_filesystem%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
-                              uselib_store = 'BOOST_FILESYSTEM')
-
-    conf.check_cxx(fragment = """
-                             #include <boost/date_time.hpp>\n
-                             int main() { boost::gregorian::day_clock::local_day(); }\n
-                             """, msg = 'Checking for boost datetime library',
-                              libpath = '/usr/local/lib',
-                              lib = ['boost_date_time%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
-                              uselib_store = 'BOOST_DATETIME')
-
-    conf.check_cxx(fragment = """
-                             #include <boost/signals2.hpp>\n
-                             int main() { boost::signals2::signal<void (int)> x; }\n
-                             """,
-                              msg = 'Checking for boost signals2 library',
-                              uselib_store = 'BOOST_SIGNALS2')
-
-    conf.check_cc(fragment = """
-                             #include <glib.h>
-                             int main() { g_format_size (1); }
-                             """, msg = 'Checking for g_format_size ()',
-                             uselib = 'GLIB',
-                             define_name = 'HAVE_G_FORMAT_SIZE',
-                             mandatory = False)
-
-    conf.check_cc(fragment = """
-                             extern "C" {
-                               #include <libavutil/avutil.h>
-                             }
-                             int main() { AVPixelFormat f; }
-                             """, msg = 'Checking for AVPixelFormat',
-                             uselib = 'AVUTIL',
-                             define_name = 'HAVE_AV_PIXEL_FORMAT',
-                             mandatory = False)
-
-    conf.check_cc(fragment = """
-                             extern "C" {
-                               #include <libavcodec/avcodec.h>
-                             }
-                             int main() { AVFrame* f; av_frame_get_best_effort_timestamp(f); }
-                             """, msg = 'Checking for av_frame_get_best_effort_timestamp',
-                             uselib = 'AVCODEC',
-                             define_name = 'HAVE_AV_FRAME_GET_BEST_EFFORT_TIMESTAMP',
-                             mandatory = False)
-
-    conf.check_cc(fragment = """
-                             extern "C" {
-                               #include <libavfilter/buffersrc.h>
-                             }
-                             int main() { } 
-                             """, msg = 'Checking for buffersrc.h',
-                             uselib = 'AVCODEC',
-                             define_name = 'HAVE_BUFFERSRC_H',
-                             mandatory = False)
+        conf.env.STLIB_OPENJPEG   = ['openjpeg']
+
+    # Dependencies which are always dynamically linked
+    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)
+    conf.check_cfg(package= '', path=conf.options.magickpp_config, args='--cppflags --cxxflags --libs', uselib_store='MAGICK', mandatory=True)
+    conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XML++', mandatory=True)
+
+    conf.check_cxx(fragment="""
+                            #include <boost/version.hpp>\n
+                            #if BOOST_VERSION < 104500\n
+                            #error boost too old\n
+                            #endif\n
+                            int main(void) { return 0; }\n
+                            """,
+                   mandatory=True,
+                   msg='Checking for boost library >= 1.45',
+                   okmsg='yes',
+                   errmsg='too old\nPlease install boost version 1.45 or higher.')
+
+    conf.check_cc(fragment="""
+                           #include <libssh/libssh.h>\n
+                           int main () {\n
+                           ssh_session s = ssh_new ();\n
+                           return 0;\n
+                           }
+                           """, msg='Checking for library libssh', mandatory=True, lib='ssh', uselib_store='SSH')
+
+    conf.check_cxx(fragment="""
+                           #include <boost/thread.hpp>\n
+                           int main() { boost::thread t (); }\n
+                           """, msg='Checking for boost threading library',
+                           libpath='/usr/local/lib',
+                            lib=[boost_thread, 'boost_system%s' % boost_lib_suffix],
+                            uselib_store='BOOST_THREAD')
+
+    conf.check_cxx(fragment="""
+                           #include <boost/filesystem.hpp>\n
+                           int main() { boost::filesystem::copy_file ("a", "b"); }\n
+                           """, msg='Checking for boost filesystem library',
+                            libpath='/usr/local/lib',
+                            lib=['boost_filesystem%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
+                            uselib_store='BOOST_FILESYSTEM')
+
+    conf.check_cxx(fragment="""
+                           #include <boost/date_time.hpp>\n
+                           int main() { boost::gregorian::day_clock::local_day(); }\n
+                           """, msg='Checking for boost datetime library',
+                            libpath='/usr/local/lib',
+                            lib=['boost_date_time%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
+                            uselib_store='BOOST_DATETIME')
+
+    conf.check_cxx(fragment="""
+                           #include <boost/signals2.hpp>\n
+                           int main() { boost::signals2::signal<void (int)> x; }\n
+                           """,
+                            msg='Checking for boost signals2 library',
+                            uselib_store='BOOST_SIGNALS2')
+
+    conf.check_cc(fragment="""
+                           #include <glib.h>
+                           int main() { g_format_size (1); }
+                           """, msg='Checking for g_format_size ()',
+                           uselib='GLIB',
+                           define_name='HAVE_G_FORMAT_SIZE',
+                           mandatory=False)
+
+    conf.find_program('msgfmt', var='MSGFMT')
+    
+    datadir = conf.env.DATADIR
+    if not datadir:
+        datadir = os.path.join(conf.env.PREFIX, 'share')
+    
+    conf.define('LOCALEDIR', os.path.join(datadir, 'locale'))
+    conf.define('DATADIR', datadir)
 
     conf.recurse('src')
     conf.recurse('test')
 
 def build(bld):
-    create_version_cc(VERSION)
+    create_version_cc(VERSION, bld.env.CXXFLAGS)
 
     bld.recurse('src')
     bld.recurse('test')
     if bld.env.TARGET_WINDOWS:
-        bld.recurse('windows')
-
-    d = { 'PREFIX' : '${PREFIX' }
+        bld.recurse('platform/windows')
+    if bld.env.TARGET_LINUX:
+        bld.recurse('platform/linux')
+    if bld.env.TARGET_OSX:
+        bld.recurse('platform/osx')
 
-    obj = bld(features = 'subst')
-    obj.source = 'dvdomatic.desktop.in'
-    obj.target = 'dvdomatic.desktop'
-    obj.dict = d
-
-    bld.install_files('${PREFIX}/share/applications', 'dvdomatic.desktop')
     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/dcpomatic', 'icons/taskbar_icon.png')
 
     bld.add_post_fun(post)
 
+def git_revision():
+    if not os.path.exists('.git'):
+        return None
+
+    cmd = "LANG= git log --abbrev HEAD^..HEAD ."
+    output = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0].splitlines()
+    o = output[0].decode('utf-8')
+    return o.replace("commit ", "")[0:10]
+
 def dist(ctx):
-    ctx.excl = 'TODO core *~ src/wx/*~ src/lib/*~ builds/*~ doc/manual/*~ src/tools/*~ *.pyc .waf* build .git deps alignment hacks sync *.tar.bz2 *.exe .lock* *build-windows doc/manual/pdf doc/manual/html'
-
-def create_version_cc(version):
-    if os.path.exists('.git'):
-        cmd = "LANG= git log --abbrev HEAD^..HEAD ."
-        output = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0].splitlines()
-        o = output[0].decode('utf-8')
-        commit = o.replace ("commit ", "")[0:10]
-    else:
+    r = git_revision()
+    if r is not None:
+        f = open('.git_revision', 'w')
+        print >>f,r
+    f.close()
+
+    ctx.excl = """
+               TODO core *~ src/wx/*~ src/lib/*~ builds/*~ doc/manual/*~ src/tools/*~ *.pyc .waf* build .git
+               deps alignment hacks sync *.tar.bz2 *.exe .lock* *build-windows doc/manual/pdf doc/manual/html
+               GRSYMS GRTAGS GSYMS GTAGS
+               """
+
+
+def create_version_cc(version, cxx_flags):
+    commit = git_revision()
+    if commit is None and os.path.exists('.git_revision'):
+        f = open('.git_revision', 'r')
+        commit = f.readline().strip()
+    
+    if commit is None:
         commit = 'release'
 
     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 * dcpomatic_cxx_flags = \"%s\";\n' % t[:-1]
+
         print('Writing version information to src/lib/version.cc')
         o = open('src/lib/version.cc', 'w')
         o.write(text)
@@ -232,3 +271,9 @@ def create_version_cc(version):
 def post(ctx):
     if ctx.cmd == 'install':
         ctx.exec_command('/sbin/ldconfig')
+
+def pot(bld):
+    bld.recurse('src')
+
+def pot_merge(bld):
+    bld.recurse('src')