diff options
94 files changed, 1318 insertions, 840 deletions
@@ -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. @@ -1,6 +1,5 @@ -add9a03356d3d392e234354df4800b9042e0f426 -01919a9e691375de4eb0069bc8cf179bee4dd7b6 -1d63d0309d071254fcf4da65d3710e94fadd38e8 -0c0211871d0be5b3409adfc88d2979ca5b439b0a -wscript/cscript etc. cleanups -2a3bfb06c68afd1aa4daaa14ece050689ea47927 +2a595178e42734336983693e8150609554b6a08d +4bcdf16458e460dd4a78d634dfe69f2a44182541 +21f33acd3580c6e5c4ec1e7449b419c3178aa8ab +Multi-stream audio stuff. +681d95c83868310330984ae65589a1021bbe07d6 @@ -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 = [] diff --git a/debian/changelog b/debian/changelog index 43750e790..cb6baec5e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -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 diff --git a/debian/rules b/debian/rules index b51089f8b..3e75b0090 100755 --- a/debian/rules +++ b/debian/rules @@ -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 index 000000000..439ab9154 --- /dev/null +++ b/hacks/apply_to_port @@ -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 index 000000000..1b51e0ee1 --- /dev/null +++ b/hacks/next_to_port @@ -0,0 +1,3 @@ +#!/bin/bash + +head -n 1 TO_PORT | xargs git show diff --git a/hacks/start_servers.sh b/hacks/start_servers.sh index 629b49523..8f2b2859b 100644 --- a/hacks/start_servers.sh +++ b/hacks/start_servers.sh @@ -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 \ diff --git a/hacks/stop_servers.sh b/hacks/stop_servers.sh index 7ea031ff0..b785ceaa0 100644 --- a/hacks/stop_servers.sh +++ b/hacks/stop_servers.sh @@ -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 index 7f39716d4..000000000 --- a/platform/linux/dcpomatic.spec.in +++ /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 || : diff --git a/platform/linux/wscript b/platform/linux/wscript index 336c1bcb0..5f1dc48a2 100644 --- a/platform/linux/wscript +++ b/platform/linux/wscript @@ -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']) @@ -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 $* diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index 079fe884e..cdf623876 100644 --- a/src/lib/analyse_audio_job.cc +++ b/src/lib/analyse_audio_job.cc @@ -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]); diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h index 6f64dd272..0f9605eed 100644 --- a/src/lib/analyse_audio_job.h +++ b/src/lib/analyse_audio_job.h @@ -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; diff --git a/src/lib/audio_analysis.cc b/src/lib/audio_analysis.cc index 19a0d876e..73422a9be 100644 --- a/src/lib/audio_analysis.cc +++ b/src/lib/audio_analysis.cc @@ -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 @@ -18,14 +18,17 @@ */ #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 ()); } diff --git a/src/lib/audio_analysis.h b/src/lib/audio_analysis.h index 865d64781..9387ec896 100644 --- a/src/lib/audio_analysis.h +++ b/src/lib/audio_analysis.h @@ -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 @@ -17,19 +17,16 @@ */ -/** @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 diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index 22376e3e3..f6133947a 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -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 */ } diff --git a/src/lib/content.cc b/src/lib/content.cc index fcc658717..b9e8367e1 100644 --- a/src/lib/content.cc +++ b/src/lib/content.cc @@ -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 diff --git a/src/lib/content.h b/src/lib/content.h index c6cede5fa..2b966110b 100644 --- a/src/lib/content.h +++ b/src/lib/content.h @@ -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>); diff --git a/src/lib/cross.cc b/src/lib/cross.cc index 9894d885f..285fbe1ce 100644 --- a/src/lib/cross.cc +++ b/src/lib/cross.cc @@ -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 diff --git a/src/lib/dcp_decoder.cc b/src/lib/dcp_decoder.cc index 51d16b43c..3bfbd7720 100644 --- a/src/lib/dcp_decoder.cc +++ b/src/lib/dcp_decoder.cc @@ -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> (); diff --git a/src/lib/dcp_decoder.h b/src/lib/dcp_decoder.h index 8afebff57..3a05325c7 100644 --- a/src/lib/dcp_decoder.h +++ b/src/lib/dcp_decoder.h @@ -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; diff --git a/src/lib/dcp_subtitle_decoder.cc b/src/lib/dcp_subtitle_decoder.cc index e3c06378b..93a122590 100644 --- a/src/lib/dcp_subtitle_decoder.cc +++ b/src/lib/dcp_subtitle_decoder.cc @@ -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 */ diff --git a/src/lib/dcp_subtitle_decoder.h b/src/lib/dcp_subtitle_decoder.h index 2326b31ad..52e400416 100644 --- a/src/lib/dcp_subtitle_decoder.h +++ b/src/lib/dcp_subtitle_decoder.h @@ -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; diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h index dc9b0cd8a..ae8f25199 100644 --- a/src/lib/dcpomatic_time.h +++ b/src/lib/dcpomatic_time.h @@ -193,6 +193,7 @@ class ContentTimePeriod { public: ContentTimePeriod () {} + ContentTimePeriod (ContentTime f, ContentTime t) : from (f) , to (t) diff --git a/src/lib/decoder.h b/src/lib/decoder.h index c1b859865..0703a5426 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -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 diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index 3a42b169f..a52b53b04 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -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 diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index 76ba43567..d6edb2bdb 100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.h @@ -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 diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index bd01b280b..35e15a331 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -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); -} diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 0334a30e2..6f027ce1c 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -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; diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc index 4409526dc..8afd4c164 100644 --- a/src/lib/ffmpeg_examiner.cc +++ b/src/lib/ffmpeg_examiner.cc @@ -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); } } diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h index b873222c1..34d4b1e0d 100644 --- a/src/lib/ffmpeg_examiner.h +++ b/src/lib/ffmpeg_examiner.h @@ -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; }; diff --git a/src/lib/ffmpeg_subtitle_stream.cc b/src/lib/ffmpeg_subtitle_stream.cc index 3d8fd4e83..77a56e330 100644 --- a/src/lib/ffmpeg_subtitle_stream.cc +++ b/src/lib/ffmpeg_subtitle_stream.cc @@ -18,6 +18,13 @@ */ #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(). @@ -26,11 +33,54 @@ 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; } diff --git a/src/lib/ffmpeg_subtitle_stream.h b/src/lib/ffmpeg_subtitle_stream.h index b16b825e7..3ed931b8c 100644 --- a/src/lib/ffmpeg_subtitle_stream.h +++ b/src/lib/ffmpeg_subtitle_stream.h @@ -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; }; diff --git a/src/lib/film.cc b/src/lib/film.cc index 80755a4cb..35773c797 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -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 diff --git a/src/lib/film.h b/src/lib/film.h index 3cd370a0d..f61062be0 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -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); diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc index 78201fc23..250c8f845 100644 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -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; diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h index 242f69477..ec90051da 100644 --- a/src/lib/image_decoder.h +++ b/src/lib/image_decoder.h @@ -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; diff --git a/src/lib/job.cc b/src/lib/job.cc index eadafbf73..c4d93ddc1 100644 --- a/src/lib/job.cc +++ b/src/lib/job.cc @@ -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 */ diff --git a/src/lib/job.h b/src/lib/job.h index 7c6707880..8fe87747c 100644 --- a/src/lib/job.h +++ b/src/lib/job.h @@ -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>); diff --git a/src/lib/job_manager.cc b/src/lib/job_manager.cc index 2b727b0aa..b5b64a77e 100644 --- a/src/lib/job_manager.cc +++ b/src/lib/job_manager.cc @@ -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); diff --git a/src/lib/job_manager.h b/src/lib/job_manager.h index 9d8620cbb..b946c1a98 100644 --- a/src/lib/job_manager.h +++ b/src/lib/job_manager.h @@ -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: diff --git a/src/lib/kdm.cc b/src/lib/kdm.cc index 3f88bbd9d..8949736f8 100644 --- a/src/lib/kdm.cc +++ b/src/lib/kdm.cc @@ -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 ()); diff --git a/src/lib/player.cc b/src/lib/player.cc index 436ae3fe8..640253c6d 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -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)); diff --git a/src/lib/render_subtitles.cc b/src/lib/render_subtitles.cc index bc89fd3f8..9620eacbf 100644 --- a/src/lib/render_subtitles.cc +++ b/src/lib/render_subtitles.cc @@ -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; diff --git a/src/lib/server_finder.cc b/src/lib/server_finder.cc index f347132e4..726437ea5 100644 --- a/src/lib/server_finder.cc +++ b/src/lib/server_finder.cc @@ -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 (); diff --git a/src/lib/server_finder.h b/src/lib/server_finder.h index 3fab6864a..dc62f998d 100644 --- a/src/lib/server_finder.h +++ b/src/lib/server_finder.h @@ -18,9 +18,10 @@ */ #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/ui_signaller.cc b/src/lib/signal_manager.cc index 4cb34da51..7c2b3e11a 100644 --- a/src/lib/ui_signaller.cc +++ b/src/lib/signal_manager.cc @@ -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 @@ -17,8 +17,7 @@ */ -#include "ui_signaller.h" - -/** Global UISignaller instance */ -UISignaller* ui_signaller = 0; +#include "signal_manager.h" +/** Global SignalManager instance */ +SignalManager* signal_manager = 0; diff --git a/src/lib/ui_signaller.h b/src/lib/signal_manager.h index ee4d230d4..ae4306e30 100644 --- a/src/lib/ui_signaller.h +++ b/src/lib/signal_manager.h @@ -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 @@ -17,43 +17,28 @@ */ -#ifndef DCPOMATIC_UI_SIGNALLER_H -#define DCPOMATIC_UI_SIGNALLER_H +#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 UISignaller : public boost::noncopyable +class SignalManager : public boost::noncopyable { public: - /** Create a UISignaller. Must be called from the UI thread */ - UISignaller () + /** Create a SignalManager. Must be called from the UI thread */ + SignalManager () : _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) { @@ -68,11 +53,30 @@ public: /** 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... */ + /* 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 */ @@ -81,6 +85,6 @@ private: boost::thread::id _ui_thread; }; -extern UISignaller* ui_signaller; +extern SignalManager* signal_manager; #endif diff --git a/src/lib/signaller.h b/src/lib/signaller.h new file mode 100644 index 000000000..4ef9b38b3 --- /dev/null +++ b/src/lib/signaller.h @@ -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 diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index 602014d58..09059a8b0 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -65,7 +65,7 @@ SndfileDecoder::~SndfileDecoder () } bool -SndfileDecoder::pass () +SndfileDecoder::pass (PassReason) { if (_remaining == 0) { return true; diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h index 5ebe1da7b..68c8633a0 100644 --- a/src/lib/sndfile_decoder.h +++ b/src/lib/sndfile_decoder.h @@ -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; diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc index 552a96b8f..6ed2e5254 100644 --- a/src/lib/subrip_decoder.cc +++ b/src/lib/subrip_decoder.cc @@ -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 */ diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h index ad9d04e40..264ca8899 100644 --- a/src/lib/subrip_decoder.h +++ b/src/lib/subrip_decoder.h @@ -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; }; diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc index 9b2aa8ab0..2efe9afb6 100644 --- a/src/lib/subtitle_decoder.cc +++ b/src/lib/subtitle_decoder.cc @@ -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 diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h index d7faaa014..8ba74404f 100644 --- a/src/lib/subtitle_decoder.h +++ b/src/lib/subtitle_decoder.h @@ -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; }; diff --git a/src/lib/types.h b/src/lib/types.h index f3877d0d5..e7017a295 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -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/update.cc b/src/lib/update.cc index a05df8ef3..f433ff991 100644 --- a/src/lib/update.cc +++ b/src/lib/update.cc @@ -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 * diff --git a/src/lib/update.h b/src/lib/update.h index 5bb9e9501..461217a37 100644 --- a/src/lib/update.h +++ b/src/lib/update.h @@ -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 (); diff --git a/src/lib/util.cc b/src/lib/util.cc index bffbe90d4..99d9ba2c4 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -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); } + diff --git a/src/lib/util.h b/src/lib/util.h index c1f7a78c7..44bd7dced 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -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); diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index b7cf1641b..31dc3cdc2 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -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. */ diff --git a/src/lib/writer.cc b/src/lib/writer.cc index 31c265e2f..1a11a482b 100644 --- a/src/lib/writer.cc +++ b/src/lib/writer.cc @@ -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; } diff --git a/src/lib/wscript b/src/lib/wscript index 24aa7c134..5956c73d6 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -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' diff --git a/src/tools/dcpomatic.cc b/src/tools/dcpomatic.cc index e59220785..904e39fda 100644 --- a/src/tools/dcpomatic.cc +++ b/src/tools/dcpomatic.cc @@ -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 () diff --git a/src/tools/dcpomatic_batch.cc b/src/tools/dcpomatic_batch.cc index da8a61414..ae2f3a2c5 100644 --- a/src/tools/dcpomatic_batch.cc +++ b/src/tools/dcpomatic_batch.cc @@ -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) diff --git a/src/tools/dcpomatic_cli.cc b/src/tools/dcpomatic_cli.cc index 4facdd4d1..0307cac9c 100644 --- a/src/tools/dcpomatic_cli.cc +++ b/src/tools/dcpomatic_cli.cc @@ -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 (); diff --git a/src/tools/dcpomatic_create.cc b/src/tools/dcpomatic_create.cc index 304f4f697..d121eb0cc 100644 --- a/src/tools/dcpomatic_create.cc +++ b/src/tools/dcpomatic_create.cc @@ -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) { diff --git a/src/tools/wscript b/src/tools/wscript index 175cebc96..ffd77c6cb 100644 --- a/src/tools/wscript +++ b/src/tools/wscript @@ -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' diff --git a/src/wscript b/src/wscript index f3a6a8c7b..8ec39963f 100644 --- a/src/wscript +++ b/src/wscript @@ -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: diff --git a/src/wx/audio_dialog.cc b/src/wx/audio_dialog.cc index 1d41fc185..fcae9c30f 100644 --- a/src/wx/audio_dialog.cc +++ b/src/wx/audio_dialog.cc @@ -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)); + } +} diff --git a/src/wx/audio_dialog.h b/src/wx/audio_dialog.h index b27785292..aef8ea944 100644 --- a/src/wx/audio_dialog.h +++ b/src/wx/audio_dialog.h @@ -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; diff --git a/src/wx/audio_panel.cc b/src/wx/audio_panel.cc index 2a41aeb2d..4d783ca9d 100644 --- a/src/wx/audio_panel.cc +++ b/src/wx/audio_panel.cc @@ -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 ()); } diff --git a/src/wx/dcp_panel.cc b/src/wx/dcp_panel.cc index 82872ad84..ccfe5711c 100644 --- a/src/wx/dcp_panel.cc +++ b/src/wx/dcp_panel.cc @@ -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)); diff --git a/src/wx/dolby_certificate_dialog.cc b/src/wx/dolby_certificate_dialog.cc index 5e094844d..ad43f6479 100644 --- a/src/wx/dolby_certificate_dialog.cc +++ b/src/wx/dolby_certificate_dialog.cc @@ -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); } } diff --git a/src/wx/doremi_certificate_dialog.cc b/src/wx/doremi_certificate_dialog.cc index 4b5d58b37..578a7a72d 100644 --- a/src/wx/doremi_certificate_dialog.cc +++ b/src/wx/doremi_certificate_dialog.cc @@ -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); } } diff --git a/src/wx/download_certificate_dialog.cc b/src/wx/download_certificate_dialog.cc index a8a712334..a0c41fd76 100644 --- a/src/wx/download_certificate_dialog.cc +++ b/src/wx/download_certificate_dialog.cc @@ -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); +} + + diff --git a/src/wx/download_certificate_dialog.h b/src/wx/download_certificate_dialog.h index 804c0c762..40e11de45 100644 --- a/src/wx/download_certificate_dialog.h +++ b/src/wx/download_certificate_dialog.h @@ -32,6 +32,7 @@ public: protected: void add_common_widgets (); + void downloaded (bool done); boost::function<void (boost::filesystem::path)> _load; wxStaticText* _message; diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index 311ec734c..0938d52a4 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -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 diff --git a/src/wx/kdm_dialog.cc b/src/wx/kdm_dialog.cc index 3e6301482..8f4f8622d 100644 --- a/src/wx/kdm_dialog.cc +++ b/src/wx/kdm_dialog.cc @@ -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); } diff --git a/src/wx/key_dialog.cc b/src/wx/key_dialog.cc index d7c809609..70229c7a9 100644 --- a/src/wx/key_dialog.cc +++ b/src/wx/key_dialog.cc @@ -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); } diff --git a/src/wx/wscript b/src/wx/wscript index 7b5904b4b..370f59c62 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -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_ui_signaller.cc b/src/wx/wx_signal_manager.cc index 8fc6670d6..3d8b9992a 100644 --- a/src/wx/wx_ui_signaller.cc +++ b/src/wx/wx_signal_manager.cc @@ -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 @@ -18,16 +18,16 @@ */ #include <wx/wx.h> -#include "wx_ui_signaller.h" +#include "wx_signal_manager.h" -wxUISignaller::wxUISignaller (wxEvtHandler* h) +wxSignalManager::wxSignalManager (wxEvtHandler* h) : _handler (h) { } void -wxUISignaller::wake_ui () +wxSignalManager::wake_ui () { wxCommandEvent event (-1, -1); _handler->AddPendingEvent (event); diff --git a/src/wx/wx_ui_signaller.h b/src/wx/wx_signal_manager.h index 63f2049cd..ad18e6880 100644 --- a/src/wx/wx_ui_signaller.h +++ b/src/wx/wx_signal_manager.h @@ -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 @@ -17,18 +17,18 @@ */ -#include "lib/ui_signaller.h" +#include "lib/signal_manager.h" class wxEvtHandler; -/** @class wxUISignaller - * @brief UISignaller for the wxWidgets event loop +/** @class wxSignalManager + * @brief SignalManager for the wxWidgets event loop */ -class wxUISignaller : public UISignaller +class wxSignalManager : public SignalManager { public: - wxUISignaller (wxEvtHandler *); + wxSignalManager (wxEvtHandler *); void wake_ui (); private: diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc index e9c07c91f..9c13cfbcb 100644 --- a/src/wx/wx_util.cc +++ b/src/wx/wx_util.cc @@ -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); +} diff --git a/src/wx/wx_util.h b/src/wx/wx_util.h index dfa0fca5e..f2ab2d8c5 100644 --- a/src/wx/wx_util.h +++ b/src/wx/wx_util.h @@ -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); diff --git a/test/audio_analysis_test.cc b/test/audio_analysis_test.cc index 279944919..7be9ca549 100644 --- a/test/audio_analysis_test.cc +++ b/test/audio_analysis_test.cc @@ -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 (); +} diff --git a/test/audio_decoder_test.cc b/test/audio_decoder_test.cc index a14e2f9be..b1f672356 100644 --- a/test/audio_decoder_test.cc +++ b/test/audio_decoder_test.cc @@ -40,7 +40,7 @@ public: , _position (0) {} - bool pass () + bool pass (PassReason) { AudioFrame const N = min ( AudioFrame (2000), diff --git a/test/film_metadata_test.cc b/test/film_metadata_test.cc index 01cff5b06..90f81deb6 100644 --- a/test/film_metadata_test.cc +++ b/test/film_metadata_test.cc @@ -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"); diff --git a/test/image_test.cc b/test/image_test.cc index 9aca9878d..5561f021c 100644 --- a/test/image_test.cc +++ b/test/image_test.cc @@ -172,67 +172,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) { diff --git a/test/isdcf_name_test.cc b/test/isdcf_name_test.cc index 5f6ccc5e1..b54ce205f 100644 --- a/test/isdcf_name_test.cc +++ b/test/isdcf_name_test.cc @@ -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"); } diff --git a/test/test.cc b/test/test.cc index 9201e5b4c..0de7ace62 100644 --- a/test/test.cc +++ b/test/test.cc @@ -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 (); diff --git a/test/wscript b/test/wscript index ffc93ea5e..d28b96f41 100644 --- a/test/wscript +++ b/test/wscript @@ -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' @@ -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') |
