Merge branch '2.0' of git.carlh.net:git/dcpomatic into 2.0
authorCarl Hetherington <cth@carlh.net>
Wed, 20 May 2015 15:29:25 +0000 (16:29 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 20 May 2015 15:29:25 +0000 (16:29 +0100)
98 files changed:
ChangeLog
TO_PORT
cscript
debian/changelog
debian/rules
hacks/apply_to_port [new file with mode: 0755]
hacks/next_to_port [new file with mode: 0755]
hacks/start_servers.sh
hacks/stop_servers.sh
platform/linux/dcpomatic.spec.in [deleted file]
platform/linux/wscript
run/tests
src/lib/analyse_audio_job.cc
src/lib/analyse_audio_job.h
src/lib/audio_analysis.cc
src/lib/audio_analysis.h
src/lib/audio_decoder.cc
src/lib/content.cc
src/lib/content.h
src/lib/cross.cc
src/lib/dcp_decoder.cc
src/lib/dcp_decoder.h
src/lib/dcp_subtitle_decoder.cc
src/lib/dcp_subtitle_decoder.h
src/lib/dcpomatic_time.h
src/lib/decoder.h
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_decoder.h
src/lib/ffmpeg_examiner.cc
src/lib/ffmpeg_examiner.h
src/lib/ffmpeg_subtitle_stream.cc
src/lib/ffmpeg_subtitle_stream.h
src/lib/film.cc
src/lib/film.h
src/lib/image_decoder.cc
src/lib/image_decoder.h
src/lib/job.cc
src/lib/job.h
src/lib/job_manager.cc
src/lib/job_manager.h
src/lib/kdm.cc
src/lib/player.cc
src/lib/render_subtitles.cc
src/lib/server_finder.cc
src/lib/server_finder.h
src/lib/signal_manager.cc [new file with mode: 0644]
src/lib/signal_manager.h [new file with mode: 0644]
src/lib/signaller.h [new file with mode: 0644]
src/lib/sndfile_decoder.cc
src/lib/sndfile_decoder.h
src/lib/subrip_decoder.cc
src/lib/subrip_decoder.h
src/lib/subtitle_decoder.cc
src/lib/subtitle_decoder.h
src/lib/types.h
src/lib/ui_signaller.cc [deleted file]
src/lib/ui_signaller.h [deleted file]
src/lib/update.cc
src/lib/update.h
src/lib/util.cc
src/lib/util.h
src/lib/video_decoder.cc
src/lib/writer.cc
src/lib/wscript
src/tools/dcpomatic.cc
src/tools/dcpomatic_batch.cc
src/tools/dcpomatic_cli.cc
src/tools/dcpomatic_create.cc
src/tools/wscript
src/wscript
src/wx/audio_dialog.cc
src/wx/audio_dialog.h
src/wx/audio_panel.cc
src/wx/dcp_panel.cc
src/wx/dolby_certificate_dialog.cc
src/wx/doremi_certificate_dialog.cc
src/wx/download_certificate_dialog.cc
src/wx/download_certificate_dialog.h
src/wx/film_viewer.cc
src/wx/kdm_dialog.cc
src/wx/key_dialog.cc
src/wx/wscript
src/wx/wx_signal_manager.cc [new file with mode: 0644]
src/wx/wx_signal_manager.h [new file with mode: 0644]
src/wx/wx_ui_signaller.cc [deleted file]
src/wx/wx_ui_signaller.h [deleted file]
src/wx/wx_util.cc
src/wx/wx_util.h
test/audio_analysis_test.cc
test/audio_decoder_test.cc
test/film_metadata_test.cc
test/image_test.cc
test/isdcf_name_test.cc
test/test.cc
test/wscript
wscript

index 6d740423933dcf647b631ceba271fdd5cbb41063..e2d734168b2a9ea7a5a7c1e80e4d115cfcf9f0de 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,42 @@
+2015-05-17  Carl Hetherington  <cth@carlh.net>
+
+       * Fix Update DCP name on changing DCP standard (#570).
+
+2015-05-14  Carl Hetherington  <cth@carlh.net>
+
+       * Version 2.0.47 released.
+
+2015-05-13  c.hetherington  <cth@carlh.net>
+
+       * Various fixes to embedded image subtitle
+       handling.
+
+2015-05-10  Carl Hetherington  <cth@carlh.net>
+
+       * Version 2.0.46 released.
+
+2015-05-10  Carl Hetherington  <cth@carlh.net>
+
+       * Version 2.0.45 released.
+
+2015-05-10  Carl Hetherington  <cth@carlh.net>
+
+       * Version 2.0.44 released.
+
+2015-05-10  Carl Hetherington  <cth@carlh.net>
+
+       * Fix sometimes-missing channel labels on OS X audio analysis.
+
+2015-05-09  Carl Hetherington  <cth@carlh.net>
+
+       * Efficiency fix for cases where there is a lot of processing
+       power.
+
+       * Add UTF-8 content type to KDM emails to try to fix #549.
+
+       * Disable OK until a download succeeds in the certificate
+       downloader (#404).
+
 2015-05-07  Carl Hetherington  <cth@carlh.net>
 
        * Version 2.0.43 released.
diff --git a/TO_PORT b/TO_PORT
index 4c78bdb9ca62aba305b84cb7e827efcdfe0f3be1..6cf16c46443dc98afe4ae89c204abc4eaa423935 100644 (file)
--- a/TO_PORT
+++ b/TO_PORT
@@ -1,6 +1,5 @@
-add9a03356d3d392e234354df4800b9042e0f426
-01919a9e691375de4eb0069bc8cf179bee4dd7b6
-1d63d0309d071254fcf4da65d3710e94fadd38e8
-0c0211871d0be5b3409adfc88d2979ca5b439b0a
-wscript/cscript etc. cleanups
-2a3bfb06c68afd1aa4daaa14ece050689ea47927
+2a595178e42734336983693e8150609554b6a08d
+4bcdf16458e460dd4a78d634dfe69f2a44182541
+21f33acd3580c6e5c4ec1e7449b419c3178aa8ab
+Multi-stream audio stuff.
+681d95c83868310330984ae65589a1021bbe07d6
diff --git a/cscript b/cscript
index e113e71013ea0fb22421910f8be5e932daef7aac..cdf060210da049f7d5371cae9ee6af8ab4999f6b 100644 (file)
--- a/cscript
+++ b/cscript
@@ -1,3 +1,21 @@
+#
+#    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
 import glob
 import shutil
 import os
@@ -26,48 +44,6 @@ deb_depends['12.04'] = {'libc6': '2.15',
                         'libcurl3': '7.22.0-3ubuntu4',
                         'libzip2': '0.10-1ubuntu1'}
 
-deb_depends['12.10'] = {'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',
-                        'libxmlsec1-openssl': '1.2.18-2',
-                        'libboost-date-time1.49.0': '1.49.0',
-                        'libcurl3': '7.27.0-1ubuntu1',
-                        'libzip2': '0.10.1-1.1'}
-
-deb_depends['13.04'] = {'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',
-                        'libxmlsec1-openssl': '1.2.18-2',
-                        'libboost-date-time1.49.0': '1.49.0',
-                        'libcurl3': '7.29.0-1ubuntu3',
-                        'libzip2': '0.10.1-1.1'}
-
-deb_depends['13.10'] = {'libc6': '2.17-93',
-                        'libssh-4': '0.5.4',
-                        'libboost-filesystem1.53.0': '1.53.0',
-                        'libboost-thread1.53.0': '1.53.0',
-                        'libsndfile1': '1.0.25',
-                        'libmagick++5': '8:6.7.7.10',
-                        'libxml++2.6-2': '2.36.0',
-                        'libgtk2.0-0': '2.24.20',
-                        'libxmlsec1': '1.2.18-2',
-                        'libxmlsec1-openssl': '1.2.18-2',
-                        'libboost-date-time1.49.0': '1.49.0',
-                        'libcurl3': '7.29.0-1ubuntu3',
-                        'libzip2': '0.10.1-1.1'}
-
 deb_depends['14.04'] = {'libc6': '2.19-0ubuntu6',
                         'libssh-4': '0.6.1-0ubuntu3',
                         'libboost-filesystem1.54.0': '1.54.0-4ubuntu3',
@@ -82,6 +58,20 @@ deb_depends['14.04'] = {'libc6': '2.19-0ubuntu6',
                         'libcurl3': '7.35.0-1ubuntu2',
                         'libzip2': '0.10.1-1.2'}
 
+deb_depends['15.04'] = {'libc6': '2.21-0ubuntu4',
+                        'libssh-4': '0.6.3-3ubuntu3',
+                        'libboost-filesystem1.55.0': '1.55.0+dfsg-3ubuntu2',
+                        'libboost-thread1.55.0': '1.55.0+dfsg-3ubuntu2',
+                        'libsndfile1': '1.0.25-9.1',
+                        'libmagick++-6.q16-5': '8:6.8.9.9-5',
+                        'libxml++2.6-2': '2.36.0-2.1',
+                        'libgtk2.0-0': '2.24.27-0ubuntu1',
+                        'libxmlsec1': '1.2.20-2ubuntu2',
+                        'libxmlsec1-openssl': '1.2.20-2ubuntu2',
+                        'libboost-date-time1.55.0': '1.55.0+dfsg-3ubuntu2',
+                        'libcurl3': '7.38.0-3ubuntu2',
+                        'libzip2': '0.11.2-1.2'}
+
 deb_depends['7'] = {'libc6': '2.13',
                     'libssh-4': '0.5.4',
                     'libboost-filesystem1.49.0': '1.49.0',
@@ -154,29 +144,90 @@ def make_control(debian_version, bits, filename, debug):
         print >>f,'  This package contains the debugging symbols for dcpomatic.'
         print >>f,''
 
+def make_spec(filename, version, target):
+    """Make a .spec file for a RPM build"""
+    f = open(filename, 'w')
+    print >>f,'Summary:A program that generates Digital Cinema Packages (DCPs) from video and audio files'
+    print >>f,'Name:dcpomatic2'
+    print >>f,'Version:%s' % version
+    print >>f,'Release:1%{?dist}'
+    print >>f,'License:GPL'
+    print >>f,'Group:Applications/Multimedia'
+    print >>f,'URL:http://dcpomatic.com/'
+    print >>f,'Requires: ImageMagick-c++, glibmm24, libzip'
+    print >>f,''
+    print >>f,'%description'
+    print >>f,'DCP-o-matic generates Digital Cinema Packages (DCPs) from video and audio '
+    print >>f,'files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant '
+    print >>f,'digital projectors.'
+    print >>f,''
+    print >>f,'%files'
+    print >>f,'%{_bindir}/dcpomatic2'
+    print >>f,'%{_bindir}/dcpomatic2_batch'
+    print >>f,'%{_bindir}/dcpomatic2_cli'
+    print >>f,'%{_bindir}/dcpomatic2_create'
+    print >>f,'%{_bindir}/dcpomatic2_kdm'
+    print >>f,'%{_bindir}/dcpomatic2_server'
+    print >>f,'%{_bindir}/dcpomatic2_server_cli'
+    print >>f,'%{_datadir}/applications/dcpomatic2.desktop'
+    print >>f,'%{_datadir}/applications/dcpomatic2_batch.desktop'
+    print >>f,'%{_datadir}/applications/dcpomatic2_server.desktop'
+    print >>f,'%{_datadir}/dcpomatic/taskbar_icon.png'
+    for r in ['128x128', '22x22', '32x32', '48x48', '64x64']:
+        print >>f,'%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2.png' % r
+    for l in ['de_DE', 'es_ES', 'fr_FR', 'it_IT', 'sv_SE', 'nl_NL']:
+        print >>f,'%%{_datadir}/locale/%s/LC_MESSAGES/dcpomatic2.mo' % l
+        print >>f,'%%{_datadir}/locale/%s/LC_MESSAGES/libdcpomatic2-wx.mo' % l
+        print >>f,'%%{_datadir}/locale/%s/LC_MESSAGES/libdcpomatic2.mo' % l
+    print >>f,''
+    print >>f,'%prep'
+    print >>f,'rm -rf $RPM_BUILD_DIR/dcpomatic-%s' % version
+    print >>f,'tar xjf $RPM_SOURCE_DIR/dcpomatic-%s.tar.bz2' % version
+    print >>f,'%build'
+    print >>f,'cd dcpomatic-%s' % version
+    print >>f,'export PKG_CONFIG_PATH=%s/lib/pkgconfig:/usr/local/lib/pkgconfig' % target.directory
+    print >>f,'CXXFLAGS="-I%s/include" LDFLAGS="-L%s/lib" ./waf configure --prefix=%%{buildroot}/usr --install-prefix=/usr %s' % (target.directory, target.directory, configure_options(target))
+    print >>f,'./waf'
+    print >>f,'%install'
+    print >>f,'cd dcpomatic-%s' % version
+    print >>f,'./waf install'
+    print >>f,''
+    print >>f,'%post'
+    print >>f,'/bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null || :'
+    print >>f,''
+    print >>f,'%postun'
+    print >>f,'if [ $1 -eq 0 ] ; then'
+    print >>f,'    /bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null'
+    print >>f,'    /usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :'
+    print >>f,'fi'
+    print >>f,''
+    print >>f,'%posttrans'
+    print >>f,'/usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :'
+
 def dependencies(target):
-    return (('ffmpeg-cdist', 'f69bb08'),
-            ('libdcp', 'e0906dd'),
+    return (('ffmpeg-cdist', '0492ad2'),
+            ('libdcp', 'b386248'),
             ('libsub', 'f66b11f'))
 
-def build(target, options):
-    cmd = './waf configure --prefix=%s' % target.directory
+def configure_options(target):
+    opt = ''
     if target.debug:
-        cmd += ' --enable-debug'
+        opt += ' --enable-debug'
     if target.platform == 'windows':
-        cmd += ' --target-windows'
+        opt += ' --target-windows'
     elif target.platform == 'linux':
-        if target.distro == 'debian' or target.distro == 'ubuntu':
-            cmd += ' --target-debian'
-            if target.version == 'unstable':
-                cmd += ' --debian-unstable'
-        elif target.distro == 'centos':
+        opt += ' --static-dcpomatic --static-openjpeg --static-wxwidgets --static-ffmpeg --static-dcp --static-sub --static-cxml'
+        if target.distro == 'centos':
+            opt += ' --static-xmlsec --static-ssh --disable-tests'
             if target.version == '6.5':
-                cmd += ' --target-centos-6 --disable-tests'
+                opt += ' --static-boost --static-xmlpp'
             elif target.version == '7':
-                cmd += ' --target-centos-7 --disable-tests'
+                opt += ' --workaround-gssapi'
 
-    target.command(cmd)
+    return opt
+
+def build(target, options):
+    target.command('./waf configure --prefix=%s %s' % (target.directory, configure_options(target)))
     target.command('./waf')
 
     if target.platform == 'linux' or target.platform == 'osx':
@@ -209,8 +260,15 @@ def package_debian(target, cpu, 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'))
-    if target.version == 'unstable':
-        target.set('CDIST_EXTRA_CONFIGURE', '--debian-unstable')
+
+    target.set('CDIST_CONFIGURE', '"' + configure_options(target) + '"')
+    if target.debug:
+        target.set('CDIST_DEBUG_PACKAGE', '--dbg-package=dcpomatic-dbg')
+    if target.version == '15.04':
+        target.set('CDIST_LOCALE_PREFIX', '/usr/share/locale')
+    else:
+        target.set('CDIST_LOCALE_PREFIX', '/usr/local/share/locale')
+
     target.command('dpkg-buildpackage -uc -us')
     
     debs = []
@@ -233,6 +291,7 @@ def package_centos(target, cpu, version):
         "%s/SOURCES/dcpomatic-%s.tar.bz2" % (topdir, version)
         )
 
+    make_spec('build/platform/linux/dcpomatic2.spec', version, target)
     target.command('rpmbuild --define \'_topdir %s\' -bb build/platform/linux/dcpomatic2.spec' % topdir)
     rpms = []
 
index 43750e79099328c7d00686dc5c5abb4caeb1cfbe..cb6baec5ec380aa99ecda6a4ef1f69357e322913 100644 (file)
@@ -1,4 +1,4 @@
-dcpomatic (2.0.43-1) UNRELEASED; urgency=low
+dcpomatic (2.0.47-1) UNRELEASED; urgency=low
 
   * New upstream release.
   * New upstream release.
@@ -222,8 +222,12 @@ dcpomatic (2.0.43-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>  Thu, 07 May 2015 00:48:20 +0100
+ -- Carl Hetherington <carl@d1stkfactory>  Thu, 14 May 2015 09:09:37 +0100
 
 dcpomatic (0.87-1) UNRELEASED; urgency=low
 
index b51089f8b59d7160f07ba3f8c7340cf216f03f20..3e75b00900597184667c2f05c5b08382f50a10f9 100755 (executable)
@@ -14,7 +14,7 @@
 
 override_dh_auto_configure:
        LINKFLAGS=$(CDIST_LINKFLAGS) CXXFLAGS="$(CXXFLAGS) $(CDIST_CXXFLAGS)" PKG_CONFIG_PATH=$(CDIST_PKG_CONFIG_PATH) \
-                ./waf configure --prefix=/usr --target-debian --enable-debug $(CDIST_EXTRA_CONFIGURE)
+                ./waf configure --prefix=/usr $(CDIST_CONFIGURE)
 
 override_dh_auto_build:
        ./waf build
@@ -22,17 +22,17 @@ override_dh_auto_build:
 override_dh_auto_install:
        ./waf install --destdir=debian/dcpomatic
        mkdir -p debian/dcpomatic/usr/share/locale/de/LC_MESSAGES/
-       cp -a /usr/local/share/locale/de/LC_MESSAGES/wxstd.mo debian/dcpomatic/usr/share/locale/de/LC_MESSAGES/dcpomatic-wxstd.mo
+       cp -a $(CDIST_LOCALE_PREFIX)/de/LC_MESSAGES/wxstd.mo debian/dcpomatic/usr/share/locale/de/LC_MESSAGES/dcpomatic-wxstd.mo
        mkdir -p debian/dcpomatic/usr/share/locale/es/LC_MESSAGES/
-       cp -a /usr/local/share/locale/es/LC_MESSAGES/wxstd.mo debian/dcpomatic/usr/share/locale/es/LC_MESSAGES/dcpomatic-wxstd.mo
+       cp -a $(CDIST_LOCALE_PREFIX)/es/LC_MESSAGES/wxstd.mo debian/dcpomatic/usr/share/locale/es/LC_MESSAGES/dcpomatic-wxstd.mo
        mkdir -p debian/dcpomatic/usr/share/locale/fr/LC_MESSAGES/
-       cp -a /usr/local/share/locale/fr/LC_MESSAGES/wxstd.mo debian/dcpomatic/usr/share/locale/fr/LC_MESSAGES/dcpomatic-wxstd.mo
+       cp -a $(CDIST_LOCALE_PREFIX)/fr/LC_MESSAGES/wxstd.mo debian/dcpomatic/usr/share/locale/fr/LC_MESSAGES/dcpomatic-wxstd.mo
        mkdir -p debian/dcpomatic/usr/share/locale/it/LC_MESSAGES/
-       cp -a /usr/local/share/locale/it/LC_MESSAGES/wxstd.mo debian/dcpomatic/usr/share/locale/it/LC_MESSAGES/dcpomatic-wxstd.mo
+       cp -a $(CDIST_LOCALE_PREFIX)/it/LC_MESSAGES/wxstd.mo debian/dcpomatic/usr/share/locale/it/LC_MESSAGES/dcpomatic-wxstd.mo
        mkdir -p debian/dcpomatic/usr/share/locale/sv/LC_MESSAGES/
-       cp -a /usr/local/share/locale/sv/LC_MESSAGES/wxstd.mo debian/dcpomatic/usr/share/locale/sv/LC_MESSAGES/dcpomatic-wxstd.mo
+       cp -a $(CDIST_LOCALE_PREFIX)/sv/LC_MESSAGES/wxstd.mo debian/dcpomatic/usr/share/locale/sv/LC_MESSAGES/dcpomatic-wxstd.mo
        mkdir -p debian/dcpomatic/usr/share/locale/nl/LC_MESSAGES/
-       cp -a /usr/local/share/locale/nl/LC_MESSAGES/wxstd.mo debian/dcpomatic/usr/share/locale/nl/LC_MESSAGES/dcpomatic-wxstd.mo
+       cp -a $(CDIST_LOCALE_PREFIX)/nl/LC_MESSAGES/wxstd.mo debian/dcpomatic/usr/share/locale/nl/LC_MESSAGES/dcpomatic-wxstd.mo
 
 .PHONY: override_dh_strip
 override_dh_strip:
diff --git a/hacks/apply_to_port b/hacks/apply_to_port
new file mode 100755 (executable)
index 0000000..439ab91
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+if [ "$1" == "" ]; then
+  echo "Syntax: $0 commit-message"
+  exit 1
+fi
+
+commit=`head -n 1 TO_PORT`
+msg="$commit from master; $*"
+echo $msg
+
+read -p "Commit? " -n 1 -r
+echo
+if [[ $REPLY =~ ^[Yy]$ ]]
+then
+    sed -i '1d' TO_PORT
+    git commit -a -m "$msg"
+fi
\ No newline at end of file
diff --git a/hacks/next_to_port b/hacks/next_to_port
new file mode 100755 (executable)
index 0000000..1b51e0e
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+head -n 1 TO_PORT | xargs git show
index 629b49523cabe52501d51342b1d946c0fa4a9054..8f2b2859b2f81a76081d8397cc3651e94a587fe3 100644 (file)
@@ -1,8 +1,4 @@
 #!/bin/bash
 
-dsh -m cs2-1  -m cs2-2  -m cs2-3  -m cs2-4  -m cs2-5  -m cs2-6  -m cs2-7  -m cs2-8 \
+dsh -m cs2-17 -m cs2-18 -m cs2-19 -m cs2-20 \
     "screen -dmS dcpomatic bash -c 'cd src/dcpomatic2; LD_LIBRARY_PATH=$HOME/ubuntu/lib run/dcpomatic_server_cli --verbose'"
-
-#    -m cs2-9  -m cs2-10 -m cs2-11 -m cs2-12 -m cs2-13 -m cs2-14 -m cs2-15 -m cs2-16 \
-#    -m cs2-17 -m cs2-18 -m cs2-19 -m cs2-20                               -m cs2-24 \
-#    -m cs2-25 \
index 7ea031ff0f4d14ce4f7c1da099ae9d5b48d17157..b785ceaa0559623f1efcbaf6f98f0b80c746fe76 100644 (file)
@@ -1,7 +1,4 @@
 #!/bin/bash
 
-dsh -m cs2-1  -m cs2-2  -m cs2-3  -m cs2-4  -m cs2-5  -m cs2-6  -m cs2-7  -m cs2-8 \
-    -m cs2-9  -m cs2-10 -m cs2-11 -m cs2-12 -m cs2-13 -m cs2-14 -m cs2-15 -m cs2-16 \
-    -m cs2-17 -m cs2-18 -m cs2-19 -m cs2-20                               -m cs2-24 \
-    -m cs2-25 \
+dsh -m cs2-17 -m cs2-18 -m cs2-19 -m cs2-20 \
     killall dcpomatic_server_cli
diff --git a/platform/linux/dcpomatic.spec.in b/platform/linux/dcpomatic.spec.in
deleted file mode 100644 (file)
index 7f39716..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-Summary:A program that generates Digital Cinema Packages (DCPs) from video and audio files
-Name:dcpomatic2
-Version:@VERSION@
-Release:1%{?dist}
-License:GPL
-Group:Applications/Multimedia
-URL:http://dcpomatic.com/
-Requires: ImageMagick-c++, glibmm24, libzip
-
-%description
-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.
-
-%files
-%{_bindir}/dcpomatic2
-%{_bindir}/dcpomatic2_batch
-%{_bindir}/dcpomatic2_cli
-%{_bindir}/dcpomatic2_create
-%{_bindir}/dcpomatic2_kdm
-%{_bindir}/dcpomatic2_server
-%{_bindir}/dcpomatic2_server_cli
-%{_datadir}/applications/dcpomatic2.desktop
-%{_datadir}/applications/dcpomatic2_batch.desktop
-%{_datadir}/applications/dcpomatic2_server.desktop
-%{_datadir}/dcpomatic2/taskbar_icon.png
-%{_datadir}/dcpomatic2/LiberationSans-Regular.ttf
-%{_datadir}/icons/hicolor/128x128/apps/dcpomatic2.png
-%{_datadir}/icons/hicolor/22x22/apps/dcpomatic2.png
-%{_datadir}/icons/hicolor/32x32/apps/dcpomatic2.png
-%{_datadir}/icons/hicolor/48x48/apps/dcpomatic2.png
-%{_datadir}/icons/hicolor/64x64/apps/dcpomatic2.png
-%{_datadir}/locale/de_DE/LC_MESSAGES/dcpomatic2.mo
-%{_datadir}/locale/de_DE/LC_MESSAGES/libdcpomatic2-wx.mo
-%{_datadir}/locale/de_DE/LC_MESSAGES/libdcpomatic2.mo
-%{_datadir}/locale/es_ES/LC_MESSAGES/dcpomatic2.mo
-%{_datadir}/locale/es_ES/LC_MESSAGES/libdcpomatic2-wx.mo
-%{_datadir}/locale/es_ES/LC_MESSAGES/libdcpomatic2.mo
-%{_datadir}/locale/fr_FR/LC_MESSAGES/dcpomatic2.mo
-%{_datadir}/locale/fr_FR/LC_MESSAGES/libdcpomatic2-wx.mo
-%{_datadir}/locale/fr_FR/LC_MESSAGES/libdcpomatic2.mo
-%{_datadir}/locale/it_IT/LC_MESSAGES/dcpomatic2.mo
-%{_datadir}/locale/it_IT/LC_MESSAGES/libdcpomatic2-wx.mo
-%{_datadir}/locale/it_IT/LC_MESSAGES/libdcpomatic2.mo
-%{_datadir}/locale/sv_SE/LC_MESSAGES/dcpomatic2.mo
-%{_datadir}/locale/sv_SE/LC_MESSAGES/libdcpomatic2-wx.mo
-%{_datadir}/locale/sv_SE/LC_MESSAGES/libdcpomatic2.mo
-%{_datadir}/locale/nl_NL/LC_MESSAGES/dcpomatic2.mo
-%{_datadir}/locale/nl_NL/LC_MESSAGES/libdcpomatic2-wx.mo
-%{_datadir}/locale/nl_NL/LC_MESSAGES/libdcpomatic2.mo
-
-%prep
-rm -rf $RPM_BUILD_DIR/dcpomatic-@VERSION@
-tar xjf $RPM_SOURCE_DIR/dcpomatic-@VERSION@.tar.bz2
-%build
-cd dcpomatic-@VERSION@
-export PKG_CONFIG_PATH=@INSTALL_PREFIX@/lib/pkgconfig:/usr/local/lib/pkgconfig
-CXXFLAGS="-I@INSTALL_PREFIX@/include" LDFLAGS="-L@INSTALL_PREFIX@/lib" ./waf configure --prefix=%{buildroot}/usr --install-prefix=/usr --target-centos-@CENTOS_VERSION@ --disable-tests
-./waf
-%install
-cd dcpomatic-@VERSION@
-./waf install
-
-%post
-/bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null || :
-
-%postun
-if [ $1 -eq 0 ] ; then
-    /bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null
-    /usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :
-fi
-
-%posttrans
-/usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :
index 336c1bcb002aefee8c18fe4a478832e3cd186336..5f1dc48a243a4601d2d163f82488e48a818f8e4d 100644 (file)
@@ -17,14 +17,4 @@ def build(bld):
     obj.INSTALL_PREFIX = bld.env.INSTALL_PREFIX
     obj.VERSION = bld.env.VERSION
 
-    obj = bld(features='subst')
-    obj.source = 'dcpomatic.spec.in'
-    obj.target = 'dcpomatic2.spec'
-    obj.INSTALL_PREFIX = bld.env.INSTALL_PREFIX
-    obj.VERSION = bld.env.VERSION
-    if bld.env.TARGET_CENTOS_6:
-        obj.CENTOS_VERSION = '6'
-    elif bld.env.TARGET_CENTOS_7:
-        obj.CENTOS_VERSION = '7'
-
     bld.install_files('${PREFIX}/share/applications', ['dcpomatic2.desktop', 'dcpomatic2_batch.desktop', 'dcpomatic2_server.desktop'])
index 9b94ada80452c305ee67d31cf9e4436f800005a0..e9c02589375a47d2a60d291390b890153b73ada6 100755 (executable)
--- a/run/tests
+++ b/run/tests
@@ -3,6 +3,7 @@
 # e.g. --run_tests=foo
 
 export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
+export DCPOMATIC_LINUX_SHARE_PREFIX=`pwd`
 if [ "$1" == "--debug" ]; then
     shift;
     gdb --args build/test/unit-tests --catch_system_errors=no $*
index 079fe884e762f76980f1b08799f659363ed9ef8b..cdf6238767e21bc7ba0931a04edaf52d75de1277 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -39,6 +39,8 @@ AnalyseAudioJob::AnalyseAudioJob (shared_ptr<const Film> f, shared_ptr<AudioCont
        , _content (c)
        , _done (0)
        , _samples_per_point (1)
+       , _overall_peak (0)
+       , _overall_peak_frame (0)
 {
 
 }
@@ -81,6 +83,7 @@ AnalyseAudioJob::run ()
                set_progress (t.seconds() / _film->length().seconds());
        }
 
+       _analysis->set_peak (_overall_peak, DCPTime::from_frames (_overall_peak_frame, _film->audio_frame_rate ()));
        _analysis->write (content->audio_analysis_path ());
        
        set_progress (1);
@@ -101,6 +104,15 @@ AnalyseAudioJob::analyse (shared_ptr<const AudioBuffers> b)
                        _current[j][AudioPoint::RMS] += pow (s, 2);
                        _current[j][AudioPoint::PEAK] = max (_current[j][AudioPoint::PEAK], fabsf (s));
 
+                       float const as = fabs (s);
+
+                       _current[j][AudioPoint::PEAK] = max (_current[j][AudioPoint::PEAK], as);
+
+                       if (as > _overall_peak) {
+                               _overall_peak = as;
+                               _overall_peak_frame = _done + i;
+                       }
+
                        if ((_done % _samples_per_point) == 0) {
                                _current[j][AudioPoint::RMS] = sqrt (_current[j][AudioPoint::RMS] / _samples_per_point);
                                _analysis->add_point (j, _current[j]);
index 6f64dd27248c13f029aab6257c35181f57ad972b..0f9605eedddba6c386fc472dafd4a644cda5b5bc 100644 (file)
@@ -52,6 +52,9 @@ private:
        int64_t _samples_per_point;
        std::vector<AudioPoint> _current;
 
+       float _overall_peak;
+       AudioFrame _overall_peak_frame;
+
        boost::shared_ptr<AudioAnalysis> _analysis;
 
        static const int _num_points;
index 19a0d876ebc75ab962de69a5408ef42abaa640a0..73422a9be9ad8a2f3a6039d7487c284ddfe4f8bf 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
 
     This 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 "audio_analysis.h"
-#include "dcpomatic_assert.h"
 #include "cross.h"
+#include "util.h"
+#include "raw_convert.h"
+#include <libxml++/libxml++.h>
 #include <boost/filesystem.hpp>
+#include <boost/foreach.hpp>
 #include <stdint.h>
 #include <cmath>
-#include <cassert>
 #include <cstdio>
 #include <iostream>
+#include <inttypes.h>
 
 using std::ostream;
 using std::istream;
@@ -34,6 +37,7 @@ using std::vector;
 using std::cout;
 using std::max;
 using std::list;
+using boost::shared_ptr;
 
 AudioPoint::AudioPoint ()
 {
@@ -42,14 +46,10 @@ AudioPoint::AudioPoint ()
        }
 }
 
-AudioPoint::AudioPoint (FILE* f)
+AudioPoint::AudioPoint (cxml::ConstNodePtr node)
 {
-       for (int i = 0; i < COUNT; ++i) {
-               int n = fscanf (f, "%f", &_data[i]);
-               if (n != 1) {
-                       _data[i] = 0;
-               }
-       }
+       _data[PEAK] = node->number_child<float> ("Peak");
+       _data[RMS] = node->number_child<float> ("RMS");
 }
 
 AudioPoint::AudioPoint (AudioPoint const & other)
@@ -74,14 +74,12 @@ AudioPoint::operator= (AudioPoint const & other)
 }
 
 void
-AudioPoint::write (FILE* f) const
+AudioPoint::as_xml (xmlpp::Element* parent) const
 {
-       for (int i = 0; i < COUNT; ++i) {
-               fprintf (f, "%f\n", _data[i]);
-       }
+       parent->add_child ("Peak")->add_child_text (raw_convert<string> (_data[PEAK]));
+       parent->add_child ("RMS")->add_child_text (raw_convert<string> (_data[RMS]));
 }
        
-
 AudioAnalysis::AudioAnalysis (int channels)
 {
        _data.resize (channels);
@@ -89,33 +87,21 @@ AudioAnalysis::AudioAnalysis (int channels)
 
 AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
 {
-       FILE* f = fopen_boost (filename, "r");
-       if (!f) {
-               throw OpenFileError (filename);
-       }
+       cxml::Document f ("AudioAnalysis");
+       f.read_file (filename);
 
-       int channels = 0;
-       fscanf (f, "%d", &channels);
-       _data.resize (channels);
+       BOOST_FOREACH (cxml::NodePtr i, f.node_children ("Channel")) {
+               vector<AudioPoint> channel;
 
-       for (int i = 0; i < channels; ++i) {
-               int points;
-               fscanf (f, "%d", &points);
-               if (feof (f)) {
-                       fclose (f);
-                       return;
-               }
-               
-               for (int j = 0; j < points; ++j) {
-                       _data[i].push_back (AudioPoint (f));
-                       if (feof (f)) {
-                               fclose (f);
-                               return;
-                       }
+               BOOST_FOREACH (cxml::NodePtr j, i->node_children ("Point")) {
+                       channel.push_back (AudioPoint (j));
                }
+
+               _data.push_back (channel);
        }
 
-       fclose (f);
+       _peak = f.number_child<float> ("Peak");
+       _peak_time = DCPTime (f.number_child<DCPTime::Type> ("PeakTime"));
 }
 
 void
@@ -148,22 +134,20 @@ AudioAnalysis::points (int c) const
 void
 AudioAnalysis::write (boost::filesystem::path filename)
 {
-       boost::filesystem::path tmp = filename;
-       tmp.replace_extension (".tmp");
+       shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
+       xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
 
-       FILE* f = fopen_boost (tmp, "w");
-       if (!f) {
-               throw OpenFileError (tmp);
+       BOOST_FOREACH (vector<AudioPoint>& i, _data) {
+               xmlpp::Element* channel = root->add_child ("Channel");
+               BOOST_FOREACH (AudioPoint& j, i) {
+                       j.as_xml (channel->add_child ("Point"));
+               }
        }
 
-       fprintf (f, "%ld\n", _data.size ());
-       for (vector<vector<AudioPoint> >::iterator i = _data.begin(); i != _data.end(); ++i) {
-               fprintf (f, "%ld\n", i->size ());
-               for (vector<AudioPoint>::iterator j = i->begin(); j != i->end(); ++j) {
-                       j->write (f);
-               }
+       if (_peak) {
+               root->add_child("Peak")->add_child_text (raw_convert<string> (_peak.get ()));
+               root->add_child("PeakTime")->add_child_text (raw_convert<string> (_peak_time.get().get ()));
        }
 
-       fclose (f);
-       boost::filesystem::rename (tmp, filename);
+       doc->write_to_file_formatted (filename.string ());
 }
index 865d647816e00ebcb6a8dac6c617db936ddd2329..9387ec896b67b92f072a3d31b0509a4373dc03e3 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
 
 */
 
-/** @file  src/lib/audio_analysis.h
- *  @brief AudioAnalysis and AudioPoint classes.
- */
-
 #ifndef DCPOMATIC_AUDIO_ANALYSIS_H
 #define DCPOMATIC_AUDIO_ANALYSIS_H
 
-#include <boost/filesystem.hpp>
 #include <vector>
+#include <list>
+#include <boost/filesystem.hpp>
+#include <boost/optional.hpp>
+#include <libcxml/cxml.h>
+#include "types.h"
 
-/** @class AudioPoint
- *  @brief A single point of an audio analysis for one portion of one channel.
- */
 class AudioPoint
 {
 public:
@@ -40,11 +37,11 @@ public:
        };
 
        AudioPoint ();
-       AudioPoint (FILE *);
+       AudioPoint (cxml::ConstNodePtr node);
        AudioPoint (AudioPoint const &);
        AudioPoint& operator= (AudioPoint const &);
 
-       void write (FILE *) const;
+       void as_xml (xmlpp::Element *) const;
        
        float& operator[] (int t) {
                return _data[t];
@@ -54,14 +51,6 @@ private:
        float _data[COUNT];
 };
 
-/** @class AudioAnalysis
- *  @brief An analysis of the audio data in a piece of AudioContent.
- *
- *  This is a set of AudioPoints for each channel.  The AudioPoints
- *  each represent some measurement of the audio over a portion of the
- *  content.  For example each AudioPoint may give the RMS level of
- *  a 1-minute portion of the audio.
- */
 class AudioAnalysis : public boost::noncopyable
 {
 public:
@@ -69,15 +58,29 @@ public:
        AudioAnalysis (boost::filesystem::path);
 
        void add_point (int c, AudioPoint const & p);
+       void set_peak (float peak, DCPTime time) {
+               _peak = peak;
+               _peak_time = time;
+       }
        
        AudioPoint get_point (int c, int p) const;
        int points (int c) const;
        int channels () const;
 
+       boost::optional<float> peak () const {
+               return _peak;
+       }
+
+       boost::optional<DCPTime> peak_time () const {
+               return _peak_time;
+       }
+
        void write (boost::filesystem::path);
 
 private:
        std::vector<std::vector<AudioPoint> > _data;
+       boost::optional<float> _peak;
+       boost::optional<DCPTime> _peak_time;
 };
 
 #endif
index 22376e3e36bb2c19d19fb77f82000f3a7cba987e..f6133947a0e4ad7e6ef57d38cd063102eb682411 100644 (file)
@@ -80,10 +80,10 @@ AudioDecoder::get_audio (AudioFrame frame, AudioFrame length, bool accurate)
         */
        if (accurate) {
                /* Keep stuffing data into _decoded_audio until we have enough data, or the subclass does not want to give us any more */
-               while ((_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end) && !pass ()) {}
+               while ((_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end) && !pass (PASS_REASON_AUDIO)) {}
                decoded_offset = frame - _decoded_audio.frame;
        } else {
-               while (_decoded_audio.audio->frames() < length && !pass ()) {}
+               while (_decoded_audio.audio->frames() < length && !pass (PASS_REASON_AUDIO)) {}
                /* Use decoded_offset of 0, as we don't really care what frames we return */
        }
 
index fcc658717900eb8e4468883815332e5d3f40c72e..b9e8367e1c3025ba0b0ea992c6a5c8830de60efb 100644 (file)
@@ -24,7 +24,6 @@
 #include "content.h"
 #include "util.h"
 #include "content_factory.h"
-#include "ui_signaller.h"
 #include "exceptions.h"
 #include "film.h"
 #include "safe_stringstream.h"
@@ -153,9 +152,7 @@ Content::examine (shared_ptr<Job> job)
 void
 Content::signal_changed (int p)
 {
-       if (ui_signaller) {
-               ui_signaller->emit (boost::bind (boost::ref (Changed), shared_from_this (), p, _change_signals_frequent));
-       }
+       emit (boost::bind (boost::ref (Changed), shared_from_this (), p, _change_signals_frequent));
 }
 
 void
index c6cede5fa6cafde4d04f18dc1943a3551c858a2c..2b966110bfd398d65f58f545a8052db5ccff8f6b 100644 (file)
@@ -25,6 +25,7 @@
 #define DCPOMATIC_CONTENT_H
 
 #include "types.h"
+#include "signaller.h"
 #include "dcpomatic_time.h"
 #include <libxml++/libxml++.h>
 #include <libcxml/cxml.h>
@@ -53,7 +54,7 @@ public:
 /** @class Content
  *  @brief A piece of content represented by one or more files on disk.
  */
-class Content : public boost::enable_shared_from_this<Content>, public boost::noncopyable
+class Content : public boost::enable_shared_from_this<Content>, public Signaller, public boost::noncopyable
 {
 public:
        Content (boost::shared_ptr<const Film>);
index 9894d885f1cea6e0c7a3a7e8f7d9d02a66d8b735..285fbe1ce4352e0852dda1b457b75bdd94e5b5a3 100644 (file)
@@ -155,6 +155,10 @@ boost::filesystem::path
 shared_path ()
 {
 #ifdef DCPOMATIC_LINUX
+       char const * p = getenv ("DCPOMATIC_LINUX_SHARE_PREFIX");
+       if (p) {
+               return p;
+       }
        return boost::filesystem::canonical (LINUX_SHARE_PREFIX);
 #endif
 #ifdef DCPOMATIC_WINDOWS
index 51d16b43c6d767f03c2b2b85237fc06a657c8ea3..3bfbd7720c9e04cf99b951db455e306bd2be3cb9 100644 (file)
@@ -55,7 +55,7 @@ DCPDecoder::DCPDecoder (shared_ptr<const DCPContent> c)
 }
 
 bool
-DCPDecoder::pass ()
+DCPDecoder::pass (PassReason)
 {
        if (_reel == _reels.end () || !_dcp_content->can_be_played ()) {
                return true;
@@ -133,7 +133,13 @@ DCPDecoder::seek (ContentTime t, bool accurate)
 
 
 list<ContentTimePeriod>
-DCPDecoder::subtitles_during (ContentTimePeriod, bool) const
+DCPDecoder::image_subtitles_during (ContentTimePeriod, bool) const
+{
+       return list<ContentTimePeriod> ();
+}
+
+list<ContentTimePeriod>
+DCPDecoder::text_subtitles_during (ContentTimePeriod, bool) const
 {
        /* XXX */
        return list<ContentTimePeriod> ();
index 8afebff57dbe0858dc1ccb20e10de74b9acc45fe..3a05325c744ec195ed0b3320193840804b4e4dc1 100644 (file)
@@ -38,9 +38,11 @@ public:
        DCPDecoder (boost::shared_ptr<const DCPContent>);
 
 private:
+       bool pass (PassReason);
        void seek (ContentTime t, bool accurate);
-       bool pass ();
-       std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const;
+       
+       std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod, bool starting) const;
+       std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod, bool starting) const;
 
        ContentTime _next;
        std::list<boost::shared_ptr<dcp::Reel> > _reels;
index e3c06378b8eabd86216d8f08394e1db57152fab6..93a122590749b848d3eea519177c5f8bd61ce56a 100644 (file)
@@ -46,7 +46,7 @@ DCPSubtitleDecoder::seek (ContentTime time, bool accurate)
 }
 
 bool
-DCPSubtitleDecoder::pass ()
+DCPSubtitleDecoder::pass (PassReason)
 {
        if (_next == _subtitles.end ()) {
                return true;
@@ -61,7 +61,13 @@ DCPSubtitleDecoder::pass ()
 }
 
 list<ContentTimePeriod>
-DCPSubtitleDecoder::subtitles_during (ContentTimePeriod p, bool starting) const
+DCPSubtitleDecoder::image_subtitles_during (ContentTimePeriod, bool) const
+{
+       return list<ContentTimePeriod> ();
+}
+       
+list<ContentTimePeriod>
+DCPSubtitleDecoder::text_subtitles_during (ContentTimePeriod p, bool starting) const
 {
        /* XXX: inefficient */
 
index 2326b31ad8098afdfb6fa96135b0872732208771..52e40041683f61119a7445dc0fcc11d1ff1b9387 100644 (file)
@@ -28,11 +28,12 @@ public:
        DCPSubtitleDecoder (boost::shared_ptr<const DCPSubtitleContent>);
 
 protected:
+       bool pass (PassReason);
        void seek (ContentTime time, bool accurate);
-       bool pass ();
 
 private:
-       std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const;
+       std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod, bool starting) const;
+       std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod, bool starting) const;
 
        std::list<dcp::SubtitleString> _subtitles;
        std::list<dcp::SubtitleString>::const_iterator _next;
index dc9b0cd8ac680c6e1a3803d7805c76cdd9d14b57..ae8f251996088814f507ff63f0b2c6427ea68d97 100644 (file)
@@ -193,6 +193,7 @@ class ContentTimePeriod
 {
 public:
        ContentTimePeriod () {}
+       
        ContentTimePeriod (ContentTime f, ContentTime t)
                : from (f)
                , to (t)
index c1b8598650357b6a9978efdbcc28eb495ba69511..0703a542686933e3065f3b3cd5a343498ef0cebd 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -41,6 +41,7 @@ public:
        virtual ~Decoder () {}
 
 protected:     
+
        /** Seek so that the next pass() will yield the next thing
         *  (video/sound frame, subtitle etc.) at or after the requested
         *  time.  Pass accurate = true to try harder to ensure that, at worst,
@@ -50,7 +51,14 @@ protected:
         *  it may seek to just the right spot.
         */
        virtual void seek (ContentTime time, bool accurate) = 0;
-       virtual bool pass () = 0;
+
+       enum PassReason {
+               PASS_REASON_VIDEO,
+               PASS_REASON_AUDIO,
+               PASS_REASON_SUBTITLE
+       };
+       
+       virtual bool pass (PassReason reason) = 0;
 };
 
 #endif
index 3a42b169fd3000a3cce43fcd76b73f27177b4cb5..a52b53b04099251b8f84a7d1e9446f869c7ffc31 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -365,33 +365,22 @@ FFmpegContent::audio_analysis_path () const
           analyses for each stream.
        */
 
-       boost::filesystem::path p = film->audio_analysis_dir ();
-       string name = digest();
+       boost::filesystem::path p = AudioContent::audio_analysis_path ();
        if (audio_stream ()) {
-               name += "_" + audio_stream()->identifier ();
+               p = p.string() + "_" + audio_stream()->identifier ();
        }
-       p /= name;
        return p;
 }
 
 list<ContentTimePeriod>
 FFmpegContent::subtitles_during (ContentTimePeriod period, bool starting) const
 {
-       list<ContentTimePeriod> d;
-       
        shared_ptr<FFmpegSubtitleStream> stream = subtitle_stream ();
        if (!stream) {
-               return d;
-       }
-
-       /* XXX: inefficient */
-       for (vector<ContentTimePeriod>::const_iterator i = stream->periods.begin(); i != stream->periods.end(); ++i) {
-               if ((starting && period.contains (i->from)) || (!starting && period.overlaps (*i))) {
-                       d.push_back (*i);
-               }
+               return list<ContentTimePeriod> ();
        }
 
-       return d;
+       return stream->subtitles_during (period, starting);
 }
 
 bool
index 76ba43567d9961ea2c1798c3ebfe13fb27e9772b..d6edb2bdbd10978688c8c9c88bd614b52507baa6 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
index bd01b280b65bc223101aac0282ef4c4bc5f71b40..35e15a331c2cd58c0bd7a2926b21ca340af456ab 100644 (file)
@@ -132,7 +132,7 @@ FFmpegDecoder::flush ()
 }
 
 bool
-FFmpegDecoder::pass ()
+FFmpegDecoder::pass (PassReason reason)
 {
        int r = av_read_frame (_format_context, &_packet);
 
@@ -153,12 +153,13 @@ FFmpegDecoder::pass ()
        }
 
        int const si = _packet.stream_index;
+       shared_ptr<const FFmpegContent> fc = _ffmpeg_content;
 
-       if (si == _video_stream && !_ignore_video) {
+       if (si == _video_stream && !_ignore_video && reason != PASS_REASON_SUBTITLE) {
                decode_video_packet ();
-       } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) {
+       } else if (fc->audio_stream() && fc->audio_stream()->uses_index (_format_context, si) && reason != PASS_REASON_SUBTITLE) {
                decode_audio_packet ();
-       } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si)) {
+       } else if (fc->subtitle_stream() && fc->subtitle_stream()->uses_index (_format_context, si)) {
                decode_subtitle_packet ();
        }
 
@@ -301,6 +302,7 @@ FFmpegDecoder::seek (ContentTime time, bool accurate)
 {
        VideoDecoder::seek (time, accurate);
        AudioDecoder::seek (time, accurate);
+       SubtitleDecoder::seek (time, accurate);
 
        /* If we are doing an `accurate' seek, we need to use pre-roll, as
           we don't really know what the seek will give us.
@@ -425,35 +427,69 @@ FFmpegDecoder::decode_subtitle_packet ()
        if (avcodec_decode_subtitle2 (subtitle_codec_context(), &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) {
                return;
        }
-
-       /* 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) {
-               image_subtitle (ContentTimePeriod (), shared_ptr<Image> (), dcpomatic::Rect<double> ());
+               /* Sometimes we get an empty AVSubtitle, which is used by some codecs to
+                  indicate that the previous subtitle should stop.  We can ignore it here.
+               */
                return;
        } else if (sub.num_rects > 1) {
                throw DecodeError (_("multi-part subtitles not yet supported"));
        }
-               
+
        /* Subtitle PTS (within the source, not taking into account any of the
-          source that we may have chopped off for the DCP)
+          source that we may have chopped off for the DCP).
        */
-       ContentTimePeriod period = subtitle_period (sub) + _pts_offset;
-
+       FFmpegSubtitlePeriod sub_period = subtitle_period (sub);
+       ContentTimePeriod period;
+       period.from = sub_period.from + _pts_offset;
+       if (sub_period.to) {
+               /* We already know the subtitle period `to' time */
+               period.to = sub_period.to.get() + _pts_offset;
+       } else {
+               /* We have to look up the `to' time in the stream's records */
+               period.to = ffmpeg_content()->subtitle_stream()->find_subtitle_to (sub_period.from);
+       }
+       
        AVSubtitleRect const * rect = sub.rects[0];
 
-       if (rect->type != SUBTITLE_BITMAP) {
-               /* XXX */
-               // throw DecodeError (_("non-bitmap subtitles not yet supported"));
-               return;
+       switch (rect->type) {
+       case SUBTITLE_NONE:
+               break;
+       case SUBTITLE_BITMAP:
+               decode_bitmap_subtitle (rect, period);
+               break;
+       case SUBTITLE_TEXT:
+               cout << "XXX: SUBTITLE_TEXT " << rect->text << "\n";
+               break;
+       case SUBTITLE_ASS:
+               cout << "XXX: SUBTITLE_ASS " << rect->ass << "\n";
+               break;
        }
+       
+       avsubtitle_free (&sub);
+}
+
+list<ContentTimePeriod>
+FFmpegDecoder::image_subtitles_during (ContentTimePeriod p, bool starting) const
+{
+       return _ffmpeg_content->subtitles_during (p, starting);
+}
 
+list<ContentTimePeriod>
+FFmpegDecoder::text_subtitles_during (ContentTimePeriod, bool) const
+{
+       return list<ContentTimePeriod> ();
+}
+
+void
+FFmpegDecoder::decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTimePeriod period)
+{
        /* Note RGBA is expressed little-endian, so the first byte in the word is R, second
           G, third B, fourth A.
        */
        shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::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 BGRA palette which is here
@@ -462,7 +498,7 @@ FFmpegDecoder::decode_subtitle_packet ()
        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;
@@ -473,25 +509,15 @@ FFmpegDecoder::decode_subtitle_packet ()
                sub_p += rect->pict.linesize[0];
                out_p += image->stride()[0] / sizeof (uint32_t);
        }
-
+       
        dcp::Size const vs = _ffmpeg_content->video_size ();
-
-       image_subtitle (
-               period,
-               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
-                       )
+       dcpomatic::Rect<double> const scaled_rect (
+               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
                );
        
-       avsubtitle_free (&sub);
+       image_subtitle (period, image, scaled_rect);
 }
 
-list<ContentTimePeriod>
-FFmpegDecoder::subtitles_during (ContentTimePeriod p, bool starting) const
-{
-       return _ffmpeg_content->subtitles_during (p, starting);
-}
index 0334a30e203f4064d8ef4d0ea788eb38d0a2ef2a..6f027ce1c1ba2687df7d774aa22207883f373a27 100644 (file)
@@ -27,6 +27,7 @@
 #include "audio_decoder.h"
 #include "subtitle_decoder.h"
 #include "ffmpeg.h"
+#include "rect.h"
 extern "C" {
 #include <libavcodec/avcodec.h>
 }
@@ -52,8 +53,8 @@ public:
 private:
        friend struct ::ffmpeg_pts_offset_test;
 
+       bool pass (PassReason reason);
        void seek (ContentTime time, bool);
-       bool pass ();
        void flush ();
 
        AVSampleFormat audio_sample_format () const;
@@ -63,10 +64,13 @@ private:
        void decode_audio_packet ();
        void decode_subtitle_packet ();
 
+       void decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTimePeriod period);
+
        void maybe_add_subtitle ();
        boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
 
-       std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const;
+       std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod, bool starting) const;
+       std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod, bool starting) const;
        
        boost::shared_ptr<Log> _log;
        
index 4409526dc4872015897357ffa385a28259a6acf2..8afd4c164a85a54c1fc8dd11e147bc49cd407334 100644 (file)
@@ -150,13 +150,18 @@ FFmpegExaminer::subtitle_packet (AVCodecContext* context, shared_ptr<FFmpegSubti
        int frame_finished;
        AVSubtitle sub;
        if (avcodec_decode_subtitle2 (context, &sub, &frame_finished, &_packet) >= 0 && frame_finished) {
-               ContentTimePeriod const period = subtitle_period (sub);
-               if (sub.num_rects == 0 && !stream->periods.empty () && stream->periods.back().to > period.from) {
-                       /* Finish the last subtitle */
-                       stream->periods.back().to = period.from;
+               FFmpegSubtitlePeriod const period = subtitle_period (sub);
+               if (sub.num_rects <= 0 && _last_subtitle_start) {
+                       stream->add_subtitle (ContentTimePeriod (_last_subtitle_start.get (), period.from));
+                       _last_subtitle_start = optional<ContentTime> ();
                } else if (sub.num_rects == 1) {
-                       stream->periods.push_back (period);
+                       if (period.to) {
+                               stream->add_subtitle (ContentTimePeriod (period.from, period.to.get ()));
+                       } else {
+                               _last_subtitle_start = period.from;
+                       }
                }
+               avsubtitle_free (&sub);
        }
 }
 
index b873222c112e3d06c77bb989475f2a69f4bc7bbe..34d4b1e0d341304f4f9ff903f7debcc958421a40 100644 (file)
@@ -55,7 +55,7 @@ private:
        std::string audio_stream_name (AVStream* s) const;
        std::string subtitle_stream_name (AVStream* s) const;
        boost::optional<ContentTime> frame_time (AVStream* s) const;
-       
+
        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
        std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
        boost::optional<ContentTime> _first_video;
@@ -64,4 +64,6 @@ private:
         */
        ContentTime _video_length;
        bool _need_video_length;
+
+       boost::optional<ContentTime> _last_subtitle_start;
 };
index 3d8fd4e8375466710cb51ffd4a6a7a4ac950c75e..77a56e330297f608c84e6077c797190d5cad2809 100644 (file)
 */
 
 #include "ffmpeg_subtitle_stream.h"
+#include "raw_convert.h"
+#include <libxml++/libxml++.h>
+#include <boost/foreach.hpp>
+
+using std::string;
+using std::map;
+using std::list;
 
 /** Construct a SubtitleStream from a value returned from to_string().
  *  @param t String returned from to_string().
 FFmpegSubtitleStream::FFmpegSubtitleStream (cxml::ConstNodePtr node)
        : FFmpegStream (node)
 {
-       
+       BOOST_FOREACH (cxml::NodePtr i, node->node_children ("Period")) {
+               add_subtitle (
+                       ContentTimePeriod (
+                               ContentTime (node->number_child<ContentTime::Type> ("From")),
+                               ContentTime (node->number_child<ContentTime::Type> ("To"))
+                               )
+                       );
+       }
 }
 
 void
 FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
 {
        FFmpegStream::as_xml (root);
+
+       for (map<ContentTime, ContentTime>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
+               xmlpp::Node* node = root->add_child ("Subtitle");
+               node->add_child("From")->add_child_text (raw_convert<string> (i->first.get ()));
+               node->add_child("To")->add_child_text (raw_convert<string> (i->second.get ()));
+       }
+}
+
+void
+FFmpegSubtitleStream::add_subtitle (ContentTimePeriod period)
+{
+       DCPOMATIC_ASSERT (_subtitles.find (period.from) == _subtitles.end ());
+       _subtitles[period.from] = period.to;
+}
+
+list<ContentTimePeriod> 
+FFmpegSubtitleStream::subtitles_during (ContentTimePeriod period, bool starting) const
+{
+       list<ContentTimePeriod> d;
+       
+       /* XXX: inefficient */
+       for (map<ContentTime, ContentTime>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
+               if ((starting && period.contains (i->first)) || (!starting && period.overlaps (ContentTimePeriod (i->first, i->second)))) {
+                       d.push_back (ContentTimePeriod (i->first, i->second));
+               }
+       }
+
+       return d;
+}
+
+ContentTime
+FFmpegSubtitleStream::find_subtitle_to (ContentTime from) const
+{
+       map<ContentTime, ContentTime>::const_iterator i = _subtitles.find (from);
+       DCPOMATIC_ASSERT (i != _subtitles.end ());
+       return i->second;
 }
index b16b825e7d76b3a401bc36c94a28b382771b1cd8..3ed931b8c996b1691cf0acbe25dc9434d28b1cd1 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -31,6 +31,11 @@ public:
 
        void as_xml (xmlpp::Node *) const;
 
-       std::vector<ContentTimePeriod> periods;
+       void add_subtitle (ContentTimePeriod period);
+       std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod period, bool starting) const;
+       ContentTime find_subtitle_to (ContentTime from) const;
+
+private:
+       std::map<ContentTime, ContentTime> _subtitles;
 };
 
index 80755a4cb8c20972a415cb66495adba1f4725162..35773c797e25c591c20b65c819a1415a1d8bdc99 100644 (file)
@@ -32,7 +32,6 @@
 #include "exceptions.h"
 #include "examine_content_job.h"
 #include "config.h"
-#include "ui_signaller.h"
 #include "playlist.h"
 #include "player.h"
 #include "dcp_content_type.h"
@@ -790,9 +789,7 @@ Film::signal_changed (Property p)
                break;
        }
 
-       if (ui_signaller) {
-               ui_signaller->emit (boost::bind (boost::ref (Changed), p));
-       }
+       emit (boost::bind (boost::ref (Changed), p));
 }
 
 void
@@ -995,9 +992,7 @@ Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
                signal_changed (NAME);
        }
 
-       if (ui_signaller) {
-               ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
-       }
+       emit (boost::bind (boost::ref (ContentChanged), c, p));
 }
 
 void
index 3cd370a0d109a753c14314592346518208f3fae2..f61062be0cc3bfde33d368e3c12e0941a90b79d9 100644 (file)
@@ -29,6 +29,7 @@
 #include "types.h"
 #include "isdcf_metadata.h"
 #include "frame_rate_change.h"
+#include "signaller.h"
 #include "ratio.h"
 #include <dcp/key.h>
 #include <dcp/encrypted_kdm.h>
@@ -55,7 +56,7 @@ struct isdcf_name_test;
  *
  *  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>, public boost::noncopyable
+class Film : public boost::enable_shared_from_this<Film>, public Signaller, public boost::noncopyable
 {
 public:
        Film (boost::filesystem::path, bool log = true);
index 78201fc23d9d7b838d30ed6edbb0d09e4c258369..250c8f845b6c2f10141aac2c3c46dd2e7fe55dbd 100644 (file)
@@ -43,7 +43,7 @@ ImageDecoder::ImageDecoder (shared_ptr<const ImageContent> c)
 }
 
 bool
-ImageDecoder::pass ()
+ImageDecoder::pass (PassReason)
 {
        if (_video_position >= _image_content->video_length().frames (_image_content->video_frame_rate ())) {
                return true;
index 242f69477826a499d915505cd0b9486808aba68d..ec90051daf8e43c45a565d69d9ad43f212636c52 100644 (file)
@@ -34,10 +34,9 @@ public:
                return _image_content;
        }
 
-       void seek (ContentTime, bool);
-
 private:
-       bool pass ();
+       bool pass (PassReason);
+       void seek (ContentTime, bool);
        
        boost::shared_ptr<const ImageContent> _image_content;
        boost::shared_ptr<ImageProxy> _image;
index eadafbf73a1f8d6b20c3ffca9527d90a68f2fb18..c4d93ddc14b733b5d913fa1411a3cddf0f76648d 100644 (file)
@@ -27,7 +27,6 @@
 #include "job.h"
 #include "util.h"
 #include "cross.h"
-#include "ui_signaller.h"
 #include "exceptions.h"
 #include "film.h"
 #include "log.h"
@@ -203,8 +202,8 @@ Job::set_state (State s)
                }
        }
 
-       if (finished && ui_signaller) {
-               ui_signaller->emit (boost::bind (boost::ref (Finished)));
+       if (finished) {
+               emit (boost::bind (boost::ref (Finished)));
        }       
 }
 
@@ -239,9 +238,7 @@ Job::set_progress (float p, bool force)
                _pause_changed.wait (lm2);
        }
 
-       if (ui_signaller) {
-               ui_signaller->emit (boost::bind (boost::ref (Progress)));
-       }
+       emit (boost::bind (boost::ref (Progress)));
 }
 
 /** @return fractional progress of the current sub-job, if known */
@@ -301,9 +298,7 @@ Job::set_progress_unknown ()
        _progress.reset ();
        lm.unlock ();
 
-       if (ui_signaller) {
-               ui_signaller->emit (boost::bind (boost::ref (Progress)));
-       }
+       emit (boost::bind (boost::ref (Progress)));
 }
 
 /** @return Human-readable status of this job */
index 7c6707880d752cc62d3c7a0c602c7f7272ebaff2..8fe87747c2186a465b48799ab8b4734bc9a58fe6 100644 (file)
@@ -24,6 +24,7 @@
 #ifndef DCPOMATIC_JOB_H
 #define DCPOMATIC_JOB_H
 
+#include "signaller.h"
 #include <boost/thread/mutex.hpp>
 #include <boost/enable_shared_from_this.hpp>
 #include <boost/signals2.hpp>
@@ -35,7 +36,7 @@ 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>, public boost::noncopyable
+class Job : public boost::enable_shared_from_this<Job>, public Signaller, public boost::noncopyable
 {
 public:
        Job (boost::shared_ptr<const Film>);
index 2b727b0aaa43985942674f07d4f881051369236b..b5b64a77eccc9d9f000d8512ee3a2101142a059b 100644 (file)
@@ -26,7 +26,6 @@
 #include "job_manager.h"
 #include "job.h"
 #include "cross.h"
-#include "ui_signaller.h"
 
 using std::string;
 using std::list;
@@ -64,9 +63,7 @@ JobManager::add (shared_ptr<Job> j)
                _jobs.push_back (j);
        }
 
-       if (ui_signaller) {
-               ui_signaller->emit (boost::bind (boost::ref (JobAdded), weak_ptr<Job> (j)));
-       }
+       emit (boost::bind (boost::ref (JobAdded), weak_ptr<Job> (j)));
        
        return j;
 }
@@ -138,9 +135,7 @@ JobManager::scheduler ()
 
                if (active_jobs != _last_active_jobs) {
                        _last_active_jobs = active_jobs;
-                       if (ui_signaller) {
-                               ui_signaller->emit (boost::bind (boost::ref (ActiveJobsChanged), active_jobs));
-                       }
+                       emit (boost::bind (boost::ref (ActiveJobsChanged), active_jobs));
                }
 
                dcpomatic_sleep (1);
index 9d8620cbb58165ef320d40497235a92736e325e6..b946c1a98e0836893eb758b2d19554d4643871e2 100644 (file)
@@ -21,6 +21,7 @@
  *  @brief A simple scheduler for jobs.
  */
 
+#include "signaller.h"
 #include <boost/thread/mutex.hpp>
 #include <boost/thread.hpp>
 #include <boost/signals2.hpp>
@@ -32,7 +33,7 @@ extern void wait_for_jobs ();
 /** @class JobManager
  *  @brief A simple scheduler for jobs.
  */
-class JobManager : public boost::noncopyable
+class JobManager : public Signaller, public boost::noncopyable
 {
 public:
 
index 3f88bbd9dbefe5748db4efb3a369b8b9e02d57d0..8949736f80353eb8987cb623e68bd9ca81c0e77b 100644 (file)
@@ -253,6 +253,8 @@ email_kdms (
                if (!Config::instance()->kdm_bcc().empty ()) {
                        quickmail_add_bcc (mail, Config::instance()->kdm_bcc().c_str ());
                }
+
+               quickmail_add_header (mail, "Content-Type: text/plain; charset=UTF-8");
                
                string body = Config::instance()->kdm_email().c_str();
                boost::algorithm::replace_all (body, "$CPL_NAME", film->dcp_name ());
index 436ae3fe88fb33adaee5aacb1c2f3cfd99799b7f..640253c6d97da8e80ca4bca5b604a5ab3f989b78 100644 (file)
@@ -376,9 +376,11 @@ Player::get_video (DCPTime time, bool accurate)
        list<PositionImage> c = transform_image_subtitles (ps.image);
        copy (c.begin(), c.end(), back_inserter (sub_images));
 
-       /* Text subtitles (rendered to images) */
-       sub_images.push_back (render_subtitles (ps.text, _video_container_size));
-       
+       /* Text subtitles (rendered to an image) */
+       if (!ps.text.empty ()) {
+               sub_images.push_back (render_subtitles (ps.text, _video_container_size));
+       }
+
        if (!sub_images.empty ()) {
                for (list<shared_ptr<PlayerVideo> >::const_iterator i = pvf.begin(); i != pvf.end(); ++i) {
                        (*i)->set_subtitle (merge (sub_images));
index bc89fd3f88dc85977e2d765227401dde059f0275..9620eacbf5c58ebe4a3dc847262067c6802ea54e 100644 (file)
@@ -50,10 +50,6 @@ calculate_position (dcp::VAlign v_align, double v_position, int target_height, i
 PositionImage
 render_subtitles (list<dcp::SubtitleString> subtitles, dcp::Size target)
 {
-       if (subtitles.empty ()) {
-               return PositionImage ();
-       }
-
        /* Estimate height that the subtitle image needs to be */
        optional<int> top;
        optional<int> bottom;
index f347132e452a7f3dc0fa1caf996d4ea5e4988c31..726437ea57b3eaa14fdea90667c38a053f5963cb 100644 (file)
@@ -22,7 +22,6 @@
 #include "util.h"
 #include "config.h"
 #include "cross.h"
-#include "ui_signaller.h"
 #include "dcpomatic_socket.h"
 #include "raw_convert.h"
 #include <libcxml/cxml.h>
@@ -173,7 +172,7 @@ ServerFinder::handle_accept (boost::system::error_code ec, shared_ptr<Socket> so
                        boost::mutex::scoped_lock lm (_mutex);
                        _servers.push_back (sd);
                }
-               ui_signaller->emit (boost::bind (boost::ref (ServerFound), sd));
+               emit (boost::bind (boost::ref (ServerFound), sd));
        }
 
        start_accept ();
index 3fab6864a332ef43b3b62a59bf2ccab75d49aae6..dc62f998deb0d79f5e9926753fbd5c81c95f7559 100644 (file)
 */
 
 #include "server.h"
+#include "signaller.h"
 #include <boost/signals2.hpp>
 
-class ServerFinder : public ExceptionStore
+class ServerFinder : public Signaller, public ExceptionStore
 {
 public:
        boost::signals2::connection connect (boost::function<void (ServerDescription)>);
diff --git a/src/lib/signal_manager.cc b/src/lib/signal_manager.cc
new file mode 100644 (file)
index 0000000..7c2b3e1
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the 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 "signal_manager.h"
+
+/** Global SignalManager instance */
+SignalManager* signal_manager = 0;
diff --git a/src/lib/signal_manager.h b/src/lib/signal_manager.h
new file mode 100644 (file)
index 0000000..ae4306e
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the 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_SIGNAL_MANAGER_H
+#define DCPOMATIC_SIGNAL_MANAGER_H
+
+#include <boost/bind.hpp>
+#include <boost/asio.hpp>
+#include <boost/thread.hpp>
+
+class Signaller;
+
+/** A class to allow signals to be emitted from non-UI threads and handled
+ *  by a UI thread.
+ */
+class SignalManager : public boost::noncopyable
+{
+public:
+       /** Create a SignalManager.  Must be called from the UI thread */
+       SignalManager ()
+               : _work (_service)
+       {
+               _ui_thread = boost::this_thread::get_id ();
+       }
+
+       /* Do something next time the UI is idle */
+       template <typename T>
+       void when_idle (T f) {
+               _service.post (f);
+       }
+
+       /** Call this in the UI when it is idle */
+       size_t ui_idle () {
+               /* This executes any functors that have been post()ed to _service */
+               return _service.poll ();
+       }
+
+       /** This should wake the UI and make it call ui_idle() */
+       virtual void wake_ui () {
+               /* This is only a sensible implementation when there is no GUI */
+               ui_idle ();
+       }
+
+private:
+       /** Emit a signal from any thread whose handlers will be called in the UI
+        *  thread.  Use something like:
+        *
+        *  ui_signaller->emit (boost::bind (boost::ref (SomeSignal), parameter));
+        */
+       template <typename T>
+       void emit (T f) {
+               if (boost::this_thread::get_id() == _ui_thread) {
+                       /* already in the UI thread */
+                       f ();
+               } else {
+                       /* non-UI thread; post to the service and wake up the UI */
+                       _service.post (f);
+                       wake_ui ();
+               }
+       }
+
+       friend class Signaller;
+       
+       /** A io_service which is used as the conduit for messages */
+       boost::asio::io_service _service;
+       /** Object required to keep io_service from stopping when it has nothing to do */
+       boost::asio::io_service::work _work;
+       /** The UI thread's ID */
+       boost::thread::id _ui_thread;
+};
+
+extern SignalManager* signal_manager;
+
+#endif
diff --git a/src/lib/signaller.h b/src/lib/signaller.h
new file mode 100644 (file)
index 0000000..4ef9b38
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+    Copyright (C) 2015 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the 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_SIGNALLER_H
+#define DCPOMATIC_SIGNALLER_H
+
+#include "signal_manager.h"
+#include <boost/thread/mutex.hpp>
+#include <boost/signals2.hpp>
+
+class WrapperBase
+{
+public:
+       WrapperBase ()
+               : _valid (true)
+               , _finished (false)
+       {}
+
+       virtual ~WrapperBase () {}
+
+       /* Can be called from any thread */
+       void invalidate ()
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _valid = false;
+       }
+
+       bool finished () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _finished;
+       }
+
+protected:
+       /* Protect _valid and _finished */
+       mutable boost::mutex _mutex;
+       bool _valid;
+       bool _finished;
+};
+
+/** Helper class to manage lifetime of signals, specifically to address
+ *  the problem where an object containing a signal is deleted before
+ *  its signal is emitted.
+ */
+template <class T>
+class Wrapper : public WrapperBase
+{
+public:
+       Wrapper (T signal)
+               : _signal (signal)
+       {
+
+       }
+
+       /* Called by the UI thread only */
+       void signal ()
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               if (_valid) {
+                       _signal ();
+               }
+               _finished = true;
+       }
+
+private:
+       T _signal;
+};
+
+/** Parent for any class which needs to raise cross-thread signals (from non-UI
+ *  to UI).  Subclasses should call, e.g. emit (boost::bind (boost::ref (MySignal), foo, bar));
+ */
+class Signaller
+{
+public:
+       /* Can be called from any thread */
+       virtual ~Signaller () {
+               boost::mutex::scoped_lock lm (_mutex);
+               for (std::list<WrapperBase*>::iterator i = _wrappers.begin(); i != _wrappers.end(); ++i) {
+                       (*i)->invalidate ();
+               }
+       }
+
+       /* Can be called from any thread */
+       template <class T>
+       void emit (T signal)
+       {
+               Wrapper<T>* w = new Wrapper<T> (signal);
+               if (signal_manager) {
+                       signal_manager->emit (boost::bind (&Wrapper<T>::signal, w));
+               }
+               
+               boost::mutex::scoped_lock lm (_mutex);
+
+               /* Clean up finished Wrappers */
+               std::list<WrapperBase*>::iterator i = _wrappers.begin ();
+               while (i != _wrappers.end ()) {
+                       std::list<WrapperBase*>::iterator tmp = i;
+                       ++tmp;
+                       if ((*i)->finished ()) {
+                               delete *i;
+                               _wrappers.erase (i);
+                       }
+                       i = tmp;
+               }
+
+               /* Add the new one */
+               _wrappers.push_back (w);
+       }
+
+private:
+       /* Protect _wrappers */
+       boost::mutex _mutex;
+       std::list<WrapperBase*> _wrappers;
+};
+
+#endif
index 602014d5883d2dba1381588bb44a7036debc9c96..09059a8b0717adbf2b8df12b11c6ec48a941477b 100644 (file)
@@ -65,7 +65,7 @@ SndfileDecoder::~SndfileDecoder ()
 }
 
 bool
-SndfileDecoder::pass ()
+SndfileDecoder::pass (PassReason)
 {
        if (_remaining == 0) {
                return true;
index 5ebe1da7b251852ee523df1e24fc9597406570f2..68c8633a0d604df5e7e6aa3f19dfc07993422d2e 100644 (file)
@@ -30,14 +30,13 @@ public:
        SndfileDecoder (boost::shared_ptr<const SndfileContent> c);
        ~SndfileDecoder ();
 
-       void seek (ContentTime, bool);
-
        int audio_channels () const;
        ContentTime audio_length () const;
        int audio_frame_rate () const;
 
 private:
-       bool pass ();
+       bool pass (PassReason);
+       void seek (ContentTime, bool);
        
        boost::shared_ptr<const SndfileContent> _sndfile_content;
        SNDFILE* _sndfile;
index 552a96b8f325416fe1b2c60ac0470ddb19422276..6ed2e5254fb5c9be4847fd00c3df4eb31a42b865 100644 (file)
@@ -48,7 +48,7 @@ SubRipDecoder::seek (ContentTime time, bool accurate)
 }
 
 bool
-SubRipDecoder::pass ()
+SubRipDecoder::pass (PassReason)
 {
        if (_next >= _subtitles.size ()) {
                return true;
@@ -85,7 +85,13 @@ SubRipDecoder::pass ()
 }
 
 list<ContentTimePeriod>
-SubRipDecoder::subtitles_during (ContentTimePeriod p, bool starting) const
+SubRipDecoder::image_subtitles_during (ContentTimePeriod, bool) const
+{
+       return list<ContentTimePeriod> ();
+}
+
+list<ContentTimePeriod>
+SubRipDecoder::text_subtitles_during (ContentTimePeriod p, bool starting) const
 {
        /* XXX: inefficient */
 
index ad9d04e4003029f6a9b99b62714b4fbdd0a13a6f..264ca88996b2b93dafd4b1521f4c4f3a98f8f609 100644 (file)
@@ -32,10 +32,11 @@ public:
 
 protected:
        void seek (ContentTime time, bool accurate);
-       bool pass ();
+       bool pass (PassReason);
 
 private:
-       std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const;
+       std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod, bool starting) const;
+       std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod, bool starting) const;
        
        size_t _next;
 };
index 9b2aa8ab04198f8108606474897c2445973dfc93..2efe9afb692bdebcf3b4a3d8e44c7e655a3f02c9 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -33,7 +33,8 @@ SubtitleDecoder::SubtitleDecoder (shared_ptr<const SubtitleContent> c)
 }
 
 /** Called by subclasses when an image subtitle is ready.
- *  Image may be 0 to say that there is no current subtitle.
+ *  @param period Period of the subtitle.
+ *  @param image Subtitle image.
  *  @param rect Area expressed as a fraction of the video frame that this subtitle
  *  is for (e.g. a width of 0.5 means the width of the subtitle is half the width
  *  of the video frame)
@@ -50,12 +51,11 @@ SubtitleDecoder::text_subtitle (list<dcp::SubtitleString> s)
        _decoded_text_subtitles.push_back (ContentTextSubtitle (s));
 }
 
+/** @param sp Full periods of subtitles that are showing or starting during the specified period */
 template <class T>
 list<T>
-SubtitleDecoder::get (list<T> const & subs, ContentTimePeriod period, bool starting)
+SubtitleDecoder::get (list<T> const & subs, list<ContentTimePeriod> const & sp, ContentTimePeriod period, bool starting)
 {
-       /* Get the full periods of the subtitles that are showing or starting during the specified period */
-       list<ContentTimePeriod> sp = subtitles_during (period, starting);
        if (sp.empty ()) {
                /* Nothing in this period */
                return list<T> ();
@@ -70,7 +70,7 @@ SubtitleDecoder::get (list<T> const & subs, ContentTimePeriod period, bool start
         *  (a) give us what we want, or
         *  (b) hit the end of the decoder.
         */
-       while (!pass() && (subs.empty() || (subs.back().period().to < sp.back().to))) {}
+       while (!pass(PASS_REASON_SUBTITLE) && (subs.empty() || (subs.back().period().to < sp.back().to))) {}
 
        /* Now look for what we wanted in the data we have collected */
        /* XXX: inefficient */
@@ -82,19 +82,36 @@ SubtitleDecoder::get (list<T> const & subs, ContentTimePeriod period, bool start
                }
        }
 
+       /* Discard anything in _decoded_image_subtitles that is outside 5 seconds either side of period */
+       
+       list<ContentImageSubtitle>::iterator i = _decoded_image_subtitles.begin();
+       while (i != _decoded_image_subtitles.end()) {
+               list<ContentImageSubtitle>::iterator tmp = i;
+               ++tmp;
+
+               if (
+                       i->period().to < (period.from - ContentTime::from_seconds (5)) ||
+                       i->period().from > (period.to + ContentTime::from_seconds (5))
+                       ) {
+                       _decoded_image_subtitles.erase (i);
+               }
+
+               i = tmp;
+       }
+
        return out;
 }
 
 list<ContentTextSubtitle>
 SubtitleDecoder::get_text_subtitles (ContentTimePeriod period, bool starting)
 {
-       return get<ContentTextSubtitle> (_decoded_text_subtitles, period, starting);
+       return get<ContentTextSubtitle> (_decoded_text_subtitles, text_subtitles_during (period, starting), period, starting);
 }
 
 list<ContentImageSubtitle>
 SubtitleDecoder::get_image_subtitles (ContentTimePeriod period, bool starting)
 {
-       return get<ContentImageSubtitle> (_decoded_image_subtitles, period, starting);
+       return get<ContentImageSubtitle> (_decoded_image_subtitles, image_subtitles_during (period, starting), period, starting);
 }
 
 void
index d7faaa0145900658edb2441597a47b0d5e3af968..8ba74404f2d26dbc3a80c9494c20dd233e9396ef 100644 (file)
@@ -49,12 +49,13 @@ protected:
 
 private:
        template <class T>
-       std::list<T> get (std::list<T> const & subs, ContentTimePeriod period, bool starting);
+       std::list<T> get (std::list<T> const & subs, std::list<ContentTimePeriod> const & sp, ContentTimePeriod period, bool starting);
 
        /** @param starting true if we want only subtitles that start during the period, otherwise
         *  we want subtitles that overlap the period.
         */
-       virtual std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod period, bool starting) const = 0;
+       virtual std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod period, bool starting) const = 0;
+       virtual std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod period, bool starting) const = 0;
        
        boost::shared_ptr<const SubtitleContent> _subtitle_content;
 };
index f3877d0d554844775b0163be1138df194322c6d8..e7017a2950c7b79fdfe20ee89d724badf53f4a23 100644 (file)
@@ -22,6 +22,7 @@
 
 #include "dcpomatic_time.h"
 #include "position.h"
+#include "rect.h"
 #include <dcp/util.h>
 #include <boost/shared_ptr.hpp>
 #include <vector>
diff --git a/src/lib/ui_signaller.cc b/src/lib/ui_signaller.cc
deleted file mode 100644 (file)
index 4cb34da..0000000
+++ /dev/null
@@ -1,24 +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 "ui_signaller.h"
-
-/** Global UISignaller instance */
-UISignaller* ui_signaller = 0;
-
diff --git a/src/lib/ui_signaller.h b/src/lib/ui_signaller.h
deleted file mode 100644 (file)
index ee4d230..0000000
+++ /dev/null
@@ -1,86 +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 DCPOMATIC_UI_SIGNALLER_H
-#define DCPOMATIC_UI_SIGNALLER_H
-
-#include <boost/bind.hpp>
-#include <boost/asio.hpp>
-#include <boost/thread.hpp>
-
-/** A class to allow signals to be emitted from non-UI threads and handled
- *  by a UI thread.
- */
-class UISignaller : public boost::noncopyable
-{
-public:
-       /** Create a UISignaller.  Must be called from the UI thread */
-       UISignaller ()
-               : _work (_service)
-       {
-               _ui_thread = boost::this_thread::get_id ();
-       }
-
-       /** Emit a signal from any thread whose handlers will be called in the UI
-        *  thread.  Use something like:
-        *
-        *  ui_signaller->emit (boost::bind (boost::ref (SomeSignal), parameter));
-        */
-       template <typename T>
-       void emit (T f) {
-               if (boost::this_thread::get_id() == _ui_thread) {
-                       /* already in the UI thread */
-                       f ();
-               } else {
-                       /* non-UI thread; post to the service and wake up the UI */
-                       _service.post (f);
-                       wake_ui ();
-               }
-       }
-
-       /* Do something next time the UI is idle */
-       template <typename T>
-       void when_idle (T f) {
-               _service.post (f);
-       }
-
-       /** Call this in the UI when it is idle */
-       size_t ui_idle () {
-               /* This executes any functors that have been post()ed to _service */
-               return _service.poll ();
-       }
-
-       /** This should wake the UI and make it call ui_idle() */
-       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 */
-       boost::asio::io_service _service;
-       /** Object required to keep io_service from stopping when it has nothing to do */
-       boost::asio::io_service::work _work;
-       /** The UI thread's ID */
-       boost::thread::id _ui_thread;
-};
-
-extern UISignaller* ui_signaller;
-
-#endif
index a05df8ef304dae26ee0d4b509113b3d8502198c0..f433ff991607afa4e15b8052fbf0e185c12495ec 100644 (file)
@@ -19,7 +19,6 @@
 
 #include "update.h"
 #include "version.h"
-#include "ui_signaller.h"
 #include "safe_stringstream.h"
 #include "config.h"
 #include "util.h"
@@ -168,7 +167,7 @@ UpdateChecker::set_state (State s)
                _emits++;
        }
 
-       ui_signaller->emit (boost::bind (boost::ref (StateChanged)));
+       emit (boost::bind (boost::ref (StateChanged)));
 }
 
 UpdateChecker *
index 5bb9e95016de1ed2bace2ec556fd54f9dccfef07..461217a372ea61ae3802f0018a0026da76ff2339 100644 (file)
@@ -21,6 +21,7 @@
  *  @brief UpdateChecker class.
  */
 
+#include "signaller.h"
 #include <curl/curl.h>
 #include <boost/signals2.hpp>
 #include <boost/thread/mutex.hpp>
@@ -30,7 +31,7 @@
 struct update_checker_test;
 
 /** Class to check for the existance of an update for DCP-o-matic on a remote server */
-class UpdateChecker : public boost::noncopyable
+class UpdateChecker : public Signaller, public boost::noncopyable
 {
 public:
        UpdateChecker ();
index bffbe90d402b2bca5cf8b74852c58556265c03ea..99d9ba2c4f55654f12e2af866585121b2f6cb92e 100644 (file)
@@ -564,18 +564,21 @@ wrapped_av_malloc (size_t s)
        }
        return p;
 }
-               
-ContentTimePeriod
+
+FFmpegSubtitlePeriod
 subtitle_period (AVSubtitle const & sub)
 {
        ContentTime const packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE);
 
-       ContentTimePeriod period (
+       if (sub.end_display_time == static_cast<uint32_t> (-1)) {
+               /* End time is not known */
+               return FFmpegSubtitlePeriod (packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3));
+       }
+       
+       return FFmpegSubtitlePeriod (
                packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3),
                packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3)
                );
-
-       return period;
 }
 
 map<string, string>
@@ -667,3 +670,4 @@ write_frame_info (FILE* file, int frame, Eyes eyes, dcp::FrameInfo info)
        fwrite (&info.size, sizeof (info.size), 1, file);
        fwrite (info.hash.c_str(), 1, info.hash.size(), file);
 }
+
index c1f7a78c745318b33a173627c80f3d664128a0bc..44bd7dcedceab0e48800bd45f9cf0a7418ac5566 100644 (file)
@@ -74,7 +74,24 @@ extern int dcp_audio_frame_rate (int);
 extern int stride_round_up (int, int const *, int);
 extern int round_to (float n, int r);
 extern void* wrapped_av_malloc (size_t);
-extern ContentTimePeriod subtitle_period (AVSubtitle const &);
+
+class FFmpegSubtitlePeriod
+{
+public:
+       FFmpegSubtitlePeriod (ContentTime f)
+               : from (f)
+       {}
+
+       FFmpegSubtitlePeriod (ContentTime f, ContentTime t)
+               : from (f)
+               , to (t)
+       {}
+
+       ContentTime from;
+       boost::optional<ContentTime> to;
+};
+
+extern FFmpegSubtitlePeriod subtitle_period (AVSubtitle const &);
 extern void set_backtrace_file (boost::filesystem::path);
 extern dcp::FrameInfo read_frame_info (FILE* file, int frame, Eyes eyes);
 extern void write_frame_info (FILE* file, int frame, Eyes eyes, dcp::FrameInfo info);
index b7cf1641b57d6b999c7c62b3c05f405e1f7b7075..31dc3cdc204836e821b385b0280d2c13f2defb31 100644 (file)
@@ -96,7 +96,7 @@ VideoDecoder::get_video (VideoFrame frame, bool accurate)
                                break;
                        }
 
-                       if (pass ()) {
+                       if (pass (PASS_REASON_VIDEO)) {
                                /* The decoder has nothing more for us */
                                break;
                        }
@@ -113,7 +113,7 @@ VideoDecoder::get_video (VideoFrame frame, bool accurate)
                dec = decoded_video (frame);
        } else {
                /* Any frame will do: use the first one that comes out of pass() */
-               while (_decoded_video.empty() && !pass ()) {}
+               while (_decoded_video.empty() && !pass (PASS_REASON_VIDEO)) {}
                if (!_decoded_video.empty ()) {
                        dec.push_back (_decoded_video.front ());
                }
@@ -237,7 +237,7 @@ VideoDecoder::video (shared_ptr<const ImageProxy> image, VideoFrame frame)
        if (_ignore_video) {
                return;
        }
-       
+
        /* We may receive the same frame index twice for 3D, and we need to know
           when that happens.
        */
index 31c265e2f73e82a60a590d251e99b89c729027a6..1a11a482bf0b0155d948848474e5cc18333b0296 100644 (file)
@@ -366,21 +366,24 @@ try
                        }
 
                        DCPOMATIC_ASSERT (i != _queue.rend());
-                       QueueItem qi = *i;
-
                        ++_pushed_to_disk;
-                       
                        lock.unlock ();
 
+                       /* i is valid here, even though we don't hold a lock on the mutex,
+                          since list iterators are unaffected by insertion and only this
+                          thread could erase the last item in the list.
+                       */
+
                        LOG_GENERAL (
                                "Writer full (awaiting %1 [last eye was %2]); pushes %3 to disk",
                                _last_written_frame + 1,
-                               _last_written_eyes, qi.frame
+                               _last_written_eyes, i->frame
                                );
                        
-                       qi.encoded->write (_film, qi.frame, qi.eyes);
+                       i->encoded->write (_film, i->frame, i->eyes);
+                       
                        lock.lock ();
-                       qi.encoded.reset ();
+                       i->encoded.reset ();
                        --_queued_full_in_memory;
                }
 
index 24aa7c13438461b5295f23979f440640cab86326..5956c73d65c4f35ed1ef885f1529f4fefa57d9b0 100644 (file)
@@ -1,3 +1,21 @@
+#
+#    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
 import os
 import i18n
 
@@ -91,7 +109,7 @@ sources = """
           transcode_job.cc
           transcoder.cc
           types.cc
-          ui_signaller.cc
+          signal_manager.cc
           update.cc
           upmixer_a.cc
           util.cc
@@ -102,7 +120,7 @@ sources = """
           """
 
 def build(bld):
-    if bld.env.BUILD_STATIC:
+    if bld.env.STATIC_DCPOMATIC:
         obj = bld(features = 'cxx cxxstlib')
     else:
         obj = bld(features = 'cxx cxxshlib')
@@ -123,7 +141,7 @@ def build(bld):
 
     if bld.env.TARGET_WINDOWS:
         obj.uselib += ' WINSOCK2 BFD DBGHELP IBERTY SHLWAPI MSWSOCK BOOST_LOCALE'
-    if bld.env.BUILD_STATIC:
+    if bld.env.STATIC_DCPOMATIC:
         obj.uselib += ' XMLPP'
 
     obj.target = 'dcpomatic2'
index e59220785b82aefb3234cedc6ac2c9c90de68fbc..904e39fdaf3a048a8a2a60d67b9322690364c962 100644 (file)
@@ -21,7 +21,7 @@
 #include "lib/config.h"
 #include "lib/util.h"
 #include "lib/version.h"
-#include "lib/ui_signaller.h"
+#include "lib/signal_manager.h"
 #include "lib/log.h"
 #include "lib/job_manager.h"
 #include "lib/transcode_job.h"
@@ -39,7 +39,7 @@
 #include "wx/wx_util.h"
 #include "wx/new_film_dialog.h"
 #include "wx/properties_dialog.h"
-#include "wx/wx_ui_signaller.h"
+#include "wx/wx_signal_manager.h"
 #include "wx/about_dialog.h"
 #include "wx/kdm_dialog.h"
 #include "wx/servers_list_dialog.h"
@@ -841,7 +841,7 @@ private:
                        }
                }
 
-               ui_signaller = new wxUISignaller (this);
+               signal_manager = new wxSignalManager (this);
                Bind (wxEVT_IDLE, boost::bind (&App::idle, this));
 
                Bind (wxEVT_TIMER, boost::bind (&App::check, this));
@@ -909,7 +909,7 @@ private:
 
        void idle ()
        {
-               ui_signaller->ui_idle ();
+               signal_manager->ui_idle ();
        }
 
        void check ()
index da8a614142ad4ad3bd5d5ca2db7e1fba9eac7ccc..ae2f3a2c59d7bf8d8e148190e5cc17477521d157 100644 (file)
@@ -29,7 +29,7 @@
 #include "lib/job_manager.h"
 #include "wx/wx_util.h"
 #include "wx/about_dialog.h"
-#include "wx/wx_ui_signaller.h"
+#include "wx/wx_signal_manager.h"
 #include "wx/job_manager_view.h"
 
 using std::exception;
@@ -225,7 +225,7 @@ class App : public wxApp
                f->Maximize ();
                f->Show ();
 
-               ui_signaller = new wxUISignaller (this);
+               signal_manager = new wxSignalManager (this);
                this->Bind (wxEVT_IDLE, boost::bind (&App::idle, this));
 
                shared_ptr<Film> film;
@@ -244,7 +244,7 @@ class App : public wxApp
 
        void idle ()
        {
-               ui_signaller->ui_idle ();
+               signal_manager->ui_idle ();
        }
 
        void OnInitCmdLine (wxCmdLineParser& parser)
index 4facdd4d1a868df0e67d9cac2e28861d3434ed21..0307cac9c9712ddb2205a2aab1aae4bedba7df80 100644 (file)
@@ -30,7 +30,7 @@
 #include "lib/cross.h"
 #include "lib/config.h"
 #include "lib/log.h"
-#include "lib/ui_signaller.h"
+#include "lib/signal_manager.h"
 #include "lib/server_finder.h"
 #include "lib/json_server.h"
 
@@ -119,7 +119,7 @@ main (int argc, char* argv[])
        film_dir = argv[optind];
                        
        dcpomatic_setup ();
-       ui_signaller = new UISignaller ();
+       signal_manager = new SignalManager ();
        
        if (no_remote) {
                ServerFinder::instance()->disable ();
index 304f4f697ab6e8a031c44dd2a74c3b896f2c448a..d121eb0ccf4caea4030a13e3ac12aa8b574c0e36 100644 (file)
@@ -28,7 +28,7 @@
 #include "lib/util.h"
 #include "lib/content_factory.h"
 #include "lib/job_manager.h"
-#include "lib/ui_signaller.h"
+#include "lib/signal_manager.h"
 #include "lib/job.h"
 #include "lib/dcp_content_type.h"
 #include "lib/ratio.h"
@@ -59,11 +59,11 @@ help (string n)
             << "  -o, --output <dir>            output directory\n";
 }
 
-class SimpleUISignaller : public UISignaller
+class SimpleSignalManager : public SignalManager
 {
 public:
        /* Do nothing in this method so that UI events happen in our thread
-          when we call UISignaller::ui_idle().
+          when we call SignalManager::ui_idle().
        */
        void wake_ui () {}
 };
@@ -161,7 +161,7 @@ main (int argc, char* argv[])
                exit (EXIT_FAILURE);
        }
 
-       ui_signaller = new SimpleUISignaller ();
+       signal_manager = new SimpleSignalManager ();
 
        try {
                shared_ptr<Film> film (new Film (output, false));
@@ -184,7 +184,7 @@ main (int argc, char* argv[])
                JobManager* jm = JobManager::instance ();
 
                while (jm->work_to_do ()) {}
-               while (ui_signaller->ui_idle() > 0) {}
+               while (signal_manager->ui_idle() > 0) {}
 
                ContentList content = film->content ();
                for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
index 175cebc96993b5a6e1345705bbf6153931744e74..ffd77c6cb01483a5ec090bbe2c1b9842b93ab906 100644 (file)
@@ -1,3 +1,21 @@
+#
+#    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
 import os
 import glob
 from waflib import Logs
@@ -9,9 +27,8 @@ def configure(conf):
         conf.env.append_value('LINKFLAGS', ['-mconsole'])
 
 def build(bld):
-
-    uselib =  'BOOST_THREAD BOOST_DATETIME BOOST_FILESYSTEM OPENJPEG DCP CXML SNDFILE ZIP XMLPP SSH '
-    uselib += 'AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC WXWIDGETS SUB CURL GLIB CAIROMM PANGOMM MAGICK '
+    uselib =  'BOOST_THREAD BOOST_DATETIME OPENJPEG DCP XMLSEC CXML XMLPP AVFORMAT AVFILTER AVCODEC '
+    uselib += 'AVUTIL SWSCALE POSTPROC CURL BOOST_FILESYSTEM SSH WXWIDGETS ZIP CAIROMM PANGOMM SUB MAGICK SNDFILE '
 
     if bld.env.TARGET_WINDOWS:
         uselib += 'WINSOCK2'
index f3a6a8c7beab1d8480273ee29099a99e4fb6888b..8ec39963fd5ad9f503ba3a2860eb9a6cfd4630ff 100644 (file)
@@ -1,3 +1,21 @@
+#
+#    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
 def configure(conf):
     conf.recurse('tools')
     if not conf.env.DISABLE_GUI:
index 1d41fc1854acf23647b9d2fbdf0c85761cded4a4..fcae9c30f9f85835adb0b3833d785cb5ec0cd4f7 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -29,31 +29,43 @@ using boost::shared_ptr;
 using boost::bind;
 using boost::optional;
 
-AudioDialog::AudioDialog (wxWindow* parent)
+AudioDialog::AudioDialog (wxWindow* parent, shared_ptr<Film> film)
        : wxDialog (parent, wxID_ANY, _("Audio"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
+       , _film (film)
        , _plot (0)
 {
+       wxFont subheading_font (*wxNORMAL_FONT);
+       subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
+
        wxBoxSizer* sizer = new wxBoxSizer (wxHORIZONTAL);
+       
+       wxBoxSizer* left = new wxBoxSizer (wxVERTICAL);
 
        _plot = new AudioPlot (this);
-       sizer->Add (_plot, 1, wxALL | wxEXPAND, 12);
+       left->Add (_plot, 1, wxALL | wxEXPAND, 12);
+       _peak_time = new wxStaticText (this, wxID_ANY, wxT (""));
+       left->Add (_peak_time, 0, wxALL, 12);
+
+       sizer->Add (left, 1, wxALL, 12);
 
-       wxBoxSizer* side = new wxBoxSizer (wxVERTICAL);
+       wxBoxSizer* right = new wxBoxSizer (wxVERTICAL);
 
        {
                wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Channels"));
-               side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
+               m->SetFont (subheading_font);
+               right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, 16);
        }
 
        for (int i = 0; i < MAX_DCP_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);
+               right->Add (_channel_checkbox[i], 0, 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);
+               m->SetFont (subheading_font);
+               right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
        }
        
        wxString const types[] = {
@@ -63,20 +75,21 @@ AudioDialog::AudioDialog (wxWindow* parent)
 
        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);
+               right->Add (_type_checkbox[i], 0, 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);
+               m->SetFont (subheading_font);
+               right->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);
+       right->Add (_smoothing, 0, wxEXPAND);
 
-       sizer->Add (side, 0, wxALL, 12);
+       sizer->Add (right, 0, wxALL, 12);
 
        SetSizer (sizer);
        sizer->Layout ();
@@ -107,14 +120,21 @@ AudioDialog::try_to_load_analysis ()
 
        if (!boost::filesystem::exists (_content->audio_analysis_path())) {
                _plot->set_analysis (shared_ptr<AudioAnalysis> ());
+               _analysis.reset ();
                _analysis_finished_connection = _content->analyse_audio (bind (&AudioDialog::analysis_finished, this));
                return;
        }
+
+       try {
+               _analysis.reset (new AudioAnalysis (_content->audio_analysis_path ()));
+       } catch (xmlpp::exception& e) {
+               /* Probably an old-style analysis file: recreate it */
+               _analysis_finished_connection = _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);
+       _plot->set_analysis (_analysis);
+       setup_peak_time ();
 
        /* Set up some defaults if no check boxes are checked */
        
@@ -139,6 +159,8 @@ AudioDialog::try_to_load_analysis ()
                        _plot->set_type_visible (i, true);
                }
        }
+
+       Refresh ();
 }
 
 void
@@ -173,6 +195,7 @@ AudioDialog::content_changed (int p)
 {
        if (p == AudioContentProperty::AUDIO_GAIN) {
                _plot->set_gain (_content->audio_gain ());
+               setup_peak_time ();
        } else if (p == AudioContentProperty::AUDIO_MAPPING) {
                try_to_load_analysis ();
        }
@@ -196,3 +219,32 @@ AudioDialog::smoothing_changed ()
 {
        _plot->set_smoothing (_smoothing->GetValue ());
 }
+
+void
+AudioDialog::setup_peak_time ()
+{
+       if (!_analysis || !_analysis->peak ()) {
+               return;
+       }
+       
+       shared_ptr<Film> film = _film.lock ();
+       if (!film) {
+               return;
+       }
+       
+       float peak_dB = 20 * log10 (_analysis->peak().get()) + _content->audio_gain();
+       
+       _peak_time->SetLabel (
+               wxString::Format (
+                       _("Peak is %.2fdB at %s"),
+                       peak_dB,
+                       time_to_timecode (_analysis->peak_time().get(), film->video_frame_rate ()).data ()
+                       )
+               );
+       
+       if (peak_dB > -3) {
+               _peak_time->SetForegroundColour (wxColour (255, 0, 0));
+       } else {
+               _peak_time->SetForegroundColour (wxColour (0, 0, 0));
+       }
+}
index b277852928d19f53e3849464a0541d24c65288c3..aef8ea944c86707bbb81f01461448d01f48b5b20 100644 (file)
@@ -29,7 +29,7 @@ class Film;
 class AudioDialog : public wxDialog
 {
 public:
-       AudioDialog (wxWindow *);
+       AudioDialog (wxWindow *, boost::shared_ptr<Film> film);
 
        void set_content (boost::shared_ptr<AudioContent>);
 
@@ -40,9 +40,13 @@ private:
        void smoothing_changed ();
        void try_to_load_analysis ();
        void analysis_finished ();
+       void setup_peak_time ();
 
        boost::shared_ptr<AudioContent> _content;
+       boost::shared_ptr<AudioAnalysis> _analysis;
+       boost::weak_ptr<Film> _film;
        AudioPlot* _plot;
+       wxStaticText* _peak_time;
        wxCheckBox* _channel_checkbox[MAX_DCP_AUDIO_CHANNELS];
        wxCheckBox* _type_checkbox[AudioPoint::COUNT];
        wxSlider* _smoothing;
index 2a41aeb2def0543b497bd3879d2ee643af24f90f..4d783ca9da71b9e6f5ec69c2b87fb7afaf8ee6e2 100644 (file)
@@ -217,7 +217,7 @@ AudioPanel::show_clicked ()
                return;
        }
        
-       _audio_dialog = new AudioDialog (this);
+       _audio_dialog = new AudioDialog (this, _parent->film ());
        _audio_dialog->Show ();
        _audio_dialog->set_content (ac.front ());
 }
index 82872ad8477d46f8277c450a768b8e6571350bc8..ccfe5711cf717b3b9586be6d0188308cc0f88a1c 100644 (file)
@@ -344,6 +344,7 @@ DCPPanel::film_changed (int p)
                break;
        case Film::INTEROP:
                checked_set (_standard, _film->interop() ? 1 : 0);
+               setup_dcp_name ();
                break;
        default:
                break;
@@ -636,7 +637,7 @@ DCPPanel::make_audio_panel ()
        int r = 0;
        add_label_to_grid_bag_sizer (grid, panel, _("Channels"), true, wxGBPosition (r, 0));
        _audio_channels = new wxChoice (panel, wxID_ANY);
-       for (int i = 2; i <= 16; i += 2) {
+       for (int i = 2; i <= 12; i += 2) {
                _audio_channels->Append (wxString::Format ("%d", i));
        }
        grid->Add (_audio_channels, wxGBPosition (r, 1));
index 5e094844de59697771c0af68827733ceddebf983..ad43f64795fb731eb77d46b20690cd04f5a43acd 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2014-2015 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -21,7 +21,7 @@
 #include <curl/curl.h>
 #include "lib/compose.hpp"
 #include "lib/internet.h"
-#include "lib/ui_signaller.h"
+#include "lib/signal_manager.h"
 #include "dolby_certificate_dialog.h"
 #include "wx_util.h"
 
@@ -80,7 +80,7 @@ DolbyCertificateDialog::setup_countries ()
        /* See DoremiCertificateDialog for discussion about this daft delay */
        wxMilliSleep (200);
 #endif
-       ui_signaller->when_idle (boost::bind (&DolbyCertificateDialog::finish_setup_countries, this));
+       signal_manager->when_idle (boost::bind (&DolbyCertificateDialog::finish_setup_countries, this));
 }
 
 void
@@ -103,7 +103,7 @@ DolbyCertificateDialog::country_selected ()
 #ifdef DCPOMATIC_OSX
        wxMilliSleep (200);
 #endif 
-       ui_signaller->when_idle (boost::bind (&DolbyCertificateDialog::finish_country_selected, this));
+       signal_manager->when_idle (boost::bind (&DolbyCertificateDialog::finish_country_selected, this));
 }
 
 void
@@ -126,7 +126,7 @@ DolbyCertificateDialog::cinema_selected ()
 #ifdef DCPOMATIC_OSX
        wxMilliSleep (200);
 #endif
-       ui_signaller->when_idle (boost::bind (&DolbyCertificateDialog::finish_cinema_selected, this));
+       signal_manager->when_idle (boost::bind (&DolbyCertificateDialog::finish_cinema_selected, this));
 }
 
 void
@@ -154,13 +154,14 @@ DolbyCertificateDialog::serial_selected ()
 void
 DolbyCertificateDialog::download ()
 {
+       downloaded (false);
        _message->SetLabel (_("Downloading certificate"));
 
 #ifdef DCPOMATIC_OSX
        wxMilliSleep (200);
 #endif
 
-       ui_signaller->when_idle (boost::bind (&DolbyCertificateDialog::finish_download, this));
+       signal_manager->when_idle (boost::bind (&DolbyCertificateDialog::finish_download, this));
 }
 
 void
@@ -189,5 +190,6 @@ DolbyCertificateDialog::finish_download ()
                _message->SetLabel (std_to_wx (error.get ()));
        } else {
                _message->SetLabel (_("Certificate downloaded"));
+               downloaded (true);
        }
 }
index 4b5d58b375536c475a26c6612e03bd0ae4c26351..578a7a72da1c7d706fc92d60d5a34e6982b2b435 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2014-2015 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -21,7 +21,7 @@
 #include <zip.h>
 #include "lib/compose.hpp"
 #include "lib/util.h"
-#include "lib/ui_signaller.h"
+#include "lib/signal_manager.h"
 #include "lib/internet.h"
 #include "doremi_certificate_dialog.h"
 #include "wx_util.h"
@@ -51,6 +51,7 @@ DoremiCertificateDialog::download ()
                return;
        }
 
+       downloaded (false);
        _message->SetLabel (_("Downloading certificate"));
 
 #ifdef DCPOMATIC_OSX   
@@ -58,7 +59,7 @@ DoremiCertificateDialog::download ()
        wxMilliSleep (200);
 #endif 
 
-       ui_signaller->when_idle (boost::bind (&DoremiCertificateDialog::finish_download, this, serial));
+       signal_manager->when_idle (boost::bind (&DoremiCertificateDialog::finish_download, this, serial));
 }
 
 void
@@ -101,6 +102,7 @@ DoremiCertificateDialog::finish_download (string serial)
                error_dialog (this, std_to_wx (error.get ()));
        } else {
                _message->SetLabel (_("Certificate downloaded"));
+               downloaded (true);
        }
 }
 
index a8a71233494c35b163bd42b1932d3f99266c9115..a0c41fd765901e4b60067caf3ee0af55d278382e 100644 (file)
@@ -17,9 +17,9 @@
 
 */
 
-#include <boost/bind.hpp>
-#include "download_certificate_dialog.h"
 #include "wx_util.h"
+#include "download_certificate_dialog.h"
+#include <boost/bind.hpp>
 
 using boost::function;
 
@@ -50,4 +50,16 @@ DownloadCertificateDialog::add_common_widgets ()
        _download->Enable (false);
 
        layout ();
+
+       wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK, this));
+       ok->Enable (false);
 }
+
+void
+DownloadCertificateDialog::downloaded (bool done)
+{
+       wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK, this));
+       ok->Enable (done);
+}
+
+       
index 804c0c762556f7af2b0d600a35e3a043b80858dc..40e11de45619bbaec1b8b960c77316a50fd1688d 100644 (file)
@@ -32,6 +32,7 @@ public:
 
 protected:
        void add_common_widgets ();
+       void downloaded (bool done);
        
        boost::function<void (boost::filesystem::path)> _load;
        wxStaticText* _message;
index 311ec734c45734aff0a5dab1295a8fc5e3178a9c..0938d52a4cdd094af5cfe0255addf9376e5b52e1 100644 (file)
@@ -354,16 +354,7 @@ FilmViewer::set_position_text ()
        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 (_position.seconds() * fps)) + 1));
-       
-       double w = _position.seconds ();
-       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));
+       _timecode->SetLabel (time_to_timecode (_position, fps));
 }
 
 void
index 3e630148268bfee9f7a7807a901eb67f2d0dcc04..8f4f8622df0ca3dd93c43b7e326b2e93d97a4367 100644 (file)
@@ -273,7 +273,7 @@ KDMDialog::setup_sensitivity ()
        _edit_screen->Enable (ss);
        _remove_screen->Enable (ss);
 
-       wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK));
+       wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK, this));
        if (ok) {
                ok->Enable ((selected_cinemas().size() > 0 || selected_screens().size() > 0) && sd);
        }
index d7c8096095471e4e505bc29ad3f02ad089cf309b..70229c7a90babaebdb7ca5953db533f7803b9c2a 100644 (file)
@@ -62,7 +62,7 @@ KeyDialog::key () const
 void
 KeyDialog::key_changed ()
 {
-       wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK));
+       wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK, this));
        ok->Enable (_key->GetValue().Length() == 32);
 }
 
index 7b5904b4bcf882b3fc84cb985b58c38eeba616c4..370f59c621d5d3ff8ddf4f3c27db2921a2be50a6 100644 (file)
@@ -1,4 +1,24 @@
+#
+#    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
 import os
+import subprocess
+import shlex
 import glob
 from waflib import Logs
 import i18n
@@ -60,38 +80,44 @@ sources = """
           update_dialog.cc
           video_panel.cc
           wx_util.cc
-          wx_ui_signaller.cc
+          wx_signal_manager.cc
           """
 
 def configure(conf):
-    args = '--cppflags --cxxflags'
-    if not conf.env.BUILD_STATIC:
-        args += ' --libs std,richtext'
-
-    conf.check_cfg(msg='Checking for wxWidgets', package='', path=conf.options.wx_config, args=args,
-                   uselib_store='WXWIDGETS', mandatory=True)
+    conf.check_cfg(msg='Checking for wxWidgets',
+                   package='',
+                   path='wx-config',
+                   args='--cppflags --cxxflags --libs std,richtext',
+                   uselib_store='WXWIDGETS',
+                   mandatory=True)
 
-    if conf.env.BUILD_STATIC:
+    if conf.options.static_wxwidgets:
        # 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_richtext-3.0', 'wx_gtk2u_xrc-3.0', 'wx_gtk2u_qa-3.0', 'wx_baseu_net-3.0', 'wx_gtk2u_html-3.0',
-                                    'wx_gtk2u_adv-3.0', 'wx_gtk2u_core-3.0', 'wx_baseu_xml-3.0', 'wx_baseu-3.0']
-        conf.env.LIB_WXWIDGETS = ['tiff', 'SM', 'dl', 'jpeg', 'png', 'X11', 'expat']
-        if conf.env.TARGET_DEBIAN and conf.env.DEBIAN_UNSTABLE:
-            conf.env.LIB_WXWIDGETS.append('Xext')
-            conf.env.LIB_WXWIDGETS.append('X11')
-
-        if conf.env.TARGET_CENTOS_7:
-            conf.env.LIB_WXWIDGETS.append('Xxf86vm')
+        # check_cfg().  It puts the static libraries into LINKFLAGS_WXWIDGETS, so fish them out.
+        stlibs = []
+        new_linkflags = []
+        stlib_paths = []
+        for f in conf.env.LINKFLAGS_WXWIDGETS:
+            if f.startswith('/'):
+                d = os.path.dirname(f)
+                if not d in stlib_paths:
+                    stlib_paths.append(d)
+                stlibs.append(os.path.basename(f)[3:-2])
+            else:
+                new_linkflags.append(f)
+        
+        conf.env.STLIB_WXWIDGETS = stlibs
+        conf.env.LINKFLAGS_WXWIDGETS = new_linkflags
+        conf.env.STLIBPATH_WXWIDGETS = stlib_paths
  
     conf.in_msg = 1
-    wx_version = conf.check_cfg(package='', path=conf.options.wx_config, args='--version').strip()
+    wx_version = conf.check_cfg(package='', path='wx-config', args='--version').strip()
     conf.im_msg = 0
     if not wx_version.startswith('3.0.'):
         conf.fatal('wxwidgets version 3.0.x is required; %s found' % wx_version)
 
 def build(bld):
-    if bld.env.BUILD_STATIC:
+    if bld.env.STATIC_DCPOMATIC:
         obj = bld(features = 'cxx cxxstlib')
     else:
         obj = bld(features = 'cxx cxxshlib')
diff --git a/src/wx/wx_signal_manager.cc b/src/wx/wx_signal_manager.cc
new file mode 100644 (file)
index 0000000..3d8b999
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the 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_signal_manager.h"
+
+wxSignalManager::wxSignalManager (wxEvtHandler* h)
+       : _handler (h)
+{
+
+}
+
+void
+wxSignalManager::wake_ui ()
+{
+       wxCommandEvent event (-1, -1);
+       _handler->AddPendingEvent (event);
+}
diff --git a/src/wx/wx_signal_manager.h b/src/wx/wx_signal_manager.h
new file mode 100644 (file)
index 0000000..ad18e68
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the 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/signal_manager.h"
+
+class wxEvtHandler;
+
+/** @class wxSignalManager
+ *  @brief SignalManager for the wxWidgets event loop
+ */
+
+class wxSignalManager : public SignalManager
+{
+public:
+       wxSignalManager (wxEvtHandler *);
+       void wake_ui ();
+
+private:
+       wxEvtHandler* _handler;
+};
diff --git a/src/wx/wx_ui_signaller.cc b/src/wx/wx_ui_signaller.cc
deleted file mode 100644 (file)
index 8fc6670..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the 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_ui_signaller.h"
-
-wxUISignaller::wxUISignaller (wxEvtHandler* h)
-       : _handler (h)
-{
-
-}
-
-void
-wxUISignaller::wake_ui ()
-{
-       wxCommandEvent event (-1, -1);
-       _handler->AddPendingEvent (event);
-}
diff --git a/src/wx/wx_ui_signaller.h b/src/wx/wx_ui_signaller.h
deleted file mode 100644 (file)
index 63f2049..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
-    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the 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/ui_signaller.h"
-
-class wxEvtHandler;
-
-/** @class wxUISignaller
- *  @brief UISignaller for the wxWidgets event loop
- */
-
-class wxUISignaller : public UISignaller
-{
-public:
-       wxUISignaller (wxEvtHandler *);
-       void wake_ui ();
-
-private:
-       wxEvtHandler* _handler;
-};
index e9c07c91fe7bf2a566532f328ed7681d000e1d8b..9c13cfbcb69e741cbff7f8d8e42c8804bf62494e 100644 (file)
@@ -330,3 +330,17 @@ context_translation (wxString s)
 
        return t;
 }
+
+wxString
+time_to_timecode (DCPTime t, float fps)
+{
+       double w = t.seconds ();
+       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);
+       return wxString::Format (wxT("%02d:%02d:%02d.%02d"), h, m, s, f);
+}
index dfa0fca5e4c6e1d1744a7b675ad5bc9a93a88f15..f2ab2d8c599311573da9013204f49b2424dc725e 100644 (file)
@@ -24,6 +24,7 @@
 #ifndef DCPOMATIC_WX_UTIL_H
 #define DCPOMATIC_WX_UTIL_H
 
+#include "lib/dcpomatic_time.h"
 #include <wx/wx.h>
 #include <wx/gbsizer.h>
 #include <boost/function.hpp>
@@ -65,6 +66,7 @@ extern wxString std_to_wx (std::string);
 extern void dcpomatic_setup_i18n ();
 extern wxString context_translation (wxString);
 extern std::string string_client_data (wxClientData* o);
+extern wxString time_to_timecode (DCPTime t, float fps);
 
 extern void checked_set (wxFilePickerCtrl* widget, std::string value);
 extern void checked_set (wxSpinCtrl* widget, int value);
index 2799449191575571a55e3c2694a9032265608fee..7be9ca549d8f0b39ac2d7d804a6123666a4fbb1a 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@
 #include "lib/film.h"
 #include "lib/sndfile_content.h"
 #include "lib/dcp_content_type.h"
+#include "lib/ffmpeg_content.h"
 #include "lib/ratio.h"
 #include "test.h"
 
@@ -54,6 +55,10 @@ BOOST_AUTO_TEST_CASE (audio_analysis_serialisation_test)
                }
        }
 
+       float const peak = random_float ();
+       DCPTime const peak_time = DCPTime (rand ());
+       a.set_peak (peak, peak_time);
+
        a.write ("build/test/audio_analysis_serialisation_test");
 
        srand (1);
@@ -67,9 +72,14 @@ BOOST_AUTO_TEST_CASE (audio_analysis_serialisation_test)
                        BOOST_CHECK_CLOSE (p[AudioPoint::RMS],  random_float (), 1);
                }
        }
+       
+       BOOST_CHECK (b.peak ());
+       BOOST_CHECK_CLOSE (b.peak().get(), peak, 1);
+       BOOST_CHECK (b.peak_time ());
+       BOOST_CHECK_EQUAL (b.peak_time().get(), peak_time);
 }
 
-void
+static void
 finished ()
 {
 
@@ -90,3 +100,17 @@ BOOST_AUTO_TEST_CASE (audio_analysis_test)
        c->analyse_audio (boost::bind (&finished));
        wait_for_jobs ();
 }
+
+/* Check that audio analysis works (i.e. runs without error) with a -ve delay */
+BOOST_AUTO_TEST_CASE (audio_analysis_negative_delay_test)
+{
+       shared_ptr<Film> film = new_test_film ("audio_analysis_negative_delay_test");
+       film->set_name ("audio_analysis_negative_delay_test");
+       shared_ptr<AudioContent> c (new FFmpegContent (film, private_data / "boon_telly.mkv"));
+       c->set_audio_delay (-250);
+       film->examine_and_add_content (c);
+       wait_for_jobs ();
+       
+       c->analyse_audio (boost::bind (&finished));
+       wait_for_jobs ();
+}
index a14e2f9be0ace14299c1c6d756828699af53236c..b1f672356415f9c62b38db16bfdd9f955da12c72 100644 (file)
@@ -40,7 +40,7 @@ public:
                , _position (0)
        {}
 
-       bool pass ()
+       bool pass (PassReason)
        {
                AudioFrame const N = min (
                        AudioFrame (2000),
index 01cff5b06c46fbb993eb397f68bdefc9a1329a0e..90f81deb6497ad168450ff24665ef4677eceeb3a 100644 (file)
@@ -39,7 +39,7 @@ BOOST_AUTO_TEST_CASE (film_metadata_test)
        boost::filesystem::path dir = test_film_dir ("film_metadata_test");
 
        f->_isdcf_date = boost::gregorian::from_undelimited_string ("20130211");
-       BOOST_CHECK (f->container() == 0);
+       BOOST_CHECK (f->container() == Ratio::from_id ("185"));
        BOOST_CHECK (f->dcp_content_type() == 0);
 
        f->set_name ("fred");
index 9aca9878d813277c03785159b5a296e48112c9b3..5561f021c5a57afb3c489910a6de6213a9778ce3 100644 (file)
@@ -171,67 +171,6 @@ BOOST_AUTO_TEST_CASE (crop_image_test2)
        }
 }
 
-static
-boost::shared_ptr<Image>
-read_file (string file)
-{
-       Magick::Image magick_image (file.c_str ());
-       dcp::Size size (magick_image.columns(), magick_image.rows());
-
-       boost::shared_ptr<Image> image (new Image (PIX_FMT_RGB24, size, true));
-
-#ifdef DCPOMATIC_IMAGE_MAGICK  
-       using namespace MagickCore;
-#endif 
-       
-       uint8_t* p = image->data()[0];
-       for (int y = 0; y < size.height; ++y) {
-               uint8_t* q = p;
-               for (int x = 0; x < size.width; ++x) {
-                       Magick::Color c = magick_image.pixelColor (x, y);
-#ifdef DCPOMATIC_IMAGE_MAGICK                  
-                       *q++ = c.redQuantum() * 255 / QuantumRange;
-                       *q++ = c.greenQuantum() * 255 / QuantumRange;
-                       *q++ = c.blueQuantum() * 255 / QuantumRange;
-#else                  
-                       *q++ = c.redQuantum() * 255 / MaxRGB;
-                       *q++ = c.greenQuantum() * 255 / MaxRGB;
-                       *q++ = c.blueQuantum() * 255 / MaxRGB;
-#endif                 
-               }
-               p += image->stride()[0];
-       }
-
-       return image;
-}
-
-static
-void
-write_file (shared_ptr<Image> image, string file)
-{
-#ifdef DCPOMATIC_IMAGE_MAGICK  
-       using namespace MagickCore;
-#endif 
-       
-       Magick::Image magick_image (Magick::Geometry (image->size().width, image->size().height), Magick::Color (0, 0, 0));
-       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) {
-#ifdef DCPOMATIC_IMAGE_MAGICK
-                       Magick::Color c (q[0] * QuantumRange / 256, q[1] * QuantumRange / 256, q[2] * QuantumRange / 256);
-#else                  
-                       Magick::Color c (q[0] * MaxRGB / 256, q[1] * MaxRGB / 256, q[2] * MaxRGB / 256);
-#endif                 
-                       magick_image.pixelColor (x, y, c);
-                       q += 3;
-               }
-               p += image->stride()[0];
-       }
-       
-       magick_image.write (file.c_str ());
-}
-
 static
 void
 crop_scale_window_single (AVPixelFormat in_format, dcp::Size in_size, Crop crop, dcp::Size inter_size, dcp::Size out_size)
index 5f6ccc5e1f79c4c6665f79d4b5e0b484b21f2935..b54ce205fdb0df2e0d74b7c3b3ab39573c94c4ec 100644 (file)
@@ -147,14 +147,14 @@ BOOST_AUTO_TEST_CASE (isdcf_name_test)
        mapping.set (0, dcp::RS, 1.0);
        sound->set_audio_mapping (mapping);
        BOOST_CHECK_EQUAL (film->isdcf_name(false), "LikeShouting_XSN-2_F-133_DE-FR_US-R_51_4K_DI_20140704_PP_SMPTE_VF");
-       mapping.set (0, dcp::CHANNEL_7, 1.0);
+       mapping.set (0, dcp::HI, 1.0);
        sound->set_audio_mapping (mapping);
        BOOST_CHECK_EQUAL (film->isdcf_name(false), "LikeShouting_XSN-2_F-133_DE-FR_US-R_51_4K_DI_20140704_PP_SMPTE_VF");
        film->set_audio_channels (8);
-       mapping.set (0, dcp::CHANNEL_7, 1.0);
+       mapping.set (0, dcp::HI, 1.0);
        sound->set_audio_mapping (mapping);
        BOOST_CHECK_EQUAL (film->isdcf_name(false), "LikeShouting_XSN-2_F-133_DE-FR_US-R_61_4K_DI_20140704_PP_SMPTE_VF");
-       mapping.set (0, dcp::CHANNEL_8, 1.0);
+       mapping.set (0, dcp::VI, 1.0);
        sound->set_audio_mapping (mapping);
        BOOST_CHECK_EQUAL (film->isdcf_name(false), "LikeShouting_XSN-2_F-133_DE-FR_US-R_71_4K_DI_20140704_PP_SMPTE_VF");
 }
index 9201e5b4cfceb7beaa205ba8015ba660298e882d..0de7ace62d737641dcb7c93b612daa94b50e121f 100644 (file)
@@ -29,7 +29,7 @@
 #include <dcp/dcp.h>
 #include "lib/config.h"
 #include "lib/util.h"
-#include "lib/ui_signaller.h"
+#include "lib/signal_manager.h"
 #include "lib/film.h"
 #include "lib/job_manager.h"
 #include "lib/job.h"
@@ -51,7 +51,7 @@ using boost::scoped_array;
 
 boost::filesystem::path private_data = boost::filesystem::path ("..") / boost::filesystem::path ("dcpomatic-test-private");
 
-class TestUISignaller : public UISignaller
+class TestSignalManager : public SignalManager
 {
 public:
        /* No wakes in tests: we call ui_idle ourselves */
@@ -70,14 +70,14 @@ struct TestConfig
                Config::instance()->set_num_local_encoding_threads (1);
                Config::instance()->set_server_port_base (61920);
                Config::instance()->set_default_isdcf_metadata (ISDCFMetadata ());
-               Config::instance()->set_default_container (static_cast<Ratio*> (0));
+               Config::instance()->set_default_container (Ratio::from_id ("185"));
                Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
                Config::instance()->set_default_audio_delay (0);
                Config::instance()->set_default_j2k_bandwidth (100000000);
 
                ServerFinder::instance()->disable ();
 
-               ui_signaller = new TestUISignaller ();
+               signal_manager = new TestSignalManager ();
        }
 
        ~TestConfig ()
@@ -285,7 +285,7 @@ wait_for_jobs ()
 {
        JobManager* jm = JobManager::instance ();
        while (jm->work_to_do ()) {
-               ui_signaller->ui_idle ();
+               signal_manager->ui_idle ();
        }
        if (jm->errors ()) {
                int N = 0;
@@ -305,7 +305,7 @@ wait_for_jobs ()
                }
        }
 
-       ui_signaller->ui_idle ();
+       signal_manager->ui_idle ();
 
        /* Discard all jobs so we lose any we just reported an error in */
        JobManager::drop ();
index ffc93ea5edce5fee784dea3a01958061d949b34f..d28b96f417023af5ea2acaf6e0f12f9929ac8371 100644 (file)
@@ -1,3 +1,21 @@
+#
+#    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
 def configure(conf):
     boost_test_suffix=''
     if conf.env.TARGET_WINDOWS:
@@ -13,7 +31,7 @@ def build(bld):
     obj = bld(features='cxx cxxprogram')
     obj.name   = 'unit-tests'
     obj.uselib =  'BOOST_TEST BOOST_THREAD BOOST_FILESYSTEM BOOST_DATETIME SNDFILE DCP OPENJPEG CAIROMM PANGOMM XMLPP '
-    obj.uselib += 'AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML MAGICK SUB GLIB CURL '
+    obj.uselib += 'AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML MAGICK SUB GLIB CURL SSH XMLSEC '
     if bld.env.TARGET_WINDOWS:
         obj.uselib += 'WINSOCK2'
     obj.use    = 'libdcpomatic2'
diff --git a/wscript b/wscript
index 644d69b8d4b228a3f88c8330354858c3d4a7923c..98516102c37bec716ac896620f5cbcaa63cd9339 100644 (file)
--- a/wscript
+++ b/wscript
@@ -1,11 +1,31 @@
+#
+#    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
 import subprocess
 import os
+import shlex
 import sys
 import distutils
 import distutils.spawn
+from waflib import Logs
 
 APPNAME = 'dcpomatic'
-VERSION = '2.0.43devel'
+VERSION = '2.0.47devel'
 
 def options(opt):
     opt.load('compiler_cxx')
@@ -14,188 +34,61 @@ def options(opt):
     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('--disable-tests',     action='store_true', default=False, help='disable building of tests')
-    opt.add_option('--target-windows',    action='store_true', default=False, help='set up to do a cross-compile to make a Windows package')
-    opt.add_option('--target-debian',     action='store_true', default=False, help='set up to compile for a Debian/Ubuntu package')
-    opt.add_option('--debian-unstable',   action='store_true', default=False, help='add extra libraries to static-build correctly on Debian unstable')
-    opt.add_option('--target-centos-6',   action='store_true', default=False, help='set up to compile for a Centos 6.5 package')
-    opt.add_option('--target-centos-7',   action='store_true', default=False, help='set up to compile for a Centos 7 package')
-    opt.add_option('--magickpp-config',   action='store',      default='Magick++-config', help='path to Magick++-config')
-    opt.add_option('--wx-config',         action='store',      default='wx-config', help='path to wx-config')
-    opt.add_option('--address-sanitizer', action='store_true', default=False, help='build with address sanitizer')
     opt.add_option('--install-prefix',                         default=None,  help='prefix of where DCP-o-matic will be installed')
-
-def static_ffmpeg(conf):
-    conf.check_cfg(package='libavformat', args='--cflags', uselib_store='AVFORMAT', mandatory=True)
-    conf.env.STLIB_AVFORMAT = ['avformat']
-    conf.check_cfg(package='libavfilter', args='--cflags', uselib_store='AVFILTER', mandatory=True)
-    conf.env.STLIB_AVFILTER = ['avfilter', 'swresample']
-    conf.check_cfg(package='libavcodec', args='--cflags', uselib_store='AVCODEC', mandatory=True)
-    # lzma link is needed by Centos 7, at least
-    conf.env.STLIB_AVCODEC = ['avcodec']
-    conf.env.LIB_AVCODEC = ['z', 'lzma']
-    conf.check_cfg(package='libavutil', args='--cflags', uselib_store='AVUTIL', mandatory=True)
-    conf.env.STLIB_AVUTIL = ['avutil']
-    conf.check_cfg(package='libswscale', args='--cflags', uselib_store='SWSCALE', mandatory=True)
-    conf.env.STLIB_SWSCALE = ['swscale']
-    conf.check_cfg(package='libswresample', args='--cflags', uselib_store='SWRESAMPLE', mandatory=True)
-    conf.env.STLIB_SWRESAMPLE = ['swresample']
-    conf.check_cfg(package='libpostproc', args='--cflags', uselib_store='POSTPROC', mandatory=True)
-    conf.env.STLIB_POSTPROC = ['postproc']
-
-def dynamic_ffmpeg(conf):
-    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=True)
-    conf.check_cfg(package='libpostproc', args='--cflags --libs', uselib_store='POSTPROC', mandatory=True)
-
-def static_openjpeg(conf):
-    conf.check_cfg(package='libopenjpeg', args='--cflags', atleast_version='1.5.0', uselib_store='OPENJPEG', mandatory=True)
-    conf.check_cfg(package='libopenjpeg', args='--cflags', max_version='1.5.2', mandatory=True)
-    conf.env.STLIB_OPENJPEG = ['openjpeg']
-
-def dynamic_openjpeg(conf):
-    conf.check_cfg(package='libopenjpeg', args='--cflags --libs', atleast_version='1.5.0', uselib_store='OPENJPEG', mandatory=True)
-    conf.check_cfg(package='libopenjpeg', args='--cflags --libs', max_version='1.5.2', mandatory=True)
-
-def static_sub(conf):
-    conf.check_cfg(package='libsub-1.0', atleast_version='1.0.0', args='--cflags', uselib_store='SUB', mandatory=True)
-    conf.env.DEFINES_SUB = [f.replace('\\', '') for f in conf.env.DEFINES_SUB]
-    conf.env.STLIB_SUB = ['sub-1.0']
-
-def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh):
-    conf.check_cfg(package='libdcp-1.0', atleast_version='1.0', args='--cflags', uselib_store='DCP', mandatory=True)
-    conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
-    conf.env.STLIB_DCP = ['dcp-1.0', 'asdcp-libdcp-1.0', 'kumu-libdcp-1.0']
-    conf.env.LIB_DCP = ['glibmm-2.4', 'ssl', 'crypto', 'bz2', 'xslt']
-
-    if static_boost:
-        conf.env.STLIB_DCP.append('boost_system')
-
-    if static_xmlpp:
-        conf.env.STLIB_DCP.append('xml++-2.6')
-    else:
-        conf.env.LIB_DCP.append('xml++-2.6')
-
-    if static_xmlsec:
-        conf.env.STLIB_DCP.append('xmlsec1-openssl')
-        conf.env.STLIB_DCP.append('xmlsec1')
-    else:
-        conf.env.LIB_DCP.append('xmlsec1-openssl')
-        conf.env.LIB_DCP.append('xmlsec1')
-
-    if static_ssh:
-        conf.env.STLIB_DCP.append('ssh')
-    else:
-        conf.env.LIB_DCP.append('ssh')
-
-def dynamic_dcp(conf):
-    conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
-    conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
-
-def dynamic_sub(conf):
-    conf.check_cfg(package='libsub-1.0', atleast_version='1.0.0', args='--cflags --libs', uselib_store='SUB', mandatory=True)
-    conf.env.DEFINES_SUB = [f.replace('\\', '') for f in conf.env.DEFINES_SUB]
-
-def dynamic_ssh(conf):
-    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')
-
-def dynamic_boost(conf, lib_suffix, thread):
-    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_cxx(fragment="""
-                           #include <boost/thread.hpp>\n
-                           int main() { boost::thread t (); }\n
-                           """, msg='Checking for boost threading library',
-                           libpath='/usr/local/lib',
-                            lib=[thread, 'boost_system%s' % 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' % lib_suffix, 'boost_system%s' % 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' % lib_suffix, 'boost_system%s' % 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')
-
-def static_boost(conf, lib_suffix):
-    conf.env.STLIB_BOOST_THREAD = ['boost_thread']
-    conf.env.STLIB_BOOST_FILESYSTEM = ['boost_filesystem%s' % lib_suffix]
-    conf.env.STLIB_BOOST_DATETIME = ['boost_date_time%s' % lib_suffix, 'boost_system%s' % lib_suffix]
-    conf.env.STLIB_BOOST_SIGNALS2 = ['boost_signals2']
+    opt.add_option('--target-windows',    action='store_true', default=False, help='set up to do a cross-compile to make a Windows package')
+    opt.add_option('--static-dcpomatic',  action='store_true', default=False, help='link to components of DCP-o-matic statically')
+    opt.add_option('--static-boost',      action='store_true', default=False, help='link statically to Boost')
+    opt.add_option('--static-openjpeg',   action='store_true', default=False, help='link statically to OpenJPEG')
+    opt.add_option('--static-wxwidgets',  action='store_true', default=False, help='link statically to wxWidgets')
+    opt.add_option('--static-ffmpeg',     action='store_true', default=False, help='link statically to FFmpeg')
+    opt.add_option('--static-xmlpp',      action='store_true', default=False, help='link statically to libxml++')
+    opt.add_option('--static-xmlsec',     action='store_true', default=False, help='link statically to xmlsec')
+    opt.add_option('--static-ssh',        action='store_true', default=False, help='link statically to libssh')
+    opt.add_option('--static-cxml',       action='store_true', default=False, help='link statically to libcxml')
+    opt.add_option('--static-dcp',        action='store_true', default=False, help='link statically to libdcp')
+    opt.add_option('--static-sub',        action='store_true', default=False, help='link statically to libsub')
+    opt.add_option('--static-curl',       action='store_true', default=False, help='link statically to libcurl')
+    opt.add_option('--workaround-gssapi', action='store_true', default=False, help='link to gssapi_krb5')
 
 def configure(conf):
     conf.load('compiler_cxx')
     if conf.options.target_windows:
         conf.load('winres')
 
-    # conf.options -> conf.env
-    conf.env.TARGET_WINDOWS = conf.options.target_windows
+    # Save conf.options that we need elsewhere in conf.env
     conf.env.DISABLE_GUI = conf.options.disable_gui
     conf.env.DISABLE_TESTS = conf.options.disable_tests
-    conf.env.TARGET_DEBIAN = conf.options.target_debian
-    conf.env.DEBIAN_UNSTABLE = conf.options.debian_unstable
-    conf.env.TARGET_CENTOS_6 = conf.options.target_centos_6
-    conf.env.TARGET_CENTOS_7 = conf.options.target_centos_7
-    conf.env.VERSION = VERSION
+    conf.env.TARGET_WINDOWS = conf.options.target_windows
     conf.env.TARGET_OSX = sys.platform == 'darwin'
     conf.env.TARGET_LINUX = not conf.env.TARGET_WINDOWS and not conf.env.TARGET_OSX
-    # true if we should build dcpomatic/libdcpomatic/libdcpomatic-wx statically
-    conf.env.BUILD_STATIC = conf.options.target_debian or conf.options.target_centos_6 or conf.options.target_centos_7
+    conf.env.VERSION = VERSION
+    conf.env.DEBUG = conf.options.enable_debug
+    conf.env.STATIC_DCPOMATIC = conf.options.static_dcpomatic
     if conf.options.install_prefix is None:
         conf.env.INSTALL_PREFIX = conf.env.PREFIX
     else:
         conf.env.INSTALL_PREFIX = conf.options.install_prefix
 
     # Common CXXFLAGS
-    conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS', '-D__STDC_LIMIT_MACROS', '-msse', '-ffast-math', '-fno-strict-aliasing',
-                                       '-Wall', '-Wno-attributes', '-Wextra', '-Wno-unused-result', '-D_FILE_OFFSET_BITS=64'])
+    conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS',
+                                       '-D__STDC_LIMIT_MACROS',
+                                       '-D__STDC_FORMAT_MACROS',
+                                       '-msse',
+                                       '-ffast-math',
+                                       '-fno-strict-aliasing',
+                                       '-Wall',
+                                       '-Wno-attributes',
+                                       '-Wextra',
+                                       '-Wno-unused-result',
+                                       '-D_FILE_OFFSET_BITS=64'])
 
     if conf.options.enable_debug:
         conf.env.append_value('CXXFLAGS', ['-g', '-DDCPOMATIC_DEBUG'])
     else:
         conf.env.append_value('CXXFLAGS', '-O2')
 
-    if conf.options.address_sanitizer:
-        conf.env.append_value('CXXFLAGS', ['-fsanitize=address', '-fno-omit-frame-pointer'])
-        conf.env.append_value('LINKFLAGS', ['-fsanitize=address'])
-
     #
-    # Platform-specific CFLAGS hacks and other tinkering
+    # Windows/Linux/OS X specific
     #
 
     # Windows
@@ -220,6 +113,14 @@ def configure(conf):
         conf.check(lib='mswsock', uselib_store='MSWSOCK', msg="Checking for library mswsock")
         boost_lib_suffix = '-mt'
         boost_thread = 'boost_thread_win32-mt'
+        conf.check_cxx(fragment="""
+                               #include <boost/locale.hpp>\n
+                               int main() { std::locale::global (boost::locale::generator().generate ("")); }\n
+                               """,
+                               msg='Checking for boost locale library',
+                               libpath='/usr/local/lib',
+                               lib=['boost_locale%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
+                               uselib_store='BOOST_LOCALE')
 
     # POSIX
     if conf.env.TARGET_LINUX or conf.env.TARGET_OSX:
@@ -234,17 +135,8 @@ def configure(conf):
         conf.env.append_value('CXXFLAGS', '-DLINUX_LOCALE_PREFIX="%s/share/locale"' % conf.env['INSTALL_PREFIX'])
         conf.env.append_value('CXXFLAGS', '-DLINUX_SHARE_PREFIX="%s/share/dcpomatic2"' % conf.env['INSTALL_PREFIX'])
         conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_LINUX')
-
-    if conf.env.TARGET_DEBIAN:
-        # 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.TARGET_CENTOS_6 or conf.env.TARGET_CENTOS_7:
-        # libavcodec seems to be linked against this on Centos
-        conf.check_cfg(package='liblzma', args='--cflags --libs', uselib_store='LZMA', mandatory=True)
-        
-    if not conf.env.DISABLE_GUI and conf.env.TARGET_LINUX:
-        conf.check_cfg(package='gtk+-2.0', args='--cflags --libs', uselib_store='GTK', mandatory=True)
+        if not conf.env.DISABLE_GUI:
+            conf.check_cfg(package='gtk+-2.0', args='--cflags --libs', uselib_store='GTK', mandatory=True)
 
     # OSX
     if conf.env.TARGET_OSX:
@@ -253,97 +145,203 @@ def configure(conf):
 
     #
     # Dependencies.
-    # There's probably a neater way of expressing these, but I've gone for brute force for now.
     #
 
-    if conf.env.TARGET_DEBIAN:
-        conf.check_cfg(package='libcxml', atleast_version='0.11', args='--cflags', uselib_store='CXML', mandatory=True)
-        conf.env.STLIB_CXML = ['cxml']
-        conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XMLPP', mandatory=True)
-        conf.check_cfg(package='libcurl', args='--cflags --libs', uselib_store='CURL', mandatory=True)
-        static_ffmpeg(conf)
-        static_openjpeg(conf)
-        static_sub(conf)
-        static_dcp(conf, False, False, False, False)
-        dynamic_boost(conf, boost_lib_suffix, boost_thread)
-
-    if conf.env.TARGET_CENTOS_6:
-        # Centos 6.5's boost is too old, so we build a new version statically in the chroot
-        conf.check_cfg(package='libcxml', atleast_version='0.08', args='--cflags --libs-only-L', uselib_store='CXML', mandatory=True)
-        conf.env.STLIB_CXML = ['cxml', 'boost_filesystem']
-        conf.check_cfg(package='libcurl', args='--cflags --libs-only-L', uselib_store='CURL', mandatory=True)
+    # It should be possible to use check_cfg for both dynamic and static linking, but
+    # e.g. pkg-config --libs --static foo returns some libraries that should be statically
+    # linked and others that should be dynamic.  This doesn't work too well with waf
+    # as it wants them separate.
+
+    # libcurl
+    if conf.options.static_curl:
         conf.env.STLIB_CURL = ['curl']
         conf.env.LIB_CURL = ['ssh2', 'idn']
-        static_ffmpeg(conf)
-        static_openjpeg(conf)
-        static_sub(conf)
-        static_dcp(conf, True, True, True, True)
-        static_boost(conf, boost_lib_suffix)
-
-    if conf.env.TARGET_CENTOS_7:
-        # Centos 7's boost is ok so we link it dynamically
-        conf.check_cfg(package='libcxml', atleast_version='0.08', args='--cflags', uselib_store='CXML', mandatory=True)
-        conf.env.STLIB_CXML = ['cxml']
+    else:
         conf.check_cfg(package='libcurl', args='--cflags --libs', uselib_store='CURL', mandatory=True)
-        conf.env.LIB_SSH = ['gssapi_krb5']
-        conf.env.LIB_XMLPP = ['xml2']
-        conf.env.LIB_XMLSEC = ['ltdl']
-        static_ffmpeg(conf)
-        static_openjpeg(conf)
-        static_sub(conf)
-        static_dcp(conf, False, True, True, True)
-        dynamic_boost(conf, boost_lib_suffix, boost_thread)
 
-    if conf.env.TARGET_WINDOWS:
-        conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XMLPP', mandatory=True)
-        conf.check_cfg(package='libcurl', args='--cflags --libs', uselib_store='CURL', mandatory=True)
-        conf.check_cxx(fragment="""
-                               #include <boost/locale.hpp>\n
-                               int main() { std::locale::global (boost::locale::generator().generate ("")); }\n
-                               """, msg='Checking for boost locale library',
-                                libpath='/usr/local/lib',
-                                lib=['boost_locale%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
-                                uselib_store='BOOST_LOCALE')
-        dynamic_boost(conf, boost_lib_suffix, boost_thread)
-        dynamic_ffmpeg(conf)
-        dynamic_openjpeg(conf)
-        dynamic_dcp(conf)
-        dynamic_sub(conf)
-        dynamic_ssh(conf)
-
-    # Not packaging; just a straight build
-    if not conf.env.TARGET_WINDOWS and not conf.env.TARGET_DEBIAN and not conf.env.TARGET_CENTOS_6 and not conf.env.TARGET_CENTOS_7:
-        conf.check_cfg(package='libcxml', atleast_version='0.08', args='--cflags --libs', uselib_store='CXML', mandatory=True)
-        conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XMLPP', mandatory=True)
-        conf.check_cfg(package='libcurl', args='--cflags --libs', uselib_store='CURL', mandatory=True)
-        dynamic_boost(conf, boost_lib_suffix, boost_thread)
-        dynamic_ffmpeg(conf)
-        dynamic_dcp(conf)
-        dynamic_sub(conf)
-        dynamic_openjpeg(conf)
-        dynamic_ssh(conf)
-
-    # Dependencies which are always dynamically linked
+
+    # libsndfile
     conf.check_cfg(package='sndfile', args='--cflags --libs', uselib_store='SNDFILE', mandatory=True)
+
+    # glib
     conf.check_cfg(package='glib-2.0', args='--cflags --libs', uselib_store='GLIB', mandatory=True)
-    if distutils.spawn.find_executable(conf.options.magickpp_config):
-        conf.check_cfg(package='', path=conf.options.magickpp_config, args='--cppflags --cxxflags --libs', uselib_store='MAGICK', mandatory=True)
+
+    # ImageMagick / GraphicsMagick
+    if distutils.spawn.find_executable('Magick++-config'):
+        conf.check_cfg(package='', path='Magick++-config', args='--cppflags --cxxflags --libs', uselib_store='MAGICK', mandatory=True)
         conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_IMAGE_MAGICK')
     else:
-        conf.check_cfg(package='GraphicsMagick++', args='--cflags --libs', uselib_store='MAGICK', mandatory=True)
-        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_GRAPHICS_MAGICK')
+        image = conf.check_cfg(package='ImageMagick++', args='--cflags --libs', uselib_store='MAGICK', mandatory=False)
+        graphics = conf.check_cfg(package='GraphicsMagick++', args='--cflags --libs', uselib_store='MAGICK', mandatory=False)
+        if image is None and graphics is None:
+            Logs.pprint('RED', 'Neither ImageMagick++ nor GraphicsMagick++ found: one or the other is required')
+        if image is not None:
+            conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_IMAGE_MAGICK')
+        if graphics is not None:
+            conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_GRAPHICS_MAGICK')
         
+    # libzip
     conf.check_cfg(package='libzip', args='--cflags --libs', uselib_store='ZIP', mandatory=True)
+
+    # pangomm
     conf.check_cfg(package='pangomm-1.4', args='--cflags --libs', uselib_store='PANGOMM', mandatory=True)
+
+    # cairomm
     conf.check_cfg(package='cairomm-1.0', args='--cflags --libs', uselib_store='CAIROMM', mandatory=True)
 
-    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)
+    # libcxml
+    if conf.options.static_cxml:
+        conf.check_cfg(package='libcxml', atleast_version='0.08', args='--cflags', uselib_store='CXML', mandatory=True)
+        conf.env.STLIB_CXML = ['cxml']
+    else:
+        conf.check_cfg(package='libcxml', atleast_version='0.08', args='--cflags --libs', uselib_store='CXML', mandatory=True)
+
+    # libssh
+    if conf.options.static_ssh:
+        conf.env.STLIB_SSH = ['ssh']
+        if conf.options.workaround_gssapi:
+            conf.env.LIB_SSH = ['gssapi_krb5']
+    else:
+        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')
+
+    # libdcp
+    if conf.options.static_dcp:
+        conf.check_cfg(package='libdcp-1.0', atleast_version='1.00.0', args='--cflags', uselib_store='DCP', mandatory=True)
+        conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
+        conf.env.STLIB_DCP = ['dcp-1.0', 'asdcp-libdcp-1.0', 'kumu-libdcp-1.0']
+        conf.env.LIB_DCP = ['glibmm-2.4', 'ssl', 'crypto', 'bz2', 'xslt']
+    else:
+        conf.check_cfg(package='libdcp-1.0', atleast_version='1.00.0', args='--cflags --libs', uselib_store='DCP', mandatory=True)
+        conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
+
+    # libsub
+    if conf.options.static_sub:
+        conf.check_cfg(package='libsub-1.0', atleast_version='1.00.0', args='--cflags', uselib_store='DCP', mandatory=True)
+        conf.env.DEFINES_SUB = [f.replace('\\', '') for f in conf.env.DEFINES_SUB]
+        conf.env.STLIB_SUB = ['sub-1.0']
+    else:
+        conf.check_cfg(package='libsub-1.0', atleast_version='1.00.0', args='--cflags --libs', uselib_store='DCP', mandatory=True)
+        conf.env.DEFINES_SUB = [f.replace('\\', '') for f in conf.env.DEFINES_SUB]
+
+    # libxml++
+    if conf.options.static_xmlpp:
+        conf.env.STLIB_XMLPP = ['xml++-2.6']
+    else:
+        conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XMLPP', mandatory=True)
+
+    # libxmlsec
+    if conf.options.static_xmlsec:
+        conf.env.STLIB_XMLSEC = ['xmlsec1-openssl', 'xmlsec1']
+    else:
+        conf.env.LIB_XMLSEC = ['xmlsec1-openssl', 'xmlsec1']
+
+    # OpenJPEG
+    if conf.options.static_openjpeg:
+        conf.check_cfg(package='libopenjpeg', args='--cflags', atleast_version='1.5.0', uselib_store='OPENJPEG', mandatory=True)
+        conf.check_cfg(package='libopenjpeg', args='--cflags', max_version='1.5.2', mandatory=True)
+        conf.env.STLIB_OPENJPEG = ['openjpeg']
+    else:
+        conf.check_cfg(package='libopenjpeg', args='--cflags --libs', atleast_version='1.5.0', uselib_store='OPENJPEG', mandatory=True)
+        conf.check_cfg(package='libopenjpeg', args='--cflags --libs', max_version='1.5.2', mandatory=True)
+
+    # FFmpeg
+    if conf.options.static_ffmpeg:
+        names = ['avformat', 'avfilter', 'avcodec', 'avutil', 'swscale', 'swresample', 'postproc']
+        for name in names:
+            static = subprocess.Popen(shlex.split('pkg-config --static --libs lib%s' % name), stdout=subprocess.PIPE).communicate()[0]
+            libs = []
+            stlibs = []
+            include = []
+            libpath = []
+            for s in static.split():
+                if s.startswith('-L'):
+                    libpath.append(s[2:])
+                elif s.startswith('-I'):
+                    include.append(s[2:])
+                elif s.startswith('-l'):
+                    if s[2:] not in names:
+                        libs.append(s[2:])
+                    else:
+                        stlibs.append(s[2:])
+
+            conf.env['LIB_%s' % name.upper()] = libs
+            conf.env['STLIB_%s' % name.upper()] = stlibs
+            conf.env['INCLUDE_%s' % name.upper()] = include
+            conf.env['LIBPATH_%s' % name.upper()] = libpath
+    else:
+        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=True)
+        conf.check_cfg(package='libpostproc', args='--cflags --libs', uselib_store='POSTPROC', mandatory=True)
+
+    # Boost
+    if conf.options.static_boost:
+        conf.env.STLIB_BOOST_THREAD = ['boost_thread']
+        conf.env.STLIB_BOOST_FILESYSTEM = ['boost_filesystem%s' % boost_lib_suffix]
+        conf.env.STLIB_BOOST_DATETIME = ['boost_date_time%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix]
+        conf.env.STLIB_BOOST_SIGNALS2 = ['boost_signals2']
+        conf.env.STLIB_BOOST_SYSTEM = ['boost_system']
+    else:
+        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_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')
+
+    # Other stuff
 
     conf.find_program('msgfmt', var='MSGFMT')
     
@@ -358,6 +356,36 @@ def configure(conf):
     if not conf.env.DISABLE_TESTS:
         conf.recurse('test')
 
+    Logs.pprint('YELLOW', '')
+    if conf.env.TARGET_WINDOWS:
+        Logs.pprint('YELLOW', '\t' + 'Target'.ljust(25) + ': Windows')
+    elif conf.env.TARGET_LINUX:
+        Logs.pprint('YELLOW', '\t' + 'Target'.ljust(25) + ': Linux')
+    elif conf.env.TARGET_OSX:
+        Logs.pprint('YELLOW', '\t' + 'Target'.ljust(25) + ': OS X')
+
+    def report(name, variable):
+        linkage = ''
+        if variable:
+            linkage = 'static'
+        else:
+            linkage = 'dynamic'
+        Logs.pprint('YELLOW', '\t%s: %s' % (name.ljust(25), linkage))
+
+    report('DCP-o-matic libraries', conf.options.static_dcpomatic)
+    report('Boost', conf.options.static_boost)
+    report('OpenJPEG', conf.options.static_openjpeg)
+    report('wxWidgets', conf.options.static_wxwidgets)
+    report('FFmpeg', conf.options.static_ffmpeg)
+    report('libxml++', conf.options.static_xmlpp)
+    report('xmlsec', conf.options.static_xmlsec)
+    report('libssh', conf.options.static_ssh)
+    report('libcxml', conf.options.static_cxml)
+    report('libdcp', conf.options.static_dcp)
+    report('libcurl', conf.options.static_curl)
+
+    Logs.pprint('YELLOW', '')
+
 def build(bld):
     create_version_cc(VERSION, bld.env.CXXFLAGS)
 
@@ -374,9 +402,8 @@ def build(bld):
     for r in ['22x22', '32x32', '48x48', '64x64', '128x128']:
         bld.install_files('${PREFIX}/share/icons/hicolor/%s/apps' % r, 'icons/%s/dcpomatic2.png' % r)
 
-    if bld.env.TARGET_LINUX:
-        bld.install_files('${PREFIX}/share/dcpomatic2', 'icons/taskbar_icon.png')
-        bld.install_files('${PREFIX}/share/dcpomatic2', 'LiberationSans-Regular.ttf')
+    if not bld.env.TARGET_WINDOWS:
+        bld.install_files('${PREFIX}/share/dcpomatic', 'icons/taskbar_icon.png')
 
     bld.add_post_fun(post)
 
@@ -402,7 +429,6 @@ def dist(ctx):
                GRSYMS GRTAGS GSYMS GTAGS
                """
 
-
 def create_version_cc(version, cxx_flags):
     commit = git_revision()
     if commit is None and os.path.exists('.git_revision'):
@@ -442,4 +468,4 @@ def pot_merge(bld):
     bld.recurse('src')
 
 def tags(bld):
-    os.system('etags src/lib/*.cc src/lib/*.h src/wx/*.cc src/wx/*.h src/tools/*.cc src/tools/*.h test/*.cc')
+    os.system('etags src/lib/*.cc src/lib/*.h src/wx/*.cc src/wx/*.h src/tools/*.cc src/tools/*.h')