From: Carl Hetherington Date: Tue, 30 Apr 2024 09:20:53 +0000 (+0200) Subject: Supporters update. X-Git-Tag: v2.16.82 X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=HEAD;hp=e389f9980249f51ed98564ffff208b67c3bcfbe2 Supporters update. --- diff --git a/DEVELOP.md b/DEVELOP.md index 01da64ec7..51b63ff5c 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -4,6 +4,49 @@ This file collects a few notes relevant to DCP-o-matic developers. There is als [on the web site](https://dcpomatic.com/development). +## Building on macOS/arm64 + +Build `osx-environment` in `$HOME` +``` +bash platform/osx/copy_resources.sh +source platform/osx/set_paths.sh +./waf configure --target-macos-arm64 +``` + + +## Disk writer logging + +As we have no `film' folder to log to during disk writes, the logs end up: + +### macOS + +* Disk writer backend: `/var/log/dcpomatic_disk_writer_{out,err}.log` +* Disk writer frontend: `/Users/$USER/Library/Preferences/com.dcpomatic/2/2.16/disk.log` + +### Windows + +* Disk writer backend: `c:\Users\$USER\AppData\Local\dcpomatic\2.16.0\disk_writer.log` +* Disk writer frontend: `c:\Users\$USER\AppData\Local\dcpomatic\2.16.0\disk.log` + +### Linux + +* Disk writer backend: `/home/$USER/.config/dcpomatic/2.16.0/disk_writer.log` +* Disk writer frontend: `/home/$USER/.config/dcpomatic/2.16.0/disk.log` + + +## Branches + +The main dcpomatic repo has the following branches: + +* `main` - the main development branch; contains 2.16.x versions +* `v2.17.x` - development branch for v2.17.x versions; `main` is merged into this branch. + +The `test/data` submodule has the following branches: + +* `v2.16.x` - branch for use with v2.16.x versions +* `v2.18.x` - branch for use with v2.17.x versions (as will become v2.18.x) + + ## Player stress testing If you configure DCP-o-matic with `--enable-player-stress-test` you can make a script which @@ -58,9 +101,20 @@ to load a script file called `stress` and start executing it. - ./waf pot - cp build/src/lib/libdcpomatic.pot src/lib/po/$LANG.po - cp build/src/wx/libdcpomatic-wx.pot src/wx/po/$LANG.po -- cp build/src/tools/libdcpomatic-wx.pot src/tools/po/$LANG.po +- cp build/src/tools/dcpomatic.pot src/tools/po/$LANG.po - sed -i "s/CHARSET/UTF-8/" src/{lib,wx,tools}/po/$LANG.po - Commit / push - Add credit to `src/wx/about_dialog.cc` and database. - Add to `i18n.php` on website and `update-i18n-stats` script, then run `update-i18n-stats` script. + +## Taking screenshots for the manual + +The manual PDF looks nice if vector screenshots are used. These can be taken as follows: + +- Build `gtk-vector-screenshot.git` (using meson/ninja) +- Copy `libgtk-vector-screenshot.so` to `/usr/local/lib/gtk-3.0/modules/` +- Run DCP-o-matic using `run/dcpomatic --screenshot` +- Start `take-vector-screenshot`, click "Take screenshot" then click on the DCP-o-matic window. +- Find a PDF in `/tmp/dcpomatic2.pdf` +- Copy this to `doc/manual/raw-screenshots` diff --git a/cscript b/cscript index 6cb19914d..dd7f9c44a 100644 --- a/cscript +++ b/cscript @@ -29,18 +29,27 @@ deb_build_depends = dict() deb_build_depends_base = ['debhelper', 'g++', 'pkg-config', 'libsndfile1-dev', 'libgtk2.0-dev', 'libx264-dev'] -for v in ['16.04', '18.04', '20.04', '20.10', '21.04', '21.10']: +for v in ['16.04', '18.04', '20.04']: deb_build_depends[v] = copy.deepcopy(deb_build_depends_base) deb_build_depends[v].extend(['libssh-dev', 'python']) -for v in ['22.04', '22.10']: +for v in ['22.04']: deb_build_depends[v] = copy.deepcopy(deb_build_depends_base) deb_build_depends[v].extend(['libssh-dev', 'python3.10']) +for v in ['23.04', '23.10']: + deb_build_depends[v] = copy.deepcopy(deb_build_depends_base) + deb_build_depends[v].extend(['libssh-dev', 'python3.11']) +for v in ['24.04']: + deb_build_depends[v] = copy.deepcopy(deb_build_depends_base) + deb_build_depends[v].extend(['libssh-dev', 'python3.12']) for v in ['9', '10']: deb_build_depends[v] = copy.deepcopy(deb_build_depends_base) deb_build_depends[v].extend(['libssh-gcrypt-dev', 'python']) for v in ['11']: deb_build_depends[v] = copy.deepcopy(deb_build_depends_base) deb_build_depends[v].extend(['libssh-gcrypt-dev', 'python3.9']) +for v in ['12']: + deb_build_depends[v] = copy.deepcopy(deb_build_depends_base) + deb_build_depends[v].extend(['libssh-gcrypt-dev', 'python3.11']) deb_build_depends['unstable'] = copy.deepcopy(deb_build_depends_base) deb_build_depends['unstable'].extend(['python3']) @@ -139,8 +148,8 @@ deb_depends['22.04'].extend(['libboost-filesystem1.74.0', 'libnanomsg5', 'libdav1d5']) -deb_depends['22.10'] = copy.deepcopy(deb_depends_base) -deb_depends['22.10'].extend(['libboost-filesystem1.74.0', +deb_depends['23.04'] = copy.deepcopy(deb_depends_base) +deb_depends['23.04'].extend(['libboost-filesystem1.74.0', 'libboost-thread1.74.0', 'libboost-regex1.74.0', 'libboost-date-time1.74.0', @@ -148,15 +157,60 @@ deb_depends['22.10'].extend(['libboost-filesystem1.74.0', 'libpangomm-1.4-1v5', 'libxml++2.6-2v5', 'libzip4', - 'libicu71', + 'libicu72', 'libnettle8', 'libssh-4', 'libx264-164', 'libcurl4', 'libpulse0', 'libxerces-c3.2', - 'libnanomsg5']) + 'libnanomsg5', + 'libwxgtk3.2-1', + 'libwxgtk-gl3.2-1']) +deb_depends['23.10'] = copy.deepcopy(deb_depends_base) +deb_depends['23.10'].extend(['libboost-filesystem1.74.0', + 'libboost-thread1.74.0', + 'libboost-regex1.74.0', + 'libboost-date-time1.74.0', + 'libcairomm-1.0-1v5', + 'libpangomm-1.4-1v5', + 'libxml++2.6-2v5', + 'libzip4', + 'libicu72', + 'libnettle8', + 'libssh-4', + 'libx264-164', + 'libcurl4', + 'libpulse0', + 'libxerces-c3.2', + 'libnanomsg5', + 'libwxgtk3.2-1', + 'libwxgtk-gl3.2-1']) + +def debs(boost, icu, x264): + output = copy.deepcopy(deb_depends_base) + output.extend(['libboost-filesystem' + boost, + 'libboost-thread' + boost, + 'libboost-regex' + boost, + 'libboost-date-time' + boost, + 'libcairomm-1.0-1v5', + 'libpangomm-1.4-1v5', + 'libxml++2.6-2v5', + 'libzip4', + 'libicu' + icu, + 'libnettle8', + 'libssh-4', + 'libx264-' + x264, + 'libcurl4', + 'libpulse0', + 'libxerces-c3.2', + 'libnanomsg5', + 'libwxgtk3.2-1', + 'libwxgtk-gl3.2-1']) + return output + +deb_depends['24.04'] = debs(boost='1.83.0', icu='74', x264='164') deb_depends['9'] = copy.deepcopy(deb_depends_base) deb_depends['9'].extend(['libboost-filesystem1.62.0', @@ -229,6 +283,29 @@ deb_depends_gui['11'] = [ 'libxcb-xfixes0', 'libasound2', 'libpulse0' ] +deb_depends['12'] = copy.deepcopy(deb_depends_base) +deb_depends['12'].extend(['libboost-filesystem1.74.0', + 'libboost-thread1.74.0', + 'libboost-regex1.74.0', + 'libboost-date-time1.74.0', + 'libxml++2.6-2v5', + 'libzip4', + 'libcairomm-1.0-1v5', + 'libpangomm-1.4-1v5', + 'libicu72', + 'libssh-4', + 'libssh-gcrypt-4', + 'libnettle8', + 'libx264-164', + 'libcurl4', + 'libxerces-c3.2', + 'libnanomsg5']) + +deb_depends_gui['12'] = [ 'libxcb-xfixes0', + 'libxcb-shape0', + 'libasound2', + 'libpulse0' ] + deb_depends['unstable'] = copy.deepcopy(deb_depends_base) deb_depends['unstable'].extend(['libboost-filesystem1.67.0', 'libboost-thread1.67.0', @@ -354,6 +431,8 @@ def make_spec(filename, version, target, options, requires=None): print('%{_bindir}/dcpomatic2_openssl', file=f) print('%{_bindir}/dcpomatic2_combiner', file=f) print('%{_bindir}/dcpomatic2_verify', file=f) + print('%{_bindir}/dcpomatic2_kdm_inspect', file=f) + print('%{_bindir}/dcpomatic2_map', file=f) if can_build_disk(target): print('%{_bindir}/dcpomatic2_disk', file=f) print('%caps(cap_dac_override=ep) %{_bindir}/dcpomatic2_disk_writer', file=f) @@ -368,15 +447,23 @@ def make_spec(filename, version, target, options, requires=None): if can_build_disk(target): print('%{_datadir}/applications/dcpomatic2_disk.desktop', file=f) print('%{_datadir}/dcpomatic2/dcpomatic2_server_small.png', file=f) - print('%{_datadir}/dcpomatic2/select.png', file=f) - print('%{_datadir}/dcpomatic2/sequence.png', file=f) - print('%{_datadir}/dcpomatic2/snap.png', file=f) - print('%{_datadir}/dcpomatic2/zoom.png', file=f) - print('%{_datadir}/dcpomatic2/zoom_all.png', file=f) - print('%{_datadir}/dcpomatic2/tick.png', file=f) - print('%{_datadir}/dcpomatic2/no_tick.png', file=f) - print('%{_datadir}/dcpomatic2/link.png', file=f) + print('%{_datadir}/dcpomatic2/select_white.png', file=f) + print('%{_datadir}/dcpomatic2/select_black.png', file=f) + print('%{_datadir}/dcpomatic2/sequence_white.png', file=f) + print('%{_datadir}/dcpomatic2/sequence_black.png', file=f) + print('%{_datadir}/dcpomatic2/snap_white.png', file=f) + print('%{_datadir}/dcpomatic2/snap_black.png', file=f) + print('%{_datadir}/dcpomatic2/zoom_white.png', file=f) + print('%{_datadir}/dcpomatic2/zoom_black.png', file=f) + print('%{_datadir}/dcpomatic2/zoom_all_white.png', file=f) + print('%{_datadir}/dcpomatic2/zoom_all_black.png', file=f) + print('%{_datadir}/dcpomatic2/link_black.png', file=f) + print('%{_datadir}/dcpomatic2/link_white.png', file=f) print('%{_datadir}/dcpomatic2/me.jpg', file=f) + print('%{_datadir}/dcpomatic2/add_black.png', file=f) + print('%{_datadir}/dcpomatic2/add_white.png', file=f) + print('%{_datadir}/dcpomatic2/pause_black.png', file=f) + print('%{_datadir}/dcpomatic2/pause_white.png', file=f) print('%{_datadir}/dcpomatic2/LiberationSans-Regular.ttf', file=f) print('%{_datadir}/dcpomatic2/LiberationSans-Italic.ttf', file=f) print('%{_datadir}/dcpomatic2/LiberationSans-Bold.ttf', file=f) @@ -392,7 +479,8 @@ def make_spec(filename, version, target, options, requires=None): print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_disk.png' % r, file=f) print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_combiner.png' % r, file=f) for l in ['de_DE', 'es_ES', 'fr_FR', 'it_IT', 'sv_SE', 'nl_NL', 'ru_RU', 'pl_PL', 'da_DK', - 'pt_PT', 'pt_BR', 'sk_SK', 'cs_CZ', 'uk_UA', 'zh_CN', 'tr_TR', 'sl_SI', 'hu_HU']: + 'pt_PT', 'pt_BR', 'sk_SK', 'cs_CZ', 'uk_UA', 'zh_CN', 'tr_TR', 'sl_SI', 'hu_HU', + 'ka_KA', 'fa_IR']: print('%%{_datadir}/locale/%s/LC_MESSAGES/dcpomatic2.mo' % l, file=f) print('%%{_datadir}/locale/%s/LC_MESSAGES/libdcpomatic2-wx.mo' % l, file=f) print('%%{_datadir}/locale/%s/LC_MESSAGES/libdcpomatic2.mo' % l, file=f) @@ -419,6 +507,7 @@ def make_spec(filename, version, target, options, requires=None): print('/bin/cp -r %s/src/libdcp/xsd %%{buildroot}/usr/share/libdcp' % target.directory, file=f) print('/bin/cp %s/src/libdcp/ratings %%{buildroot}/usr/share/libdcp' % target.directory, file=f) print('/bin/mv %s/bin/dcpverify %%{buildroot}/usr/bin/dcpomatic2_verify' % target.directory, file=f) + print('/bin/mv %s/bin/dcpkdm %%{buildroot}/usr/bin/dcpomatic2_kdm_inspect' % target.directory, file=f) print('', file=f) print('%post', file=f) print('/bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null || :', file=f) @@ -441,23 +530,23 @@ def dependencies(target, options): ffmpeg_options = {} if target.platform != 'linux' or target.distro != 'arch': - deps = [('ffmpeg-cdist', '4721b55de017702b0d1c8ce1163331378905c637', ffmpeg_options)] + deps = [('ffmpeg', '7276e269a93c2ae30e302c34708e8095ac5475e8', ffmpeg_options)] else: # Use distro-provided FFmpeg on Arch deps = [] - deps.append(('libdcp', 'v1.8.29')) - deps.append(('libsub', 'v1.6.32')) - deps.append(('leqm-nrt', '93ae9e6')) + deps.append(('libdcp', 'v1.8.99')) + deps.append(('libsub', 'v1.6.47')) + deps.append(('leqm-nrt', '30dcaea1373ac62fba050e02ce5b0c1085797a23')) deps.append(('rtaudio', 'f619b76')) # We get our OpenSSL libraries from the environment, but we # also need a patched openssl binary to make certificates. # This dependency is to get that binary, which is added into # the appropriate place later - deps.append(('openssl', '7f29dd5')) + deps.append(('openssl', '54298369cacfe0ae01c5aa42ace8a463fd2e7a2e')) if can_build_disk(target): - deps.append(('lwext4', 'ee865fa65f05e348cd4e0bce0552a2725ad5663a')) - deps.append(('ffcmp', 'da96af56f3ddf074f2044a0cd6e50c95184fd169')) + deps.append(('lwext4', 'ab082923a791b58478d1d9939d65a0583566ac1f')) + deps.append(('ffcmp', '53c853d2935de3f2b0d53777529e48c102afd237')) return deps @@ -467,11 +556,11 @@ def configure_options(target, options, for_package=False): opt = ' --warnings-are-errors' if for_package or not ( - (target.platform == 'linux' and target.distro == 'ubuntu' and target.version == '18.04') or + (target.platform == 'linux' and target.distro == 'ubuntu' and target.version in ['18.04', '22.04']) or (target.platform == 'osx') or (target.platform == 'windows') ): - # Currently we only build tests on Ubuntu 18.04, macOS and Windows + # Currently we only build tests on macOS, Windows, and some Ubuntu versions opt += ' --disable-tests' if target.debug: @@ -500,7 +589,7 @@ def configure_options(target, options, for_package=False): opt += ' --enable-disk' if target.platform == 'osx' and target.arch == 'arm64': - opt += ' --target-macos-arm64 --wx-config=%s/wx-config' % target.bin + opt += ' --wx-config=%s/wx-config' % target.bin return opt @@ -667,6 +756,7 @@ def package_debian(target, cpu, version, options): target.set('CDIST_CONFIGURE', '"' + configure_options(target, options, for_package=True) + '"') target.set('CDIST_PACKAGE', f'dcpomatic{suffix}') + target.set('CDIST_WX_VERSION', "3.2" if target.version in ("23.04", "23.10", "24.04") else "3.1") if not target.debug: target.set('CDIST_DEBUG_PACKAGE_FLAG', '--no-ddebs') @@ -711,13 +801,17 @@ def package_rpm(target, cpu, version, options): return rpms -def make_appimage(target, nice_name, internal_name, version): +def make_appimage(target, nice_name, internal_name, version, extra_binaries=None): nice_filename = nice_name.replace(' ', '_') appdir = f'build/{nice_filename}.AppDir' os.makedirs(f'{appdir}/usr/bin') target.command(f'cp {target.directory}/bin/{internal_name} {appdir}/usr/bin') target.command(f'cp {target.directory}/src/openssl/apps/openssl {appdir}/usr/bin/dcpomatic2_openssl') target.command(f'cp {target.directory}/bin/dcpverify {appdir}/usr/bin/dcpomatic2_verify') + target.command(f'cp {target.directory}/bin/dcpkdm {appdir}/usr/bin/dcpomatic2_kdm_inspect') + if extra_binaries: + for bin in extra_binaries: + target.command(f'cp {target.directory}/bin/{bin} {appdir}/usr/bin') target.command(f'mkdir -p {appdir}/usr/share/libdcp') target.command(f'cp -r {target.directory}/share/dcpomatic2 {appdir}/usr/share/') target.command(f'cp -r {target.directory}/share/libdcp/xsd {appdir}/usr/share/libdcp/') @@ -726,8 +820,8 @@ def make_appimage(target, nice_name, internal_name, version): lib = 'usr/lib/x86_64-linux-gnu' target.command(f'mkdir -p build/{nice_filename}.AppDir/{lib}/gdk-pixbuf-2.0/2.10.0') target.command(f'cp -a /{lib}/gdk-pixbuf-2.0 build/{nice_filename}.AppDir/usr/lib/x86_64-linux-gnu/') - target.command('apt update') - for package in ['libc6', 'libglib2.0-0', 'gnome-settings-daemon-schemas', 'librsvg2-common', 'libgdk-pixbuf2.0-0', 'libpango-1.0-0', 'libpangoft2-1.0-0', 'libpangocairo-1.0-0']: + target.command('sudo apt update') + for package in ['libc6', 'libglib2.0-0', 'gnome-settings-daemon-schemas', 'librsvg2-common', 'libgdk-pixbuf2.0-0', 'libpango-1.0-0', 'libpangoft2-1.0-0', 'libpangocairo-1.0-0', 'libthai0']: target.command(f'apt download {package}') target.command(f'dpkg-deb -x {package}*.deb {appdir}') target.command(f'glib-compile-schemas {appdir}/usr/share/glib-2.0/schemas') @@ -767,7 +861,7 @@ def package(target, version, options): elif target.platform == 'linux': if target.detail == 'appimage': out = [] - out.append(make_appimage(target, 'DCP-o-matic', 'dcpomatic2', version)) + out.append(make_appimage(target, 'DCP-o-matic', 'dcpomatic2', version, ('dcpomatic2_map', 'dcpomatic2_cli'))) out.append(make_appimage(target, 'DCP-o-matic Player', 'dcpomatic2_player', version)) out.append(make_appimage(target, 'DCP-o-matic Playlist Editor', 'dcpomatic2_playlist', version)) out.append(make_appimage(target, 'DCP-o-matic KDM Creator', 'dcpomatic2_kdm', version)) @@ -792,28 +886,7 @@ def package(target, version, options): if 'part' in options: cmd += ' -b ' + options['part'] target.command(cmd) - packages = [] - for x in glob.glob('build/platform/osx/DCP-o-matic*.dmg'): - a = os.path.abspath(x) - if x.find("Player") != -1: - packages.append((a, "com.dcpomatic.player")) - elif x.find("Playlist Editor") != -1: - packages.append((a, "com.dcpomatic.playlist")) - elif x.find("KDM Creator") != -1: - packages.append((a, "com.dcpomatic.kdm")) - elif x.find("Batch Converter") != -1: - packages.append((a, "com.dcpomatic.batch")) - elif x.find("Encode Server") != -1: - packages.append((a, "com.dcpomatic.server")) - elif x.find("Disk Writer") != -1: - packages.append((a, "com.dcpomatic.disk")) - elif x.find("Combiner") != -1: - packages.append((a, "com.dcpomatic.combiner")) - elif x.find("Editor") != -1: - packages.append((a, "com.dcpomatic.editor")) - else: - packages.append((a, "com.dcpomatic")) - return packages + return glob.glob('build/platform/osx/DCP-o-matic*.dmg') elif target.platform == 'docker': shutil.copyfile(target.deb, 'build/platform/docker') f = open('build/platform/docker/Dockerfile', 'w') @@ -850,7 +923,7 @@ def test(target, options, test): if target.platform == 'windows': cmd = 'run\\tests ' else: - cmd = 'run/tests --log_level=test_suite ' + cmd = 'run/tests --check --log_level=test_suite ' if target.debug: cmd += '--backtrace ' if test is not None: diff --git a/debian/rules b/debian/rules index 1d1090d75..fead4a0b0 100755 --- a/debian/rules +++ b/debian/rules @@ -22,23 +22,24 @@ override_dh_auto_build: override_dh_auto_install: ./waf install --destdir=debian/$(CDIST_PACKAGE) mkdir -p debian/$(CDIST_PACKAGE)/usr/share/locale/de/LC_MESSAGES/ - cp -a /usr/share/locale/de/LC_MESSAGES/wxstd-3.1.mo debian/$(CDIST_PACKAGE)/usr/share/locale/de/LC_MESSAGES/dcpomatic2-wxstd.mo + cp -a /usr/share/locale/de/LC_MESSAGES/wxstd-$(CDIST_WX_VERSION).mo debian/$(CDIST_PACKAGE)/usr/share/locale/de/LC_MESSAGES/dcpomatic2-wxstd.mo mkdir -p debian/$(CDIST_PACKAGE)/usr/share/locale/es/LC_MESSAGES/ - cp -a /usr/share/locale/es/LC_MESSAGES/wxstd-3.1.mo debian/$(CDIST_PACKAGE)/usr/share/locale/es/LC_MESSAGES/dcpomatic2-wxstd.mo + cp -a /usr/share/locale/es/LC_MESSAGES/wxstd-$(CDIST_WX_VERSION).mo debian/$(CDIST_PACKAGE)/usr/share/locale/es/LC_MESSAGES/dcpomatic2-wxstd.mo mkdir -p debian/$(CDIST_PACKAGE)/usr/share/locale/fr/LC_MESSAGES/ - cp -a /usr/share/locale/fr/LC_MESSAGES/wxstd-3.1.mo debian/$(CDIST_PACKAGE)/usr/share/locale/fr/LC_MESSAGES/dcpomatic2-wxstd.mo + cp -a /usr/share/locale/fr/LC_MESSAGES/wxstd-$(CDIST_WX_VERSION).mo debian/$(CDIST_PACKAGE)/usr/share/locale/fr/LC_MESSAGES/dcpomatic2-wxstd.mo mkdir -p debian/$(CDIST_PACKAGE)/usr/share/locale/it/LC_MESSAGES/ - cp -a /usr/share/locale/it/LC_MESSAGES/wxstd-3.1.mo debian/$(CDIST_PACKAGE)/usr/share/locale/it/LC_MESSAGES/dcpomatic2-wxstd.mo + cp -a /usr/share/locale/it/LC_MESSAGES/wxstd-$(CDIST_WX_VERSION).mo debian/$(CDIST_PACKAGE)/usr/share/locale/it/LC_MESSAGES/dcpomatic2-wxstd.mo mkdir -p debian/$(CDIST_PACKAGE)/usr/share/locale/sv/LC_MESSAGES/ - cp -a /usr/share/locale/sv/LC_MESSAGES/wxstd-3.1.mo debian/$(CDIST_PACKAGE)/usr/share/locale/sv/LC_MESSAGES/dcpomatic2-wxstd.mo + cp -a /usr/share/locale/sv/LC_MESSAGES/wxstd-$(CDIST_WX_VERSION).mo debian/$(CDIST_PACKAGE)/usr/share/locale/sv/LC_MESSAGES/dcpomatic2-wxstd.mo mkdir -p debian/$(CDIST_PACKAGE)/usr/share/locale/nl/LC_MESSAGES/ - cp -a /usr/share/locale/nl/LC_MESSAGES/wxstd-3.1.mo debian/$(CDIST_PACKAGE)/usr/share/locale/nl/LC_MESSAGES/dcpomatic2-wxstd.mo + cp -a /usr/share/locale/nl/LC_MESSAGES/wxstd-$(CDIST_WX_VERSION).mo debian/$(CDIST_PACKAGE)/usr/share/locale/nl/LC_MESSAGES/dcpomatic2-wxstd.mo mkdir -p debian/$(CDIST_PACKAGE)/usr/share/locale/pl/LC_MESSAGES/ - cp -a /usr/share/locale/pl/LC_MESSAGES/wxstd-3.1.mo debian/$(CDIST_PACKAGE)/usr/share/locale/pl/LC_MESSAGES/dcpomatic2-wxstd.mo + cp -a /usr/share/locale/pl/LC_MESSAGES/wxstd-$(CDIST_WX_VERSION).mo debian/$(CDIST_PACKAGE)/usr/share/locale/pl/LC_MESSAGES/dcpomatic2-wxstd.mo mkdir -p debian/$(CDIST_PACKAGE)/usr/share/locale/ru/LC_MESSAGES/ - cp -a /usr/share/locale/ru/LC_MESSAGES/wxstd-3.1.mo debian/$(CDIST_PACKAGE)/usr/share/locale/ru/LC_MESSAGES/dcpomatic2-wxstd.mo + cp -a /usr/share/locale/ru/LC_MESSAGES/wxstd-$(CDIST_WX_VERSION).mo debian/$(CDIST_PACKAGE)/usr/share/locale/ru/LC_MESSAGES/dcpomatic2-wxstd.mo cp -a $(CDIST_DIRECTORY)/src/openssl/apps/openssl debian/$(CDIST_PACKAGE)/usr/bin/dcpomatic2_openssl cp -a $(CDIST_DIRECTORY)/src/libdcp/build/tools/dcpverify debian/$(CDIST_PACKAGE)/usr/bin/dcpomatic2_verify + cp -a $(CDIST_DIRECTORY)/src/libdcp/build/tools/dcpkdm debian/$(CDIST_PACKAGE)/usr/bin/dcpomatic2_kdm_inspect cp -ar $(CDIST_DIRECTORY)/share/libdcp debian/$(CDIST_PACKAGE)/usr/share .PHONY: override_dh_strip diff --git a/doc/design/fonts b/doc/design/fonts new file mode 100644 index 000000000..c431d52e9 --- /dev/null +++ b/doc/design/fonts @@ -0,0 +1,59 @@ +How a font makes its way through the encoding process + + +Import a DCP containing some subtitles with fonts. + +* Examiner + +Builds _fonts containing (font-ID, font-TTF-data) +Add to allocator (asset-ID, font-ID) + +font-ID will be unique in its own asset, but not more widely. + +Use the allocator to set the font ID to N_font-ID where N is an integer unique for all fonts in the DCP. + +If there's no fonts in the DCP, add one with an empty ID - we want something in the content for users +to edit. + + +Now the text content contains fonts with IDs unique within the content. + + +* DCP Decoder + +Some subtitle arrives with an "original" font ID. +Use an allocator (built the same way as in the examiner) to replace the ID with a new one N_font-ID. + + +Q: Why do we need the allocator? +A: Because we need an ID to refer to each font in the content (to be stored in metadata.xml) + and we need to turn this ID back into an actual Font C++ object so it must be unique within + the content. Also we allow these fonts to have their settings altered so they must have unique + IDs for that. + + +* Text Decoder + +Calls content->get_font() to get the Font C++ object by the (newly-allocated) ID. This works because +the allocated font-ID is unique within the content. + +The Font C++ object pointer is written to the subtitle. + + +* Player + +Passes subtitles through. + + +* Writer + +Gets all fonts, puts them in the font ID map using the font's original ID. This is OK because we +don't need uniqueness in the DCP any more. + + +* Reel Writer + +Gets subtitles, uses font ID map to find the ID from the Font C++ object pointer. Puts this ID in +the font and writes it to the asset. Ensures the required LoadFont is added. + + diff --git a/doc/manual/Makefile b/doc/manual/Makefile index 7413f2422..03f3db87e 100644 --- a/doc/manual/Makefile +++ b/doc/manual/Makefile @@ -19,7 +19,8 @@ SCREENSHOTS := file-new.pdf new-film.pdf video-select-content-file.pdf \ making-dcp.pdf filters.pdf video-tab.pdf audio-tab.pdf \ audio-plot.pdf audio-map-eg1.pdf audio-map-eg2.pdf audio-map-eg3.pdf kdm.pdf \ kdm-creator.pdf export.pdf advanced-content.pdf disk-writer-notice.pdf disk-writer.pdf \ - markers.pdf prefs-notifications.pdf prefs-cover-sheet.pdf + markers.pdf prefs-notifications.pdf prefs-cover-sheet.pdf add-screen.pdf \ + advanced-player.pdf playlist-editor.pdf playlist-editor-prefs.pdf XML := dcpomatic.xml @@ -87,7 +88,7 @@ screenshots/audio-map-eg3.pdf: raw-screenshots/audio-map-eg3.pdf python3 pdf_crop_by.py $< $@ 10 75 1350 630 screenshots/dcp-tab.pdf: raw-screenshots/dcp-tab.pdf - python3 pdf_crop_by.py $< $@ 0 250 1480 30 + python3 pdf_crop_by.py $< $@ 0 350 1240 30 # For HTML: convert diagrams from SVG to PNG diff --git a/doc/manual/cli.py b/doc/manual/cli.py index 3a2493b4d..88e75b0da 100644 --- a/doc/manual/cli.py +++ b/doc/manual/cli.py @@ -7,7 +7,7 @@ print('') print('') os.chdir('../..') -for l in subprocess.run(['run/%s' % sys.argv[1], '--help'], stderr=subprocess.PIPE).stderr.splitlines(): +for l in subprocess.run(['run/%s' % sys.argv[1], '--help'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.splitlines(): l = l.strip().decode('UTF-8') if not l.startswith('-'): continue diff --git a/doc/manual/dcpomatic.xml b/doc/manual/dcpomatic.xml index 6b6ce83c2..ee687ad6e 100644 --- a/doc/manual/dcpomatic.xml +++ b/doc/manual/dcpomatic.xml @@ -320,7 +320,20 @@ making the DCP. Choose Make DCP from the -Jobs menu. DCP-o-matic will encode your DCP. +Jobs menu. Before encoding your DCP, DCP-o-matic +will run a series of checks on your film to look for various conditions +that might cause problems when playing back the DCP. If any potential +problems are found, DCP-o-matic will show you a list of hints. +Each hint describes the condition that was found and gives +advice on how to resolve it. If hints are found and reported, you can +either Make DCP anyway (without adjusting any +settings), or Go back in order to make +adjustments before encoding the DCP. + + + +If no hints were found (or you pressed Make DCP +after hints were displayed), DCP-o-matic will encode your DCP. This may take some time (many hours in some cases). While the job is in progress, DCP-o-matic will update you on how it is getting on with the progress bar in the bottom of its window, as shown in Copy as name and the ISDCF name will be copied into the Name box. You can then edit it as you wish. The DCP name should not matter (in that it should not affect how the DCP ingests or plays) but -projectionists will appreciate it if you use the standard naming -scheme as it makes it easier to identify details of the content. +projectionists will appreciate it if you use the +standard naming scheme +as it makes it easier to identify details of the content. + + + +If there is spoken language in your project's audio you can tick the +Audio Language checkbox and then specify the language +of that audio. This information will be used for the ISDCF name, written into +the DCP cover sheet, and added as metadata to the audio MXF files in the DCP. @@ -2375,7 +2396,6 @@ emailed to the appropriate cinema email addresses. Click
Creating KDMs using a DKDM -
It can be inconvenient to need a whole DCP-o-matic project just to @@ -2442,11 +2462,73 @@ a target certificate. You can get DCP-o-matic's target certificate by opening Preferences and clicking Export DCP decryption certificate... in the Keys tab. + + + + +
+Creating KDMs for a distributor + + +Sometimes you have an encrypted DCP and you want to allow somebody else +(for example, a distributor) to make KDMs for the DCP on your behalf. + + + +The normal way to do this is to send the distributor a KDM which they +can use with their own KDM creation system. Such a KDM is often called +a DKDM (the ‘D’ stands for Distribution). +It is the same as a normal KDM except that it is made to work with another +computer, rather than with a projection system. + + + +To make a DKDM for a distributor you will first need to ask them to send you +a decryption certificate. This should be a small file, usually with the extension +.pem. + + + +Once you have the certificate, you will need to add a ‘fake’ cinema +and screen to the list in DCP-o-matic. This is because making a KDM for another +computer uses the same process internally as making one for a projection system, +it's just that DCP-o-matic does not have a nice way to present that. + + +In either the KDM window in the main DCP-o-matic, or the KDM creator, first add +a new cinema by clicking Add Cinema..., giving it a name +(perhaps the name of the distributor). + + + +Then select this new cinema and click Add Screen... to open +the screen dialog box, as shown in . + + +
+ Adding a screen + + + + + +
+ + +Here you can give any name (perhaps just ‘DKDM’). Then click Get from file... +and choose the certificate file that the distributor gave you. Finally, click OK. + + + +Now you can create a KDM for this screen, and send it to the distributor. Using that KDM the distributor +can then make KDMs for your DCP for anybody (and also, of course, decrypt the DCP if they wanted to).
+ +
Encryption keys @@ -3740,6 +3822,96 @@ The full details of OV and VF files are discussed in + +
+Advanced playback mode + + +By default, the DCP-o-matic player is set up to load and play back single DCPs, mostly for checking purposes. +There is also a second, more experimental mode, which is more suited to using DCP-o-matic for ‘presentation’ +applications, such as playing back DCPs via a projector. In this mode you can set up basic playlists, in a similar +way to how most commercial playback servers work. + + + +Using DCP-o-matic for theatrical exhibition is not widely tested, and I would not advise depending +on it without plenty of testing in your particular environment. + + + +The ‘advanced’ playback mode uses two windows (instead of the usual one): + + Full-screen playback window. + Control window. + +The idea is that these windows are spread over two monitors (or one monitor and one projector). + + + +To enable ‘advanced’ mode, load the player and go to the preferences and open the General +tab of preferences, then choose full screen with separate advanced controls from the +Start player as drop-down list. Then close and re-start the player. + +
+ + +On loading the player in ‘advanced’ mode you will see a control window like the one in . + + +
+ Player in advanced mode + + + + + +
+ + +The important parts are the list of playlists, in the top left, and the current playlist, on the right. Double-click a playlist to load it, +and then press the Play button to start playback. + + + +Creating playlists must be done using the separate DCP-o-matic Playlist Editor. As with the other tools, this is included +with the normal installer on Windows and most Linux distributions, but must be downloaded separately on macOS or if you are using AppImage. + + + +The first important step after loading the playlist editor for the first time is to set the location of your playlists, the content (i.e. DCPs) +that they will use, and a folder containing any KDMs that the content needs. This can be done in the Playlist Editor preferences window, +as shown in . + + +
+ Playlist editor preferences + + + + + +
+ + +Once you have done this you can start creating playlists in the main editor window. Each playlist you create will be shown in the player and +so be available for playback. The playlist editor is shown in . + + +
+ Playlist editor + + + + + +
+ + +The top half of the window shows the current list of playlists. Create a new one by clicking the New button. You can +then edit its name and add remove, or reorder content in the bottom half of the window. Playlists are saved automatically each time they +are changed. + + diff --git a/doc/manual/dcpomatic_cli.xml b/doc/manual/dcpomatic_cli.xml index 531ca813b..debf6259f 100644 --- a/doc/manual/dcpomatic_cli.xml +++ b/doc/manual/dcpomatic_cli.xml @@ -13,5 +13,9 @@ -d, --dcp-path — echo DCP's path to stdout on successful completion (implies -n) -c, --config <dir> — directory containing config.xml and cinemas.xml --dump — just dump a summary of the film's settings; don't encode +--no-check — don't check project's content files for changes before making the DCP +--export-format <format> — export project to a file, rather than making a DCP: specify mov or mp4 +--export-filename <filename> — filename to export to with --export-format +--hints — analyze film for hints before encoding and abort if any are found \ No newline at end of file diff --git a/doc/manual/dcpomatic_create.xml b/doc/manual/dcpomatic_create.xml index 215a131d9..0aa8b4f48 100644 --- a/doc/manual/dcpomatic_create.xml +++ b/doc/manual/dcpomatic_create.xml @@ -8,17 +8,20 @@ -c, --dcp-content-type <type> — FTR, SHR, TLR, TST, XSN, RTG, TSR, POL, PSA or ADV -f, --dcp-frame-rate <rate> — set DCP video frame rate (otherwise guessed from content) --container-ratio <ratio> — 119, 133, 137, 138, 166, 178, 185 or 239 ---content-ratio <ratio> — 119, 133, 137, 138, 166, 178, 185 or 239 -s, --still-length <n> — number of seconds that still content should last --standard <standard> — SMPTE or interop (default SMPTE) --no-use-isdcf-name — do not use an ISDCF name; use the specified name unmodified ---no-sign — do not sign the DCP --config <dir> — directory containing config.xml and cinemas.xml ---fourk — make a 4K DCP rather than a 2K one +--twok — make a 2K DCP instead of choosing a resolution based on the content +--fourk — make a 4K DCP instead of choosing a resolution based on the content -o, --output <dir> — output directory --threed — make a 3D DCP --j2k-bandwidth <Mbit/s> — J2K bandwidth in Mbit/s --left-eye — next piece of content is for the left eye --right-eye — next piece of content is for the right eye +--channel <channel> — next piece of content should be mapped to audio channel L, R, C, Lfe, Ls or Rs +--gain — next piece of content should have the given audio gain (in dB) +--cpl <id> — CPL ID to use from the next piece of content (which is a DCP) +--kdm <file> — KDM for next piece of content \ No newline at end of file diff --git a/doc/manual/dcpomatic_kdm_cli.xml b/doc/manual/dcpomatic_kdm_cli.xml index 283763a34..fc5635959 100644 --- a/doc/manual/dcpomatic_kdm_cli.xml +++ b/doc/manual/dcpomatic_kdm_cli.xml @@ -8,12 +8,13 @@ -t, --valid-to — valid to time (in local time zone of the cinema) (e.g. “2014-09-28 01:41:51”) -d, --valid-duration — valid duration (e.g. “1 day”, “4 hours”, “2 weeks”) -F, --formulation — modified-transitional-1, multiple-modified-transitional-1, dci-any or dci-specific [default modified-transitional-1] --a, --disable-forensic-marking-picture — disable forensic marking of pictures essences +-p, --disable-forensic-marking-picture — disable forensic marking of pictures essences -a, --disable-forensic-marking-audio — disable forensic marking of audio essences (optionally above a given channel, e.g 12) +-e, --email — email KDMs to cinemas -z, --zip — ZIP each cinema's KDMs into its own file -v, --verbose — be verbose --c, --cinema — specify a cinema, either by name or email address --S, --screen — screen description +-c, --cinema — cinema name (when using -C) or name/email (to filter cinemas) +-S, --screen — screen name (when using -C) or screen name (to filter screens when using -c) -C, --certificate — file containing projector certificate -T, --trusted-device — file containing a trusted device's certificate --list-cinemas — list known cinemas from the DCP-o-matic settings diff --git a/doc/manual/pptex.py b/doc/manual/pptex.py index 85653e5a5..fade5943c 100755 --- a/doc/manual/pptex.py +++ b/doc/manual/pptex.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # Farcical script to remove newlines after # \begin{sidebar} in dblatex' .tex output; @@ -14,15 +14,15 @@ import tempfile import shutil f = open(sys.argv[1]) -t = tempfile.NamedTemporaryFile(delete = False) +t = tempfile.NamedTemporaryFile(delete=False) remove_next = False -while 1: +while True: l = f.readline() if l == '': break if not remove_next: - print>>t,l, + t.write(l.encode('UTF-8')) remove_next = False diff --git a/doc/manual/raw-screenshots/add-screen.pdf b/doc/manual/raw-screenshots/add-screen.pdf new file mode 100644 index 000000000..6c0754106 Binary files /dev/null and b/doc/manual/raw-screenshots/add-screen.pdf differ diff --git a/doc/manual/raw-screenshots/advanced-player.pdf b/doc/manual/raw-screenshots/advanced-player.pdf new file mode 100644 index 000000000..b7c7e8c26 Binary files /dev/null and b/doc/manual/raw-screenshots/advanced-player.pdf differ diff --git a/doc/manual/raw-screenshots/dcp-tab.pdf b/doc/manual/raw-screenshots/dcp-tab.pdf index ff835ef2f..27f333918 100644 Binary files a/doc/manual/raw-screenshots/dcp-tab.pdf and b/doc/manual/raw-screenshots/dcp-tab.pdf differ diff --git a/doc/manual/raw-screenshots/playlist-editor-prefs.pdf b/doc/manual/raw-screenshots/playlist-editor-prefs.pdf new file mode 100644 index 000000000..22bf8a60d Binary files /dev/null and b/doc/manual/raw-screenshots/playlist-editor-prefs.pdf differ diff --git a/doc/manual/raw-screenshots/playlist-editor.pdf b/doc/manual/raw-screenshots/playlist-editor.pdf new file mode 100644 index 000000000..113c0f965 Binary files /dev/null and b/doc/manual/raw-screenshots/playlist-editor.pdf differ diff --git a/graphics/add_black.png b/graphics/add_black.png new file mode 100644 index 000000000..60f5dae10 Binary files /dev/null and b/graphics/add_black.png differ diff --git a/graphics/add_white.png b/graphics/add_white.png new file mode 100644 index 000000000..64d8ca6d9 Binary files /dev/null and b/graphics/add_white.png differ diff --git a/graphics/link.png b/graphics/link.png deleted file mode 100644 index 3249d3710..000000000 Binary files a/graphics/link.png and /dev/null differ diff --git a/graphics/link_black.png b/graphics/link_black.png new file mode 100644 index 000000000..3249d3710 Binary files /dev/null and b/graphics/link_black.png differ diff --git a/graphics/link_white.png b/graphics/link_white.png new file mode 100644 index 000000000..274e47a38 Binary files /dev/null and b/graphics/link_white.png differ diff --git a/graphics/osx/preferences/advanced.png b/graphics/osx/preferences/advanced.png deleted file mode 100644 index 50c086ed6..000000000 Binary files a/graphics/osx/preferences/advanced.png and /dev/null differ diff --git a/graphics/osx/preferences/advanced@2x.png b/graphics/osx/preferences/advanced@2x.png deleted file mode 100644 index e2cb8e361..000000000 Binary files a/graphics/osx/preferences/advanced@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/advanced@2x_black.png b/graphics/osx/preferences/advanced@2x_black.png new file mode 100644 index 000000000..cca13f60d Binary files /dev/null and b/graphics/osx/preferences/advanced@2x_black.png differ diff --git a/graphics/osx/preferences/advanced@2x_white.png b/graphics/osx/preferences/advanced@2x_white.png new file mode 100644 index 000000000..97ee570fd Binary files /dev/null and b/graphics/osx/preferences/advanced@2x_white.png differ diff --git a/graphics/osx/preferences/advanced_black.png b/graphics/osx/preferences/advanced_black.png new file mode 100644 index 000000000..c3e7a1189 Binary files /dev/null and b/graphics/osx/preferences/advanced_black.png differ diff --git a/graphics/osx/preferences/advanced_white.png b/graphics/osx/preferences/advanced_white.png new file mode 100644 index 000000000..467e050a6 Binary files /dev/null and b/graphics/osx/preferences/advanced_white.png differ diff --git a/graphics/osx/preferences/cover_sheet.png b/graphics/osx/preferences/cover_sheet.png deleted file mode 100644 index 18eaf0a40..000000000 Binary files a/graphics/osx/preferences/cover_sheet.png and /dev/null differ diff --git a/graphics/osx/preferences/cover_sheet@2x.png b/graphics/osx/preferences/cover_sheet@2x.png deleted file mode 100644 index d5ee6ebdb..000000000 Binary files a/graphics/osx/preferences/cover_sheet@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/cover_sheet@2x_black.png b/graphics/osx/preferences/cover_sheet@2x_black.png new file mode 100644 index 000000000..54d722404 Binary files /dev/null and b/graphics/osx/preferences/cover_sheet@2x_black.png differ diff --git a/graphics/osx/preferences/cover_sheet@2x_white.png b/graphics/osx/preferences/cover_sheet@2x_white.png new file mode 100644 index 000000000..ca58a3a40 Binary files /dev/null and b/graphics/osx/preferences/cover_sheet@2x_white.png differ diff --git a/graphics/osx/preferences/cover_sheet_black.png b/graphics/osx/preferences/cover_sheet_black.png new file mode 100644 index 000000000..2a0bd1b52 Binary files /dev/null and b/graphics/osx/preferences/cover_sheet_black.png differ diff --git a/graphics/osx/preferences/cover_sheet_white.png b/graphics/osx/preferences/cover_sheet_white.png new file mode 100644 index 000000000..61bf526a3 Binary files /dev/null and b/graphics/osx/preferences/cover_sheet_white.png differ diff --git a/graphics/osx/preferences/defaults.png b/graphics/osx/preferences/defaults.png deleted file mode 100644 index a2c2217d7..000000000 Binary files a/graphics/osx/preferences/defaults.png and /dev/null differ diff --git a/graphics/osx/preferences/defaults@2x.png b/graphics/osx/preferences/defaults@2x.png deleted file mode 100644 index 53ab3968a..000000000 Binary files a/graphics/osx/preferences/defaults@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/defaults@2x_black.png b/graphics/osx/preferences/defaults@2x_black.png new file mode 100644 index 000000000..cdfbcf578 Binary files /dev/null and b/graphics/osx/preferences/defaults@2x_black.png differ diff --git a/graphics/osx/preferences/defaults@2x_white.png b/graphics/osx/preferences/defaults@2x_white.png new file mode 100644 index 000000000..8b8840aa7 Binary files /dev/null and b/graphics/osx/preferences/defaults@2x_white.png differ diff --git a/graphics/osx/preferences/defaults_black.png b/graphics/osx/preferences/defaults_black.png new file mode 100644 index 000000000..1ae1ade3a Binary files /dev/null and b/graphics/osx/preferences/defaults_black.png differ diff --git a/graphics/osx/preferences/defaults_white.png b/graphics/osx/preferences/defaults_white.png new file mode 100644 index 000000000..529465ad8 Binary files /dev/null and b/graphics/osx/preferences/defaults_white.png differ diff --git a/graphics/osx/preferences/email.png b/graphics/osx/preferences/email.png deleted file mode 100644 index 8d5136d82..000000000 Binary files a/graphics/osx/preferences/email.png and /dev/null differ diff --git a/graphics/osx/preferences/email@2x.png b/graphics/osx/preferences/email@2x.png deleted file mode 100644 index 0a814951d..000000000 Binary files a/graphics/osx/preferences/email@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/email@2x_black.png b/graphics/osx/preferences/email@2x_black.png new file mode 100644 index 000000000..c6fa8f377 Binary files /dev/null and b/graphics/osx/preferences/email@2x_black.png differ diff --git a/graphics/osx/preferences/email@2x_white.png b/graphics/osx/preferences/email@2x_white.png new file mode 100644 index 000000000..9710aec38 Binary files /dev/null and b/graphics/osx/preferences/email@2x_white.png differ diff --git a/graphics/osx/preferences/email_black.png b/graphics/osx/preferences/email_black.png new file mode 100644 index 000000000..09f040b9e Binary files /dev/null and b/graphics/osx/preferences/email_black.png differ diff --git a/graphics/osx/preferences/email_white.png b/graphics/osx/preferences/email_white.png new file mode 100644 index 000000000..88f46b85b Binary files /dev/null and b/graphics/osx/preferences/email_white.png differ diff --git a/graphics/osx/preferences/general.png b/graphics/osx/preferences/general.png deleted file mode 100644 index c75898c24..000000000 Binary files a/graphics/osx/preferences/general.png and /dev/null differ diff --git a/graphics/osx/preferences/general@2x.png b/graphics/osx/preferences/general@2x.png deleted file mode 100644 index af79038a1..000000000 Binary files a/graphics/osx/preferences/general@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/general@2x_black.png b/graphics/osx/preferences/general@2x_black.png new file mode 100644 index 000000000..8638c0e03 Binary files /dev/null and b/graphics/osx/preferences/general@2x_black.png differ diff --git a/graphics/osx/preferences/general@2x_white.png b/graphics/osx/preferences/general@2x_white.png new file mode 100644 index 000000000..4d899f81f Binary files /dev/null and b/graphics/osx/preferences/general@2x_white.png differ diff --git a/graphics/osx/preferences/general_black.png b/graphics/osx/preferences/general_black.png new file mode 100644 index 000000000..12756381e Binary files /dev/null and b/graphics/osx/preferences/general_black.png differ diff --git a/graphics/osx/preferences/general_white.png b/graphics/osx/preferences/general_white.png new file mode 100644 index 000000000..4ceba8298 Binary files /dev/null and b/graphics/osx/preferences/general_white.png differ diff --git a/graphics/osx/preferences/identifiers.png b/graphics/osx/preferences/identifiers.png deleted file mode 100644 index c1c93924d..000000000 Binary files a/graphics/osx/preferences/identifiers.png and /dev/null differ diff --git a/graphics/osx/preferences/identifiers@2x.png b/graphics/osx/preferences/identifiers@2x.png deleted file mode 100644 index 33b5499a7..000000000 Binary files a/graphics/osx/preferences/identifiers@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/identifiers@2x_black.png b/graphics/osx/preferences/identifiers@2x_black.png new file mode 100644 index 000000000..9d31ca744 Binary files /dev/null and b/graphics/osx/preferences/identifiers@2x_black.png differ diff --git a/graphics/osx/preferences/identifiers@2x_white.png b/graphics/osx/preferences/identifiers@2x_white.png new file mode 100644 index 000000000..9e5537d3a Binary files /dev/null and b/graphics/osx/preferences/identifiers@2x_white.png differ diff --git a/graphics/osx/preferences/identifiers_black.png b/graphics/osx/preferences/identifiers_black.png new file mode 100644 index 000000000..0407d264a Binary files /dev/null and b/graphics/osx/preferences/identifiers_black.png differ diff --git a/graphics/osx/preferences/identifiers_white.png b/graphics/osx/preferences/identifiers_white.png new file mode 100644 index 000000000..f5dc45c9c Binary files /dev/null and b/graphics/osx/preferences/identifiers_white.png differ diff --git a/graphics/osx/preferences/kdm_email.png b/graphics/osx/preferences/kdm_email.png deleted file mode 100644 index adbf81439..000000000 Binary files a/graphics/osx/preferences/kdm_email.png and /dev/null differ diff --git a/graphics/osx/preferences/kdm_email@2x.png b/graphics/osx/preferences/kdm_email@2x.png deleted file mode 100644 index 32dbaa7e3..000000000 Binary files a/graphics/osx/preferences/kdm_email@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/kdm_email@2x_black.png b/graphics/osx/preferences/kdm_email@2x_black.png new file mode 100644 index 000000000..027f4d418 Binary files /dev/null and b/graphics/osx/preferences/kdm_email@2x_black.png differ diff --git a/graphics/osx/preferences/kdm_email@2x_white.png b/graphics/osx/preferences/kdm_email@2x_white.png new file mode 100644 index 000000000..0cea20911 Binary files /dev/null and b/graphics/osx/preferences/kdm_email@2x_white.png differ diff --git a/graphics/osx/preferences/kdm_email_black.png b/graphics/osx/preferences/kdm_email_black.png new file mode 100644 index 000000000..aab0fd5f8 Binary files /dev/null and b/graphics/osx/preferences/kdm_email_black.png differ diff --git a/graphics/osx/preferences/kdm_email_white.png b/graphics/osx/preferences/kdm_email_white.png new file mode 100644 index 000000000..e7d82ce3a Binary files /dev/null and b/graphics/osx/preferences/kdm_email_white.png differ diff --git a/graphics/osx/preferences/keys.png b/graphics/osx/preferences/keys.png deleted file mode 100644 index a512081a1..000000000 Binary files a/graphics/osx/preferences/keys.png and /dev/null differ diff --git a/graphics/osx/preferences/keys@2x.png b/graphics/osx/preferences/keys@2x.png deleted file mode 100644 index cdca69915..000000000 Binary files a/graphics/osx/preferences/keys@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/keys@2x_black.png b/graphics/osx/preferences/keys@2x_black.png new file mode 100644 index 000000000..7b3e30a21 Binary files /dev/null and b/graphics/osx/preferences/keys@2x_black.png differ diff --git a/graphics/osx/preferences/keys@2x_white.png b/graphics/osx/preferences/keys@2x_white.png new file mode 100644 index 000000000..13611e18f Binary files /dev/null and b/graphics/osx/preferences/keys@2x_white.png differ diff --git a/graphics/osx/preferences/keys_black.png b/graphics/osx/preferences/keys_black.png new file mode 100644 index 000000000..bd5e94b55 Binary files /dev/null and b/graphics/osx/preferences/keys_black.png differ diff --git a/graphics/osx/preferences/keys_white.png b/graphics/osx/preferences/keys_white.png new file mode 100644 index 000000000..2dd470c55 Binary files /dev/null and b/graphics/osx/preferences/keys_white.png differ diff --git a/graphics/osx/preferences/locations.png b/graphics/osx/preferences/locations.png deleted file mode 100644 index 215ed3f61..000000000 Binary files a/graphics/osx/preferences/locations.png and /dev/null differ diff --git a/graphics/osx/preferences/locations@2x.png b/graphics/osx/preferences/locations@2x.png deleted file mode 100644 index b06decb37..000000000 Binary files a/graphics/osx/preferences/locations@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/locations@2x_black.png b/graphics/osx/preferences/locations@2x_black.png new file mode 100644 index 000000000..97a93cc2c Binary files /dev/null and b/graphics/osx/preferences/locations@2x_black.png differ diff --git a/graphics/osx/preferences/locations@2x_white.png b/graphics/osx/preferences/locations@2x_white.png new file mode 100644 index 000000000..073a88811 Binary files /dev/null and b/graphics/osx/preferences/locations@2x_white.png differ diff --git a/graphics/osx/preferences/locations_black.png b/graphics/osx/preferences/locations_black.png new file mode 100644 index 000000000..0574e29a7 Binary files /dev/null and b/graphics/osx/preferences/locations_black.png differ diff --git a/graphics/osx/preferences/locations_white.png b/graphics/osx/preferences/locations_white.png new file mode 100644 index 000000000..de89507ce Binary files /dev/null and b/graphics/osx/preferences/locations_white.png differ diff --git a/graphics/osx/preferences/non_standard@2x_black.png b/graphics/osx/preferences/non_standard@2x_black.png new file mode 100644 index 000000000..4b2eb469e Binary files /dev/null and b/graphics/osx/preferences/non_standard@2x_black.png differ diff --git a/graphics/osx/preferences/non_standard@2x_white.png b/graphics/osx/preferences/non_standard@2x_white.png new file mode 100644 index 000000000..be532c1d0 Binary files /dev/null and b/graphics/osx/preferences/non_standard@2x_white.png differ diff --git a/graphics/osx/preferences/non_standard_black.png b/graphics/osx/preferences/non_standard_black.png new file mode 100644 index 000000000..6c9fd6a56 Binary files /dev/null and b/graphics/osx/preferences/non_standard_black.png differ diff --git a/graphics/osx/preferences/non_standard_white.png b/graphics/osx/preferences/non_standard_white.png new file mode 100644 index 000000000..6eec090da Binary files /dev/null and b/graphics/osx/preferences/non_standard_white.png differ diff --git a/graphics/osx/preferences/notifications.png b/graphics/osx/preferences/notifications.png deleted file mode 100644 index c6e8a6276..000000000 Binary files a/graphics/osx/preferences/notifications.png and /dev/null differ diff --git a/graphics/osx/preferences/notifications@2x.png b/graphics/osx/preferences/notifications@2x.png deleted file mode 100644 index 4349de64a..000000000 Binary files a/graphics/osx/preferences/notifications@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/notifications@2x_black.png b/graphics/osx/preferences/notifications@2x_black.png new file mode 100644 index 000000000..350e4f93e Binary files /dev/null and b/graphics/osx/preferences/notifications@2x_black.png differ diff --git a/graphics/osx/preferences/notifications@2x_white.png b/graphics/osx/preferences/notifications@2x_white.png new file mode 100644 index 000000000..d79e28d22 Binary files /dev/null and b/graphics/osx/preferences/notifications@2x_white.png differ diff --git a/graphics/osx/preferences/notifications_black.png b/graphics/osx/preferences/notifications_black.png new file mode 100644 index 000000000..14e35f334 Binary files /dev/null and b/graphics/osx/preferences/notifications_black.png differ diff --git a/graphics/osx/preferences/notifications_white.png b/graphics/osx/preferences/notifications_white.png new file mode 100644 index 000000000..e97f14ae6 Binary files /dev/null and b/graphics/osx/preferences/notifications_white.png differ diff --git a/graphics/osx/preferences/servers.png b/graphics/osx/preferences/servers.png deleted file mode 100644 index 86be5bf0e..000000000 Binary files a/graphics/osx/preferences/servers.png and /dev/null differ diff --git a/graphics/osx/preferences/servers@2x.png b/graphics/osx/preferences/servers@2x.png deleted file mode 100644 index 7e188018b..000000000 Binary files a/graphics/osx/preferences/servers@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/servers@2x_black.png b/graphics/osx/preferences/servers@2x_black.png new file mode 100644 index 000000000..c8810d764 Binary files /dev/null and b/graphics/osx/preferences/servers@2x_black.png differ diff --git a/graphics/osx/preferences/servers@2x_white.png b/graphics/osx/preferences/servers@2x_white.png new file mode 100644 index 000000000..09308d523 Binary files /dev/null and b/graphics/osx/preferences/servers@2x_white.png differ diff --git a/graphics/osx/preferences/servers_black.png b/graphics/osx/preferences/servers_black.png new file mode 100644 index 000000000..c2a9a689e Binary files /dev/null and b/graphics/osx/preferences/servers_black.png differ diff --git a/graphics/osx/preferences/servers_white.png b/graphics/osx/preferences/servers_white.png new file mode 100644 index 000000000..d1c3ec247 Binary files /dev/null and b/graphics/osx/preferences/servers_white.png differ diff --git a/graphics/osx/preferences/sound.png b/graphics/osx/preferences/sound.png deleted file mode 100644 index 0998f5fde..000000000 Binary files a/graphics/osx/preferences/sound.png and /dev/null differ diff --git a/graphics/osx/preferences/sound@2x.png b/graphics/osx/preferences/sound@2x.png deleted file mode 100644 index 8207d34dd..000000000 Binary files a/graphics/osx/preferences/sound@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/sound@2x_black.png b/graphics/osx/preferences/sound@2x_black.png new file mode 100644 index 000000000..b92bc8acb Binary files /dev/null and b/graphics/osx/preferences/sound@2x_black.png differ diff --git a/graphics/osx/preferences/sound@2x_white.png b/graphics/osx/preferences/sound@2x_white.png new file mode 100644 index 000000000..6ccfb5421 Binary files /dev/null and b/graphics/osx/preferences/sound@2x_white.png differ diff --git a/graphics/osx/preferences/sound_black.png b/graphics/osx/preferences/sound_black.png new file mode 100644 index 000000000..dec0bbdcf Binary files /dev/null and b/graphics/osx/preferences/sound_black.png differ diff --git a/graphics/osx/preferences/sound_white.png b/graphics/osx/preferences/sound_white.png new file mode 100644 index 000000000..a9ece9a38 Binary files /dev/null and b/graphics/osx/preferences/sound_white.png differ diff --git a/graphics/osx/preferences/tms.png b/graphics/osx/preferences/tms.png deleted file mode 100644 index cb34101ef..000000000 Binary files a/graphics/osx/preferences/tms.png and /dev/null differ diff --git a/graphics/osx/preferences/tms@2x.png b/graphics/osx/preferences/tms@2x.png deleted file mode 100644 index ddbf71955..000000000 Binary files a/graphics/osx/preferences/tms@2x.png and /dev/null differ diff --git a/graphics/osx/preferences/tms@2x_black.png b/graphics/osx/preferences/tms@2x_black.png new file mode 100644 index 000000000..a4e016996 Binary files /dev/null and b/graphics/osx/preferences/tms@2x_black.png differ diff --git a/graphics/osx/preferences/tms@2x_white.png b/graphics/osx/preferences/tms@2x_white.png new file mode 100644 index 000000000..6fc63341c Binary files /dev/null and b/graphics/osx/preferences/tms@2x_white.png differ diff --git a/graphics/osx/preferences/tms_black.png b/graphics/osx/preferences/tms_black.png new file mode 100644 index 000000000..89c928e29 Binary files /dev/null and b/graphics/osx/preferences/tms_black.png differ diff --git a/graphics/osx/preferences/tms_white.png b/graphics/osx/preferences/tms_white.png new file mode 100644 index 000000000..b3deaef76 Binary files /dev/null and b/graphics/osx/preferences/tms_white.png differ diff --git a/graphics/pause_black.png b/graphics/pause_black.png new file mode 100644 index 000000000..e2584b796 Binary files /dev/null and b/graphics/pause_black.png differ diff --git a/graphics/pause_white.png b/graphics/pause_white.png new file mode 100644 index 000000000..6e2b18126 Binary files /dev/null and b/graphics/pause_white.png differ diff --git a/graphics/select.png b/graphics/select.png index e7fb24106..aa0dbdcc5 100644 Binary files a/graphics/select.png and b/graphics/select.png differ diff --git a/graphics/select_black.png b/graphics/select_black.png new file mode 100644 index 000000000..aa0dbdcc5 Binary files /dev/null and b/graphics/select_black.png differ diff --git a/graphics/select_white.png b/graphics/select_white.png new file mode 100644 index 000000000..aa48c770a Binary files /dev/null and b/graphics/select_white.png differ diff --git a/graphics/sequence.png b/graphics/sequence.png index 85475031c..c16b7096f 100644 Binary files a/graphics/sequence.png and b/graphics/sequence.png differ diff --git a/graphics/sequence_black.png b/graphics/sequence_black.png new file mode 100644 index 000000000..c16b7096f Binary files /dev/null and b/graphics/sequence_black.png differ diff --git a/graphics/sequence_white.png b/graphics/sequence_white.png new file mode 100644 index 000000000..67592d0d9 Binary files /dev/null and b/graphics/sequence_white.png differ diff --git a/graphics/snap.png b/graphics/snap.png index 5beda9454..01580dcbd 100644 Binary files a/graphics/snap.png and b/graphics/snap.png differ diff --git a/graphics/snap_black.png b/graphics/snap_black.png new file mode 100644 index 000000000..01580dcbd Binary files /dev/null and b/graphics/snap_black.png differ diff --git a/graphics/snap_white.png b/graphics/snap_white.png new file mode 100644 index 000000000..801e9c599 Binary files /dev/null and b/graphics/snap_white.png differ diff --git a/graphics/splash.png b/graphics/splash.png index 0a9488dd3..31da602b6 100644 Binary files a/graphics/splash.png and b/graphics/splash.png differ diff --git a/graphics/src/batch_black.svg b/graphics/src/batch_black.svg new file mode 100644 index 000000000..3affcc9e1 --- /dev/null +++ b/graphics/src/batch_black.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + diff --git a/graphics/src/batch_white.svg b/graphics/src/batch_white.svg new file mode 100644 index 000000000..bf7bb0901 --- /dev/null +++ b/graphics/src/batch_white.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/graphics/src/link_black.svg b/graphics/src/link_black.svg new file mode 100644 index 000000000..00d4f6b16 --- /dev/null +++ b/graphics/src/link_black.svg @@ -0,0 +1,78 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/graphics/src/link_white.svg b/graphics/src/link_white.svg new file mode 100644 index 000000000..e16a2a266 --- /dev/null +++ b/graphics/src/link_white.svg @@ -0,0 +1,78 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/graphics/src/no_tick.svg b/graphics/src/no_tick.svg deleted file mode 100644 index 82bd047f2..000000000 --- a/graphics/src/no_tick.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - diff --git a/graphics/src/preferences.svg b/graphics/src/preferences.svg deleted file mode 100644 index 22e61c78b..000000000 --- a/graphics/src/preferences.svg +++ /dev/null @@ -1,1077 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/graphics/src/preferences_black.svg b/graphics/src/preferences_black.svg new file mode 100644 index 000000000..7db9867bb --- /dev/null +++ b/graphics/src/preferences_black.svg @@ -0,0 +1,1117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/graphics/src/preferences_white.svg b/graphics/src/preferences_white.svg new file mode 100644 index 000000000..333208197 --- /dev/null +++ b/graphics/src/preferences_white.svg @@ -0,0 +1,1510 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/graphics/src/select.svg b/graphics/src/select.svg deleted file mode 100644 index 0ebba99c6..000000000 --- a/graphics/src/select.svg +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/graphics/src/sequence.svg b/graphics/src/sequence.svg deleted file mode 100644 index 80ff46fcb..000000000 --- a/graphics/src/sequence.svg +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/graphics/src/snap.svg b/graphics/src/snap.svg deleted file mode 100644 index 93efd695e..000000000 --- a/graphics/src/snap.svg +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/graphics/src/tick.svg b/graphics/src/tick.svg deleted file mode 100644 index 8ed6cc2f5..000000000 --- a/graphics/src/tick.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/graphics/src/timeline_black.svg b/graphics/src/timeline_black.svg new file mode 100644 index 000000000..58e27720a --- /dev/null +++ b/graphics/src/timeline_black.svg @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/graphics/src/timeline_white.svg b/graphics/src/timeline_white.svg new file mode 100644 index 000000000..14dd8ccce --- /dev/null +++ b/graphics/src/timeline_white.svg @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/graphics/src/zoom.svg b/graphics/src/zoom.svg deleted file mode 100644 index 26e4c36b7..000000000 --- a/graphics/src/zoom.svg +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/graphics/src/zoom_all.svg b/graphics/src/zoom_all.svg deleted file mode 100644 index a1b253a77..000000000 --- a/graphics/src/zoom_all.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/graphics/update b/graphics/update index f518b5b5b..46964470b 100755 --- a/graphics/update +++ b/graphics/update @@ -80,9 +80,11 @@ else # OS X preferences icons mkdir -p osx/preferences - for i in defaults email kdm_email cover_sheet keys tms notifications locations sound identifiers servers general advanced; do - inkbatch --inkscape $INKSCAPE -i bounds-$i -o osx/preferences/$i.png --width 32 --height 32 src/preferences.svg - inkbatch --inkscape $INKSCAPE -i bounds-$i -o osx/preferences/$i@2x.png --width 64 --height 64 src/preferences.svg + for c in black white; do + for i in defaults email kdm_email cover_sheet keys tms notifications locations sound identifiers servers general advanced non_standard; do + inkbatch --inkscape $INKSCAPE -i bounds-$i -o osx/preferences/${i}_${c}.png --width 32 --height 32 src/preferences_$c.svg + inkbatch --inkscape $INKSCAPE -i bounds-$i -o osx/preferences/${i}@2x_${c}.png --width 64 --height 64 src/preferences_$c.svg + done done # OS X menu bar icon for the server (in dark and light mode) @@ -93,16 +95,23 @@ else $INKSCAPE_EXPORT --export-filename=splash.png src/splash.svg -w 400 -h 300 # Timeline toolbar icons (all platforms) - for i in select zoom zoom_all snap sequence; do - $INKSCAPE_EXPORT --export-filename=$i.png src/$i.svg -w 32 -h 32 + for c in black white; do + for i in select zoom zoom_all snap sequence; do + inkbatch --inkscape $INKSCAPE -i timeline-$i -o ${i}_${c}.png --width 32 --height 32 src/timeline_$c.svg + done done - # Playlist editor tick/no-tick - $INKSCAPE_EXPORT --export-filename=tick.png src/tick.svg -w 16 -h 16 - $INKSCAPE_EXPORT --export-filename=no_tick.png src/no_tick.svg -w 16 -h 16 + # Batch converter toolbar icons (all platforms) + for c in black white; do + for i in add pause; do + inkbatch --inkscape $INKSCAPE -i batch-$i -o ${i}_${c}.png --width 32 --height 32 src/batch_$c.svg + done + done # Link icon - $INKSCAPE_EXPORT --export-filename=link.png src/link.svg -w 9 -h 16 + for c in black white; do + $INKSCAPE_EXPORT --export-filename=link_$c.png src/link_$c.svg -w 9 -h 16 + done # favicon mkdir -p web diff --git a/graphics/wscript b/graphics/wscript index c87904683..663e28287 100644 --- a/graphics/wscript +++ b/graphics/wscript @@ -38,13 +38,23 @@ def build(bld): # Install stuff for POSIX systems if not bld.env.TARGET_WINDOWS_64 and not bld.env.TARGET_WINDOWS_32 and not bld.env.DISABLE_GUI: bld.install_as('${PREFIX}/share/dcpomatic2/dcpomatic2_server_small.png', 'linux/16/dcpomatic2.png') - bld.install_files('${PREFIX}/share/dcpomatic2', 'splash.png') - bld.install_files('${PREFIX}/share/dcpomatic2', 'zoom.png') - bld.install_files('${PREFIX}/share/dcpomatic2', 'zoom_all.png') - bld.install_files('${PREFIX}/share/dcpomatic2', 'select.png') - bld.install_files('${PREFIX}/share/dcpomatic2', 'snap.png') - bld.install_files('${PREFIX}/share/dcpomatic2', 'sequence.png') - bld.install_files('${PREFIX}/share/dcpomatic2', 'me.jpg') - bld.install_files('${PREFIX}/share/dcpomatic2', 'tick.png') - bld.install_files('${PREFIX}/share/dcpomatic2', 'no_tick.png') - bld.install_files('${PREFIX}/share/dcpomatic2', 'link.png') + for icon in ( + 'splash.png', + 'zoom_white.png', + 'zoom_black.png', + 'select_white.png', + 'select_black.png', + 'snap_white.png', + 'snap_black.png', + 'sequence_white.png', + 'sequence_black.png', + 'zoom_all_white.png', + 'zoom_all_black.png', + 'me.jpg', + 'link_white.png', + 'link_black.png', + 'add_black.png', + 'add_white.png', + 'pause_black.png', + 'pause_white.png'): + bld.install_files('${PREFIX}/share/dcpomatic2', icon) diff --git a/graphics/zoom.png b/graphics/zoom.png index 6a024a91f..4633c8cb3 100644 Binary files a/graphics/zoom.png and b/graphics/zoom.png differ diff --git a/graphics/zoom_all.png b/graphics/zoom_all.png index 4c2edbf28..19ccb7148 100644 Binary files a/graphics/zoom_all.png and b/graphics/zoom_all.png differ diff --git a/graphics/zoom_all_black.png b/graphics/zoom_all_black.png new file mode 100644 index 000000000..19ccb7148 Binary files /dev/null and b/graphics/zoom_all_black.png differ diff --git a/graphics/zoom_all_white.png b/graphics/zoom_all_white.png new file mode 100644 index 000000000..ed376ec37 Binary files /dev/null and b/graphics/zoom_all_white.png differ diff --git a/graphics/zoom_black.png b/graphics/zoom_black.png new file mode 100644 index 000000000..4633c8cb3 Binary files /dev/null and b/graphics/zoom_black.png differ diff --git a/graphics/zoom_white.png b/graphics/zoom_white.png new file mode 100644 index 000000000..50890f120 Binary files /dev/null and b/graphics/zoom_white.png differ diff --git a/hacks/certtool b/hacks/certtool new file mode 100755 index 000000000..4a865e49a --- /dev/null +++ b/hacks/certtool @@ -0,0 +1,75 @@ +#!/usr/bin/python3 + +import argparse +import os +from pathlib import Path +import subprocess +import sys +import tempfile + + +parser = argparse.ArgumentParser() +parser.add_argument('-c', '--check', help='check a .dom settings export file on stdin', action='store_true') +parser.add_argument('-s', '--split', help='split certificates and private keys from stdin', action='store_true') +parser.add_argument('-p', '--prefix', help='output filename prefix when doing --split', type=Path, default='./') +args = parser.parse_args() + +cert = None +certs = [] +private_key = None + +for line in sys.stdin.readlines(): + if line.find('BEGIN CERTIFICATE') != -1: + cert = line + elif line.find('END CERTIFICATE') != -1: + cert += line + certs.append(cert) + cert = None + elif cert: + cert += line + elif line.find('BEGIN RSA PRIVATE KEY') != -1: + private_key = line + elif line.find('END RSA PRIVATE') != -1: + private_key += line + elif private_key: + private_key += line + +if len(certs) != 3: + print(f'Expected 3 certificates but found {len(certs)}.', file=sys.stderr) + exit(1) + +if args.check: + if private_key is None: + print('Found no private key', file=sys.stderr) + exit(1) + + leaf_cert_modulus = None + with tempfile.NamedTemporaryFile(mode='w', delete=False) as leaf: + print(certs[2], file=leaf) + leaf.close() + process = subprocess.run(['openssl', 'x509', '-modulus', '-noout', '-in', leaf.name], capture_output=True) + leaf_cert_modulus = process.stdout + + leaf_key_modulus = None + with tempfile.NamedTemporaryFile('w', delete=False) as key: + print(private_key, file=key) + key.close() + process = subprocess.run(['openssl', 'rsa', '-modulus', '-noout', '-in', key.name], capture_output=True, check=True) + leaf_key_modulus = process.stdout + + if leaf_cert_modulus != leaf_key_modulus: + print('Leaf certificate and private key don''t match.', file=sys.stderr) + exit(1) + else: + print('Leaf certificates and private key match.') + +elif args.split: + + for index, cert in enumerate(certs): + with open(f'{args.prefix.name}cert_{index}.pem', 'w') as output: + print(cert, file=output) + + if private_key: + with open(f'{args.prefix.name}private_key.pem', 'w') as output: + print(private_key, file=output) + diff --git a/hacks/i18nup b/hacks/i18nup index 1bc679ce5..c4f8fcae8 100644 --- a/hacks/i18nup +++ b/hacks/i18nup @@ -1,5 +1,5 @@ #!/bin/bash -changes=`git status -s | grep "^ M"` +changes=`git status -s | grep "^.*M"` check=`echo "$changes" | grep -v /po/` if [ "$check" != "" ]; then echo "Non i18n updates would be committed" @@ -51,6 +51,9 @@ elif [[ `echo $changes | grep sl_SI` != "" ]]; then elif [[ `echo $changes | grep hu_HU` != "" ]]; then language="hu_HU" translator="Áron Németh" +elif [[ `echo $changes | grep fa_IR` != "" ]]; then + language="fa_IR" + translator="Soleyman Rahmani" else echo "Unknown language" exit 1 diff --git a/hacks/pixfmts.c b/hacks/pixfmts.c index d77c10c55..922d38add 100644 --- a/hacks/pixfmts.c +++ b/hacks/pixfmts.c @@ -29,6 +29,8 @@ int main() SHOW(AV_PIX_FMT_GBRP); SHOW(AV_PIX_FMT_YUVA444P10LE); SHOW(AV_PIX_FMT_XYZ12LE); + SHOW(AV_PIX_FMT_NV20LE); + SHOW(AV_PIX_FMT_RGBA64BE); SHOW(AV_PIX_FMT_YUV444P12LE); SHOW(AV_PIX_FMT_GBRP12BE); SHOW(AV_PIX_FMT_GBRP12LE); diff --git a/hacks/test_timings b/hacks/test_timings index 932fb395f..05c680513 100755 --- a/hacks/test_timings +++ b/hacks/test_timings @@ -3,20 +3,20 @@ import sys if len(sys.argv) < 2: - print('Syntax %s ' % sys.argv[0], file=sys.stderr) - sys.exit(1) + file = sys.stdin +else: + file = open(sys.argv[1]) tests = {} -with open(sys.argv[1]) as f: - while True: - l = f.readline() - if l == '': - break +while True: + l = file.readline() + if l == '': + break - s = l.split() - if len(s) == 8 and s[7][-2:] == 'us': - tests[float(s[7][:-2]) / 1000000] = s[4][1:-2] + s = l.split() + if len(s) == 8 and s[7][-2:] == 'us': + tests[float(s[7][:-2]) / 1000000] = s[4][1:-2] for t in sorted(tests): s = int(t) @@ -24,4 +24,4 @@ for t in sorted(tests): s -= h * 3600 m = s // 60 s -= m * 60 - print("%30s %02d:%02d:%02d (%f)" % (tests[t], h, m, s, t)) + print("%50s %02d:%02d:%02d (%f)" % (tests[t], h, m, s, t)) diff --git a/i18n.py b/i18n.py index a3589ff82..f2b032157 100644 --- a/i18n.py +++ b/i18n.py @@ -20,7 +20,7 @@ def pot(dir, sources, name): except: pass - command('xgettext --from-code=UTF-8 -d %s -s --keyword=_ --keyword=S_ --add-comments=/ -p %s -o %s.pot %s' % (name, d, name, s)) + command('xgettext --from-code=UTF-8 -d %s -s --keyword=_ --keyword=S_ --add-comments=TRANSLATORS: -p %s -o %s.pot %s' % (name, d, name, s)) def pot_merge(dir, name): for f in glob.glob(os.path.join(os.getcwd(), dir, 'po', '*.po')): diff --git a/platform/osx/copy_resources.sh b/platform/osx/copy_resources.sh index 82ca60de9..06b6e5985 100644 --- a/platform/osx/copy_resources.sh +++ b/platform/osx/copy_resources.sh @@ -1,7 +1,4 @@ mkdir -p build/src/Resources cp -r ../libdcp/{tags,xsd,ratings} build/src/Resources -cp graphics/{link,splash}.png build/src/Resources -cp graphics/{select,snap,sequence,zoom,zoom_all}.png build/src/Resources -cp graphics/osx/preferences/*.png build/src/Resources cp fonts/*.ttf build/src/Resources ln -s $(which openssl) build/src/tools/openssl diff --git a/platform/osx/make_dmg.sh b/platform/osx/make_dmg.sh index 3ae60143f..bebd089bd 100644 --- a/platform/osx/make_dmg.sh +++ b/platform/osx/make_dmg.sh @@ -62,6 +62,8 @@ cat < entitlements.plist com.apple.security.cs.allow-dyld-environment-variables + com.apple.security.cs.allow-unsigned-executable-memory + EOF @@ -224,52 +226,40 @@ function copy_resources { cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_disk.icns "$dest" cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_combiner.icns "$dest" cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_editor.icns "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/defaults.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/defaults@2x.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/kdm_email.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/kdm_email@2x.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/email.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/email@2x.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/servers.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/servers@2x.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/tms.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/tms@2x.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/keys.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/keys@2x.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/cover_sheet.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/cover_sheet@2x.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/notifications.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/notifications@2x.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/sound.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/sound@2x.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/identifiers.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/identifiers@2x.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/general.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/general@2x.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/advanced.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/advanced@2x.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/locations.png "$dest" - cp $prefix/src/dcpomatic/graphics/osx/preferences/locations@2x.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/defaults*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/kdm_email*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/email*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/servers*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/tms*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/keys*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/cover_sheet*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/notifications*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/sound*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/identifiers*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/general*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/advanced*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/locations*.png "$dest" + cp $prefix/src/dcpomatic/graphics/osx/preferences/non_standard*.png "$dest" cp $prefix/src/dcpomatic/fonts/LiberationSans-Regular.ttf "$dest" cp $prefix/src/dcpomatic/fonts/LiberationSans-Italic.ttf "$dest" cp $prefix/src/dcpomatic/fonts/LiberationSans-Bold.ttf "$dest" cp $prefix/src/dcpomatic/fonts/fonts.conf.osx "$dest"/fonts.conf cp $prefix/src/dcpomatic/graphics/splash.png "$dest" - cp $prefix/src/dcpomatic/graphics/zoom.png "$dest" - cp $prefix/src/dcpomatic/graphics/zoom_all.png "$dest" - cp $prefix/src/dcpomatic/graphics/select.png "$dest" - cp $prefix/src/dcpomatic/graphics/snap.png "$dest" - cp $prefix/src/dcpomatic/graphics/sequence.png "$dest" + cp $prefix/src/dcpomatic/graphics/zoom*.png "$dest" + cp $prefix/src/dcpomatic/graphics/zoom_all*.png "$dest" + cp $prefix/src/dcpomatic/graphics/select*.png "$dest" + cp $prefix/src/dcpomatic/graphics/snap*.png "$dest" + cp $prefix/src/dcpomatic/graphics/sequence*.png "$dest" cp $prefix/src/dcpomatic/graphics/me.jpg "$dest" - cp $prefix/src/dcpomatic/graphics/link.png "$dest" - cp $prefix/src/dcpomatic/graphics/tick.png "$dest" - cp $prefix/src/dcpomatic/graphics/no_tick.png "$dest" + cp $prefix/src/dcpomatic/graphics/link*.png "$dest" + cp $prefix/src/dcpomatic/graphics/add*.png "$dest" + cp $prefix/src/dcpomatic/graphics/pause*.png "$dest" cp -r $prefix/share/libdcp/xsd "$dest" cp -r $prefix/share/libdcp/tags "$dest" cp -r $prefix/share/libdcp/ratings "$dest" # i18n: DCP-o-matic .mo files - for lang in de_DE es_ES fr_FR it_IT sv_SE nl_NL ru_RU pl_PL da_DK pt_PT pt_BR sk_SK cs_CZ uk_UA zh_CN tr_TR sl_SI hu_HU; do + for lang in de_DE es_ES fr_FR it_IT sv_SE nl_NL ru_RU pl_PL da_DK pt_PT pt_BR sk_SK cs_CZ uk_UA zh_CN tr_TR sl_SI hu_HU ka_KA fa_IR; do mkdir -p "$dest/$lang/LC_MESSAGES" cp $prefix/src/dcpomatic/build/src/lib/mo/$lang/*.mo "$dest/$lang/LC_MESSAGES" cp $prefix/src/dcpomatic/build/src/wx/mo/$lang/*.mo "$dest/$lang/LC_MESSAGES" @@ -478,6 +468,13 @@ function copy_verify { relink_relative "${rl[@]}" } +function copy_kdm { + copy $ROOT src/libdcp/build/tools/dcpkdm "$approot/MacOS" + mv "$approot/MacOS/dcpkdm" "$approot/MacOS/dcpomatic2_kdm_inspect" + rl=("$approot/MacOS/dcpomatic2_kdm_inspect" "$approot/Frameworks/"*.dylib) + relink_relative "${rl[@]}" +} + if [ "$ARCH2" == "" ]; then prefix=$ROOT else @@ -490,13 +487,15 @@ if [[ "$BUILD" == *main* ]]; then copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2 "$approot/MacOS" copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_cli "$approot/MacOS" copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_create "$approot/MacOS" + copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_map "$approot/MacOS" copy $ROOT bin/ffprobe "$approot/MacOS" copy $ROOT src/openssl/apps/openssl "$approot/MacOS" copy_verify + copy_kdm cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2.Info.plist "$approot/Info.plist" - rl=("$approot/MacOS/dcpomatic2" "$approot/MacOS/dcpomatic2_cli" "$approot/MacOS/dcpomatic2_create" "$approot/MacOS/ffprobe" "$approot/Frameworks/"*.dylib) + rl=("$approot/MacOS/dcpomatic2" "$approot/MacOS/dcpomatic2_cli" "$approot/MacOS/dcpomatic2_create" "$approot/MacOS/dcpomatic2_map" "$approot/MacOS/ffprobe" "$approot/Frameworks/"*.dylib) relink_relative "${rl[@]}" - make_dmg "$appdir" "" "DCP-o-matic" "dcpomatic2_verify openssl ffprobe dcpomatic2_cli dcpomatic2_create dcpomatic2" + make_dmg "$appdir" "" "DCP-o-matic" "dcpomatic2_verify dcpomatic2_kdm_inspect openssl ffprobe dcpomatic2_cli dcpomatic2_create dcpomatic2_map dcpomatic2" fi if [[ "$BUILD" == *kdm* ]]; then @@ -506,10 +505,11 @@ if [[ "$BUILD" == *kdm* ]]; then copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_kdm_cli "$approot/MacOS" copy $ROOT src/openssl/apps/openssl "$approot/MacOS" copy_verify + copy_kdm cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_kdm.Info.plist "$approot/Info.plist" rl=("$approot/MacOS/dcpomatic2_kdm" "$approot/MacOS/dcpomatic2_kdm_cli" "$approot/Frameworks/"*.dylib) relink_relative "${rl[@]}" - make_dmg "$appdir" "" "DCP-o-matic KDM Creator" "dcpomatic2_verify openssl dcpomatic2_kdm_cli dcpomatic2_kdm" + make_dmg "$appdir" "" "DCP-o-matic KDM Creator" "dcpomatic2_verify dcpomatic2_kdm_inspect openssl dcpomatic2_kdm_cli dcpomatic2_kdm" fi if [[ "$BUILD" == *server* ]]; then @@ -519,10 +519,11 @@ if [[ "$BUILD" == *server* ]]; then copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_server_cli "$approot/MacOS" copy $ROOT src/openssl/apps/openssl "$approot/MacOS" copy_verify + copy_kdm cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_server.Info.plist "$approot/Info.plist" rl=("$approot/MacOS/dcpomatic2_server" "$approot/MacOS/dcpomatic2_server_cli" "$approot/Frameworks/"*.dylib) relink_relative "${rl[@]}" - make_dmg "$appdir" "" "DCP-o-matic Encode Server" "dcpomatic2_verify openssl dcpomatic2_server_cli dcpomatic2_server" + make_dmg "$appdir" "" "DCP-o-matic Encode Server" "dcpomatic2_verify dcpomatic2_kdm_inspect openssl dcpomatic2_server_cli dcpomatic2_server" fi if [[ "$BUILD" == *batch* ]]; then @@ -531,10 +532,11 @@ if [[ "$BUILD" == *batch* ]]; then copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_batch "$approot/MacOS" copy $ROOT src/openssl/apps/openssl "$approot/MacOS" copy_verify + copy_kdm cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_batch.Info.plist "$approot/Info.plist" rl=("$approot/MacOS/dcpomatic2_batch" "$approot/Frameworks/"*.dylib) relink_relative "${rl[@]}" - make_dmg "$appdir" "" "DCP-o-matic Batch Converter" "dcpomatic2_verify openssl dcpomatic2_batch" + make_dmg "$appdir" "" "DCP-o-matic Batch Converter" "dcpomatic2_verify dcpomatic2_kdm_inspect openssl dcpomatic2_batch" fi if [[ "$BUILD" == *player* ]]; then @@ -543,10 +545,11 @@ if [[ "$BUILD" == *player* ]]; then copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_player "$approot/MacOS" copy $ROOT src/openssl/apps/openssl "$approot/MacOS" copy_verify + copy_kdm cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_player.Info.plist "$approot/Info.plist" rl=("$approot/MacOS/dcpomatic2_player" "$approot/Frameworks/"*.dylib) relink_relative "${rl[@]}" - make_dmg "$appdir" "" "DCP-o-matic Player" "dcpomatic2_verify openssl dcpomatic2_player" + make_dmg "$appdir" "" "DCP-o-matic Player" "dcpomatic2_verify dcpomatic2_kdm_inspect openssl dcpomatic2_player" fi if [[ "$BUILD" == *playlist* ]]; then @@ -555,10 +558,11 @@ if [[ "$BUILD" == *playlist* ]]; then copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_playlist "$approot/MacOS" copy $ROOT src/openssl/apps/openssl "$approot/MacOS" copy_verify + copy_kdm cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_playlist.Info.plist "$approot/Info.plist" rl=("$approot/MacOS/dcpomatic2_playlist" "$approot/Frameworks/"*.dylib) relink_relative "${rl[@]}" - make_dmg "$appdir" "" "DCP-o-matic Playlist Editor" "dcpomatic2_verify openssl dcpomatic2_playlist" + make_dmg "$appdir" "" "DCP-o-matic Playlist Editor" "dcpomatic2_verify dcpomatic2_kdm_inspect openssl dcpomatic2_playlist" fi if [[ "$BUILD" == *combiner* ]]; then @@ -567,10 +571,11 @@ if [[ "$BUILD" == *combiner* ]]; then copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_combiner "$approot/MacOS" copy $ROOT src/openssl/apps/openssl "$approot/MacOS" copy_verify + copy_kdm cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_combiner.Info.plist "$approot/Info.plist" rl=("$approot/MacOS/dcpomatic2_combiner" "$approot/Frameworks/"*.dylib) relink_relative "${rl[@]}" - make_dmg "$appdir" "" "DCP-o-matic Combiner" "dcpomatic2_verify openssl dcpomatic2_combiner" + make_dmg "$appdir" "" "DCP-o-matic Combiner" "dcpomatic2_verify dcpomatic2_kdm_inspect openssl dcpomatic2_combiner" fi if [[ "$BUILD" == *editor* ]]; then @@ -579,10 +584,11 @@ if [[ "$BUILD" == *editor* ]]; then copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_editor "$approot/MacOS" copy $ROOT src/openssl/apps/openssl "$approot/MacOS" copy_verify + copy_kdm cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_editor.Info.plist "$approot/Info.plist" rl=("$approot/MacOS/dcpomatic2_editor" "$approot/Frameworks/"*.dylib) relink_relative "${rl[@]}" - make_dmg "$appdir" "" "DCP-o-matic Editor" "dcpomatic2_verify openssl dcpomatic2_editor" + make_dmg "$appdir" "" "DCP-o-matic Editor" "dcpomatic2_verify dcpomatic2_kdm_inspect openssl dcpomatic2_editor" fi if [[ "$BUILD" == *disk* ]]; then @@ -591,6 +597,7 @@ if [[ "$BUILD" == *disk* ]]; then copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_disk "$approot/MacOS" copy $ROOT src/openssl/apps/openssl "$approot/MacOS" copy_verify + copy_kdm cp $prefix/src/dcpomatic/platform/osx/uninstall_disk.applescript "$approot/Resources" cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_disk.Info.plist "$approot/Info.plist" rl=("$approot/MacOS/dcpomatic2_disk" "$approot/Frameworks/"*.dylib) @@ -668,6 +675,6 @@ EOF mv $pkgbin/* "$pkgroot/Library/Application Support/com.dcpomatic/" pkgbuild --root $pkgroot --identifier com.dcpomatic.disk.writer --scripts $pkgbase/scripts "DCP-o-matic Disk Writer.pkg" - make_dmg "$appdir" "DCP-o-matic Disk Writer.pkg" "DCP-o-matic Disk Writer" "dcpomatic2_verify openssl dcpomatic2_disk" + make_dmg "$appdir" "DCP-o-matic Disk Writer.pkg" "DCP-o-matic Disk Writer" "dcpomatic2_verify dcpomatic2_kdm_inspect openssl dcpomatic2_disk" fi diff --git a/platform/osx/set_paths.sh b/platform/osx/set_paths.sh index 91d67ba94..24baa6cd6 100644 --- a/platform/osx/set_paths.sh +++ b/platform/osx/set_paths.sh @@ -1,11 +1,23 @@ -base=$HOME/workspace -env=$HOME/osx-environment/x86_64/10.10 +SDK=$1 +if [[ "$SDK" == 11 ]]; then + isysroot="-isysroot $HOME/SDK/MacOS11.0.sdk" + base=$HOME/workspace + export MACOSX_DEPLOYMENT_TARGET=10.10 +else + base=/usr/local +fi + +arch=$(uname -m) +if [[ "$arch" == arm64 ]]; then + env=$HOME/osx-environment/arm64/11.0 +else + env=$HOME/osx-environment/x86_64/10.10 +fi sdk=$HOME/SDK -export CPPFLAGS= LDFLAGS="-L$base/lib -L$env/lib -isysroot $sdk/MacOSX11.0.sdk -arch x86_64" -export LINKFLAGS="-L$base/lib -L$env/lib -isysroot $sdk/MacOSX11.0.sdk -arch x86_64" -export MACOSX_DEPLOYMENT_TARGET=10.10 -export CXXFLAGS="-I$base/include -I$env/include -isysroot $sdk/MacOSX11.0.sdk -arch x86_64" -export CFLAGS="-I$base/include -I$env/include -isysroot $sdk/MacOSX11.0.sdk -arch x86_64" +export CPPFLAGS= LDFLAGS="-L$base/lib -L$env/lib $isysroot -arch $arch" +export LINKFLAGS="-L$base/lib -L$env/lib $isysroot -arch $arch" +export CXXFLAGS="-I$base/include -I$env/include $isysroot -arch $arch" +export CFLAGS="-I$base/include -I$env/include $isysroot -arch $arch" export PATH=$env/bin:$PATH export PKG_CONFIG_PATH=$env/lib/pkgconfig:$base/lib/pkgconfig diff --git a/platform/windows/wscript b/platform/windows/wscript index 84291b3ca..17965d54c 100644 --- a/platform/windows/wscript +++ b/platform/windows/wscript @@ -1,6 +1,14 @@ from __future__ import print_function import os + +def start_menu_shortcut(file, link, target, debug=False): + if debug: + print(f'CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\{link}.lnk" "$INSTDIR\\{target}"', file=file) + else: + print(f'CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\{link}.lnk" "$INSTDIR\\{target}"', file=file) + + def write_installer(bits, dcpomatic_version, debug, disk): tools = [ @@ -13,6 +21,7 @@ def write_installer(bits, dcpomatic_version, debug, disk): ('playlist', 'Playlist Editor'), ('combiner', 'Combiner'), ('editor', 'Editor'), + ('map', 'Map'), ] if disk: @@ -37,6 +46,7 @@ def write_installer(bits, dcpomatic_version, debug, disk): print('Name "DCP-o-matic"', file=f) print('RequestExecutionLevel admin', file=f) + print('Unicode true', file=f) outfile = 'DCP-o-matic ' if debug: @@ -172,6 +182,7 @@ File "%static_deps%/bin/libdav1d.dll" File "%static_deps%/bin/libltdl-7.dll" File "%static_deps%/bin/libdl.dll" File /oname=dcpomatic2_verify.exe "%cdist_deps%/bin/dcpverify.exe" +File /oname=dcpomatic2_kdm_inspect.exe "%cdist_deps%/bin/dcpkdm.exe" File "%cdist_deps%/bin/leqm_nrt.dll" File "%cdist_deps%/bin/asdcp-carl.dll" File "%cdist_deps%/bin/kumu-carl.dll" @@ -299,6 +310,14 @@ SetOutPath "$INSTDIR\\locale\\hu_hu\\LC_MESSAGES" File "%binaries%/src/lib/mo/hu_HU/libdcpomatic2.mo" File "%binaries%/src/wx/mo/hu_HU/libdcpomatic2-wx.mo" File "%binaries%/src/tools/mo/hu_HU/dcpomatic2.mo" +SetOutPath "$INSTDIR\\locale\\ka_KA\\LC_MESSAGES" +File "%binaries%/src/lib/mo/ka_KA/libdcpomatic2.mo" +File "%binaries%/src/wx/mo/ka_KA/libdcpomatic2-wx.mo" +File "%binaries%/src/tools/mo/ka_KA/dcpomatic2.mo" +SetOutPath "$INSTDIR\\locale\\fa_IR\\LC_MESSAGES" +File "%binaries%/src/lib/mo/fa_IR/libdcpomatic2.mo" +File "%binaries%/src/wx/mo/fa_IR/libdcpomatic2-wx.mo" +File "%binaries%/src/tools/mo/fa_IR/dcpomatic2.mo" SetOutPath "$INSTDIR" File "%resources%/../../fonts/LiberationSans-Regular.ttf" @@ -306,17 +325,26 @@ File "%resources%/../../fonts/LiberationSans-Italic.ttf" File "%resources%/../../fonts/LiberationSans-Bold.ttf" File /oname=fonts.conf "%resources%/../../fonts/fonts.conf.windows" File "%graphics%/splash.png" -File "%graphics%/zoom.png" -File "%graphics%/zoom_all.png" -File "%graphics%/select.png" -File "%graphics%/snap.png" -File "%graphics%/sequence.png" +File "%graphics%/zoom_white.png" +File "%graphics%/zoom_black.png" +File "%graphics%/zoom_all_white.png" +File "%graphics%/zoom_all_black.png" +File "%graphics%/select_white.png" +File "%graphics%/select_black.png" +File "%graphics%/snap_white.png" +File "%graphics%/snap_black.png" +File "%graphics%/sequence_white.png" +File "%graphics%/sequence_black.png" File "%graphics%/me.jpg" -File "%graphics%/tick.png" -File "%graphics%/no_tick.png" -File "%graphics%/link.png" +File "%graphics%/link_white.png" +File "%graphics%/link_black.png" +File "%graphics%/add_black.png" +File "%graphics%/add_white.png" +File "%graphics%/pause_black.png" +File "%graphics%/pause_white.png" SetOutPath "$INSTDIR\\xsd" File "%cdist_deps%/share/libdcp/xsd/DCDMSubtitle-2010.xsd" +File "%cdist_deps%/share/libdcp/xsd/DCDMSubtitle-2014.xsd" File "%cdist_deps%/share/libdcp/xsd/DCSubtitle.v1.mattsson.xsd" File "%cdist_deps%/share/libdcp/xsd/Dolby-2012-AD.xsd" File "%cdist_deps%/share/libdcp/xsd/isdcf-mca.xsd" @@ -357,6 +385,7 @@ SectionEnd print('Section "DCP-o-matic 2" SEC_MASTER', file=f) print('SetOutPath "$INSTDIR\\bin"', file=f) + print("SetShellVarContext all", file=f) if debug: print('CreateDirectory "$SMPROGRAMS\\DCP-o-matic 2 debug"', file=f) @@ -374,17 +403,17 @@ File "%resources%/dcpomatic2_disk_writer.exe.manifest" """, file=f) if debug: - print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\DCP-o-matic 2 debug.lnk" "$INSTDIR\\bin\\dcpomatic2_debug.bat"', file=f) + start_menu_shortcut(f, 'DCP-o-matic 2 debug', 'bin\\dcpomatic2_debug.bat', debug=True) for s, l in tools: - print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\DCP-o-matic 2 %s debug.lnk" "$INSTDIR\\bin\\dcpomatic2_%s_debug.bat" ""' % (l, s), file=f) - print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\Uninstall DCP-o-matic 2 debug.lnk" "$INSTDIR\\Uninstall.exe"', file=f) + start_menu_shortcut(f, f'DCP-o-matic 2 {l} debug', f'bin\\dcpomatic2_{s}_debug.bat', debug=True) + start_menu_shortcut(f, 'Uninstall DCP-o-matic 2 debug', 'Uninstall.exe', debug=True) print('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic 2 debug" "DisplayName" "DCP-o-matic 2 debug (remove only)"', file=f) print('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic 2 debug" "UninstallString" "$INSTDIR\\Uninstall.exe"', file=f) else: - print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2.lnk" "$INSTDIR\\bin\\dcpomatic2.exe"', file=f) + start_menu_shortcut(f, 'DCP-o-matic 2', 'bin\\dcpomatic2.exe') for s, l in tools: - print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 %s.lnk" "$INSTDIR\\bin\\dcpomatic2_%s.exe"' % (l, s), file=f) - print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\Uninstall DCP-o-matic 2.lnk" "$INSTDIR\\Uninstall.exe"', file=f) + start_menu_shortcut(f, f'DCP-o-matic 2 {l}', f'bin\\dcpomatic2_{s}.exe') + start_menu_shortcut(f, 'Uninstall DCP-o-matic 2', 'Uninstall.exe') print('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic2" "DisplayName" "DCP-o-matic 2 (remove only)"', file=f) print('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic2" "UninstallString" "$INSTDIR\\Uninstall.exe"', file=f) @@ -409,8 +438,12 @@ SetOutPath "$INSTDIR\\bin" CreateDirectory "$SMPROGRAMS\\DCP-o-matic 2" File "%binaries%/src/tools/dcpomatic2_server_cli.exe" File "%binaries%/src/tools/dcpomatic2_server.exe" -CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 Encode Server.lnk" "$INSTDIR\\bin\\dcpomatic2_server.exe" "" "$INSTDIR\\bin\\dcpomatic2_server.exe" 0 -CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\Uninstall DCP-o-matic 2.lnk" "$INSTDIR\\Uninstall.exe" "" "$INSTDIR\\Uninstall.exe" 0 + """, file=f) + + start_menu_shortcut(f, 'DCP-o-matic 2 Encode Server', 'bin\\dcpomatic2_server.exe') + start_menu_shortcut(f, 'Uninstall DCP-o-matic 2', 'Uninstall.exe') + + print(""" SectionEnd Section "Encode server desktop shortcuts" SEC_SERVER_DESKTOP CreateShortCut "$DESKTOP\\DCP-o-matic 2 Encode Server.lnk" "$INSTDIR\\bin\\dcpomatic2_server.exe" "" @@ -444,14 +477,15 @@ SectionEnd !insertmacro MUI_LANGUAGE "English" """, file=f) - if debug: - print(""" + print(""" Section "Uninstall" +SetShellVarContext all RMDir /r "$INSTDIR\\*.*" RMDir "$INSTDIR" -Delete "$DESKTOP\\DCP-o-matic 2 debug.lnk" - """, file=f) + """, file=f) + if debug: + print('Delete "$DESKTOP\\DCP-o-matic 2 debug.lnk"', file=f) for s, l in tools: print('Delete "$DESKTOP\\DCP-o-matic 2 %s debug.lnk"' % l, file=f) @@ -463,15 +497,10 @@ DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\U SectionEnd """, file=f) else: - print(""" -Section "Uninstall" -RMDir /r "$INSTDIR\\*.*" -RMDir "$INSTDIR" -Delete "$DESKTOP\\DCP-o-matic 2.lnk" - """, file=f) - + print('Delete "$DESKTOP\\DCP-o-matic 2.lnk"', file=f) for s, l in tools: - print('Delete "$DESKTOP\\DCP-o-matic 2 %s.lnk"' % l) + print('Delete "$DESKTOP\\DCP-o-matic 2 %s.lnk"' % l, file=f) + print('Delete "$DESKTOP\\DCP-o-matic 2 Encode Server.lnk"', file=f) print(""" Delete "$SMPROGRAMS\\DCP-o-matic 2\\*.*" diff --git a/run/dcpomatic b/run/dcpomatic index 8da7d7fab..45f857ae2 100755 --- a/run/dcpomatic +++ b/run/dcpomatic @@ -4,9 +4,23 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" source $DIR/environment binary=$build/src/tools/dcpomatic2 +if [[ "$(uname -m)" == arm64 ]]; then + env=arm64/11.0 +else + env=x86_64/10.10 +fi + +export DYLD_LIBRARY_PATH=/Users/cah/osx-environment/$env/lib:/usr/local/lib + +# export ASAN_OPTIONS=verbosity=1:malloc_context_size=20:detect_leaks=1 + if [ "$1" == "--debug" ]; then shift - gdb --args $binary $* + if [[ "$(uname)" == Darwin ]]; then + /Applications/Xcode.app/Contents/Developer/usr/bin/lldb $binary $* + else + gdb --args $binary $* + fi elif [ "$1" == "--valgrind" ]; then shift valgrind --tool="memcheck" --suppressions=suppressions --track-fds=yes --show-leak-kinds=all --leak-check=full $binary $* @@ -29,5 +43,5 @@ elif [ "$1" == "--screenshot" ]; then shift GTK_PATH=/usr/local/lib/gtk-3.0 GTK_MODULES=gtk-vector-screenshot $binary "$*" else - $binary $* + $binary $* 2> >(grep -v Gtk-CRITICAL | grep -v Gtk-WARNING) fi diff --git a/run/dcpomatic_cli b/run/dcpomatic_cli index 9a5cae647..b32462e59 100755 --- a/run/dcpomatic_cli +++ b/run/dcpomatic_cli @@ -3,16 +3,18 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" source $DIR/environment +binary=$DIR/../build/src/tools/dcpomatic2_cli + if [ "$1" == "--debug" ]; then shift - gdb --args build/src/tools/dcpomatic2_cli "$@" + gdb --args $binary "$@" elif [ "$1" == "--valgrind" ]; then shift # valgrind --tool="memcheck" --num-callers=24 --suppressions=suppressions build/src/tools/dcpomatic2_cli "$@" - valgrind --tool="memcheck" --leak-check=full --show-reachable=yes --num-callers=24 --suppressions=suppressions build/src/tools/dcpomatic2_cli "$@" + valgrind --tool="memcheck" --leak-check=full --show-reachable=yes --num-callers=24 --suppressions=suppressions $binary "$@" elif [ "$1" == "--callgrind" ]; then shift - valgrind --tool="callgrind" build/src/tools/dcpomatic2_cli "$@" + valgrind --tool="callgrind" $binary "$@" else - build/src/tools/dcpomatic2_cli "$@" + $binary "$@" fi diff --git a/run/dcpomatic_disk b/run/dcpomatic_disk index a4b4ead55..ff5d8e7e5 100755 --- a/run/dcpomatic_disk +++ b/run/dcpomatic_disk @@ -3,6 +3,14 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" source $DIR/environment +if [[ "$(uname -m)" == arm64 ]]; then + env=arm64/11.0 +else + env=x86_64/10.10 +fi + +export DYLD_LIBRARY_PATH=/Users/cah/osx-environment/$env/lib:/usr/local/lib + if [ "$1" == "--debug" ]; then shift gdb --args build/src/tools/dcpomatic2_disk $* @@ -17,7 +25,7 @@ elif [ "$1" == "--massif" ]; then valgrind --tool="massif" build/src/tools/dcpomatic2_disk $* elif [ "$1" == "--i18n" ]; then shift - LANGUAGE=de_DE.UTF8 LANG=de_DE.UTF8 LC_ALL=de_DE.UTF8 build/src/tools/dcpomatic2_disk "$*" + LANGUAGE=de_DE.UTF8 LANG=de_DE.UTF8 LC_ALL=de_DE.UTF8 build/src/tools/dcpomatic2_disk $* elif [ "$1" == "--perf" ]; then shift perf record build/src/tools/dcpomatic2_disk $* diff --git a/run/dcpomatic_disk_writer b/run/dcpomatic_disk_writer index b2e1a872d..2dff961fb 100755 --- a/run/dcpomatic_disk_writer +++ b/run/dcpomatic_disk_writer @@ -3,9 +3,15 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" source $DIR/environment +if [[ "$(uname -m)" == arm64 ]]; then + env=arm64/11.0 +else + env=x86_64/10.10 +fi + +export DYLD_LIBRARY_PATH=/Users/cah/osx-environment/$env/lib:/usr/local/lib + exe=build/src/tools/dcpomatic2_disk_writer -sudo chown root $exe -sudo chmod 4755 $exe if [ "$1" == "--debug" ]; then shift @@ -29,5 +35,5 @@ elif [ "$1" == "--scaled" ]; then shift ~/src/run_scaled/run_scaled --sleep=5 --scale=0.5 $exe $* else - $exe $* + sudo DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH $exe $* fi diff --git a/run/dcpomatic_map b/run/dcpomatic_map new file mode 100755 index 000000000..f81be3067 --- /dev/null +++ b/run/dcpomatic_map @@ -0,0 +1,14 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +source $DIR/environment + +if [ "$1" == "--debug" ]; then + shift + gdb --args build/src/tools/dcpomatic2_map "$@" +elif [ "$1" == "--valgrind" ]; then + shift + valgrind --tool="memcheck" --leak-check=full --show-reachable=yes build/src/tools/dcpomatic2_map "$@" +else + build/src/tools/dcpomatic2_map "$@" +fi diff --git a/run/dcpomatic_player b/run/dcpomatic_player index 1c8a69b9e..0c565c251 100755 --- a/run/dcpomatic_player +++ b/run/dcpomatic_player @@ -2,25 +2,29 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" source $DIR/environment +binary=build/src/tools/dcpomatic2_player if [ "$1" == "--debug" ]; then shift gdb --args build/src/tools/dcpomatic2_player $* elif [ "$1" == "--valgrind" ]; then shift - valgrind --tool="memcheck" --suppressions=suppressions --track-fds=yes build/src/tools/dcpomatic2_player $* + valgrind --tool="memcheck" --suppressions=suppressions --track-fds=yes $binary $* elif [ "$1" == "--callgrind" ]; then shift - valgrind --tool="callgrind" build/src/tools/dcpomatic2_player $* + valgrind --tool="callgrind" $binary $* elif [ "$1" == "--massif" ]; then shift - valgrind --tool="massif" build/src/tools/dcpomatic2_player $* + valgrind --tool="massif" $binary $* elif [ "$1" == "--i18n" ]; then shift - LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 LC_ALL=fr_FR.UTF8 build/src/tools/dcpomatic2_player "$*" + LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 LC_ALL=fr_FR.UTF8 $binary "$*" elif [ "$1" == "--perf" ]; then shift - perf record build/src/tools/dcpomatic2_player $* + perf record $binary $* +elif [ "$1" == "--screenshot" ]; then + shift + GTK_PATH=/usr/local/lib/gtk-3.0 GTK_MODULES=gtk-vector-screenshot $binary "$*" else - build/src/tools/dcpomatic2_player $* + $binary $* fi diff --git a/run/dcpomatic_playlist b/run/dcpomatic_playlist index e240aab9c..a23c45766 100755 --- a/run/dcpomatic_playlist +++ b/run/dcpomatic_playlist @@ -2,25 +2,29 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" source $DIR/environment +binary=build/src/tools/dcpomatic2_playlist if [ "$1" == "--debug" ]; then shift - gdb --args build/src/tools/dcpomatic2_playlist $* + gdb --args $binary $* elif [ "$1" == "--valgrind" ]; then shift - valgrind --tool="memcheck" --suppressions=suppressions --track-fds=yes build/src/tools/dcpomatic2_playlist $* + valgrind --tool="memcheck" --suppressions=suppressions --track-fds=yes $binary $* elif [ "$1" == "--callgrind" ]; then shift - valgrind --tool="callgrind" build/src/tools/dcpomatic2_playlist $* + valgrind --tool="callgrind" $binary $* elif [ "$1" == "--massif" ]; then shift - valgrind --tool="massif" build/src/tools/dcpomatic2_playlist $* + valgrind --tool="massif" $binary $* elif [ "$1" == "--i18n" ]; then shift - LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 LC_ALL=fr_FR.UTF8 build/src/tools/dcpomatic2_playlist "$*" + LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 LC_ALL=fr_FR.UTF8 $binary "$*" elif [ "$1" == "--perf" ]; then shift - perf record build/src/tools/dcpomatic2_playlist $* + perf record $binary $* +elif [ "$1" == "--screenshot" ]; then + shift + GTK_PATH=/usr/local/lib/gtk-3.0 GTK_MODULES=gtk-vector-screenshot $binary $* else - build/src/tools/dcpomatic2_playlist $* + $binary $* fi diff --git a/run/environment b/run/environment index aabb463f3..aa4f77187 100644 --- a/run/environment +++ b/run/environment @@ -1,6 +1,9 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" build=$DIR/../build -export LD_LIBRARY_PATH=$build/src/lib:$build/src/wx:$build/src/asdcplib/src:/usr/local/lib64:/usr/local/lib:$LD_LIBRARY_PATH +export LD_LIBRARY_PATH=$build/src/lib:$build/src/wx:/usr/local/lib64:/usr/local/lib:$LD_LIBRARY_PATH +if [[ $(readlink -f $DIR/..) =~ (.*build/[^/]*) ]]; then + export LD_LIBRARY_PATH=${BASH_REMATCH[1]}/lib:$LD_LIBRARY_PATH +fi export DYLD_LIBRARY_PATH=$build/src/lib:$build/src/wx:$build/src/asdcplib/src:/Users/ci/osx-environment/x86_64/10.10/lib:/Users/ci/workspace/lib export DCPOMATIC_GRAPHICS=$DIR/../graphics diff --git a/run/tests b/run/tests index 301a5df71..bf4c5732e 100755 --- a/run/tests +++ b/run/tests @@ -1,6 +1,16 @@ #!/bin/bash # # e.g. --run_tests=foo +set -e + +PRIVATE_GIT="881c48805e352dfe150993814757ca974282be18" + +if [ "$1" == "--check" ]; then + shift 1 + check=1 +else + check=0 +fi if [ "$(uname)" == "Linux" ]; then export LD_LIBRARY_PATH=build/src/lib:/usr/local/lib:/usr/local/lib64:$LD_LIBRARY_PATH @@ -9,6 +19,16 @@ if [ "$(uname)" == "Linux" ]; then if [ ! -f build/test/dcpomatic2_openssl ]; then ln -s ../../../openssl/apps/openssl build/test/dcpomatic2_openssl fi + export DCPOMATIC_TEST_TOOLS_PATH=/opt/asdcplib/bin + if [ -f /src/backports/dcp_inspect ]; then + export DCPOMATIC_DCP_INSPECT=/src/backports/dcp_inspect + fi + set +e + python3 -m clairmeta.cli --help > /dev/null 2>&1 + if [ "$?" == "0" ]; then + export DCPOMATIC_CLAIRMETA=1 + fi + set -e fi if [ "$(uname)" == "Darwin" ]; then @@ -27,6 +47,20 @@ if [ "$(uname)" == "Darwin" ]; then export PATH=$PATH:/Users/ci/workspace/bin fi +if [ "$check" == "1" ]; then + if [ "$DCPOMATIC_TEST_PRIVATE" == "" ]; then + pushd ../dcpomatic-test-private + else + pushd $DCPOMATIC_TEST_PRIVATE + fi + current=$(git rev-parse HEAD) + if [ "$current" != "$PRIVATE_GIT" ]; then + echo "Unexpected dcpomatic-test-private version" + exit 1 + fi + popd +fi + if [ "$1" == "--debug" ]; then shift; gdb --args build/test/unit-tests --catch_system_errors=no --log_level=test_suite $* diff --git a/run/tests.bat b/run/tests.bat index 598338f17..a9d921788 100644 --- a/run/tests.bat +++ b/run/tests.bat @@ -4,4 +4,5 @@ xcopy ..\libdcp\tags build\tags\ copy ..\libdcp\ratings build\ copy ..\openssl\apps\openssl.exe build\test\ xcopy fonts build\fonts\ +move build\fonts\fonts.conf.windows build\fonts\fonts.conf build\test\unit-tests.exe --log_level=test_suite %1 %2 diff --git a/src/lib/active_text.cc b/src/lib/active_text.cc index 1e0fd6adb..2a5c4d836 100644 --- a/src/lib/active_text.cc +++ b/src/lib/active_text.cc @@ -32,6 +32,23 @@ using boost::optional; using namespace dcpomatic; +ActiveText::ActiveText(ActiveText&& other) + : _data(std::move(other._data)) +{ + +} + + +ActiveText& +ActiveText::operator=(ActiveText&& other) +{ + if (this != &other) { + _data = std::move(other._data); + } + return *this; +} + + /** Get the open captions that should be burnt into a given period. * @param period Period of interest. * @param always_burn_captions Always burn captions even if their content is not set to burn. diff --git a/src/lib/active_text.h b/src/lib/active_text.h index d5ce4cb07..5430f9681 100644 --- a/src/lib/active_text.h +++ b/src/lib/active_text.h @@ -45,6 +45,9 @@ public: ActiveText (ActiveText const&) = delete; ActiveText& operator= (ActiveText const&) = delete; + ActiveText(ActiveText&& other); + ActiveText& operator=(ActiveText&& other); + std::list get_burnt (dcpomatic::DCPTimePeriod period, bool always_burn_captions) const; void clear_before (dcpomatic::DCPTime time); void clear (); diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index ca0f49f57..a6ce5dcc8 100644 --- a/src/lib/analyse_audio_job.cc +++ b/src/lib/analyse_audio_job.cc @@ -47,12 +47,16 @@ using namespace boost::placeholders; #endif -/** @param from_zero true to analyse audio from time 0 in the playlist, otherwise begin at Playlist::start */ -AnalyseAudioJob::AnalyseAudioJob (shared_ptr film, shared_ptr playlist, bool from_zero) +/** @param whole_film true to analyse the whole film' audio (i.e. start from time 0 and use processors), false + * to analyse just the single piece of content in the playlist (i.e. start from Playlist::start() and do not + * use processors. + */ +AnalyseAudioJob::AnalyseAudioJob (shared_ptr film, shared_ptr playlist, bool whole_film) : Job (film) - , _analyser (film, playlist, from_zero, boost::bind(&Job::set_progress, this, _1, false)) + , _analyser(film, playlist, whole_film, boost::bind(&Job::set_progress, this, _1, false)) , _playlist (playlist) , _path (film->audio_analysis_path(playlist)) + , _whole_film(whole_film) { LOG_DEBUG_AUDIO_ANALYSIS_NC("AnalyseAudioJob::AnalyseAudioJob"); } @@ -89,6 +93,9 @@ AnalyseAudioJob::run () player->set_fast (); player->set_play_referenced (); player->Audio.connect (bind(&AudioAnalyser::analyse, &_analyser, _1, _2)); + if (!_whole_film) { + player->set_disable_audio_processor(); + } bool has_any_audio = false; for (auto c: _playlist->content()) { diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h index f8311da47..afd52c304 100644 --- a/src/lib/analyse_audio_job.h +++ b/src/lib/analyse_audio_job.h @@ -25,10 +25,9 @@ #include "audio_analyser.h" -#include "job.h" #include "audio_point.h" -#include "types.h" #include "dcpomatic_time.h" +#include "job.h" #include #include @@ -51,7 +50,7 @@ class Filter; class AnalyseAudioJob : public Job { public: - AnalyseAudioJob (std::shared_ptr, std::shared_ptr, bool from_zero); + AnalyseAudioJob(std::shared_ptr, std::shared_ptr, bool whole_film); ~AnalyseAudioJob (); std::string name () const override; @@ -71,6 +70,7 @@ private: std::shared_ptr _playlist; /** playlist's audio analysis path when the job was created */ boost::filesystem::path _path; + bool _whole_film; static const int _num_points; }; diff --git a/src/lib/analyse_subtitles_job.cc b/src/lib/analyse_subtitles_job.cc index 7f1b8ad04..b41990db5 100644 --- a/src/lib/analyse_subtitles_job.cc +++ b/src/lib/analyse_subtitles_job.cc @@ -21,6 +21,7 @@ #include "analyse_subtitles_job.h" #include "bitmap_text.h" +#include "film.h" #include "image.h" #include "player.h" #include "playlist.h" @@ -80,7 +81,9 @@ AnalyseSubtitlesJob::run () set_progress_unknown (); if (!content->text.empty()) { - while (!player->pass ()) {} + while (!player->pass ()) { + boost::this_thread::interruption_point(); + } } SubtitleAnalysis analysis (_bounding_box, content->text.front()->x_offset(), content->text.front()->y_offset()); @@ -92,7 +95,7 @@ AnalyseSubtitlesJob::run () void -AnalyseSubtitlesJob::analyse (PlayerText text, TextType type) +AnalyseSubtitlesJob::analyse(PlayerText const& text, TextType type) { if (type != TextType::OPEN_SUBTITLE) { return; @@ -106,14 +109,34 @@ AnalyseSubtitlesJob::analyse (PlayerText text, TextType type) } } - if (!text.string.empty()) { - /* We can provide dummy values for time and frame rate here as they are only used to calculate fades */ - dcp::Size const frame = _film->frame_size(); - for (auto i: render_text(text.string, frame, dcpomatic::DCPTime(), 24)) { + if (text.string.empty()) { + return; + } + + /* We can provide dummy values for time and frame rate here as they are only used to calculate fades */ + dcp::Size const frame = _film->frame_size(); + std::vector override_standard; + if (_film->interop()) { + /* Since the film is Interop there is only one way the vpositions in the subs can be interpreted + * (we assume). + */ + override_standard.push_back(dcp::SubtitleStandard::INTEROP); + } else { + /* We're using the great new SMPTE standard, which means there are two different ways that vposition + * could be interpreted; we will write SMPTE-2014 standard assets, but if the projection system uses + * SMPTE 20{07,10} instead they won't be placed how we intended. To show the user this, make the + * bounding rectangle enclose both possibilities. + */ + override_standard.push_back(dcp::SubtitleStandard::SMPTE_2007); + override_standard.push_back(dcp::SubtitleStandard::SMPTE_2014); + } + + for (auto standard: override_standard) { + for (auto i: bounding_box(text.string, frame, standard)) { dcpomatic::Rect rect ( - double(i.position.x) / frame.width, double(i.position.y) / frame.height, - double(i.image->size().width) / frame.width, double(i.image->size().height) / frame.height - ); + double(i.x) / frame.width, double(i.y) / frame.height, + double(i.width) / frame.width, double(i.height) / frame.height + ); if (!_bounding_box) { _bounding_box = rect; } else { diff --git a/src/lib/analyse_subtitles_job.h b/src/lib/analyse_subtitles_job.h index daaaf267a..c47117c57 100644 --- a/src/lib/analyse_subtitles_job.h +++ b/src/lib/analyse_subtitles_job.h @@ -20,8 +20,8 @@ #include "job.h" -#include "types.h" #include "player_text.h" +#include "text_type.h" class Film; @@ -42,7 +42,7 @@ public: } private: - void analyse (PlayerText text, TextType type); + void analyse(PlayerText const& text, TextType type); std::weak_ptr _content; boost::filesystem::path _path; diff --git a/src/lib/analytics.cc b/src/lib/analytics.cc index 75146cb45..836051fe5 100644 --- a/src/lib/analytics.cc +++ b/src/lib/analytics.cc @@ -22,6 +22,7 @@ #include "analytics.h" #include "compose.hpp" #include "exceptions.h" +#include #include #include #include @@ -110,7 +111,7 @@ Analytics::read () try { cxml::Document f ("Analytics"); - f.read_file (read_path("analytics.xml")); + f.read_file(dcp::filesystem::fix_long_path(read_path("analytics.xml"))); _successful_dcp_encodes = f.number_child("SuccessfulDCPEncodes"); } catch (...) { /* Never mind */ diff --git a/src/lib/ansi.h b/src/lib/ansi.h new file mode 100644 index 000000000..1aa1180ab --- /dev/null +++ b/src/lib/ansi.h @@ -0,0 +1,22 @@ +/* + Copyright (C) 2023 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#define UP_ONE_LINE_AND_ERASE "\033[1A\033[2K" diff --git a/src/lib/atmos_content.h b/src/lib/atmos_content.h index 8edab8c31..afa3c75a2 100644 --- a/src/lib/atmos_content.h +++ b/src/lib/atmos_content.h @@ -20,6 +20,8 @@ #include "content_part.h" +#include "types.h" +#include class Content; diff --git a/src/lib/audio_analyser.cc b/src/lib/audio_analyser.cc index c9fc2118c..6e7b9fa86 100644 --- a/src/lib/audio_analyser.cc +++ b/src/lib/audio_analyser.cc @@ -30,7 +30,6 @@ #include "film.h" #include "filter.h" #include "playlist.h" -#include "types.h" #include extern "C" { #include @@ -53,12 +52,12 @@ using namespace dcpomatic; static auto constexpr num_points = 1024; -AudioAnalyser::AudioAnalyser (shared_ptr film, shared_ptr playlist, bool from_zero, std::function set_progress) +AudioAnalyser::AudioAnalyser(shared_ptr film, shared_ptr playlist, bool whole_film, std::function set_progress) : _film (film) , _playlist (playlist) , _set_progress (set_progress) #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG - , _ebur128 (new AudioFilterGraph(film->audio_frame_rate(), film->audio_channels())) + , _ebur128(film->audio_frame_rate(), film->audio_channels()) #endif , _sample_peak (film->audio_channels()) , _sample_peak_frame (film->audio_channels()) @@ -66,13 +65,13 @@ AudioAnalyser::AudioAnalyser (shared_ptr film, shared_ptrsetup (_filters); + _filters.push_back({"ebur128", "ebur128", "audio", "ebur128=peak=true"}); + _ebur128.setup(_filters); #endif _current = std::vector(_film->audio_channels()); - if (!from_zero) { + if (!whole_film) { _start = _playlist->start().get_value_or(DCPTime()); } @@ -87,14 +86,22 @@ AudioAnalyser::AudioAnalyser (shared_ptr film, shared_ptraudio_channels(); auto content = _playlist->content(); - if (content.size() == 1 && content[0]->audio) { - leqm_channels = content[0]->audio->mapping().mapped_output_channels().size(); + if (whole_film) { + _leqm_channels = film->audio_channels(); + } else { + _leqm_channels = 0; + for (auto channel: content[0]->audio->mapping().mapped_output_channels()) { + /* This means that if, for example, a file only maps C we will + * calculate LEQ(m) for L, R and C. I'm not sure if this is + * right or not. + */ + _leqm_channels = std::min(film->audio_channels(), channel + 1); + } } /* XXX: is this right? Especially for more than 5.1? */ - vector channel_corrections(leqm_channels, 1); + vector channel_corrections(_leqm_channels, 1); add_if_required (channel_corrections, 4, -3); // Ls add_if_required (channel_corrections, 5, -3); // Rs add_if_required (channel_corrections, 6, -144); // HI @@ -109,7 +116,7 @@ AudioAnalyser::AudioAnalyser (shared_ptr film, shared_ptraudio_frame_rate(), 24, channel_corrections, @@ -125,36 +132,32 @@ AudioAnalyser::AudioAnalyser (shared_ptr film, shared_ptr (i); - } -} - - void AudioAnalyser::analyse (shared_ptr b, DCPTime time) { - LOG_DEBUG_AUDIO_ANALYSIS("Received %1 frames at %2", b->frames(), to_string(time)); + LOG_DEBUG_AUDIO_ANALYSIS("AudioAnalyser received %1 frames at %2", b->frames(), to_string(time)); DCPOMATIC_ASSERT (time >= _start); + /* In bug #2364 we had a lot of frames arriving here (~47s worth) which + * caused an OOM error on Windows. Check for the number of frames being + * reasonable here to make sure we catch this if it happens again. + */ + DCPOMATIC_ASSERT(b->frames() < 480000); #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG if (Config::instance()->analyse_ebur128 ()) { - _ebur128->process (b); + _ebur128.process(b); } #endif int const frames = b->frames (); - int const channels = b->channels (); - vector interleaved(frames * channels); + vector interleaved(frames * _leqm_channels); - for (int j = 0; j < channels; ++j) { + for (int j = 0; j < _leqm_channels; ++j) { float const* data = b->data(j); for (int i = 0; i < frames; ++i) { float s = data[i]; - interleaved[i * channels + j] = s; + interleaved[i * _leqm_channels + j] = s; float as = fabsf (s); if (as < 10e-7) { @@ -201,7 +204,7 @@ AudioAnalyser::finish () #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG if (Config::instance()->analyse_ebur128 ()) { - void* eb = _ebur128->get("Parsed_ebur128_0")->priv; + void* eb = _ebur128.get("Parsed_ebur128_0")->priv; vector true_peak; for (int i = 0; i < _film->audio_channels(); ++i) { true_peak.push_back (av_ebur128_get_true_peaks(eb)[i]); diff --git a/src/lib/audio_analyser.h b/src/lib/audio_analyser.h index 01eec36b1..3d40f8026 100644 --- a/src/lib/audio_analyser.h +++ b/src/lib/audio_analyser.h @@ -20,6 +20,7 @@ #include "audio_analysis.h" +#include "audio_filter_graph.h" #include "dcpomatic_time.h" #include "types.h" #include @@ -29,7 +30,6 @@ class AudioAnalysis; class AudioBuffers; -class AudioFilterGraph; class AudioPoint; class Film; class Filter; @@ -39,8 +39,7 @@ class Playlist; class AudioAnalyser { public: - AudioAnalyser (std::shared_ptr film, std::shared_ptr playlist, bool from_zero, std::function set_progress); - ~AudioAnalyser (); + AudioAnalyser(std::shared_ptr film, std::shared_ptr playlist, bool whole_film, std::function set_progress); AudioAnalyser (AudioAnalyser const&) = delete; AudioAnalyser& operator= (AudioAnalyser const&) = delete; @@ -65,12 +64,13 @@ private: dcpomatic::DCPTime _start; #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG - std::shared_ptr _ebur128; + AudioFilterGraph _ebur128; #endif - std::vector _filters; + std::vector _filters; Frame _samples_per_point = 1; boost::scoped_ptr _leqm; + int _leqm_channels = 0; Frame _done = 0; std::vector _sample_peak; std::vector _sample_peak_frame; diff --git a/src/lib/audio_analysis.cc b/src/lib/audio_analysis.cc index 721ffed07..b8c2e072d 100644 --- a/src/lib/audio_analysis.cc +++ b/src/lib/audio_analysis.cc @@ -20,10 +20,10 @@ #include "audio_analysis.h" +#include "audio_content.h" #include "cross.h" -#include "util.h" #include "playlist.h" -#include "audio_content.h" +#include "util.h" #include #include LIBDCP_DISABLE_WARNINGS @@ -66,7 +66,7 @@ AudioAnalysis::AudioAnalysis (int channels) AudioAnalysis::AudioAnalysis (boost::filesystem::path filename) { cxml::Document f ("AudioAnalysis"); - f.read_file (filename); + f.read_file(dcp::filesystem::fix_long_path(filename)); if (f.optional_number_child("Version").get_value_or(1) < _current_state_version) { /* Too old. Throw an exception so that this analysis is re-run. */ diff --git a/src/lib/audio_buffers.cc b/src/lib/audio_buffers.cc index 119a499b4..9b88827f7 100644 --- a/src/lib/audio_buffers.cc +++ b/src/lib/audio_buffers.cc @@ -22,7 +22,7 @@ #include "audio_buffers.h" #include "dcpomatic_assert.h" #include "maths_util.h" -#include "scope_guard.h" +#include #include #include #include @@ -81,9 +81,9 @@ void AudioBuffers::allocate (int channels, int frames) { DCPOMATIC_ASSERT (frames >= 0); - DCPOMATIC_ASSERT (channels > 0); + DCPOMATIC_ASSERT(frames == 0 || channels > 0); - ScopeGuard sg = [this]() { update_data_pointers(); }; + dcp::ScopeGuard sg = [this]() { update_data_pointers(); }; _data.resize(channels); for (int channel = 0; channel < channels; ++channel) { @@ -335,3 +335,22 @@ AudioBuffers::update_data_pointers () } } + +/** Set a new channel count, either discarding data (if new_channels is less than the current + * channels()), or filling with silence (if new_channels is more than the current channels() + */ +void +AudioBuffers::set_channels(int new_channels) +{ + DCPOMATIC_ASSERT(new_channels > 0); + + dcp::ScopeGuard sg = [this]() { update_data_pointers(); }; + + int const old_channels = channels(); + _data.resize(new_channels); + + for (int channel = old_channels; channel < new_channels; ++channel) { + _data[channel].resize(frames()); + } +} + diff --git a/src/lib/audio_buffers.h b/src/lib/audio_buffers.h index 4db0fa255..b43179663 100644 --- a/src/lib/audio_buffers.h +++ b/src/lib/audio_buffers.h @@ -60,11 +60,13 @@ public: } int frames () const { - return _data[0].size(); + return _data.empty() ? 0 : _data[0].size(); } void set_frames (int f); + void set_channels(int new_channels); + void make_silent (); void make_silent (int channel); void make_silent (int from, int frames); diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc index 82a6a84d1..16f2bd5f1 100644 --- a/src/lib/audio_content.cc +++ b/src/lib/audio_content.cc @@ -22,6 +22,7 @@ #include "audio_content.h" #include "compose.hpp" #include "config.h" +#include "constants.h" #include "exceptions.h" #include "film.h" #include "frame_rate_change.h" @@ -97,12 +98,6 @@ AudioContent::AudioContent (Content* parent, cxml::ConstNodePtr node) _fade_in = ContentTime(node->optional_number_child("AudioFadeIn").get_value_or(0)); _fade_out = ContentTime(node->optional_number_child("AudioFadeOut").get_value_or(0)); _use_same_fades_as_video = node->optional_bool_child("AudioUseSameFadesAsVideo").get_value_or(false); - - /* Backwards compatibility */ - auto r = node->optional_number_child("AudioVideoFrameRate"); - if (r) { - _parent->set_video_frame_rate (r.get()); - } } @@ -197,7 +192,6 @@ AudioContent::mapping () const merged.make_zero (); int c = 0; - int s = 0; for (auto i: streams()) { auto mapping = i->mapping (); for (int j = 0; j < mapping.input_channels(); ++j) { @@ -208,7 +202,6 @@ AudioContent::mapping () const } ++c; } - ++s; } return merged; @@ -318,7 +311,10 @@ AudioContent::add_properties (shared_ptr film, list& p if (stream) { p.push_back (UserProperty(UserProperty::AUDIO, _("Channels"), stream->channels())); - p.push_back (UserProperty(UserProperty::AUDIO, _("Content audio sample rate"), stream->frame_rate(), _("Hz"))); + p.push_back (UserProperty(UserProperty::AUDIO, _("Content sample rate"), stream->frame_rate(), _("Hz"))); + if (auto bits = stream->bit_depth()) { + p.push_back(UserProperty(UserProperty::AUDIO, _("Content bit depth"), *bits, _("bits"))); + } } FrameRateChange const frc (_parent->active_video_frame_rate(film), film->video_frame_rate()); @@ -353,18 +349,6 @@ AudioContent::add_properties (shared_ptr film, list& p } -void -AudioContent::set_streams (vector streams) -{ - ContentChangeSignaller cc (_parent, AudioContentProperty::STREAMS); - - { - boost::mutex::scoped_lock lm (_mutex); - _streams = streams; - } -} - - AudioStreamPtr AudioContent::stream () const { @@ -407,13 +391,12 @@ AudioContent::take_settings_from (shared_ptr c) set_fade_in (c->fade_in()); set_fade_out (c->fade_out()); - size_t i = 0; - size_t j = 0; + auto const streams_to_take = std::min(_streams.size(), c->_streams.size()); - while (i < _streams.size() && j < c->_streams.size()) { - _streams[i]->set_mapping (c->_streams[j]->mapping()); - ++i; - ++j; + for (auto i = 0U; i < streams_to_take; ++i) { + auto mapping = _streams[i]->mapping(); + mapping.take_from(c->_streams[i]->mapping()); + _streams[i]->set_mapping(mapping); } } @@ -426,11 +409,13 @@ AudioContent::modify_position (shared_ptr film, DCPTime& pos) const void -AudioContent::modify_trim_start (ContentTime& trim) const +AudioContent::modify_trim_start(shared_ptr film, ContentTime& trim) const { - DCPOMATIC_ASSERT (!_streams.empty()); - /* XXX: we're in trouble if streams have different rates */ - trim = trim.round (_streams.front()->frame_rate()); + /* When this trim is used it the audio will have been resampled, and using the + * DCP rate here reduces the chance of rounding errors causing audio glitches + * due to errors in placement of audio frames (#2373). + */ + trim = trim.round(film ? film->audio_frame_rate() : 48000); } diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h index 82a9de041..084871c8b 100644 --- a/src/lib/audio_content.h +++ b/src/lib/audio_content.h @@ -28,9 +28,10 @@ #define DCPOMATIC_AUDIO_CONTENT_H -#include "content_part.h" -#include "audio_stream.h" #include "audio_mapping.h" +#include "audio_stream.h" +#include "content_part.h" +#include "named_channel.h" /** @class AudioContentProperty @@ -95,13 +96,12 @@ public: void add_stream (AudioStreamPtr stream); void set_stream (AudioStreamPtr stream); - void set_streams (std::vector streams); AudioStreamPtr stream () const; void add_properties (std::shared_ptr film, std::list &) const; void modify_position (std::shared_ptr film, dcpomatic::DCPTime& pos) const; - void modify_trim_start (dcpomatic::ContentTime& pos) const; + void modify_trim_start(std::shared_ptr film, dcpomatic::ContentTime& pos) const; /** @param frame frame within the whole (untrimmed) content. * @param frame_rate The frame rate of the audio (it may have been resampled). diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index ca1faa010..61ff5d265 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -52,14 +52,14 @@ AudioDecoder::AudioDecoder (Decoder* parent, shared_ptr cont /** @param time_already_delayed true if the delay should not be added to time */ void -AudioDecoder::emit (shared_ptr film, AudioStreamPtr stream, shared_ptr data, ContentTime time, bool time_already_delayed) +AudioDecoder::emit(shared_ptr film, AudioStreamPtr stream, shared_ptr data, ContentTime time, bool flushing) { if (ignore ()) { return; } int const resampled_rate = _content->resampled_frame_rate(film); - if (!time_already_delayed) { + if (!flushing) { time += ContentTime::from_seconds (_content->delay() / 1000.0); } @@ -119,12 +119,24 @@ AudioDecoder::emit (shared_ptr film, AudioStreamPtr stream, shared_p } } - if (resampler) { - auto ro = resampler->run (data); - if (ro->frames() == 0) { + if (resampler && !flushing) { + /* It can be the the data here has a different number of channels than the stream + * it comes from (e.g. the files decoded by FFmpegDecoder sometimes have a random + * frame, often at the end, with more channels). Insert silence or discard channels + * here. + */ + if (resampler->channels() != data->channels()) { + LOG_WARNING("Received audio data with an unexpected channel count of %1 instead of %2", data->channels(), resampler->channels()); + auto data_copy = data->clone(); + data_copy->set_channels(resampler->channels()); + data = resampler->run(data_copy); + } else { + data = resampler->run(data); + } + + if (data->frames() == 0) { return; } - data = ro; } Data(stream, ContentAudio (data, _positions[stream])); diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index a8495aaa8..7417fee44 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -52,7 +52,7 @@ public: AudioDecoder (Decoder* parent, std::shared_ptr content, bool fast); boost::optional position (std::shared_ptr film) const override; - void emit (std::shared_ptr film, AudioStreamPtr stream, std::shared_ptr, dcpomatic::ContentTime, bool time_already_delayed = false); + void emit(std::shared_ptr film, AudioStreamPtr stream, std::shared_ptr, dcpomatic::ContentTime, bool flushing = false); void seek () override; void flush (); diff --git a/src/lib/audio_examiner.h b/src/lib/audio_examiner.h index a1d952c35..8f5646d0f 100644 --- a/src/lib/audio_examiner.h +++ b/src/lib/audio_examiner.h @@ -21,12 +21,14 @@ #ifndef DCPOMATIC_AUDIO_EXAMINER_H #define DCPOMATIC_AUDIO_EXAMINER_H + +#include "types.h" + + /** @file src/lib/audio_examiner.h * @brief AudioExaminer class. */ -#include "types.h" - /** @class AudioExaminer * @brief Parent for classes which examine AudioContent for their pertinent details. */ diff --git a/src/lib/audio_filter_graph.cc b/src/lib/audio_filter_graph.cc index d9e4e244f..4e3052d57 100644 --- a/src/lib/audio_filter_graph.cc +++ b/src/lib/audio_filter_graph.cc @@ -22,6 +22,8 @@ #include "audio_buffers.h" #include "audio_filter_graph.h" #include "compose.hpp" +#include "dcpomatic_assert.h" +#include "exceptions.h" extern "C" { #include #include @@ -53,6 +55,9 @@ AudioFilterGraph::AudioFilterGraph (int sample_rate, int channels) } _in_frame = av_frame_alloc (); + if (_in_frame == nullptr) { + throw std::bad_alloc(); + } } AudioFilterGraph::~AudioFilterGraph() diff --git a/src/lib/audio_filter_graph.h b/src/lib/audio_filter_graph.h index e749d0ec9..e5c55fa27 100644 --- a/src/lib/audio_filter_graph.h +++ b/src/lib/audio_filter_graph.h @@ -18,6 +18,11 @@ */ + +#ifndef DCPOMATIC_AUDIO_FILTER_GRAPH_H +#define DCPOMATIC_AUDIO_FILTER_GRAPH_H + + #include "filter_graph.h" extern "C" { #include @@ -45,3 +50,7 @@ private: int64_t _channel_layout; AVFrame* _in_frame; }; + + +#endif + diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc index 5e8bf4d04..b8aa6249f 100644 --- a/src/lib/audio_mapping.cc +++ b/src/lib/audio_mapping.cc @@ -21,8 +21,9 @@ #include "audio_mapping.h" #include "audio_processor.h" +#include "constants.h" +#include "dcpomatic_assert.h" #include "digester.h" -#include "util.h" #include #include #include @@ -60,12 +61,9 @@ AudioMapping::AudioMapping (int input_channels, int output_channels) void AudioMapping::setup (int input_channels, int output_channels) { - _input_channels = input_channels; - _output_channels = output_channels; - - _gain.resize (_input_channels); - for (int i = 0; i < _input_channels; ++i) { - _gain[i].resize (_output_channels); + _gain.resize(input_channels); + for (int i = 0; i < input_channels; ++i) { + _gain[i].resize(output_channels); } make_zero (); @@ -75,9 +73,9 @@ AudioMapping::setup (int input_channels, int output_channels) void AudioMapping::make_zero () { - for (int i = 0; i < _input_channels; ++i) { - for (int j = 0; j < _output_channels; ++j) { - _gain[i][j] = 0; + for (auto& input: _gain) { + for (auto& output: input) { + output = 0; } } } @@ -106,9 +104,11 @@ AudioMapping::make_default (AudioProcessor const * processor, optionaladd_child ("InputChannels")->add_child_text (raw_convert (_input_channels)); - node->add_child ("OutputChannels")->add_child_text (raw_convert (_output_channels)); + auto const input = input_channels(); + auto const output = output_channels(); + + node->add_child("InputChannels")->add_child_text(raw_convert(input)); + node->add_child("OutputChannels")->add_child_text(raw_convert(output)); - for (int c = 0; c < _input_channels; ++c) { - for (int d = 0; d < _output_channels; ++d) { + for (int c = 0; c < input; ++c) { + for (int d = 0; d < output; ++d) { auto t = node->add_child ("Gain"); t->set_attribute ("Input", raw_convert (c)); t->set_attribute ("Output", raw_convert (d)); @@ -242,11 +245,11 @@ string AudioMapping::digest () const { Digester digester; - digester.add (_input_channels); - digester.add (_output_channels); - for (int i = 0; i < _input_channels; ++i) { - for (int j = 0; j < _output_channels; ++j) { - digester.add (_gain[i][j]); + digester.add(input_channels()); + digester.add(output_channels()); + for (auto const& input: _gain) { + for (auto output: input) { + digester.add(output); } } @@ -285,3 +288,18 @@ AudioMapping::unmap_all () } } } + + +void +AudioMapping::take_from(AudioMapping const& other) +{ + auto input = std::min(input_channels(), other.input_channels()); + auto output = std::min(output_channels(), other.output_channels()); + + for (auto i = 0; i < input; ++i) { + for (auto o = 0; o < output; ++o) { + set(i, o, other.get(i, o)); + } + } +} + diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h index 51ab0e1e5..fe9a79789 100644 --- a/src/lib/audio_mapping.h +++ b/src/lib/audio_mapping.h @@ -64,11 +64,11 @@ public: float get (int input_channel, dcp::Channel output_channel) const; int input_channels () const { - return _input_channels; + return _gain.size(); } int output_channels () const { - return _output_channels; + return _gain.empty() ? 0 : _gain[0].size(); } std::string digest () const; @@ -76,11 +76,11 @@ public: std::list mapped_output_channels () const; void unmap_all (); + void take_from(AudioMapping const& other); + private: void setup (int input_channels, int output_channels); - int _input_channels = 0; - int _output_channels = 0; /** Linear gains */ std::vector> _gain; }; diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h index 0201fcd4b..a6b7637f6 100644 --- a/src/lib/audio_merger.h +++ b/src/lib/audio_merger.h @@ -73,5 +73,5 @@ private: }; std::list _buffers; - int const _frame_rate; + int _frame_rate; }; diff --git a/src/lib/audio_processor.h b/src/lib/audio_processor.h index e24506acd..4cfda1def 100644 --- a/src/lib/audio_processor.h +++ b/src/lib/audio_processor.h @@ -28,7 +28,7 @@ #define DCPOMATIC_AUDIO_PROCESSOR_H -#include "types.h" +#include "named_channel.h" #include #include #include diff --git a/src/lib/audio_ring_buffers.h b/src/lib/audio_ring_buffers.h index 6fb84e0d7..376ff92ee 100644 --- a/src/lib/audio_ring_buffers.h +++ b/src/lib/audio_ring_buffers.h @@ -24,8 +24,8 @@ #include "audio_buffers.h" -#include "types.h" #include "dcpomatic_time.h" +#include "types.h" #include #include diff --git a/src/lib/audio_stream.cc b/src/lib/audio_stream.cc index 43e4c5ec1..f771d44a2 100644 --- a/src/lib/audio_stream.cc +++ b/src/lib/audio_stream.cc @@ -21,22 +21,27 @@ #include "audio_stream.h" #include "audio_mapping.h" -#include "util.h" +#include "constants.h" -AudioStream::AudioStream (int frame_rate, Frame length, int channels) +using boost::optional; + + +AudioStream::AudioStream(int frame_rate, Frame length, int channels, optional bit_depth) : _frame_rate (frame_rate) , _length (length) , _mapping (AudioMapping (channels, MAX_DCP_AUDIO_CHANNELS)) + , _bit_depth(bit_depth) { } -AudioStream::AudioStream (int frame_rate, Frame length, AudioMapping mapping) +AudioStream::AudioStream(int frame_rate, Frame length, AudioMapping mapping, optional bit_depth) : _frame_rate (frame_rate) , _length (length) , _mapping (mapping) + , _bit_depth(bit_depth) { } @@ -56,3 +61,11 @@ AudioStream::channels () const boost::mutex::scoped_lock lm (_mutex); return _mapping.input_channels (); } + +optional +AudioStream::bit_depth() const +{ + boost::mutex::scoped_lock lm(_mutex); + return _bit_depth; +} + diff --git a/src/lib/audio_stream.h b/src/lib/audio_stream.h index 470d9c854..cf874242f 100644 --- a/src/lib/audio_stream.h +++ b/src/lib/audio_stream.h @@ -33,8 +33,8 @@ struct audio_sampling_rate_test; class AudioStream { public: - AudioStream (int frame_rate, Frame length, int channels); - AudioStream (int frame_rate, Frame length, AudioMapping mapping); + AudioStream(int frame_rate, Frame length, int channels, boost::optional bit_depth); + AudioStream(int frame_rate, Frame length, AudioMapping mapping, boost::optional bit_depth); virtual ~AudioStream () {} void set_mapping (AudioMapping mapping); @@ -55,6 +55,7 @@ public: } int channels () const; + boost::optional bit_depth() const; protected: mutable boost::mutex _mutex; @@ -66,6 +67,7 @@ private: int _frame_rate; Frame _length; AudioMapping _mapping; + boost::optional _bit_depth; }; diff --git a/src/lib/butler.cc b/src/lib/butler.cc index ce35b1f39..b2fbc6c60 100644 --- a/src/lib/butler.cc +++ b/src/lib/butler.cc @@ -62,7 +62,7 @@ using namespace boost::placeholders; */ Butler::Butler ( weak_ptr film, - shared_ptr player, + Player& player, AudioMapping audio_mapping, int audio_channels, function pixel_format, @@ -89,13 +89,13 @@ Butler::Butler ( , _fast (fast) , _prepare_only_proxy (prepare_only_proxy) { - _player_video_connection = _player->Video.connect (bind (&Butler::video, this, _1, _2)); - _player_audio_connection = _player->Audio.connect (bind (&Butler::audio, this, _1, _2, _3)); - _player_text_connection = _player->Text.connect (bind (&Butler::text, this, _1, _2, _3, _4)); + _player_video_connection = _player.Video.connect(bind(&Butler::video, this, _1, _2)); + _player_audio_connection = _player.Audio.connect(bind(&Butler::audio, this, _1, _2, _3)); + _player_text_connection = _player.Text.connect(bind(&Butler::text, this, _1, _2, _3, _4)); /* The butler must hear about things first, otherwise it might not sort out suspensions in time for get_video() to be called in response to this signal. */ - _player_change_connection = _player->Change.connect (bind (&Butler::player_change, this, _1, _2), boost::signals2::at_front); + _player_change_connection = _player.Change.connect(bind(&Butler::player_change, this, _1, _2), boost::signals2::at_front); _thread = boost::thread (bind(&Butler::thread, this)); #ifdef DCPOMATIC_LINUX pthread_setname_np (_thread.native_handle(), "butler"); @@ -200,7 +200,7 @@ try /* Do any seek that has been requested */ if (_pending_seek_position) { _finished = false; - _player->seek (*_pending_seek_position, _pending_seek_accurate); + _player.seek(*_pending_seek_position, _pending_seek_accurate); _pending_seek_position = optional (); } @@ -210,7 +210,7 @@ try */ while (should_run() && !_pending_seek_position) { lm.unlock (); - bool const r = _player->pass (); + bool const r = _player.pass(); lm.lock (); if (r) { _finished = true; @@ -410,7 +410,7 @@ Butler::player_change (ChangeType type, int property) if (type == ChangeType::DONE) { auto film = _film.lock(); if (film) { - _video.reset_metadata (film, _player->video_container_size()); + _video.reset_metadata(film, _player.video_container_size()); } } return; diff --git a/src/lib/butler.h b/src/lib/butler.h index 87408646b..6bb0467af 100644 --- a/src/lib/butler.h +++ b/src/lib/butler.h @@ -19,11 +19,16 @@ */ +#ifndef DCPOMATIC_BUTLER_H +#define DCPOMATIC_BUTLER_H + + #include "audio_mapping.h" #include "audio_ring_buffers.h" #include "change_signaller.h" #include "exception_store.h" #include "text_ring_buffers.h" +#include "text_type.h" #include "video_ring_buffers.h" #include #include @@ -46,7 +51,7 @@ public: Butler ( std::weak_ptr film, - std::shared_ptr player, + Player& player, AudioMapping map, int audio_channels, std::function pixel_format, @@ -101,7 +106,7 @@ private: void seek_unlocked (dcpomatic::DCPTime position, bool accurate); std::weak_ptr _film; - std::shared_ptr _player; + Player& _player; boost::thread _thread; VideoRingBuffers _video; @@ -152,3 +157,7 @@ private: boost::signals2::scoped_connection _player_text_connection; boost::signals2::scoped_connection _player_change_connection; }; + + +#endif + diff --git a/src/lib/change_signaller.cc b/src/lib/change_signaller.cc new file mode 100644 index 000000000..a1f093d14 --- /dev/null +++ b/src/lib/change_signaller.cc @@ -0,0 +1,24 @@ +/* + Copyright (C) 2018-2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "change_signaller.h" + + diff --git a/src/lib/change_signaller.h b/src/lib/change_signaller.h index 8612cf426..1d7d482df 100644 --- a/src/lib/change_signaller.h +++ b/src/lib/change_signaller.h @@ -23,6 +23,10 @@ #define DCPOMATIC_CHANGE_SIGNALLER_H +#include +#include + + enum class ChangeType { PENDING, @@ -31,30 +35,104 @@ enum class ChangeType }; +template +class ChangeSignal +{ +public: + ChangeSignal(T* thing_, P property_, ChangeType type_) + : thing(thing_) + , property(property_) + , type(type_) + {} + + T* thing; + P property; + ChangeType type; +}; + + +class ChangeSignalDespatcherBase +{ +protected: + static boost::mutex _instance_mutex; +}; + + +template +class ChangeSignalDespatcher : public ChangeSignalDespatcherBase +{ +public: + ChangeSignalDespatcher() = default; + + ChangeSignalDespatcher(ChangeSignalDespatcher const&) = delete; + ChangeSignalDespatcher& operator=(ChangeSignalDespatcher const&) = delete; + + void signal_change(ChangeSignal const& signal) + { + if (_suspended) { + boost::mutex::scoped_lock lm(_mutex); + _pending.push_back(signal); + } else { + signal.thing->signal_change(signal.type, signal.property); + } + } + + void suspend() + { + boost::mutex::scoped_lock lm(_mutex); + _suspended = true; + } + + void resume() + { + boost::mutex::scoped_lock lm(_mutex); + auto pending = _pending; + lm.unlock(); + + for (auto signal: pending) { + signal.thing->signal_change(signal.type, signal.property); + } + + lm.lock(); + _pending.clear(); + _suspended = false; + } + + static ChangeSignalDespatcher* instance() + { + static boost::mutex _instance_mutex; + static boost::mutex::scoped_lock lm(_instance_mutex); + static ChangeSignalDespatcher* _instance; + if (!_instance) { + _instance = new ChangeSignalDespatcher(); + } + return _instance; + } + +private: + std::vector> _pending; + bool _suspended = false; + boost::mutex _mutex; +}; + + template class ChangeSignaller { public: ChangeSignaller (T* t, P p) - : _thing (t) - , _property (p) - , _done (true) + : _thing(t) + , _property(p) + , _done(true) { - _thing->signal_change (ChangeType::PENDING, _property); + ChangeSignalDespatcher::instance()->signal_change({_thing, _property, ChangeType::PENDING}); } ~ChangeSignaller () { - if (_done) { - _thing->signal_change (ChangeType::DONE, _property); - } else { - _thing->signal_change (ChangeType::CANCELLED, _property); - } + ChangeSignalDespatcher::instance()->signal_change({_thing, _property, _done ? ChangeType::DONE : ChangeType::CANCELLED}); } - ChangeSignaller (ChangeSignaller const&) = delete; - ChangeSignaller& operator= (ChangeSignaller const&) = delete; - void abort () { _done = false; diff --git a/src/lib/cinema.cc b/src/lib/cinema.cc index 7e13b50ae..3b4b9d7b6 100644 --- a/src/lib/cinema.cc +++ b/src/lib/cinema.cc @@ -25,13 +25,11 @@ #include #include #include -#include -using std::list; -using std::string; -using std::shared_ptr; using std::make_shared; +using std::shared_ptr; +using std::string; using dcp::raw_convert; using dcpomatic::Screen; @@ -93,7 +91,10 @@ Cinema::add_screen (shared_ptr s) void Cinema::remove_screen (shared_ptr s) { - _screens.remove (s); + auto iter = std::find(_screens.begin(), _screens.end(), s); + if (iter != _screens.end()) { + _screens.erase(iter); + } } void diff --git a/src/lib/cinema.h b/src/lib/cinema.h index c17454db9..6c202a7bf 100644 --- a/src/lib/cinema.h +++ b/src/lib/cinema.h @@ -44,7 +44,7 @@ namespace dcpomatic { class Cinema : public std::enable_shared_from_this { public: - Cinema (std::string const & name_, std::list const & e, std::string notes_, int utc_offset_hour, int utc_offset_minute) + Cinema(std::string const & name_, std::vector const & e, std::string notes_, int utc_offset_hour, int utc_offset_minute) : name (name_) , emails (e) , notes (notes_) @@ -65,7 +65,7 @@ public: void set_utc_offset_minute (int m); std::string name; - std::list emails; + std::vector emails; std::string notes; int utc_offset_hour () const { @@ -76,12 +76,12 @@ public: return _utc_offset_minute; } - std::list> screens () const { + std::vector> screens() const { return _screens; } private: - std::list> _screens; + std::vector> _screens; /** Offset such that the equivalent time in UTC can be determined by subtracting the offset from the local time. */ diff --git a/src/lib/collator.cc b/src/lib/collator.cc new file mode 100644 index 000000000..8de1857ab --- /dev/null +++ b/src/lib/collator.cc @@ -0,0 +1,102 @@ +/* + Copyright (C) 2022 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "collator.h" +#include "dcpomatic_assert.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using std::string; +using std::vector; + + +Collator::Collator(char const* locale) +{ + UErrorCode status = U_ZERO_ERROR; + _collator = ucol_open(locale, &status); + if (_collator) { + ucol_setAttribute(_collator, UCOL_NORMALIZATION_MODE, UCOL_ON, &status); + /* Ignore case and character encoding (and probably some other things) */ + ucol_setAttribute(_collator, UCOL_STRENGTH, UCOL_PRIMARY, &status); + ucol_setAttribute(_collator, UCOL_ALTERNATE_HANDLING, UCOL_SHIFTED, &status); + } +} + + +Collator::~Collator() +{ + if (_collator) { + ucol_close (_collator); + } +} + + +vector +utf8_to_utf16(string const& utf8) +{ + vector utf16(utf8.size() + 1); + UErrorCode error = U_ZERO_ERROR; + u_strFromUTF8(utf16.data(), utf8.size() + 1, nullptr, utf8.c_str(), -1, &error); + DCPOMATIC_ASSERT(error == U_ZERO_ERROR); + return utf16; +} + + +int +Collator::compare (string const& utf8_a, string const& utf8_b) const +{ + if (_collator) { + auto utf16_a = utf8_to_utf16(utf8_a); + auto utf16_b = utf8_to_utf16(utf8_b); + return ucol_strcoll(_collator, utf16_a.data(), -1, utf16_b.data(), -1); + } else { + return strcoll(utf8_a.c_str(), utf8_b.c_str()); + } +} + + +bool +Collator::find(string pattern, string text) const +{ + if (_collator) { + auto utf16_pattern = utf8_to_utf16(pattern); + auto utf16_text = utf8_to_utf16(text); + UErrorCode status = U_ZERO_ERROR; + auto search = usearch_openFromCollator(utf16_pattern.data(), -1, utf16_text.data(), -1, _collator, nullptr, &status); + DCPOMATIC_ASSERT(search); + auto const index = usearch_first(search, &status); + usearch_close(search); + return index != -1; + } else { + transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower); + transform(text.begin(), text.end(), text.begin(), ::tolower); + return pattern.find(text) != string::npos; + } +} + diff --git a/src/lib/collator.h b/src/lib/collator.h new file mode 100644 index 000000000..3cbb40748 --- /dev/null +++ b/src/lib/collator.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 2022 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#ifndef DCPOMATIC_COLLATOR_H +#define DCPOMATIC_COLLATOR_H + + +#include + + +struct UCollator; + + +class Collator +{ +public: + Collator(char const* locale = nullptr); + ~Collator(); + + Collator(Collator const &) = delete; + Collator& operator=(Collator const&) = delete; + + int compare(std::string const& utf8_a, std::string const& utf8_b) const; + bool find(std::string pattern, std::string text) const; + +private: + UCollator* _collator = nullptr; +}; + + +#endif + diff --git a/src/lib/colour_conversion.cc b/src/lib/colour_conversion.cc index b0c81e91c..bd1b47cb1 100644 --- a/src/lib/colour_conversion.cc +++ b/src/lib/colour_conversion.cc @@ -235,6 +235,8 @@ ColourConversion::identifier () const digester.add (_adjusted_white.get().y); } + digester.add(static_cast(_yuv_to_rgb)); + auto gf = dynamic_pointer_cast (_out); if (gf) { digester.add (gf->gamma ()); diff --git a/src/lib/config.cc b/src/lib/config.cc index eeb009594..384db5cde 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -23,8 +23,8 @@ #include "colour_conversion.h" #include "compose.hpp" #include "config.h" +#include "constants.h" #include "cross.h" -#include "crypto.h" #include "dcp_content_type.h" #include "dkdm_recipient.h" #include "dkdm_wrapper.h" @@ -32,8 +32,7 @@ #include "filter.h" #include "log.h" #include "ratio.h" -#include "types.h" -#include "util.h" +#include "unzipper.h" #include "zipper.h" #include #include @@ -94,6 +93,7 @@ Config::set_defaults () _servers.clear (); _only_servers_encode = false; _tms_protocol = FileTransferProtocol::SCP; + _tms_passive = true; _tms_ip = ""; _tms_path = "."; _tms_user = ""; @@ -105,9 +105,8 @@ Config::set_defaults () _show_experimental_audio_processors = false; _language = optional (); _default_still_length = 10; - _default_container = Ratio::from_id ("185"); _default_dcp_content_type = DCPContentType::from_isdcf_name ("FTR"); - _default_dcp_audio_channels = 6; + _default_dcp_audio_channels = 8; _default_j2k_bandwidth = 150000000; _default_audio_delay = 0; _default_interop = false; @@ -141,9 +140,9 @@ Config::set_defaults () _dkdm_recipients_file = read_path ("dkdm_recipients.xml"); _show_hints_before_make_dcp = true; _confirm_kdm_email = true; - _kdm_container_name_format = dcp::NameFormat ("KDM %f %c"); - _kdm_filename_format = dcp::NameFormat ("KDM %f %c %s"); - _dkdm_filename_format = dcp::NameFormat ("DKDM %f %c %s"); + _kdm_container_name_format = dcp::NameFormat("KDM_%f_%c"); + _kdm_filename_format = dcp::NameFormat("KDM_%f_%c_%s"); + _dkdm_filename_format = dcp::NameFormat("DKDM_%f_%c_%s"); _dcp_metadata_filename_format = dcp::NameFormat ("%t"); _dcp_asset_filename_format = dcp::NameFormat ("%t"); _jump_to_selected = true; @@ -154,6 +153,7 @@ Config::set_defaults () _sound_output = optional (); _last_kdm_write_type = KDM_WRITE_FLAT; _last_dkdm_write_type = DKDM_WRITE_INTERNAL; + _default_add_file_location = DefaultAddFileLocation::SAME_AS_LAST_TIME; /* I think the scaling factor here should be the ratio of the longest frame encode time to the shortest; if the thread count is T, longest time is L @@ -186,7 +186,18 @@ Config::set_defaults () _player_kdm_directory = boost::none; _audio_mapping = boost::none; _custom_languages.clear (); - _add_files_path = boost::none; + _initial_paths.clear(); + _initial_paths["AddFilesPath"] = boost::none; + _initial_paths["AddKDMPath"] = boost::none; + _initial_paths["AddDKDMPath"] = boost::none; + _initial_paths["SelectCertificatePath"] = boost::none; + _initial_paths["AddCombinerInputPath"] = boost::none; + _initial_paths["ExportSubtitlesPath"] = boost::none; + _initial_paths["ExportVideoPath"] = boost::none; + _initial_paths["DebugLogPath"] = boost::none; + _initial_paths["CinemaDatabasePath"] = boost::none; + _initial_paths["ConfigFilePath"] = boost::none; + _initial_paths["Preferences"] = boost::none; _use_isdcf_name_by_default = true; _write_kdms_to_disk = true; _email_kdms = false; @@ -194,6 +205,8 @@ Config::set_defaults () _default_kdm_duration = RoughDuration(1, RoughDuration::Unit::WEEKS); _auto_crop_threshold = 0.1; _last_release_notes_version = boost::none; + _allow_smpte_bv20 = false; + _isdcf_name_part_length = 14; _allowed_dcp_frame_rates.clear (); _allowed_dcp_frame_rates.push_back (24); @@ -207,6 +220,9 @@ Config::set_defaults () set_notification_email_to_default (); set_cover_sheet_to_default (); + _main_divider_sash_position = {}; + _main_content_divider_sash_position = {}; + _export.set_defaults(); } @@ -282,7 +298,7 @@ Config::read_config() try { cxml::Document f ("Config"); - f.read_file (config_read_file()); + f.read_file(dcp::filesystem::fix_long_path(config_read_file())); auto version = f.optional_number_child ("Version"); if (version && *version < _current_version) { @@ -322,6 +338,7 @@ try _only_servers_encode = f.optional_bool_child ("OnlyServersEncode").get_value_or (false); _tms_protocol = static_cast(f.optional_number_child("TMSProtocol").get_value_or(static_cast(FileTransferProtocol::SCP))); + _tms_passive = f.optional_bool_child("TMSPassive").get_value_or(true); _tms_ip = f.string_child ("TMSIP"); _tms_path = f.string_child ("TMSPath"); _tms_user = f.string_child ("TMSUser"); @@ -329,16 +346,6 @@ try _language = f.optional_string_child ("Language"); - auto c = f.optional_string_child ("DefaultContainer"); - if (c) { - _default_container = Ratio::from_id (c.get ()); - } - - if (_default_container && !_default_container->used_for_container()) { - Warning (_("Your default container is not valid and has been changed to Flat (1.85:1)")); - _default_container = Ratio::from_id ("185"); - } - _default_dcp_content_type = DCPContentType::from_isdcf_name(f.optional_string_child("DefaultDCPContentType").get_value_or("FTR")); _default_dcp_audio_channels = f.optional_number_child("DefaultDCPAudioChannels").get_value_or (6); @@ -364,6 +371,20 @@ try _default_audio_delay = f.optional_number_child("DefaultAudioDelay").get_value_or (0); _default_interop = f.optional_bool_child("DefaultInterop").get_value_or (false); + try { + auto al = f.optional_string_child("DefaultAudioLanguage"); + if (al) { + _default_audio_language = dcp::LanguageTag(*al); + } + } catch (std::runtime_error&) {} + + try { + auto te = f.optional_string_child("DefaultTerritory"); + if (te) { + _default_territory = dcp::LanguageTag::RegionSubtag(*te); + } + } catch (std::runtime_error&) {} + for (auto const& i: f.node_children("DefaultMetadata")) { _default_metadata[i->string_attribute("key")] = i->content(); } @@ -487,6 +508,7 @@ try case BAD_SIGNER_UTF8_STRINGS: case BAD_SIGNER_INCONSISTENT: case BAD_SIGNER_VALIDITY_TOO_LONG: + case BAD_SIGNER_DN_QUALIFIER: _signer_chain = create_certificate_chain (); break; case BAD_DECRYPTION_INCONSISTENT: @@ -592,7 +614,9 @@ try } catch (std::runtime_error& e) {} } - _add_files_path = f.optional_string_child("AddFilesPath"); + for (auto& initial: _initial_paths) { + initial.second = f.optional_string_child(initial.first); + } _use_isdcf_name_by_default = f.optional_bool_child("UseISDCFNameByDefault").get_value_or(true); _write_kdms_to_disk = f.optional_bool_child("WriteKDMsToDisk").get_value_or(true); _email_kdms = f.optional_bool_child("EmailKDMs").get_value_or(false); @@ -604,6 +628,19 @@ try } _auto_crop_threshold = f.optional_number_child("AutoCropThreshold").get_value_or(0.1); _last_release_notes_version = f.optional_string_child("LastReleaseNotesVersion"); + _main_divider_sash_position = f.optional_number_child("MainDividerSashPosition"); + _main_content_divider_sash_position = f.optional_number_child("MainContentDividerSashPosition"); + + if (auto loc = f.optional_string_child("DefaultAddFileLocation")) { + if (*loc == "last") { + _default_add_file_location = DefaultAddFileLocation::SAME_AS_LAST_TIME; + } else if (*loc == "project") { + _default_add_file_location = DefaultAddFileLocation::SAME_AS_PROJECT; + } + } + + _allow_smpte_bv20 = f.optional_bool_child("AllowSMPTEBv20").get_value_or(false); + _isdcf_name_part_length = f.optional_number_child("ISDCFNamePartLength").get_value_or(14); _export.read(f.optional_node_child("Export")); } @@ -625,10 +662,10 @@ catch (...) { void Config::read_cinemas() { - if (boost::filesystem::exists (_cinemas_file)) { + if (dcp::filesystem::exists(_cinemas_file)) { try { cxml::Document f("Cinemas"); - f.read_file(_cinemas_file); + f.read_file(dcp::filesystem::fix_long_path(_cinemas_file)); read_cinemas(f); } catch (...) { backup(); @@ -642,10 +679,10 @@ Config::read_cinemas() void Config::read_dkdm_recipients() { - if (boost::filesystem::exists (_dkdm_recipients_file)) { + if (dcp::filesystem::exists(_dkdm_recipients_file)) { try { cxml::Document f("DKDMRecipients"); - f.read_file(_dkdm_recipients_file); + f.read_file(dcp::filesystem::fix_long_path(_dkdm_recipients_file)); read_dkdm_recipients(f); } catch (...) { backup(); @@ -714,6 +751,8 @@ Config::write_config () const root->add_child("OnlyServersEncode")->add_child_text (_only_servers_encode ? "1" : "0"); /* [XML] TMSProtocol Protocol to use to copy files to a TMS; 0 to use SCP, 1 for FTP. */ root->add_child("TMSProtocol")->add_child_text (raw_convert (static_cast (_tms_protocol))); + /* [XML] TMSPassive True to use PASV mode with TMS FTP connections. */ + root->add_child("TMSPassive")->add_child_text(_tms_passive ? "1" : "0"); /* [XML] TMSIP IP address of TMS. */ root->add_child("TMSIP")->add_child_text (_tms_ip); /* [XML] TMSPath Path on the TMS to copy files to. */ @@ -726,13 +765,6 @@ Config::write_config () const /* [XML:opt] Language Language to use in the GUI e.g. fr_FR. */ root->add_child("Language")->add_child_text (_language.get()); } - if (_default_container) { - /* [XML:opt] DefaultContainer ID of default container - to use when creating new films (185,239 or - 190). - */ - root->add_child("DefaultContainer")->add_child_text (_default_container->id ()); - } if (_default_dcp_content_type) { /* [XML:opt] DefaultDCPContentType Default content type to use when creating new films (FTR, SHR, TLR, TST, XSN, RTG, TSR, POL, @@ -764,6 +796,14 @@ Config::write_config () const root->add_child("DefaultAudioDelay")->add_child_text (raw_convert (_default_audio_delay)); /* [XML] DefaultInterop 1 to default new films to Interop, 0 for SMPTE. */ root->add_child("DefaultInterop")->add_child_text (_default_interop ? "1" : "0"); + if (_default_audio_language) { + /* [XML] DefaultAudioLanguage Default audio language to use for new films */ + root->add_child("DefaultAudioLanguage")->add_child_text(_default_audio_language->to_string()); + } + if (_default_territory) { + /* [XML] DefaultTerritory Default territory to use for new films */ + root->add_child("DefaultTerritory")->add_child_text(_default_territory->subtag()); + } for (auto const& i: _default_metadata) { auto c = root->add_child("DefaultMetadata"); c->set_attribute("key", i.first); @@ -1011,7 +1051,7 @@ Config::write_config () const } /* [XML] PlayerMode window for a single window, full for full-screen and dual for full screen playback - with controls on another monitor. + with separate (advanced) controls. */ switch (_player_mode) { case PLAYER_MODE_WINDOW: @@ -1059,9 +1099,10 @@ Config::write_config () const for (auto const& i: _custom_languages) { root->add_child("CustomLanguage")->add_child_text(i.to_string()); } - if (_add_files_path) { - /* [XML] AddFilesPath The default path that will be offered in the picker when adding files to a film. */ - root->add_child("AddFilesPath")->add_child_text(_add_files_path->string()); + for (auto const& initial: _initial_paths) { + if (initial.second) { + root->add_child(initial.first)->add_child_text(initial.second->string()); + } } root->add_child("UseISDCFNameByDefault")->add_child_text(_use_isdcf_name_by_default ? "1" : "0"); root->add_child("WriteKDMsToDisk")->add_child_text(_write_kdms_to_disk ? "1" : "0"); @@ -1071,6 +1112,21 @@ Config::write_config () const if (_last_release_notes_version) { root->add_child("LastReleaseNotesVersion")->add_child_text(*_last_release_notes_version); } + if (_main_divider_sash_position) { + root->add_child("MainDividerSashPosition")->add_child_text(raw_convert(*_main_divider_sash_position)); + } + if (_main_content_divider_sash_position) { + root->add_child("MainContentDividerSashPosition")->add_child_text(raw_convert(*_main_content_divider_sash_position)); + } + + root->add_child("DefaultAddFileLocation")->add_child_text( + _default_add_file_location == DefaultAddFileLocation::SAME_AS_LAST_TIME ? "last" : "project" + ); + + /* [XML] AllowSMPTEBv20 1 to allow the user to choose SMPTE (Bv2.0 only) as a standard, otherwise 0 */ + root->add_child("AllowSMPTEBv20")->add_child_text(_allow_smpte_bv20 ? "1" : "0"); + /* [XML] ISDCFNamePartLength Maximum length of the "name" part of an ISDCF name, which should be 14 according to the standard */ + root->add_child("ISDCFNamePartLength")->add_child_text(raw_convert(_isdcf_name_part_length)); _export.write(root->add_child("Export")); @@ -1085,8 +1141,8 @@ Config::write_config () const } f.checked_write(s.c_str(), s.bytes()); f.close(); - boost::filesystem::remove (target); - boost::filesystem::rename (tmp, target); + dcp::filesystem::remove(target); + dcp::filesystem::rename(tmp, target); } catch (xmlpp::exception& e) { string s = e.what (); trim (s); @@ -1109,8 +1165,8 @@ write_file (string root_node, string node, string version, list> t try { doc.write_to_file_formatted (file.string() + ".tmp"); - boost::filesystem::remove (file); - boost::filesystem::rename (file.string() + ".tmp", file); + dcp::filesystem::remove(file); + dcp::filesystem::rename(file.string() + ".tmp", file); } catch (xmlpp::exception& e) { string s = e.what (); trim (s); @@ -1153,7 +1209,7 @@ Config::directory_or (optional dir, boost::filesystem:: } boost::system::error_code ec; - auto const e = boost::filesystem::exists (*dir, ec); + auto const e = dcp::filesystem::exists(*dir, ec); if (ec || !e) { return a; } @@ -1165,7 +1221,7 @@ void Config::drop () { delete _instance; - _instance = 0; + _instance = nullptr; } void @@ -1248,7 +1304,7 @@ Config::add_to_player_history (boost::filesystem::path p) add_to_history_internal (_player_history, p); } -/** Remove non-existant items from the player history */ +/** Remove non-existent items from the player history */ void Config::clean_player_history () { @@ -1276,7 +1332,7 @@ Config::clean_history_internal (vector& h) h.clear (); for (auto i: old) { try { - if (boost::filesystem::is_directory(i)) { + if (dcp::filesystem::is_directory(i)) { h.push_back (i); } } catch (...) { @@ -1289,7 +1345,7 @@ Config::clean_history_internal (vector& h) bool Config::have_existing (string file) { - return boost::filesystem::exists (read_path(file)); + return dcp::filesystem::exists(read_path(file)); } @@ -1316,10 +1372,10 @@ Config::set_cinemas_file (boost::filesystem::path file) _cinemas_file = file; - if (boost::filesystem::exists (_cinemas_file)) { + if (dcp::filesystem::exists(_cinemas_file)) { /* Existing file; read it in */ cxml::Document f ("Cinemas"); - f.read_file (_cinemas_file); + f.read_file(dcp::filesystem::fix_long_path(_cinemas_file)); read_cinemas (f); } @@ -1348,12 +1404,12 @@ Config::save_template (shared_ptr film, string name) const list Config::templates () const { - if (!boost::filesystem::exists(read_path("templates"))) { + if (!dcp::filesystem::exists(read_path("templates"))) { return {}; } list n; - for (auto const& i: boost::filesystem::directory_iterator(read_path("templates"))) { + for (auto const& i: dcp::filesystem::directory_iterator(read_path("templates"))) { n.push_back (i.path().filename().string()); } return n; @@ -1362,7 +1418,7 @@ Config::templates () const bool Config::existing_template (string name) const { - return boost::filesystem::exists (template_read_path(name)); + return dcp::filesystem::exists(template_read_path(name)); } @@ -1383,13 +1439,13 @@ Config::template_write_path (string name) const void Config::rename_template (string old_name, string new_name) const { - boost::filesystem::rename (template_read_path(old_name), template_write_path(new_name)); + dcp::filesystem::rename(template_read_path(old_name), template_write_path(new_name)); } void Config::delete_template (string name) const { - boost::filesystem::remove (template_write_path(name)); + dcp::filesystem::remove(template_write_path(name)); } /** @return Path to the config.xml containing the actual settings, following a link if required */ @@ -1397,14 +1453,14 @@ boost::filesystem::path config_file (boost::filesystem::path main) { cxml::Document f ("Config"); - if (!boost::filesystem::exists (main)) { + if (!dcp::filesystem::exists(main)) { /* It doesn't exist, so there can't be any links; just return it */ return main; } /* See if there's a link */ try { - f.read_file (main); + f.read_file(dcp::filesystem::fix_long_path(main)); auto link = f.optional_string_child("Link"); if (link) { return *link; @@ -1458,7 +1514,7 @@ void Config::copy_and_link (boost::filesystem::path new_file) const { write (); - boost::filesystem::copy_file (config_read_file(), new_file, boost::filesystem::copy_option::overwrite_if_exists); + dcp::filesystem::copy_file(config_read_file(), new_file, boost::filesystem::copy_option::overwrite_if_exists); link (new_file); } @@ -1542,6 +1598,9 @@ Config::check_certificates () const if ((i.not_after().year() - i.not_before().year()) > 15) { bad = BAD_SIGNER_VALIDITY_TOO_LONG; } + if (dcp::escape_digest(i.subject_dn_qualifier()) != dcp::public_key_digest(i.public_key())) { + bad = BAD_SIGNER_DN_QUALIFIER; + } } if (!_signer_chain->chain_valid() || !_signer_chain->private_key_valid()) { @@ -1563,13 +1622,66 @@ save_all_config_as_zip (boost::filesystem::path zip_file) auto config = Config::instance(); zipper.add ("config.xml", dcp::file_to_string(config->config_read_file())); - if (boost::filesystem::exists(config->cinemas_file())) { + if (dcp::filesystem::exists(config->cinemas_file())) { zipper.add ("cinemas.xml", dcp::file_to_string(config->cinemas_file())); } - if (boost::filesystem::exists(config->dkdm_recipients_file())) { + if (dcp::filesystem::exists(config->dkdm_recipients_file())) { zipper.add ("dkdm_recipients.xml", dcp::file_to_string(config->dkdm_recipients_file())); } zipper.close (); } + +void +Config::load_from_zip(boost::filesystem::path zip_file) +{ + Unzipper unzipper(zip_file); + dcp::write_string_to_file(unzipper.get("config.xml"), config_write_file()); + + try { + dcp::write_string_to_file(unzipper.get("cinemas.xml"), cinemas_file()); + dcp::write_string_to_file(unzipper.get("dkdm_recipient.xml"), dkdm_recipients_file()); + } catch (std::runtime_error&) {} + + read(); + + changed(Property::USE_ANY_SERVERS); + changed(Property::SERVERS); + changed(Property::CINEMAS); + changed(Property::DKDM_RECIPIENTS); + changed(Property::SOUND); + changed(Property::SOUND_OUTPUT); + changed(Property::PLAYER_CONTENT_DIRECTORY); + changed(Property::PLAYER_PLAYLIST_DIRECTORY); + changed(Property::PLAYER_DEBUG_LOG); + changed(Property::HISTORY); + changed(Property::SHOW_EXPERIMENTAL_AUDIO_PROCESSORS); + changed(Property::AUDIO_MAPPING); + changed(Property::AUTO_CROP_THRESHOLD); + changed(Property::ALLOW_SMPTE_BV20); + changed(Property::ISDCF_NAME_PART_LENGTH); + changed(Property::OTHER); +} + + +void +Config::set_initial_path(string id, boost::filesystem::path path) +{ + auto iter = _initial_paths.find(id); + DCPOMATIC_ASSERT(iter != _initial_paths.end()); + iter->second = path; + changed(); +} + + +optional +Config::initial_path(string id) const +{ + auto iter = _initial_paths.find(id); + if (iter == _initial_paths.end()) { + return {}; + } + return iter->second; +} + diff --git a/src/lib/config.h b/src/lib/config.h index 8fbdeaf95..f3d080b0b 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -31,7 +31,6 @@ #include "export_config.h" #include "rough_duration.h" #include "state.h" -#include "types.h" #include #include #include @@ -80,6 +79,8 @@ public: boost::filesystem::path default_directory_or (boost::filesystem::path a) const; boost::filesystem::path default_kdm_directory_or (boost::filesystem::path a) const; + void load_from_zip(boost::filesystem::path zip_file); + enum Property { USE_ANY_SERVERS, SERVERS, @@ -94,6 +95,8 @@ public: SHOW_EXPERIMENTAL_AUDIO_PROCESSORS, AUDIO_MAPPING, AUTO_CROP_THRESHOLD, + ALLOW_SMPTE_BV20, + ISDCF_NAME_PART_LENGTH, OTHER }; @@ -130,6 +133,10 @@ public: return _tms_protocol; } + bool tms_passive() const { + return _tms_passive; + } + /** @return The IP address of a TMS that we can copy DCPs to */ std::string tms_ip () const { return _tms_ip; @@ -190,10 +197,6 @@ public: return _default_still_length; } - Ratio const * default_container () const { - return _default_container; - } - DCPContentType const * default_dcp_content_type () const { return _default_dcp_content_type; } @@ -238,6 +241,14 @@ public: return _default_interop; } + boost::optional default_audio_language() const { + return _default_audio_language; + } + + boost::optional default_territory() const { + return _default_territory; + } + std::map default_metadata () const { return _default_metadata; } @@ -421,6 +432,7 @@ public: NAG_32_ON_64, NAG_TOO_MANY_DROPPED_FRAMES, NAG_BAD_SIGNER_CHAIN_VALIDITY, + NAG_BAD_SIGNER_DN_QUALIFIER, NAG_COUNT }; @@ -558,9 +570,7 @@ public: return _custom_languages; } - boost::optional add_files_path () const { - return _add_files_path; - } + boost::optional initial_path(std::string id) const; bool use_isdcf_name_by_default () const { return _use_isdcf_name_by_default; @@ -590,6 +600,31 @@ public: return _last_release_notes_version; } + boost::optional main_divider_sash_position() const { + return _main_divider_sash_position; + } + + boost::optional main_content_divider_sash_position() const { + return _main_content_divider_sash_position; + } + + enum class DefaultAddFileLocation { + SAME_AS_LAST_TIME, + SAME_AS_PROJECT + }; + + DefaultAddFileLocation default_add_file_location() const { + return _default_add_file_location; + } + + bool allow_smpte_bv20() const { + return _allow_smpte_bv20; + } + + int isdcf_name_part_length() const { + return _isdcf_name_part_length; + } + /* SET (mostly) */ void set_master_encoding_threads (int n) { @@ -621,6 +656,10 @@ public: maybe_set (_tms_protocol, p); } + void set_tms_passive(bool passive) { + maybe_set(_tms_passive, passive); + } + /** @param i IP address of a TMS that we can copy DCPs to */ void set_tms_ip (std::string i) { maybe_set (_tms_ip, i); @@ -706,10 +745,6 @@ public: maybe_set (_default_still_length, s); } - void set_default_container (Ratio const * c) { - maybe_set (_default_container, c); - } - void set_default_dcp_content_type (DCPContentType const * t) { maybe_set (_default_dcp_content_type, t); } @@ -754,6 +789,22 @@ public: maybe_set (_default_interop, i); } + void set_default_audio_language(dcp::LanguageTag tag) { + maybe_set(_default_audio_language, tag); + } + + void unset_default_audio_language() { + maybe_set(_default_audio_language, boost::optional()); + } + + void set_default_territory(dcp::LanguageTag::RegionSubtag tag) { + maybe_set(_default_territory, tag); + } + + void unset_default_territory() { + maybe_set(_default_territory, boost::optional()); + } + void set_default_metadata (std::map const& metadata) { maybe_set (_default_metadata, metadata); } @@ -1097,10 +1148,7 @@ public: void add_custom_language (dcp::LanguageTag tag); - void set_add_files_path (boost::filesystem::path p) { - _add_files_path = p; - changed (); - } + void set_initial_path(std::string id, boost::filesystem::path path); void set_use_isdcf_name_by_default (bool use) { maybe_set (_use_isdcf_name_by_default, use); @@ -1138,6 +1186,26 @@ public: return _export; } + void set_main_divider_sash_position(int position) { + maybe_set(_main_divider_sash_position, position); + } + + void set_main_content_divider_sash_position(int position) { + maybe_set(_main_content_divider_sash_position, position); + } + + void set_default_add_file_location(DefaultAddFileLocation location) { + maybe_set(_default_add_file_location, location); + } + + void set_allow_smpte_bv20(bool allow) { + maybe_set(_allow_smpte_bv20, allow, ALLOW_SMPTE_BV20); + } + + void set_isdcf_name_part_length(int length) { + maybe_set(_isdcf_name_part_length, length, ISDCF_NAME_PART_LENGTH); + } + void changed (Property p = OTHER); boost::signals2::signal Changed; /** Emitted if read() failed on an existing Config file. There is nothing @@ -1159,6 +1227,7 @@ public: BAD_SIGNER_INCONSISTENT, ///< signer chain is somehow inconsistent BAD_DECRYPTION_INCONSISTENT, ///< KDM decryption chain is somehow inconsistent BAD_SIGNER_VALIDITY_TOO_LONG, ///< signer certificate validity periods are >10 years + BAD_SIGNER_DN_QUALIFIER, ///< some signer certificate has a bad dnQualifier (DoM #2716). }; static boost::signals2::signal Bad; @@ -1240,6 +1309,7 @@ private: std::vector _servers; bool _only_servers_encode; FileTransferProtocol _tms_protocol; + bool _tms_passive; /** The IP address of a TMS that we can copy DCPs to */ std::string _tms_ip; /** The path on a TMS that we should write DCPs to */ @@ -1264,7 +1334,6 @@ private: boost::optional _language; /** Default length of still image content (seconds) */ int _default_still_length; - Ratio const * _default_container; DCPContentType const * _default_dcp_content_type; int _default_dcp_audio_channels; std::string _dcp_issuer; @@ -1276,6 +1345,8 @@ private: int _default_j2k_bandwidth; int _default_audio_delay; bool _default_interop; + boost::optional _default_audio_language; + boost::optional _default_territory; std::map _default_metadata; /** Default directory to offer to write KDMs to; if it's not set, the home directory will be offered. @@ -1362,7 +1433,7 @@ private: boost::optional _player_kdm_directory; boost::optional _audio_mapping; std::vector _custom_languages; - boost::optional _add_files_path; + std::map> _initial_paths; bool _use_isdcf_name_by_default; bool _write_kdms_to_disk; bool _email_kdms; @@ -1370,6 +1441,11 @@ private: RoughDuration _default_kdm_duration; double _auto_crop_threshold; boost::optional _last_release_notes_version; + boost::optional _main_divider_sash_position; + boost::optional _main_content_divider_sash_position; + DefaultAddFileLocation _default_add_file_location; + bool _allow_smpte_bv20; + int _isdcf_name_part_length; ExportConfig _export; diff --git a/src/lib/constants.h b/src/lib/constants.h new file mode 100644 index 000000000..3b1871554 --- /dev/null +++ b/src/lib/constants.h @@ -0,0 +1,53 @@ +/* + Copyright (C) 2012-2020 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#ifndef DCPOMATIC_CONSTANTS_H +#define DCPOMATIC_CONSTANTS_H + + +/** The maximum number of audio channels that we can have in a DCP */ +#define MAX_DCP_AUDIO_CHANNELS 16 +/** Message broadcast to find possible encoding servers */ +#define DCPOMATIC_HELLO "I mean really, Ray, it's used." +/** Number of films to keep in history */ +#define HISTORY_SIZE 10 +#define REPORT_PROBLEM _("Please report this problem by using Help -> Report a problem or via email to carl@dcpomatic.com") +#define TEXT_FONT_ID "font" +/** Largest KDM size (in bytes) that will be accepted */ +#define MAX_KDM_SIZE (256 * 1024) +/** Number of lines that closed caption viewers will display */ +#define MAX_CLOSED_CAPTION_LINES 3 +/** Maximum line length of closed caption viewers, according to SMPTE Bv2.1 */ +#define MAX_CLOSED_CAPTION_LENGTH 32 +/** Maximum size of a subtitle / closed caption MXF in bytes, according to SMPTE Bv2.1 */ +#define MAX_TEXT_MXF_SIZE (115 * 1024 * 1024) +#define MAX_TEXT_MXF_SIZE_TEXT "115MB" +/** Maximum size of a font file, in bytes */ +#define MAX_FONT_FILE_SIZE (640 * 1024) +#define MAX_FONT_FILE_SIZE_TEXT "640KB" +/** Maximum size of the XML part of a closed caption file, according to SMPTE Bv2.1 */ +#define MAX_CLOSED_CAPTION_XML_SIZE (256 * 1024) +#define MAX_CLOSED_CAPTION_XML_SIZE_TEXT "256KB" +#define CERTIFICATE_VALIDITY_PERIOD (10 * 365) + + +#endif + diff --git a/src/lib/content.cc b/src/lib/content.cc index ed0e9af6d..6324050ec 100644 --- a/src/lib/content.cc +++ b/src/lib/content.cc @@ -66,7 +66,6 @@ int const ContentProperty::VIDEO_FRAME_RATE = 405; Content::Content () - : _change_signals_frequent (false) { } @@ -74,21 +73,18 @@ Content::Content () Content::Content (DCPTime p) : _position (p) - , _change_signals_frequent (false) { } Content::Content (boost::filesystem::path p) - : _change_signals_frequent (false) { add_path (p); } Content::Content (cxml::ConstNodePtr node) - : _change_signals_frequent (false) { for (auto i: node->node_children("Path")) { _paths.push_back (i->content()); @@ -97,7 +93,7 @@ Content::Content (cxml::ConstNodePtr node) _last_write_times.push_back (*mod); } else { boost::system::error_code ec; - auto last_write = boost::filesystem::last_write_time(i->content(), ec); + auto last_write = dcp::filesystem::last_write_time(i->content(), ec); _last_write_times.push_back (ec ? 0 : last_write); } } @@ -114,7 +110,6 @@ Content::Content (vector> c) , _trim_start (c.front()->trim_start()) , _trim_end (c.back()->trim_end()) , _video_frame_rate (c.front()->video_frame_rate()) - , _change_signals_frequent (false) { for (size_t i = 0; i < c.size(); ++i) { if (i > 0 && c[i]->trim_start() > ContentTime ()) { @@ -192,7 +187,7 @@ Content::examine (shared_ptr, shared_ptr job) _last_write_times.clear (); for (auto i: _paths) { boost::system::error_code ec; - auto last_write = boost::filesystem::last_write_time(i, ec); + auto last_write = dcp::filesystem::last_write_time(i, ec); _last_write_times.push_back (ec ? 0 : last_write); } } @@ -247,7 +242,7 @@ Content::set_position (shared_ptr film, DCPTime p, bool force_emit) void -Content::set_trim_start (ContentTime t) +Content::set_trim_start(shared_ptr film, ContentTime t) { DCPOMATIC_ASSERT (t.get() >= 0); @@ -259,14 +254,18 @@ Content::set_trim_start (ContentTime t) /* See note in ::set_position */ if (!video && audio) { - audio->modify_trim_start (t); + audio->modify_trim_start(film, t); } ContentChangeSignaller cc (this, ContentProperty::TRIM_START); { boost::mutex::scoped_lock lm (_mutex); - _trim_start = t; + if (_trim_start == t) { + cc.abort(); + } else { + _trim_start = t; + } } } @@ -340,7 +339,7 @@ bool Content::paths_valid () const { for (auto i: _paths) { - if (!boost::filesystem::exists (i)) { + if (!dcp::filesystem::exists(i)) { return false; } } @@ -360,7 +359,7 @@ Content::set_paths (vector paths) _last_write_times.clear (); for (auto i: _paths) { boost::system::error_code ec; - auto last_write = boost::filesystem::last_write_time(i, ec); + auto last_write = dcp::filesystem::last_write_time(i, ec); _last_write_times.push_back (ec ? 0 : last_write); } } @@ -407,7 +406,7 @@ Content::reel_split_points (shared_ptr) const void -Content::set_video_frame_rate (double r) +Content::set_video_frame_rate(shared_ptr film, double r) { ContentChangeSignaller cc (this, ContentProperty::VIDEO_FRAME_RATE); @@ -421,7 +420,7 @@ Content::set_video_frame_rate (double r) /* Make sure trim is still on a frame boundary */ if (video) { - set_trim_start (trim_start()); + set_trim_start(film, trim_start()); } } @@ -553,7 +552,7 @@ Content::add_path (boost::filesystem::path p) boost::mutex::scoped_lock lm (_mutex); _paths.push_back (p); boost::system::error_code ec; - auto last_write = boost::filesystem::last_write_time(p, ec); + auto last_write = dcp::filesystem::last_write_time(p, ec); _last_write_times.push_back (ec ? 0 : last_write); } @@ -563,7 +562,7 @@ Content::changed () const { bool write_time_changed = false; for (auto i = 0U; i < _paths.size(); ++i) { - if (boost::filesystem::last_write_time(_paths[i]) != last_write_time(i)) { + if (dcp::filesystem::last_write_time(_paths[i]) != last_write_time(i)) { write_time_changed = true; break; } diff --git a/src/lib/content.h b/src/lib/content.h index d0faeb9d4..f2fecddf0 100644 --- a/src/lib/content.h +++ b/src/lib/content.h @@ -31,8 +31,8 @@ #include "change_signaller.h" #include "dcpomatic_time.h" #include "signaller.h" -#include "types.h" #include "user_property.h" +#include "text_type.h" #include #include #include @@ -50,6 +50,9 @@ namespace cxml { class Job; class Film; class AtmosContent; +class AudioContent; +class TextContent; +class VideoContent; class ContentProperty { @@ -153,7 +156,7 @@ public: return _position; } - void set_trim_start (dcpomatic::ContentTime); + void set_trim_start(std::shared_ptr film, dcpomatic::ContentTime); dcpomatic::ContentTime trim_start () const { boost::mutex::scoped_lock lm (_mutex); @@ -172,6 +175,10 @@ public: return position() + length_after_trim(film); } + dcpomatic::DCPTimePeriod period(std::shared_ptr film) const { + return { position(), end(film) }; + } + dcpomatic::DCPTime length_after_trim (std::shared_ptr film) const; boost::optional video_frame_rate () const { @@ -179,7 +186,7 @@ public: return _video_frame_rate; } - void set_video_frame_rate (double r); + void set_video_frame_rate(std::shared_ptr film, double r); void unset_video_frame_rate (); double active_video_frame_rate (std::shared_ptr film) const; @@ -201,7 +208,7 @@ public: std::shared_ptr video; std::shared_ptr audio; - std::list> text; + std::vector> text; std::shared_ptr atmos; std::shared_ptr only_text () const; @@ -227,7 +234,7 @@ private: friend struct best_dcp_frame_rate_test_double; friend struct audio_sampling_rate_test; friend struct subtitle_font_id_change_test2; - template friend class ChangeSignaller; + template friend class ChangeSignalDespatcher; void signal_change (ChangeType, int); @@ -244,11 +251,12 @@ private: * else (either some video happening at the same time, or the rate of the DCP). */ boost::optional _video_frame_rate; - bool _change_signals_frequent; + bool _change_signals_frequent = false; }; typedef ChangeSignaller ContentChangeSignaller; +typedef ChangeSignalDespatcher ContentChangeSignalDespatcher; #endif diff --git a/src/lib/content_atmos.h b/src/lib/content_atmos.h index 891ca3b83..bffa94dcc 100644 --- a/src/lib/content_atmos.h +++ b/src/lib/content_atmos.h @@ -18,13 +18,16 @@ */ + #ifndef DCPOMATIC_CONTENT_ATMOS_H #define DCPOMATIC_CONTENT_ATMOS_H + #include "atmos_metadata.h" #include "types.h" #include + /** @class ContentAtmos * @brief Some Atmos data that has come out of a decoder. */ diff --git a/src/lib/content_audio.h b/src/lib/content_audio.h index 89a908b68..074d6b428 100644 --- a/src/lib/content_audio.h +++ b/src/lib/content_audio.h @@ -18,16 +18,20 @@ */ + #ifndef DCPOMATIC_CONTENT_AUDIO_H #define DCPOMATIC_CONTENT_AUDIO_H + /** @file src/lib/content_audio.h * @brief ContentAudio class. */ + #include "audio_buffers.h" #include "types.h" + /** @class ContentAudio * @brief A block of audio from a piece of content, with a timestamp as a frame within that content. */ @@ -43,4 +47,5 @@ public: Frame frame; }; + #endif diff --git a/src/lib/content_factory.cc b/src/lib/content_factory.cc index 135f3fe5f..dfa1ba55e 100644 --- a/src/lib/content_factory.cc +++ b/src/lib/content_factory.cc @@ -40,6 +40,7 @@ #include "log.h" #include "compose.hpp" #include +#include #include #include @@ -81,7 +82,8 @@ content_factory (cxml::ConstNodePtr node, int version, list& notes) "Stream", 0, node->number_child ("AudioFrameRate"), node->number_child ("AudioLength"), - AudioMapping (node->node_child ("AudioMapping"), version) + AudioMapping(node->node_child("AudioMapping"), version), + 16 ) ); @@ -110,11 +112,11 @@ content_factory (boost::filesystem::path path) { vector> content; - if (boost::filesystem::is_directory (path)) { + if (dcp::filesystem::is_directory(path)) { LOG_GENERAL ("Look in directory %1", path); - if (boost::filesystem::is_empty (path)) { + if (dcp::filesystem::is_empty(path)) { return content; } @@ -123,17 +125,17 @@ content_factory (boost::filesystem::path path) int image_files = 0; int sound_files = 0; int read = 0; - for (boost::filesystem::directory_iterator i(path); i != boost::filesystem::directory_iterator() && read < 10; ++i) { + for (dcp::filesystem::directory_iterator i(path); i != dcp::filesystem::directory_iterator() && read < 10; ++i) { LOG_GENERAL ("Checking file %1", i->path()); - if (boost::starts_with (i->path().leaf().string(), ".")) { + if (boost::starts_with(i->path().filename().string(), ".")) { /* We ignore hidden files */ LOG_GENERAL ("Ignored %1 (starts with .)", i->path()); continue; } - if (!boost::filesystem::is_regular_file(i->path())) { + if (!dcp::filesystem::is_regular_file(i->path())) { /* Ignore things which aren't files (probably directories) */ LOG_GENERAL ("Ignored %1 (not a regular file)", i->path()); continue; @@ -153,7 +155,7 @@ content_factory (boost::filesystem::path path) if (image_files > 0 && sound_files == 0) { content.push_back (make_shared(path)); } else if (image_files == 0 && sound_files > 0) { - for (auto i: boost::filesystem::directory_iterator(path)) { + for (auto i: dcp::filesystem::directory_iterator(path)) { content.push_back (make_shared(i.path())); } } @@ -167,11 +169,11 @@ content_factory (boost::filesystem::path path) if (valid_image_file (path)) { single = make_shared(path); - } else if (ext == ".srt" || ext == ".ssa" || ext == ".ass" || ext == ".stl") { + } else if (ext == ".srt" || ext == ".ssa" || ext == ".ass" || ext == ".stl" || ext == ".vtt") { single = make_shared(path); } else if (ext == ".xml") { cxml::Document doc; - doc.read_file (path); + doc.read_file(dcp::filesystem::fix_long_path(path)); if (doc.root_name() == "DCinemaSecurityMessage") { throw KDMAsContentError (); } diff --git a/src/lib/content_text.h b/src/lib/content_text.h index 06fb39cfc..51d4e8009 100644 --- a/src/lib/content_text.h +++ b/src/lib/content_text.h @@ -27,7 +27,6 @@ #include "dcpomatic_time.h" #include "rect.h" #include "string_text.h" -#include "types.h" #include #include diff --git a/src/lib/content_video.h b/src/lib/content_video.h index 8ca18576e..4fdab717a 100644 --- a/src/lib/content_video.h +++ b/src/lib/content_video.h @@ -18,13 +18,17 @@ */ + #ifndef DCPOMATIC_CONTENT_VIDEO_H #define DCPOMATIC_CONTENT_VIDEO_H + #include "types.h" + class ImageProxy; + /** @class ContentVideo * @brief A frame of video straight out of some content. */ diff --git a/src/lib/copy_dcp_details_to_film.cc b/src/lib/copy_dcp_details_to_film.cc index 5c3c79638..f6ca08638 100644 --- a/src/lib/copy_dcp_details_to_film.cc +++ b/src/lib/copy_dcp_details_to_film.cc @@ -18,25 +18,25 @@ */ + +#include "audio_content.h" #include "copy_dcp_details_to_film.h" #include "dcp_content.h" +#include "dcp_content_type.h" #include "film.h" -#include "types.h" -#include "video_content.h" -#include "audio_content.h" #include "ratio.h" -#include "dcp_content_type.h" +#include "video_content.h" #include using std::map; +using std::shared_ptr; using std::string; using std::vector; -using std::shared_ptr; void -copy_dcp_details_to_film (shared_ptr dcp, shared_ptr film) +copy_dcp_settings_to_film(shared_ptr dcp, shared_ptr film) { auto name = dcp->name (); name = name.substr (0, name.find("_")); @@ -51,7 +51,9 @@ copy_dcp_details_to_film (shared_ptr dcp, shared_ptr fil film->set_three_d (dcp->three_d()); if (dcp->video) { - film->set_container (Ratio::nearest_from_ratio(dcp->video->size().ratio())); + if (auto size = dcp->video->size()) { + film->set_container(Ratio::nearest_from_ratio(size->ratio())); + } film->set_resolution (dcp->resolution()); DCPOMATIC_ASSERT (dcp->video_frame_rate()); film->set_video_frame_rate (*dcp->video_frame_rate()); @@ -61,12 +63,17 @@ copy_dcp_details_to_film (shared_ptr dcp, shared_ptr fil film->set_audio_channels (dcp->audio->stream()->channels()); } + film->set_ratings (dcp->ratings()); + film->set_content_versions (dcp->content_versions()); +} + + +void +copy_dcp_markers_to_film(shared_ptr dcp, shared_ptr film) +{ film->clear_markers (); for (auto const& i: dcp->markers()) { - film->set_marker (i.first, dcpomatic::DCPTime(i.second.get())); + film->set_marker(i.first, dcpomatic::DCPTime(i.second.get())); } - - film->set_ratings (dcp->ratings()); - film->set_content_versions (dcp->content_versions()); } diff --git a/src/lib/copy_dcp_details_to_film.h b/src/lib/copy_dcp_details_to_film.h index 37507b8da..0948cbec1 100644 --- a/src/lib/copy_dcp_details_to_film.h +++ b/src/lib/copy_dcp_details_to_film.h @@ -26,4 +26,5 @@ class DCPContent; class Film; -extern void copy_dcp_details_to_film (std::shared_ptr dcp, std::shared_ptr film); +extern void copy_dcp_settings_to_film(std::shared_ptr dcp, std::shared_ptr film); +extern void copy_dcp_markers_to_film(std::shared_ptr dcp, std::shared_ptr film); diff --git a/src/lib/copy_to_drive_job.cc b/src/lib/copy_to_drive_job.cc index c6f9c84b0..7d208e0ec 100644 --- a/src/lib/copy_to_drive_job.cc +++ b/src/lib/copy_to_drive_job.cc @@ -97,44 +97,40 @@ CopyToDriveJob::run () } state = SETUP; while (true) { - optional s = _nanomsg.receive (10000); - if (!s) { + auto response = DiskWriterBackEndResponse::read_from_nanomsg(_nanomsg, 10000); + if (!response) { continue; } - if (*s == DISK_WRITER_OK) { + + switch (response->type()) { + case DiskWriterBackEndResponse::Type::OK: set_state (FINISHED_OK); return; - } else if (*s == DISK_WRITER_ERROR) { - auto const m = _nanomsg.receive (500); - auto const n = _nanomsg.receive (500); - throw CopyError (m.get_value_or("Unknown"), raw_convert(n.get_value_or("0"))); - } else if (*s == DISK_WRITER_FORMAT_PROGRESS) { + case DiskWriterBackEndResponse::Type::PONG: + break; + case DiskWriterBackEndResponse::Type::ERROR: + throw CopyError(response->error_message(), response->ext4_error_number(), response->platform_error_number()); + case DiskWriterBackEndResponse::Type::FORMAT_PROGRESS: if (state == SETUP) { sub (_("Formatting drive")); state = FORMAT; } - auto progress = _nanomsg.receive (500); - if (progress) { - set_progress (raw_convert(*progress)); - } - } else if (*s == DISK_WRITER_COPY_PROGRESS) { + set_progress(response->progress()); + break; + case DiskWriterBackEndResponse::Type::COPY_PROGRESS: if (state == FORMAT) { sub (_("Copying DCP")); state = COPY; } - auto progress = _nanomsg.receive (500); - if (progress) { - set_progress (raw_convert(*progress)); - } - } else if (*s == DISK_WRITER_VERIFY_PROGRESS) { + set_progress(response->progress()); + break; + case DiskWriterBackEndResponse::Type::VERIFY_PROGRESS: if (state == COPY) { sub (_("Verifying copied files")); state = VERIFY; } - auto progress = _nanomsg.receive (500); - if (progress) { - set_progress (raw_convert(*progress)); - } + set_progress(response->progress()); + break; } } } diff --git a/src/lib/create_cli.cc b/src/lib/create_cli.cc index 107311d59..1c2f2c635 100644 --- a/src/lib/create_cli.cc +++ b/src/lib/create_cli.cc @@ -23,14 +23,19 @@ #include "config.h" #include "create_cli.h" #include "dcp_content_type.h" +#include "dcpomatic_log.h" +#include "film.h" #include "ratio.h" #include #include #include -using std::string; using std::cout; +using std::make_shared; +using std::shared_ptr; +using std::string; +using std::vector; using boost::optional; @@ -40,6 +45,7 @@ string CreateCLI::_help = " -h, --help show this help\n" " -n, --name film name\n" " -t, --template template name\n" + " --no-encrypt make an unencrypted DCP\n" " -e, --encrypt make an encrypted DCP\n" " -c, --dcp-content-type FTR, SHR, TLR, TST, XSN, RTG, TSR, POL, PSA or ADV\n" " -f, --dcp-frame-rate set DCP video frame rate (otherwise guessed from content)\n" @@ -51,11 +57,12 @@ string CreateCLI::_help = " --twok make a 2K DCP instead of choosing a resolution based on the content\n" " --fourk make a 4K DCP instead of choosing a resolution based on the content\n" " -o, --output output directory\n" + " --twod make a 2D DCP\n" " --threed make a 3D DCP\n" " --j2k-bandwidth J2K bandwidth in Mbit/s\n" " --left-eye next piece of content is for the left eye\n" " --right-eye next piece of content is for the right eye\n" - " --channel next piece of content should be mapped to audio channel L, R, C, Lfe, Ls or Rs\n" + " --channel next piece of content should be mapped to audio channel L, R, C, Lfe, Ls, Rs, BsL, BsR, HI, VI\n" " --gain next piece of content should have the given audio gain (in dB)\n" " --cpl CPL ID to use from the next piece of content (which is a DCP)\n" " --kdm KDM for next piece of content\n"; @@ -119,19 +126,10 @@ argument_option ( CreateCLI::CreateCLI (int argc, char* argv[]) : version (false) - , encrypt (false) - , threed (false) - , dcp_content_type (nullptr) - , container_ratio (nullptr) - , still_length (10) - , standard (dcp::Standard::SMPTE) - , no_use_isdcf_name (false) - , twok (false) - , fourk (false) { - string dcp_content_type_string = "TST"; + optional dcp_content_type_string; string container_ratio_string; - string standard_string = "SMPTE"; + optional standard_string; int dcp_frame_rate_int = 0; string template_name_string; int j2k_bandwidth_int = 0; @@ -139,7 +137,7 @@ CreateCLI::CreateCLI (int argc, char* argv[]) optional channel; optional gain; optional kdm; - optional cpl; + optional cpl; int i = 1; while (i < argc) { @@ -156,12 +154,16 @@ CreateCLI::CreateCLI (int argc, char* argv[]) return; } - if (a == "-e" || a == "--encrypt") { - encrypt = claimed = true; + if (a == "--no-encrypt") { + _no_encrypt = claimed = true; + } else if (a == "-e" || a == "--encrypt") { + _encrypt = claimed = true; } else if (a == "--no-use-isdcf-name") { - no_use_isdcf_name = claimed = true; + _no_use_isdcf_name = claimed = true; + } else if (a == "--twod") { + _twod = claimed = true; } else if (a == "--threed") { - threed = claimed = true; + _threed = claimed = true; } else if (a == "--left-eye") { next_frame_type = VideoFrameType::THREE_D_LEFT; claimed = true; @@ -169,10 +171,10 @@ CreateCLI::CreateCLI (int argc, char* argv[]) next_frame_type = VideoFrameType::THREE_D_RIGHT; claimed = true; } else if (a == "--twok") { - twok = true; + _twok = true; claimed = true; } else if (a == "--fourk") { - fourk = true; + _fourk = true; claimed = true; } @@ -184,13 +186,23 @@ CreateCLI::CreateCLI (int argc, char* argv[]) return boost::filesystem::path(s); }; - argument_option(i, argc, argv, "-n", "--name", &claimed, &error, &name); + std::function (string s)> string_to_nonzero_int = [](string s) { + auto const value = dcp::raw_convert(s); + if (value == 0) { + return boost::optional(); + } + return boost::optional(value); + }; + + argument_option(i, argc, argv, "-n", "--name", &claimed, &error, &_name); argument_option(i, argc, argv, "-t", "--template", &claimed, &error, &template_name_string); - argument_option(i, argc, argv, "-c", "--dcp-content-type", &claimed, &error, &dcp_content_type_string); + /* See comment below about --cpl */ + argument_option(i, argc, argv, "-c", "--dcp-content-type", &claimed, &error, &dcp_content_type_string, string_to_string); argument_option(i, argc, argv, "-f", "--dcp-frame-rate", &claimed, &error, &dcp_frame_rate_int); argument_option(i, argc, argv, "", "--container-ratio", &claimed, &error, &container_ratio_string); - argument_option(i, argc, argv, "-s", "--still-length", &claimed, &error, &still_length); - argument_option(i, argc, argv, "", "--standard", &claimed, &error, &standard_string); + argument_option(i, argc, argv, "-s", "--still-length", &claimed, &error, &still_length, string_to_nonzero_int); + /* See comment below about --cpl */ + argument_option(i, argc, argv, "", "--standard", &claimed, &error, &standard_string, string_to_string); argument_option(i, argc, argv, "", "--config", &claimed, &error, &config_dir, string_to_path); argument_option(i, argc, argv, "-o", "--output", &claimed, &error, &output_dir, string_to_path); argument_option(i, argc, argv, "", "--j2k-bandwidth", &claimed, &error, &j2k_bandwidth_int); @@ -208,6 +220,14 @@ CreateCLI::CreateCLI (int argc, char* argv[]) return dcp::Channel::LS; } else if (channel == "Rs") { return dcp::Channel::RS; + } else if (channel == "BsL") { + return dcp::Channel::BSL; + } else if (channel == "BsR") { + return dcp::Channel::BSR; + } else if (channel == "HI") { + return dcp::Channel::HI; + } else if (channel == "VI") { + return dcp::Channel::VI; } else { return {}; } @@ -244,7 +264,7 @@ CreateCLI::CreateCLI (int argc, char* argv[]) } if (!template_name_string.empty()) { - template_name = template_name_string; + _template_name = template_name_string; } if (dcp_frame_rate_int) { @@ -252,30 +272,42 @@ CreateCLI::CreateCLI (int argc, char* argv[]) } if (j2k_bandwidth_int) { - j2k_bandwidth = j2k_bandwidth_int * 1000000; + _j2k_bandwidth = j2k_bandwidth_int * 1000000; } - dcp_content_type = DCPContentType::from_isdcf_name(dcp_content_type_string); - if (!dcp_content_type) { - error = String::compose("%1: unrecognised DCP content type '%2'", argv[0], dcp_content_type_string); - return; + if (dcp_content_type_string) { + _dcp_content_type = DCPContentType::from_isdcf_name(*dcp_content_type_string); + if (!_dcp_content_type) { + error = String::compose("%1: unrecognised DCP content type '%2'", argv[0], *dcp_content_type_string); + return; + } } if (!container_ratio_string.empty()) { - container_ratio = Ratio::from_id (container_ratio_string); - if (!container_ratio) { + _container_ratio = Ratio::from_id (container_ratio_string); + if (!_container_ratio) { error = String::compose("%1: unrecognised container ratio %2", argv[0], container_ratio_string); return; } } - if (standard_string != "SMPTE" && standard_string != "interop") { - error = String::compose("%1: standard must be SMPTE or interop", argv[0]); - return; + if (standard_string) { + if (*standard_string == "interop") { + _standard = dcp::Standard::INTEROP; + } else if (*standard_string == "SMPTE") { + _standard = dcp::Standard::SMPTE; + } else { + error = String::compose("%1: standard must be SMPTE or interop", argv[0]); + return; + } } - if (standard_string == "interop") { - standard = dcp::Standard::INTEROP; + if (_twod && _threed) { + error = String::compose("%1: specify one of --twod or --threed, not both", argv[0]); + } + + if (_no_encrypt && _encrypt) { + error = String::compose("%1: specify one of --no-encrypt or --encrypt, not both", argv[0]); } if (content.empty()) { @@ -283,12 +315,78 @@ CreateCLI::CreateCLI (int argc, char* argv[]) return; } - if (name.empty()) { - name = content[0].path.leaf().string(); + if (_name.empty()) { + _name = content[0].path.filename().string(); } - if (j2k_bandwidth && (*j2k_bandwidth < 10000000 || *j2k_bandwidth > Config::instance()->maximum_j2k_bandwidth())) { + if (_j2k_bandwidth && (*_j2k_bandwidth < 10000000 || *_j2k_bandwidth > Config::instance()->maximum_j2k_bandwidth())) { error = String::compose("%1: j2k-bandwidth must be between 10 and %2 Mbit/s", argv[0], (Config::instance()->maximum_j2k_bandwidth() / 1000000)); return; } } + + +shared_ptr +CreateCLI::make_film() const +{ + auto film = std::make_shared(output_dir); + dcpomatic_log = film->log(); + dcpomatic_log->set_types(Config::instance()->log_types()); + if (_template_name) { + film->use_template(_template_name.get()); + } else { + /* No template: apply our own CLI tool defaults to override the ones in Config. + * Maybe one day there will be no defaults in Config any more (as they'll be in + * a default template) and we can decide whether to use the default template + * or not. + */ + film->set_interop(false); + film->set_dcp_content_type(DCPContentType::from_isdcf_name("TST")); + } + film->set_name(_name); + + if (_container_ratio) { + film->set_container(_container_ratio); + } + if (_dcp_content_type) { + film->set_dcp_content_type(_dcp_content_type); + } + if (_standard) { + film->set_interop(*_standard == dcp::Standard::INTEROP); + } + film->set_use_isdcf_name(!_no_use_isdcf_name); + if (_no_encrypt) { + film->set_encrypted(false); + } else if (_encrypt) { + film->set_encrypted(true); + } + if (_twod) { + film->set_three_d(false); + } else if (_threed) { + film->set_three_d(true); + } + if (_twok) { + film->set_resolution(Resolution::TWO_K); + } + if (_fourk) { + film->set_resolution(Resolution::FOUR_K); + } + if (_j2k_bandwidth) { + film->set_j2k_bandwidth(*_j2k_bandwidth); + } + + int channels = 6; + for (auto cli_content: content) { + if (cli_content.channel) { + channels = std::max(channels, static_cast(*cli_content.channel) + 1); + } + } + if (channels % 2) { + ++channels; + } + + film->set_audio_channels(channels); + + return film; +} + diff --git a/src/lib/create_cli.h b/src/lib/create_cli.h index a5e0c3941..782aaf974 100644 --- a/src/lib/create_cli.h +++ b/src/lib/create_cli.h @@ -19,7 +19,7 @@ */ -#include "types.h" +#include "video_frame_type.h" #include #include #include @@ -27,8 +27,11 @@ class DCPContentType; +class Film; class Ratio; +struct create_cli_test; + class CreateCLI { @@ -45,24 +48,31 @@ public: }; bool version; - std::string name; - boost::optional template_name; - bool encrypt; - bool threed; - DCPContentType const * dcp_content_type; boost::optional dcp_frame_rate; - Ratio const * container_ratio; - int still_length; - dcp::Standard standard; - bool no_use_isdcf_name; + boost::optional still_length; boost::optional config_dir; boost::optional output_dir; boost::optional error; std::vector content; - bool twok; - bool fourk; - boost::optional j2k_bandwidth; + + std::shared_ptr make_film() const; private: + friend struct ::create_cli_test; + + boost::optional _template_name; + std::string _name; + Ratio const* _container_ratio = nullptr; + bool _no_encrypt = false; + bool _encrypt = false; + bool _twod = false; + bool _threed = false; + DCPContentType const* _dcp_content_type = nullptr; + boost::optional _standard; + bool _no_use_isdcf_name = false; + bool _twok = false; + bool _fourk = false; + boost::optional _j2k_bandwidth; + static std::string _help; }; diff --git a/src/lib/crop.cc b/src/lib/crop.cc new file mode 100644 index 000000000..d2d020a9f --- /dev/null +++ b/src/lib/crop.cc @@ -0,0 +1,64 @@ +/* + Copyright (C) 2013-2019 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "crop.h" +#include +#include +LIBDCP_DISABLE_WARNINGS +#include +LIBDCP_ENABLE_WARNINGS + + +using std::shared_ptr; +using std::string; +using dcp::raw_convert; + + +Crop::Crop(shared_ptr node) +{ + left = node->number_child("LeftCrop"); + right = node->number_child("RightCrop"); + top = node->number_child("TopCrop"); + bottom = node->number_child("BottomCrop"); +} + + +void +Crop::as_xml(xmlpp::Node* node) const +{ + node->add_child("LeftCrop")->add_child_text(raw_convert(left)); + node->add_child("RightCrop")->add_child_text(raw_convert(right)); + node->add_child("TopCrop")->add_child_text(raw_convert(top)); + node->add_child("BottomCrop")->add_child_text(raw_convert(bottom)); +} + + +bool operator==(Crop const & a, Crop const & b) +{ + return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom); +} + + +bool operator!=(Crop const & a, Crop const & b) +{ + return !(a == b); +} + diff --git a/src/lib/crop.h b/src/lib/crop.h new file mode 100644 index 000000000..00734d5e9 --- /dev/null +++ b/src/lib/crop.h @@ -0,0 +1,76 @@ +/* + Copyright (C) 2013-2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#ifndef DCPOMATIC_CROP_H +#define DCPOMATIC_CROP_H + + +#include +#include + + +namespace cxml { + class Node; +} + + +/** @struct Crop + * @brief A description of the crop of an image or video. + */ +struct Crop +{ + Crop() {} + Crop(int l, int r, int t, int b) : left (l), right (r), top (t), bottom (b) {} + explicit Crop(std::shared_ptr); + + /** Number of pixels to remove from the left-hand side */ + int left = 0; + /** Number of pixels to remove from the right-hand side */ + int right = 0; + /** Number of pixels to remove from the top */ + int top = 0; + /** Number of pixels to remove from the bottom */ + int bottom = 0; + + dcp::Size apply(dcp::Size s, int minimum = 4) const { + s.width -= left + right; + s.height -= top + bottom; + + if (s.width < minimum) { + s.width = minimum; + } + + if (s.height < minimum) { + s.height = minimum; + } + + return s; + } + + void as_xml (xmlpp::Node *) const; +}; + + +extern bool operator==(Crop const & a, Crop const & b); +extern bool operator!=(Crop const & a, Crop const & b); + + +#endif diff --git a/src/lib/cross.h b/src/lib/cross.h index 4be121d2b..6904811b7 100644 --- a/src/lib/cross.h +++ b/src/lib/cross.h @@ -29,6 +29,8 @@ #include #endif #include +/* windows.h defines this but we want to use it */ +#undef ERROR #include #include @@ -42,7 +44,7 @@ struct AVIOContext; extern void dcpomatic_sleep_seconds (int); extern void dcpomatic_sleep_milliseconds (int); extern std::string cpu_info (); -extern void run_ffprobe (boost::filesystem::path, boost::filesystem::path); +extern void run_ffprobe(boost::filesystem::path content, boost::filesystem::path out, bool err = true, std::string args = {}); extern std::list> mount_info (); extern boost::filesystem::path openssl_path (); extern void make_foreground_application (); diff --git a/src/lib/cross_common.cc b/src/lib/cross_common.cc index e8c209b21..b4d322096 100644 --- a/src/lib/cross_common.cc +++ b/src/lib/cross_common.cc @@ -172,7 +172,7 @@ osx_disks_to_drives (vector disks) continue; } for (auto& j: disks) { - if (!j.mount_points.empty() && starts_with(j.device, i.device)) { + if (&i != &j && !j.mount_points.empty() && starts_with(j.device, i.device)) { LOG_DISK("Marking %1 as mounted because %2 is", i.device, j.device); std::copy(j.mount_points.begin(), j.mount_points.end(), back_inserter(i.mount_points)); } diff --git a/src/lib/cross_linux.cc b/src/lib/cross_linux.cc index 919927fda..015158aa8 100644 --- a/src/lib/cross_linux.cc +++ b/src/lib/cross_linux.cc @@ -26,6 +26,7 @@ #include "dcpomatic_log.h" #include "exceptions.h" #include "log.h" +#include #include #include #include @@ -94,14 +95,15 @@ libdcp_resources_path () if (auto appdir = getenv("APPDIR")) { return boost::filesystem::path(appdir) / "usr" / "share" / "libdcp"; } - return boost::filesystem::canonical(LINUX_SHARE_PREFIX) / "libdcp"; + return dcp::filesystem::canonical(LINUX_SHARE_PREFIX) / "libdcp"; } void -run_ffprobe (boost::filesystem::path content, boost::filesystem::path out) +run_ffprobe(boost::filesystem::path content, boost::filesystem::path out, bool err, string args) { - string ffprobe = "ffprobe \"" + content.string() + "\" 2> \"" + out.string() + "\""; + string const redirect = err ? "2>" : ">"; + auto const ffprobe = String::compose("ffprobe %1 \"%2\" %3 \"%4\"", args.empty() ? " " : args, content.string(), redirect, out.string()); LOG_GENERAL (N_("Probing with %1"), ffprobe); int const r = system (ffprobe.c_str()); if (r == -1 || (WIFEXITED(r) && WEXITSTATUS(r) != 0)) { @@ -152,7 +154,7 @@ boost::filesystem::path openssl_path () { auto p = directory_containing_executable() / "dcpomatic2_openssl"; - if (boost::filesystem::is_regular_file(p)) { + if (dcp::filesystem::is_regular_file(p)) { return p; } diff --git a/src/lib/cross_osx.cc b/src/lib/cross_osx.cc index eb713a980..913b19103 100644 --- a/src/lib/cross_osx.cc +++ b/src/lib/cross_osx.cc @@ -25,6 +25,7 @@ #include "dcpomatic_log.h" #include "config.h" #include "exceptions.h" +#include #include #include #include @@ -84,7 +85,7 @@ cpu_info () boost::filesystem::path directory_containing_executable () { - return boost::filesystem::canonical(boost::dll::program_location()).parent_path(); + return dcp::filesystem::canonical(boost::dll::program_location()).parent_path(); } @@ -103,13 +104,18 @@ libdcp_resources_path () void -run_ffprobe (boost::filesystem::path content, boost::filesystem::path out) +run_ffprobe(boost::filesystem::path content, boost::filesystem::path out, bool err, string args) { auto path = directory_containing_executable () / "ffprobe"; + if (!dcp::filesystem::exists(path)) { + /* This is a hack but we need ffprobe during tests */ + path = "/Users/ci/workspace/bin/ffprobe"; + } + string const redirect = err ? "2>" : ">"; - string ffprobe = "\"" + path.string() + "\" \"" + content.string() + "\" 2> \"" + out.string() + "\""; + auto const ffprobe = String::compose("\"%1\" %2 \"%3\" %4 \"%5\"", path, args.empty() ? " " : args, content.string(), redirect, out.string()); LOG_GENERAL (N_("Probing with %1"), ffprobe); - system (ffprobe.c_str ()); + system (ffprobe.c_str()); } @@ -372,11 +378,15 @@ Drive::get () using namespace boost::algorithm; vector disks; + LOG_DISK_NC("Drive::get() starts"); + auto session = DASessionCreate(kCFAllocatorDefault); if (!session) { return {}; } + LOG_DISK_NC("Drive::get() has session"); + DARegisterDiskAppearedCallback (session, NULL, disk_appeared, &disks); auto run_loop = CFRunLoopGetCurrent (); DASessionScheduleWithRunLoop (session, run_loop, kCFRunLoopDefaultMode); @@ -385,7 +395,14 @@ Drive::get () DAUnregisterCallback(session, (void *) disk_appeared, &disks); CFRelease(session); - return osx_disks_to_drives (disks); + auto drives = osx_disks_to_drives(disks); + + LOG_DISK("Drive::get() found %1 drives:", drives.size()); + for (auto const& drive: drives) { + LOG_DISK("%1 %2 mounted=%3", drive.description(), drive.device(), drive.mounted() ? "yes" : "no"); + } + + return drives; } @@ -405,16 +422,23 @@ config_path (optional version) } +struct UnmountState +{ + bool success = false; + bool callback = false; +}; + + void done_callback(DADiskRef, DADissenterRef dissenter, void* context) { LOG_DISK_NC("Unmount finished"); - bool* success = reinterpret_cast (context); + auto state = reinterpret_cast(context); + state->callback = true; if (dissenter) { LOG_DISK("Error: %1", DADissenterGetStatus(dissenter)); - *success = false; } else { LOG_DISK_NC("Successful"); - *success = true; + state->success = true; } } @@ -434,18 +458,22 @@ Drive::unmount () return false; } LOG_DISK("Requesting unmount of %1 from %2", _device, thread_id()); - bool success = false; - DADiskUnmount(disk, kDADiskUnmountOptionWhole, &done_callback, &success); + UnmountState state; + DADiskUnmount(disk, kDADiskUnmountOptionWhole, &done_callback, &state); CFRelease (disk); CFRunLoopRef run_loop = CFRunLoopGetCurrent (); DASessionScheduleWithRunLoop (session, run_loop, kCFRunLoopDefaultMode); CFRunLoopStop (run_loop); - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, 0); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 5, 0); CFRelease(session); - LOG_DISK_NC("End of unmount"); - return success; + if (!state.callback) { + LOG_DISK_NC("End of unmount: timeout"); + } else { + LOG_DISK("End of unmount: %1", state.success ? "success" : "failure"); + } + return state.success; } diff --git a/src/lib/cross_windows.cc b/src/lib/cross_windows.cc index 200b72485..9181b6c8a 100644 --- a/src/lib/cross_windows.cc +++ b/src/lib/cross_windows.cc @@ -30,6 +30,7 @@ #include "dcpomatic_assert.h" #include "util.h" #include +#include #include #include extern "C" { @@ -125,37 +126,51 @@ cpu_info () void -run_ffprobe (boost::filesystem::path content, boost::filesystem::path out) +run_ffprobe(boost::filesystem::path content, boost::filesystem::path out, bool err, string args) { SECURITY_ATTRIBUTES security; security.nLength = sizeof (security); security.bInheritHandle = TRUE; security.lpSecurityDescriptor = 0; - HANDLE child_stderr_read; - HANDLE child_stderr_write; - if (!CreatePipe (&child_stderr_read, &child_stderr_write, &security, 0)) { + HANDLE child_out_read; + HANDLE child_out_write; + if (!CreatePipe(&child_out_read, &child_out_write, &security, 0)) { LOG_ERROR_NC ("ffprobe call failed (could not CreatePipe)"); return; } + if (!SetHandleInformation(child_out_read, HANDLE_FLAG_INHERIT, 0)) { + LOG_ERROR_NC("ffprobe call failed (could not SetHandleInformation)"); + return; + } + wchar_t dir[512]; MultiByteToWideChar (CP_UTF8, 0, directory_containing_executable().string().c_str(), -1, dir, sizeof(dir)); STARTUPINFO startup_info; ZeroMemory (&startup_info, sizeof (startup_info)); startup_info.cb = sizeof (startup_info); - startup_info.hStdError = child_stderr_write; + if (err) { + startup_info.hStdError = child_out_write; + } else { + startup_info.hStdOutput = child_out_write; + } startup_info.dwFlags |= STARTF_USESTDHANDLES; wchar_t command[512]; - wcscpy (command, L"ffprobe.exe \""); + wcscpy(command, L"ffprobe.exe "); + + wchar_t tmp[512]; + MultiByteToWideChar(CP_UTF8, 0, args.c_str(), -1, tmp, sizeof(tmp)); + wcscat(command, tmp); + + wcscat(command, L" \""); - wchar_t file[512]; - MultiByteToWideChar (CP_UTF8, 0, content.string().c_str(), -1, file, sizeof(file)); - wcscat (command, file); + MultiByteToWideChar(CP_UTF8, 0, dcp::filesystem::canonical(content).make_preferred().string().c_str(), -1, tmp, sizeof(tmp)); + wcscat(command, tmp); - wcscat (command, L"\""); + wcscat(command, L"\""); PROCESS_INFORMATION process_info; ZeroMemory (&process_info, sizeof (process_info)); @@ -170,12 +185,12 @@ run_ffprobe (boost::filesystem::path content, boost::filesystem::path out) return; } - CloseHandle (child_stderr_write); + CloseHandle(child_out_write); while (true) { char buffer[512]; DWORD read; - if (!ReadFile(child_stderr_read, buffer, sizeof(buffer), &read, 0) || read == 0) { + if (!ReadFile(child_out_read, buffer, sizeof(buffer), &read, 0) || read == 0) { break; } o.write(buffer, read, 1); @@ -186,7 +201,7 @@ run_ffprobe (boost::filesystem::path content, boost::filesystem::path out) WaitForSingleObject (process_info.hProcess, INFINITE); CloseHandle (process_info.hProcess); CloseHandle (process_info.hThread); - CloseHandle (child_stderr_read); + CloseHandle(child_out_read); } diff --git a/src/lib/crypto.cc b/src/lib/crypto.cc deleted file mode 100644 index 777969c10..000000000 --- a/src/lib/crypto.cc +++ /dev/null @@ -1,130 +0,0 @@ -/* - Copyright (C) 2018-2021 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - - -/* Based on code from https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption */ - - -#include "crypto.h" -#include "exceptions.h" -#include -#include -#include -#include -#include - - -using std::string; -using namespace dcpomatic; - - -/** The cipher that this code uses */ -#define CIPHER EVP_aes_256_cbc() - - -dcp::ArrayData -dcpomatic::random_iv () -{ - EVP_CIPHER const * cipher = CIPHER; - dcp::ArrayData iv (EVP_CIPHER_iv_length(cipher)); - RAND_bytes (iv.data(), iv.size()); - return iv; -} - - -dcp::ArrayData -dcpomatic::encrypt (string plaintext, dcp::ArrayData key, dcp::ArrayData iv) -{ - auto ctx = EVP_CIPHER_CTX_new (); - if (!ctx) { - throw CryptoError ("could not create cipher context"); - } - - int r = EVP_EncryptInit_ex (ctx, CIPHER, 0, key.data(), iv.data()); - if (r != 1) { - throw CryptoError ("could not initialise cipher context for encryption"); - } - - dcp::ArrayData ciphertext (plaintext.size() * 2); - - int len; - r = EVP_EncryptUpdate (ctx, ciphertext.data(), &len, (uint8_t const *) plaintext.c_str(), plaintext.size()); - if (r != 1) { - throw CryptoError ("could not encrypt data"); - } - - int ciphertext_len = len; - - r = EVP_EncryptFinal_ex (ctx, ciphertext.data() + len, &len); - if (r != 1) { - throw CryptoError ("could not finish encryption"); - } - - ciphertext.set_size (ciphertext_len + len); - - EVP_CIPHER_CTX_free (ctx); - - return ciphertext; -} - - -string -dcpomatic::decrypt (dcp::ArrayData ciphertext, dcp::ArrayData key, dcp::ArrayData iv) -{ - auto ctx = EVP_CIPHER_CTX_new (); - if (!ctx) { - throw CryptoError ("could not create cipher context"); - } - - int r = EVP_DecryptInit_ex (ctx, CIPHER, 0, key.data(), iv.data()); - if (r != 1) { - throw CryptoError ("could not initialise cipher context for decryption"); - } - - dcp::ArrayData plaintext (ciphertext.size() * 2); - - int len; - r = EVP_DecryptUpdate (ctx, plaintext.data(), &len, ciphertext.data(), ciphertext.size()); - if (r != 1) { - throw CryptoError ("could not decrypt data"); - } - - int plaintext_len = len; - - r = EVP_DecryptFinal_ex (ctx, plaintext.data() + len, &len); - if (r != 1) { - throw CryptoError ("could not finish decryption"); - } - - plaintext_len += len; - plaintext.set_size (plaintext_len + 1); - plaintext.data()[plaintext_len] = '\0'; - - EVP_CIPHER_CTX_free (ctx); - - return string ((char *) plaintext.data()); -} - - -int -dcpomatic::crypto_key_length () -{ - return EVP_CIPHER_key_length (CIPHER); -} diff --git a/src/lib/crypto.h b/src/lib/crypto.h deleted file mode 100644 index 41a93010d..000000000 --- a/src/lib/crypto.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - Copyright (C) 2018-2021 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - - -#include - - -namespace dcpomatic { - - -dcp::ArrayData random_iv (); -dcp::ArrayData encrypt (std::string plaintext, dcp::ArrayData key, dcp::ArrayData iv); -std::string decrypt (dcp::ArrayData ciphertext, dcp::ArrayData key, dcp::ArrayData iv); -int crypto_key_length (); - - -} - diff --git a/src/lib/curl_uploader.cc b/src/lib/curl_uploader.cc index 6fe7aba14..389a5d6de 100644 --- a/src/lib/curl_uploader.cc +++ b/src/lib/curl_uploader.cc @@ -20,6 +20,7 @@ #include "curl_uploader.h" +#include "dcpomatic_log.h" #include "exceptions.h" #include "config.h" #include "cross.h" @@ -43,6 +44,13 @@ read_callback (void* ptr, size_t size, size_t nmemb, void* object) } +static int +curl_debug_shim (CURL* curl, curl_infotype type, char* data, size_t size, void* userp) +{ + return reinterpret_cast(userp)->debug(curl, type, data, size); +} + + CurlUploader::CurlUploader (function set_status, function set_progress) : Uploader (set_status, set_progress) { @@ -58,6 +66,12 @@ CurlUploader::CurlUploader (function set_status, functiontms_user().c_str()); curl_easy_setopt (_curl, CURLOPT_PASSWORD, Config::instance()->tms_password().c_str()); + if (!Config::instance()->tms_passive()) { + curl_easy_setopt(_curl, CURLOPT_FTPPORT, "-"); + } + curl_easy_setopt(_curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(_curl, CURLOPT_DEBUGFUNCTION, curl_debug_shim); + curl_easy_setopt(_curl, CURLOPT_DEBUGDATA, this); } @@ -113,3 +127,14 @@ CurlUploader::read_callback (void* ptr, size_t size, size_t nmemb) return r; } + + +int +CurlUploader::debug(CURL *, curl_infotype type, char* data, size_t size) +{ + if (type == CURLINFO_TEXT && size > 0) { + LOG_GENERAL("CurlUploader: %1", string(data, size - 1)); + } + return 0; +} + diff --git a/src/lib/curl_uploader.h b/src/lib/curl_uploader.h index ea017eb83..4ee221f08 100644 --- a/src/lib/curl_uploader.h +++ b/src/lib/curl_uploader.h @@ -31,6 +31,7 @@ public: ~CurlUploader (); size_t read_callback (void* ptr, size_t size, size_t nmemb); + int debug(CURL* curl, curl_infotype type, char* data, size_t size); protected: void create_directory (boost::filesystem::path directory) override; diff --git a/src/lib/dcp_content.cc b/src/lib/dcp_content.cc index 193c9995a..bdd5e0e09 100644 --- a/src/lib/dcp_content.cc +++ b/src/lib/dcp_content.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2014-2022 Carl Hetherington + Copyright (C) 2014-2023 Carl Hetherington This file is part of DCP-o-matic. @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,7 @@ using std::cout; using std::dynamic_pointer_cast; +using std::exception; using std::function; using std::list; using std::make_shared; @@ -86,10 +88,6 @@ DCPContent::DCPContent (boost::filesystem::path p) read_directory (p); set_default_colour_conversion (); - - for (int i = 0; i < static_cast(TextType::COUNT); ++i) { - _reference_text[i] = false; - } } DCPContent::DCPContent (cxml::ConstNodePtr node, int version) @@ -101,10 +99,6 @@ DCPContent::DCPContent (cxml::ConstNodePtr node, int version) text = TextContent::from_xml (this, node, version, notes); atmos = AtmosContent::from_xml (this, node); - for (int i = 0; i < static_cast(TextType::COUNT); ++i) { - _reference_text[i] = false; - } - if (video && audio) { audio->set_stream ( make_shared ( @@ -113,7 +107,8 @@ DCPContent::DCPContent (cxml::ConstNodePtr node, int version) node->optional_number_child("AudioLength").get_value_or ( video->length() * node->number_child("AudioFrameRate") / video_frame_rate().get() ), - AudioMapping (node->node_child ("AudioMapping"), version) + AudioMapping(node->node_child("AudioMapping"), version), + 24 ) ); } @@ -128,11 +123,11 @@ DCPContent::DCPContent (cxml::ConstNodePtr node, int version) _reference_video = node->optional_bool_child ("ReferenceVideo").get_value_or (false); _reference_audio = node->optional_bool_child ("ReferenceAudio").get_value_or (false); if (version >= 37) { - _reference_text[static_cast(TextType::OPEN_SUBTITLE)] = node->optional_bool_child("ReferenceOpenSubtitle").get_value_or(false); - _reference_text[static_cast(TextType::CLOSED_CAPTION)] = node->optional_bool_child("ReferenceClosedCaption").get_value_or(false); + _reference_text[TextType::OPEN_SUBTITLE] = node->optional_bool_child("ReferenceOpenSubtitle").get_value_or(false); + _reference_text[TextType::CLOSED_CAPTION] = node->optional_bool_child("ReferenceClosedCaption").get_value_or(false); } else { - _reference_text[static_cast(TextType::OPEN_SUBTITLE)] = node->optional_bool_child("ReferenceSubtitle").get_value_or(false); - _reference_text[static_cast(TextType::CLOSED_CAPTION)] = false; + _reference_text[TextType::OPEN_SUBTITLE] = node->optional_bool_child("ReferenceSubtitle").get_value_or(false); + _reference_text[TextType::CLOSED_CAPTION] = false; } if (node->optional_string_child("Standard")) { auto const s = node->optional_string_child("Standard").get(); @@ -166,6 +161,8 @@ DCPContent::DCPContent (cxml::ConstNodePtr node, int version) for (auto i: node->node_children("ContentVersion")) { _content_versions.push_back (i->content()); } + + _active_audio_channels = node->optional_number_child("ActiveAudioChannels"); } void @@ -198,17 +195,23 @@ DCPContent::read_directory (boost::filesystem::path p) void DCPContent::read_sub_directory (boost::filesystem::path p) { + using namespace boost::filesystem; + LOG_GENERAL ("DCPContent::read_sub_directory reads %1", p.string()); - for (auto i: boost::filesystem::directory_iterator(p)) { - if (boost::filesystem::is_regular_file(i.path())) { - LOG_GENERAL ("Inside there's regular file %1", i.path().string()); - add_path (i.path()); - } else if (boost::filesystem::is_directory(i.path()) && i.path().filename() != ".AppleDouble") { - LOG_GENERAL ("Inside there's directory %1", i.path().string()); - read_sub_directory (i.path()); - } else { - LOG_GENERAL("Ignoring %1 from inside: status is %2", i.path().string(), static_cast(boost::filesystem::status(i.path()).type())); + try { + for (auto i: directory_iterator(p)) { + if (is_regular_file(i.path())) { + LOG_GENERAL ("Inside there's regular file %1", i.path().string()); + add_path (i.path()); + } else if (is_directory(i.path()) && i.path().filename() != ".AppleDouble") { + LOG_GENERAL ("Inside there's directory %1", i.path().string()); + read_sub_directory (i.path()); + } else { + LOG_GENERAL("Ignoring %1 from inside: status is %2", i.path().string(), static_cast(status(i.path()).type())); + } } + } catch (exception& e) { + LOG_GENERAL("Failed to iterate over %1: %2", p.string(), e.what()); } } @@ -220,6 +223,11 @@ DCPContent::examine (shared_ptr film, shared_ptr job) bool const needed_kdm = needs_kdm (); string const old_name = name (); + ContentChangeSignalDespatcher::instance()->suspend(); + dcp::ScopeGuard sg = []() { + ContentChangeSignalDespatcher::instance()->resume(); + }; + ContentChangeSignaller cc_texts (this, DCPContentProperty::TEXTS); ContentChangeSignaller cc_assets (this, DCPContentProperty::NEEDS_ASSETS); ContentChangeSignaller cc_kdm (this, DCPContentProperty::NEEDS_KDM); @@ -237,7 +245,7 @@ DCPContent::examine (shared_ptr film, shared_ptr job) boost::mutex::scoped_lock lm (_mutex); video = make_shared(this); } - video->take_from_examiner (examiner); + video->take_from_examiner(film, examiner); set_default_colour_conversion (); } @@ -246,11 +254,13 @@ DCPContent::examine (shared_ptr film, shared_ptr job) boost::mutex::scoped_lock lm (_mutex); audio = make_shared(this); } - auto as = make_shared(examiner->audio_frame_rate(), examiner->audio_length(), examiner->audio_channels()); + auto as = make_shared(examiner->audio_frame_rate(), examiner->audio_length(), examiner->audio_channels(), 24); audio->set_stream (as); auto m = as->mapping (); m.make_default (film ? film->audio_processor() : 0); as->set_mapping (m); + + _active_audio_channels = examiner->active_audio_channels(); } if (examiner->has_atmos()) { @@ -265,18 +275,19 @@ DCPContent::examine (shared_ptr film, shared_ptr job) atmos->set_length (examiner->atmos_length()); } - list> new_text; + vector> new_text; for (int i = 0; i < examiner->text_count(TextType::OPEN_SUBTITLE); ++i) { auto c = make_shared(this, TextType::OPEN_SUBTITLE, TextType::OPEN_SUBTITLE); c->set_language (examiner->open_subtitle_language()); - add_fonts_from_examiner(c, examiner->fonts()); + examiner->add_fonts(c); new_text.push_back (c); } for (int i = 0; i < examiner->text_count(TextType::CLOSED_CAPTION); ++i) { auto c = make_shared(this, TextType::CLOSED_CAPTION, TextType::CLOSED_CAPTION); c->set_dcp_track (examiner->dcp_text_track(i)); + examiner->add_fonts(c); new_text.push_back (c); } @@ -363,6 +374,7 @@ DCPContent::as_xml (xmlpp::Node* node, bool with_paths) const } boost::mutex::scoped_lock lm (_mutex); + node->add_child("Name")->add_child_text (_name); node->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0"); node->add_child("NeedsAssets")->add_child_text (_needs_assets ? "1" : "0"); @@ -372,8 +384,8 @@ DCPContent::as_xml (xmlpp::Node* node, bool with_paths) const node->add_child("KDMValid")->add_child_text (_kdm_valid ? "1" : "0"); node->add_child("ReferenceVideo")->add_child_text (_reference_video ? "1" : "0"); node->add_child("ReferenceAudio")->add_child_text (_reference_audio ? "1" : "0"); - node->add_child("ReferenceOpenSubtitle")->add_child_text(_reference_text[static_cast(TextType::OPEN_SUBTITLE)] ? "1" : "0"); - node->add_child("ReferenceClosedCaption")->add_child_text(_reference_text[static_cast(TextType::CLOSED_CAPTION)] ? "1" : "0"); + node->add_child("ReferenceOpenSubtitle")->add_child_text(_reference_text[TextType::OPEN_SUBTITLE] ? "1" : "0"); + node->add_child("ReferenceClosedCaption")->add_child_text(_reference_text[TextType::CLOSED_CAPTION] ? "1" : "0"); if (_standard) { switch (_standard.get ()) { case dcp::Standard::INTEROP: @@ -411,6 +423,10 @@ DCPContent::as_xml (xmlpp::Node* node, bool with_paths) const for (auto i: _content_versions) { node->add_child("ContentVersion")->add_child_text(i); } + + if (_active_audio_channels) { + node->add_child("ActiveAudioChannels")->add_child_text(raw_convert(*_active_audio_channels)); + } } DCPTime @@ -445,9 +461,11 @@ DCPContent::identifier () const s += i->identifier () + " "; } + boost::mutex::scoped_lock lm(_mutex); + s += string (_reference_video ? "1" : "0"); - for (int i = 0; i < static_cast(TextType::COUNT); ++i) { - s += string (_reference_text[i] ? "1" : "0"); + for (auto text: _reference_text) { + s += string(text ? "1" : "0"); } return s; } @@ -540,7 +558,7 @@ DCPContent::set_reference_text (TextType type, bool r) { boost::mutex::scoped_lock lm (_mutex); - _reference_text[static_cast(type)] = r; + _reference_text[type] = r; } } @@ -649,11 +667,6 @@ DCPContent::can_reference (shared_ptr film, function c) -{ - return static_cast(c->video) && c->video->use(); -} bool DCPContent::can_reference_video (shared_ptr film, string& why_not) const @@ -679,14 +692,16 @@ DCPContent::can_reference_video (shared_ptr film, string& why_not) c } /// TRANSLATORS: this string will follow "Cannot reference this DCP: " - return can_reference (film, bind (&check_video, _1), _("it overlaps other video content; remove the other content."), why_not); + return can_reference( + film, + [](shared_ptr c) { + return static_cast(c->video) && c->video->use(); + }, + _("it overlaps other video content; remove the other content."), + why_not + ); } -static -bool check_audio (shared_ptr c) -{ - return static_cast(c->audio) && !c->audio->mapping().mapped_output_channels().empty(); -} bool DCPContent::can_reference_audio (shared_ptr film, string& why_not) const @@ -705,23 +720,24 @@ DCPContent::can_reference_audio (shared_ptr film, string& why_not) c return false; } - for (auto i: decoder->reels()) { - if (!i->main_sound()) { - /// TRANSLATORS: this string will follow "Cannot reference this DCP: " - why_not = _("it does not have sound in all its reels."); - return false; - } - } + if (audio && audio->stream()) { + auto const channels = audio->stream()->channels(); + if (channels != film->audio_channels()) { + why_not = String::compose(_("it has a different number of audio channels than the project; set the project to have %1 channels."), channels); + return false; + } + } /// TRANSLATORS: this string will follow "Cannot reference this DCP: " - return can_reference (film, bind (&check_audio, _1), _("it overlaps other audio content; remove the other content."), why_not); + return can_reference( + film, [](shared_ptr c) { + return static_cast(c->audio) && !c->audio->mapping().mapped_output_channels().empty(); + }, + _("it overlaps other audio content; remove the other content."), + why_not + ); } -static -bool check_text (shared_ptr c) -{ - return !c->text.empty(); -} bool DCPContent::can_reference_text (shared_ptr film, TextType type, string& why_not) const @@ -732,6 +748,9 @@ DCPContent::can_reference_text (shared_ptr film, TextType type, stri } catch (dcp::ReadError &) { /* We couldn't read the DCP, so it's probably missing */ return false; + } catch (DCPError &) { + /* We couldn't read the DCP, so it's probably missing */ + return false; } catch (dcp::KDMDecryptionError &) { /* We have an incorrect KDM */ return false; @@ -739,22 +758,13 @@ DCPContent::can_reference_text (shared_ptr film, TextType type, stri for (auto i: decoder->reels()) { if (type == TextType::OPEN_SUBTITLE) { - if (!i->main_subtitle()) { - /// TRANSLATORS: this string will follow "Cannot reference this DCP: " - why_not = _("it does not have open subtitles in all its reels."); - return false; - } else if (i->main_subtitle()->entry_point().get_value_or(0) != 0) { + if (i->main_subtitle() && i->main_subtitle()->entry_point().get_value_or(0) != 0) { /// TRANSLATORS: this string will follow "Cannot reference this DCP: " why_not = _("one of its subtitle reels has a non-zero entry point so it must be re-written."); return false; } } if (type == TextType::CLOSED_CAPTION) { - if (i->closed_captions().empty()) { - /// TRANSLATORS: this string will follow "Cannot reference this DCP: " - why_not = _("it does not have closed captions in all its reels."); - return false; - } for (auto j: i->closed_captions()) { if (j->entry_point().get_value_or(0) != 0) { /// TRANSLATORS: this string will follow "Cannot reference this DCP: " @@ -772,7 +782,14 @@ DCPContent::can_reference_text (shared_ptr film, TextType type, stri } /// TRANSLATORS: this string will follow "Cannot reference this DCP: " - return can_reference (film, bind (&check_text, _1), _("it overlaps other text content; remove the other content."), why_not); + return can_reference( + film, + [type](shared_ptr c) { + return std::find_if(c->text.begin(), c->text.end(), [type](shared_ptr t) { return t->type() == type; }) != c->text.end(); + }, + _("they overlap other text content; remove the other content."), + why_not + ); } void @@ -783,11 +800,16 @@ DCPContent::take_settings_from (shared_ptr c) return; } + if (this == dc.get()) { + return; + } + + boost::mutex::scoped_lock lm(_mutex); + boost::mutex::scoped_lock lm2(dc->_mutex); + _reference_video = dc->_reference_video; _reference_audio = dc->_reference_audio; - for (int i = 0; i < static_cast(TextType::COUNT); ++i) { - _reference_text[i] = dc->_reference_text[i]; - } + _reference_text = dc->_reference_text; } void @@ -816,7 +838,7 @@ DCPContent::kdm_timing_window_valid () const Resolution DCPContent::resolution () const { - if (video->size().width > 2048 || video->size().height > 1080) { + if (video->size() && (video->size()->width > 2048 || video->size()->height > 1080)) { return Resolution::FOUR_K; } @@ -824,32 +846,6 @@ DCPContent::resolution () const } -void -add_fonts_from_examiner(shared_ptr text, vector>> const & all_fonts) -{ - int reel_number = 0; - for (auto reel_fonts: all_fonts) { - for (auto font: reel_fonts) { - /* Each reel could have its own font with the same ID, so we disambiguate them here - * by prepending the reel number. We do the same disambiguation when emitting the - * subtitles in the DCP decoder. - */ - font->set_id(id_for_font_in_reel(font->id(), reel_number)); - text->add_font(font); - } - ++reel_number; - } - -} - - -string -id_for_font_in_reel(string id, int reel) -{ - return String::compose("%1_%2", reel, id); -} - - void DCPContent::check_font_ids() { @@ -858,6 +854,15 @@ DCPContent::check_font_ids() } DCPExaminer examiner(shared_from_this(), true); - add_fonts_from_examiner(text.front(), examiner.fonts()); + examiner.add_fonts(text.front()); +} + + +int +DCPContent::active_audio_channels() const +{ + return _active_audio_channels.get_value_or( + (audio && audio->stream()) ? audio->stream()->channels() : 0 + ); } diff --git a/src/lib/dcp_content.h b/src/lib/dcp_content.h index efdfe30f7..3753740a2 100644 --- a/src/lib/dcp_content.h +++ b/src/lib/dcp_content.h @@ -29,7 +29,9 @@ #include "content.h" +#include "enum_indexed_vector.h" #include "font.h" +#include "resolution.h" #include #include #include @@ -123,7 +125,7 @@ public: */ bool reference_text (TextType type) const { boost::mutex::scoped_lock lm (_mutex); - return _reference_text[static_cast(type)]; + return _reference_text[type]; } bool can_reference_text (std::shared_ptr film, TextType type, std::string &) const; @@ -172,6 +174,8 @@ public: return _content_versions; } + int active_audio_channels() const; + void check_font_ids(); private: @@ -209,7 +213,7 @@ private: * rather than by rewrapping. The types here are the original text types, * not what they are being used for. */ - bool _reference_text[static_cast(TextType::COUNT)]; + EnumIndexedVector _reference_text; boost::optional _standard; boost::optional _content_kind; @@ -223,12 +227,9 @@ private: std::map _markers; std::vector _ratings; std::vector _content_versions; -}; - - -extern std::string id_for_font_in_reel(std::string id, int reel); -extern void add_fonts_from_examiner(std::shared_ptr text, std::vector>> const& fonts); + boost::optional _active_audio_channels; +}; #endif diff --git a/src/lib/dcp_content_type.cc b/src/lib/dcp_content_type.cc index ce54ce01c..bc9bf0d67 100644 --- a/src/lib/dcp_content_type.cc +++ b/src/lib/dcp_content_type.cc @@ -47,6 +47,8 @@ DCPContentType::DCPContentType (string p, dcp::ContentKind k, string d) void DCPContentType::setup_dcp_content_types () { + /// TRANSLATORS: these are the types that a DCP can have, explained in some + /// more detail here: https://registry-page.isdcf.com/contenttypes/ _dcp_content_types = { DCPContentType(_("Feature"), dcp::ContentKind::FEATURE, N_("FTR")), DCPContentType(_("Short"), dcp::ContentKind::SHORT, N_("SHR")), diff --git a/src/lib/dcp_decoder.cc b/src/lib/dcp_decoder.cc index 9064627ba..165b5bfb5 100644 --- a/src/lib/dcp_decoder.cc +++ b/src/lib/dcp_decoder.cc @@ -23,6 +23,7 @@ #include "audio_content.h" #include "audio_decoder.h" #include "config.h" +#include "constants.h" #include "dcp_content.h" #include "dcp_decoder.h" #include "digester.h" @@ -104,7 +105,6 @@ DCPDecoder::DCPDecoder (shared_ptr film, shared_ptrlazy_digest() == _lazy_digest) { _reels = old->_reels; } else { - auto cpl_list = dcp::find_and_resolve_cpls(content->directories(), tolerant); if (cpl_list.empty()) { @@ -136,6 +136,9 @@ DCPDecoder::DCPDecoder (shared_ptr film, shared_ptrstandard() + asset->subtitle_standard() ); strings.clear (); } dcp::SubtitleString is_copy = *is; - is_copy.set_font(id_for_font_in_reel(is_copy.font().get_value_or(""), _reel - _reels.begin())); + if (is_copy.font()) { + is_copy.set_font(_font_id_allocator.font_id(_reel - _reels.begin(), asset->id(), is_copy.font().get())); + } else { + is_copy.set_font(_font_id_allocator.default_font_id()); + } strings.push_back(is_copy); } @@ -340,7 +347,7 @@ DCPDecoder::pass_texts ( ContentTime::from_frames(_offset - entry_point, vfr) + ContentTime::from_seconds(b.out().as_seconds()) ), strings, - _dcp_content->standard() + asset->subtitle_standard() ); strings.clear (); } @@ -368,7 +375,7 @@ DCPDecoder::get_readers () return; } - if ((*_reel)->main_picture()) { + if (video && !video->ignore() && (*_reel)->main_picture()) { auto asset = (*_reel)->main_picture()->asset (); auto mono = dynamic_pointer_cast (asset); auto stereo = dynamic_pointer_cast (asset); @@ -387,7 +394,7 @@ DCPDecoder::get_readers () _stereo_reader.reset (); } - if ((*_reel)->main_sound()) { + if (audio && !audio->ignore() && (*_reel)->main_sound()) { _sound_reader = (*_reel)->main_sound()->asset()->start_read (); _sound_reader->set_check_hmac (false); } else { @@ -443,10 +450,12 @@ DCPDecoder::seek (ContentTime t, bool accurate) /* Pass texts in the pre-roll */ - auto const vfr = _dcp_content->active_video_frame_rate (film()); - for (int i = 0; i < pre_roll_seconds * vfr; ++i) { - pass_texts (pre, (*_reel)->main_picture()->asset()->size()); - pre += ContentTime::from_frames (1, vfr); + if (_reel != _reels.end()) { + auto const vfr = _dcp_content->active_video_frame_rate (film()); + for (int i = 0; i < pre_roll_seconds * vfr; ++i) { + pass_texts (pre, (*_reel)->main_picture()->asset()->size()); + pre += ContentTime::from_frames (1, vfr); + } } /* Seek to correct position */ diff --git a/src/lib/dcp_decoder.h b/src/lib/dcp_decoder.h index 803c93a86..2c0cd8f41 100644 --- a/src/lib/dcp_decoder.h +++ b/src/lib/dcp_decoder.h @@ -26,6 +26,7 @@ #include "atmos_metadata.h" #include "decoder.h" +#include "font_id_allocator.h" #include #include #include @@ -106,4 +107,6 @@ private: boost::optional _forced_reduction; std::string _lazy_digest; + + FontIDAllocator _font_id_allocator; }; diff --git a/src/lib/dcp_encoder.cc b/src/lib/dcp_encoder.cc index e4cb76d79..9a840c8ab 100644 --- a/src/lib/dcp_encoder.cc +++ b/src/lib/dcp_encoder.cc @@ -56,19 +56,22 @@ using namespace boost::placeholders; #endif using namespace dcpomatic; + /** Construct a DCP encoder. * @param film Film that we are encoding. * @param job Job that this encoder is being used in. */ DCPEncoder::DCPEncoder (shared_ptr film, weak_ptr job) : Encoder (film, job) + , _writer(film, job) + , _j2k_encoder(film, _writer) , _finishing (false) , _non_burnt_subtitles (false) { - _player_video_connection = _player->Video.connect (bind (&DCPEncoder::video, this, _1, _2)); - _player_audio_connection = _player->Audio.connect (bind (&DCPEncoder::audio, this, _1, _2)); - _player_text_connection = _player->Text.connect (bind (&DCPEncoder::text, this, _1, _2, _3, _4)); - _player_atmos_connection = _player->Atmos.connect (bind (&DCPEncoder::atmos, this, _1, _2, _3)); + _player_video_connection = _player.Video.connect(bind(&DCPEncoder::video, this, _1, _2)); + _player_audio_connection = _player.Audio.connect(bind(&DCPEncoder::audio, this, _1, _2)); + _player_text_connection = _player.Text.connect(bind(&DCPEncoder::text, this, _1, _2, _3, _4)); + _player_atmos_connection = _player.Atmos.connect(bind(&DCPEncoder::atmos, this, _1, _2, _3)); for (auto c: film->content ()) { for (auto i: c->text) { @@ -91,11 +94,8 @@ DCPEncoder::~DCPEncoder () void DCPEncoder::go () { - _writer = make_shared(_film, _job); - _writer->start (); - - _j2k_encoder = make_shared(_film, _writer); - _j2k_encoder->begin (); + _writer.start(); + _j2k_encoder.begin(); { auto job = _job.lock (); @@ -104,30 +104,30 @@ DCPEncoder::go () } if (_non_burnt_subtitles) { - _writer->write(_player->get_subtitle_fonts()); + _writer.write(_player.get_subtitle_fonts()); } - while (!_player->pass ()) {} + while (!_player.pass()) {} for (auto i: get_referenced_reel_assets(_film, _film->playlist())) { - _writer->write (i); + _writer.write(i); } _finishing = true; - _j2k_encoder->end (); - _writer->finish (_film->dir(_film->dcp_name())); + _j2k_encoder.end(); + _writer.finish(_film->dir(_film->dcp_name())); } void DCPEncoder::video (shared_ptr data, DCPTime time) { - _j2k_encoder->encode (data, time); + _j2k_encoder.encode(data, time); } void DCPEncoder::audio (shared_ptr data, DCPTime time) { - _writer->write (data, time); + _writer.write(data, time); auto job = _job.lock (); DCPOMATIC_ASSERT (job); @@ -138,7 +138,7 @@ void DCPEncoder::text (PlayerText data, TextType type, optional track, DCPTimePeriod period) { if (type == TextType::CLOSED_CAPTION || _non_burnt_subtitles) { - _writer->write (data, type, track, period); + _writer.write(data, type, track, period); } } @@ -146,26 +146,18 @@ DCPEncoder::text (PlayerText data, TextType type, optional track, void DCPEncoder::atmos (shared_ptr data, DCPTime time, AtmosMetadata metadata) { - _writer->write (data, time, metadata); + _writer.write(data, time, metadata); } optional DCPEncoder::current_rate () const { - if (!_j2k_encoder) { - return {}; - } - - return _j2k_encoder->current_encoding_rate (); + return _j2k_encoder.current_encoding_rate(); } Frame DCPEncoder::frames_done () const { - if (!_j2k_encoder) { - return 0; - } - - return _j2k_encoder->video_frames_enqueued (); + return _j2k_encoder.video_frames_enqueued(); } diff --git a/src/lib/dcp_encoder.h b/src/lib/dcp_encoder.h index e7514be98..ad77f6951 100644 --- a/src/lib/dcp_encoder.h +++ b/src/lib/dcp_encoder.h @@ -24,17 +24,16 @@ #include "dcpomatic_time.h" #include "encoder.h" #include "player_text.h" -#include "types.h" +#include "j2k_encoder.h" +#include "writer.h" #include class AudioBuffers; class Film; -class J2KEncoder; class Job; class Player; class PlayerVideo; -class Writer; /** @class DCPEncoder */ @@ -61,8 +60,8 @@ private: void text (PlayerText, TextType, boost::optional, dcpomatic::DCPTimePeriod); void atmos (std::shared_ptr, dcpomatic::DCPTime, AtmosMetadata metadata); - std::shared_ptr _writer; - std::shared_ptr _j2k_encoder; + Writer _writer; + J2KEncoder _j2k_encoder; bool _finishing; bool _non_burnt_subtitles; diff --git a/src/lib/dcp_examiner.cc b/src/lib/dcp_examiner.cc index 5de8c8905..88c9a5b70 100644 --- a/src/lib/dcp_examiner.cc +++ b/src/lib/dcp_examiner.cc @@ -20,11 +20,14 @@ #include "config.h" +#include "constants.h" #include "dcp_content.h" #include "dcp_examiner.h" #include "dcpomatic_log.h" #include "exceptions.h" +#include "font_id_allocator.h" #include "image.h" +#include "text_content.h" #include "util.h" #include #include @@ -63,11 +66,7 @@ using boost::optional; DCPExaminer::DCPExaminer (shared_ptr content, bool tolerant) { - shared_ptr cpl; - - for (int i = 0; i < static_cast(TextType::COUNT); ++i) { - _text_count[i] = 0; - } + shared_ptr selected_cpl; auto cpls = dcp::find_and_resolve_cpls (content->directories(), tolerant); @@ -77,47 +76,47 @@ DCPExaminer::DCPExaminer (shared_ptr content, bool tolerant) if (iter == cpls.end()) { throw CPLNotFoundError(content->cpl().get()); } - cpl = *iter; + selected_cpl = *iter; } else { /* Choose the CPL with the fewest unsatisfied references */ int least_unsatisfied = INT_MAX; - for (auto i: cpls) { + for (auto cpl: cpls) { int unsatisfied = 0; - for (auto j: i->reels()) { - if (j->main_picture() && !j->main_picture()->asset_ref().resolved()) { + for (auto const& reel: cpl->reels()) { + if (reel->main_picture() && !reel->main_picture()->asset_ref().resolved()) { ++unsatisfied; } - if (j->main_sound() && !j->main_sound()->asset_ref().resolved()) { + if (reel->main_sound() && !reel->main_sound()->asset_ref().resolved()) { ++unsatisfied; } - if (j->main_subtitle() && !j->main_subtitle()->asset_ref().resolved()) { + if (reel->main_subtitle() && !reel->main_subtitle()->asset_ref().resolved()) { ++unsatisfied; } - if (j->atmos() && !j->atmos()->asset_ref().resolved()) { + if (reel->atmos() && !reel->atmos()->asset_ref().resolved()) { ++unsatisfied; } } if (unsatisfied < least_unsatisfied) { least_unsatisfied = unsatisfied; - cpl = i; + selected_cpl = cpl; } } } - if (!cpl) { + if (!selected_cpl) { throw DCPError ("No CPLs found in DCP"); } if (content->kdm()) { - cpl->add (decrypt_kdm_with_helpful_error(content->kdm().get())); + selected_cpl->add(decrypt_kdm_with_helpful_error(content->kdm().get())); } - _cpl = cpl->id (); - _name = cpl->content_title_text (); - _content_kind = cpl->content_kind (); + _cpl = selected_cpl->id(); + _name = selected_cpl->content_title_text(); + _content_kind = selected_cpl->content_kind(); LOG_GENERAL ("Selected CPL %1", _cpl); @@ -130,132 +129,145 @@ DCPExaminer::DCPExaminer (shared_ptr content, bool tolerant) return boost::none; }; - LOG_GENERAL ("Looking at %1 reels", cpl->reels().size()); + LOG_GENERAL("Looking at %1 reels", selected_cpl->reels().size()); - for (auto i: cpl->reels()) { - LOG_GENERAL ("Reel %1", i->id()); - vector> reel_fonts; - - if (i->main_picture ()) { - if (!i->main_picture()->asset_ref().resolved()) { - /* We are missing this asset so we can't continue; examination will be repeated later */ - _needs_assets = true; - LOG_GENERAL ("Main picture %1 of reel %2 is missing", i->main_picture()->id(), i->id()); - return; - } + int reel_index = 0; + for (auto reel: selected_cpl->reels()) { + LOG_GENERAL("Reel %1", reel->id()); - LOG_GENERAL ("Main picture %1 of reel %2 found", i->main_picture()->id(), i->id()); + if (reel->main_picture()) { + /* This will mean a VF can be displayed in the timeline even if its picture asset + * is yet be resolved. + */ + _has_video = true; + _video_length += reel->main_picture()->actual_duration(); - auto const frac = i->main_picture()->edit_rate (); - float const fr = float(frac.numerator) / frac.denominator; - if (!_video_frame_rate) { - _video_frame_rate = fr; - } else if (_video_frame_rate.get() != fr) { - throw DCPError (_("Mismatched frame rates in DCP")); - } + if (!reel->main_picture()->asset_ref().resolved()) { + LOG_GENERAL("Main picture %1 of reel %2 is missing", reel->main_picture()->id(), reel->id()); + _needs_assets = true; + } else { + LOG_GENERAL("Main picture %1 of reel %2 found", reel->main_picture()->id(), reel->id()); + + auto const frac = reel->main_picture()->edit_rate(); + float const fr = float(frac.numerator) / frac.denominator; + if (!_video_frame_rate) { + _video_frame_rate = fr; + } else if (_video_frame_rate.get() != fr) { + throw DCPError (_("Mismatched frame rates in DCP")); + } - _has_video = true; - auto asset = i->main_picture()->asset(); - if (!_video_size) { - _video_size = asset->size (); - } else if (_video_size.get() != asset->size ()) { - throw DCPError (_("Mismatched video sizes in DCP")); + auto asset = reel->main_picture()->asset(); + if (!_video_size) { + _video_size = asset->size (); + } else if (_video_size.get() != asset->size ()) { + throw DCPError (_("Mismatched video sizes in DCP")); + } } - - _video_length += i->main_picture()->actual_duration(); } - if (i->main_sound ()) { - if (!i->main_sound()->asset_ref().resolved()) { - /* We are missing this asset so we can't continue; examination will be repeated later */ + if (reel->main_sound()) { + _has_audio = true; + _audio_length += reel->main_sound()->actual_duration(); + + if (!reel->main_sound()->asset_ref().resolved()) { + LOG_GENERAL("Main sound %1 of reel %2 is missing", reel->main_sound()->id(), reel->id()); _needs_assets = true; - LOG_GENERAL ("Main sound %1 of reel %2 is missing", i->main_sound()->id(), i->id()); - return; - } + } else { + LOG_GENERAL("Main sound %1 of reel %2 found", reel->main_sound()->id(), reel->id()); - LOG_GENERAL ("Main sound %1 of reel %2 found", i->main_sound()->id(), i->id()); + auto asset = reel->main_sound()->asset(); - _has_audio = true; - auto asset = i->main_sound()->asset(); + if (!_audio_channels) { + _audio_channels = asset->channels(); + } else if (_audio_channels.get() != asset->channels()) { + throw DCPError (_("Mismatched audio channel counts in DCP")); + } - if (!_audio_channels) { - _audio_channels = asset->channels (); - } else if (_audio_channels.get() != asset->channels ()) { - throw DCPError (_("Mismatched audio channel counts in DCP")); - } + _active_audio_channels = std::max(_active_audio_channels.get_value_or(0), asset->active_channels()); - if (!_audio_frame_rate) { - _audio_frame_rate = asset->sampling_rate (); - } else if (_audio_frame_rate.get() != asset->sampling_rate ()) { - throw DCPError (_("Mismatched audio sample rates in DCP")); - } + if (!_audio_frame_rate) { + _audio_frame_rate = asset->sampling_rate (); + } else if (_audio_frame_rate.get() != asset->sampling_rate ()) { + throw DCPError (_("Mismatched audio sample rates in DCP")); + } - _audio_length += i->main_sound()->actual_duration(); - _audio_language = try_to_parse_language (asset->language()); + _audio_language = try_to_parse_language (asset->language()); + } } - if (i->main_subtitle ()) { - if (!i->main_subtitle()->asset_ref().resolved()) { - /* We are missing this asset so we can't continue; examination will be repeated later */ + if (reel->main_subtitle()) { + if (!reel->main_subtitle()->asset_ref().resolved()) { + LOG_GENERAL("Main subtitle %1 of reel %2 is missing", reel->main_subtitle()->id(), reel->id()); _needs_assets = true; - LOG_GENERAL ("Main subtitle %1 of reel %2 is missing", i->main_subtitle()->id(), i->id()); - return; - } - - LOG_GENERAL ("Main subtitle %1 of reel %2 found", i->main_subtitle()->id(), i->id()); + } else { + LOG_GENERAL("Main subtitle %1 of reel %2 found", reel->main_subtitle()->id(), reel->id()); - _text_count[static_cast(TextType::OPEN_SUBTITLE)] = 1; - _open_subtitle_language = try_to_parse_language (i->main_subtitle()->language()); + _text_count[TextType::OPEN_SUBTITLE] = 1; + _open_subtitle_language = try_to_parse_language(reel->main_subtitle()->language()); - for (auto const& font: i->main_subtitle()->asset()->font_data()) { - reel_fonts.push_back(make_shared(font.first, font.second)); + auto asset = reel->main_subtitle()->asset(); + for (auto const& font: asset->font_data()) { + _fonts.push_back({reel_index, asset->id(), make_shared(font.first, font.second)}); + } } } - for (auto j: i->closed_captions()) { - if (!j->asset_ref().resolved()) { - /* We are missing this asset so we can't continue; examination will be repeated later */ - _needs_assets = true; - LOG_GENERAL ("Closed caption %1 of reel %2 is missing", j->id(), i->id()); - return; + _text_count[TextType::CLOSED_CAPTION] = std::max(_text_count[TextType::CLOSED_CAPTION], static_cast(reel->closed_captions().size())); + if (_dcp_text_tracks.size() < reel->closed_captions().size()) { + /* We only want to add 1 DCPTextTrack to _dcp_text_tracks per closed caption. I guess it's possible that different + * reels have different numbers of tracks (though I don't think they should) so make sure that _dcp_text_tracks ends + * up with the maximum. + */ + _dcp_text_tracks.clear(); + for (auto ccap: reel->closed_captions()) { + _dcp_text_tracks.push_back(DCPTextTrack(ccap->annotation_text().get_value_or(""), try_to_parse_language(ccap->language()))); } + } - LOG_GENERAL ("Closed caption %1 of reel %2 found", j->id(), i->id()); + for (auto ccap: reel->closed_captions()) { + if (!ccap->asset_ref().resolved()) { + LOG_GENERAL("Closed caption %1 of reel %2 is missing", ccap->id(), reel->id()); + _needs_assets = true; + } else { + LOG_GENERAL("Closed caption %1 of reel %2 found", ccap->id(), reel->id()); - _text_count[static_cast(TextType::CLOSED_CAPTION)]++; - _dcp_text_tracks.push_back (DCPTextTrack(j->annotation_text().get_value_or(""), try_to_parse_language(j->language()))); + auto asset = ccap->asset(); + for (auto const& font: asset->font_data()) { + _fonts.push_back({reel_index, asset->id(), make_shared(font.first, font.second)}); + } + } } - if (i->main_markers ()) { - auto rm = i->main_markers()->get(); + if (reel->main_markers ()) { + auto rm = reel->main_markers()->get(); _markers.insert (rm.begin(), rm.end()); } - if (i->atmos()) { + if (reel->atmos()) { _has_atmos = true; - _atmos_length += i->atmos()->actual_duration(); + _atmos_length += reel->atmos()->actual_duration(); if (_atmos_edit_rate != dcp::Fraction()) { - DCPOMATIC_ASSERT (i->atmos()->edit_rate() == _atmos_edit_rate); + DCPOMATIC_ASSERT(reel->atmos()->edit_rate() == _atmos_edit_rate); } - _atmos_edit_rate = i->atmos()->edit_rate(); + _atmos_edit_rate = reel->atmos()->edit_rate(); } - if (i->main_picture()) { - _reel_lengths.push_back (i->main_picture()->actual_duration()); - } else if (i->main_sound()) { - _reel_lengths.push_back (i->main_sound()->actual_duration()); - } else if (i->main_subtitle()) { - _reel_lengths.push_back (i->main_subtitle()->actual_duration()); - } else if (!i->closed_captions().empty()) { - _reel_lengths.push_back (i->closed_captions().front()->actual_duration()); - } else if (!i->atmos()) { - _reel_lengths.push_back (i->atmos()->actual_duration()); + if (reel->main_picture()) { + _reel_lengths.push_back(reel->main_picture()->actual_duration()); + } else if (reel->main_sound()) { + _reel_lengths.push_back(reel->main_sound()->actual_duration()); + } else if (reel->main_subtitle()) { + _reel_lengths.push_back(reel->main_subtitle()->actual_duration()); + } else if (!reel->closed_captions().empty()) { + _reel_lengths.push_back(reel->closed_captions().front()->actual_duration()); + } else if (!reel->atmos()) { + _reel_lengths.push_back(reel->atmos()->actual_duration()); } - _fonts.push_back(reel_fonts); + ++reel_index; } - _encrypted = cpl->any_encrypted (); + _encrypted = selected_cpl->any_encrypted(); _kdm_valid = true; LOG_GENERAL_NC ("Check that everything encrypted has a key"); @@ -266,40 +278,42 @@ DCPExaminer::DCPExaminer (shared_ptr content, bool tolerant) * asset in each reel. This checks that when we do have a key it's the right one. */ try { - for (auto i: cpl->reels()) { + for (auto i: selected_cpl->reels()) { LOG_GENERAL ("Reel %1", i->id()); - auto pic = i->main_picture()->asset(); - if (pic->encrypted() && !pic->key()) { - _kdm_valid = false; - LOG_GENERAL_NC ("Picture has no key"); - break; - } - auto mono = dynamic_pointer_cast(pic); - auto stereo = dynamic_pointer_cast(pic); - - if (mono) { - auto reader = mono->start_read(); - reader->set_check_hmac (false); - reader->get_frame(0)->xyz_image(); - } else { - auto reader = stereo->start_read(); - reader->set_check_hmac (false); - reader->get_frame(0)->xyz_image(dcp::Eye::LEFT); + if (i->main_picture() && i->main_picture()->asset_ref().resolved()) { + auto pic = i->main_picture()->asset(); + if (pic->encrypted() && !pic->key()) { + _kdm_valid = false; + LOG_GENERAL_NC ("Picture has no key"); + break; + } + auto mono = dynamic_pointer_cast(pic); + auto stereo = dynamic_pointer_cast(pic); + + if (mono) { + auto reader = mono->start_read(); + reader->set_check_hmac (false); + reader->get_frame(0)->xyz_image(); + } else { + auto reader = stereo->start_read(); + reader->set_check_hmac (false); + reader->get_frame(0)->xyz_image(dcp::Eye::LEFT); + } } - if (i->main_sound()) { - auto sound = i->main_sound()->asset (); + if (i->main_sound() && i->main_sound()->asset_ref().resolved()) { + auto sound = i->main_sound()->asset(); if (sound->encrypted() && !sound->key()) { _kdm_valid = false; LOG_GENERAL_NC ("Sound has no key"); break; } - auto reader = i->main_sound()->asset()->start_read(); + auto reader = sound->start_read(); reader->set_check_hmac (false); reader->get_frame(0); } - if (i->main_subtitle()) { + if (i->main_subtitle() && i->main_subtitle()->asset_ref().resolved()) { auto sub = i->main_subtitle()->asset(); auto mxf_sub = dynamic_pointer_cast(sub); if (mxf_sub && mxf_sub->encrypted() && !mxf_sub->key()) { @@ -310,16 +324,17 @@ DCPExaminer::DCPExaminer (shared_ptr content, bool tolerant) sub->subtitles (); } - if (i->atmos()) { - auto atmos = i->atmos()->asset(); - if (atmos->encrypted() && !atmos->key()) { - _kdm_valid = false; - LOG_GENERAL_NC ("ATMOS sound has no key"); - break; + if (i->atmos() && i->atmos()->asset_ref().resolved()) { + if (auto atmos = i->atmos()->asset()) { + if (atmos->encrypted() && !atmos->key()) { + _kdm_valid = false; + LOG_GENERAL_NC ("ATMOS sound has no key"); + break; + } + auto reader = atmos->start_read(); + reader->set_check_hmac (false); + reader->get_frame(0); } - auto reader = atmos->start_read(); - reader->set_check_hmac (false); - reader->get_frame(0); } } } catch (dcp::ReadError& e) { @@ -330,13 +345,39 @@ DCPExaminer::DCPExaminer (shared_ptr content, bool tolerant) LOG_GENERAL ("KDM is invalid: %1", e.what()); } - _standard = cpl->standard(); - _three_d = !cpl->reels().empty() && cpl->reels().front()->main_picture() && - dynamic_pointer_cast (cpl->reels().front()->main_picture()->asset()); - _ratings = cpl->ratings(); - for (auto i: cpl->content_versions()) { - _content_versions.push_back (i.label_text); + _standard = selected_cpl->standard(); + if (!selected_cpl->reels().empty()) { + auto first_reel = selected_cpl->reels()[0]; + _three_d = first_reel->main_picture() && first_reel->main_picture()->asset_ref().resolved() && dynamic_pointer_cast(first_reel->main_picture()->asset()); + } + _ratings = selected_cpl->ratings(); + for (auto version: selected_cpl->content_versions()) { + _content_versions.push_back(version.label_text); + } + + _cpl = selected_cpl->id(); +} + + +void +DCPExaminer::add_fonts(shared_ptr content) +{ + FontIDAllocator font_id_allocator; + + for (auto const& font: _fonts) { + font_id_allocator.add_font(font.reel_index, font.asset_id, font.font->id()); + } + + font_id_allocator.allocate(); + + for (auto const& font: _fonts) { + auto font_copy = make_shared(*font.font); + font_copy->set_id(font_id_allocator.font_id(font.reel_index, font.asset_id, font.font->id())); + content->add_font(font_copy); } - _cpl = cpl->id (); + if (!font_id_allocator.has_default_font()) { + content->add_font(make_shared(font_id_allocator.default_font_id(), default_font_file())); + } } + diff --git a/src/lib/dcp_examiner.h b/src/lib/dcp_examiner.h index 6b88a3c0d..04fa31ea4 100644 --- a/src/lib/dcp_examiner.h +++ b/src/lib/dcp_examiner.h @@ -38,7 +38,7 @@ class DCPContent; class DCPExaminer : public VideoExaminer, public AudioExaminer { public: - explicit DCPExaminer (std::shared_ptr, bool tolerant); + DCPExaminer(std::shared_ptr, bool tolerant); bool has_video () const override { return _has_video; @@ -48,10 +48,8 @@ public: return _video_frame_rate; } - dcp::Size video_size () const override { - DCPOMATIC_ASSERT (_has_video); - DCPOMATIC_ASSERT (_video_size); - return *_video_size; + boost::optional video_size() const override { + return _video_size; } Frame video_length () const override { @@ -90,6 +88,10 @@ public: return _audio_channels.get_value_or (0); } + int active_audio_channels() const { + return _active_audio_channels.get_value_or(0); + } + Frame audio_length () const override { return _audio_length; } @@ -103,10 +105,12 @@ public: } /** @param type TEXT_OPEN_SUBTITLE or TEXT_CLOSED_CAPTION. - * @return Number of assets of this type in this DCP. + * @return the number of "streams" of this type in the DCP. + * Reels do not affect the return value of this method: if a DCP + * has any subtitles, type=TEXT_OPEN_SUBTITLE will return 1. */ int text_count (TextType type) const { - return _text_count[static_cast(type)]; + return _text_count[type]; } boost::optional open_subtitle_language () const { @@ -167,16 +171,14 @@ public: return _atmos_edit_rate; } - /** @return fonts in each reel */ - std::vector>> fonts() const { - return _fonts; - } + void add_fonts(std::shared_ptr content); private: boost::optional _video_frame_rate; boost::optional _video_size; Frame _video_length = 0; boost::optional _audio_channels; + boost::optional _active_audio_channels; boost::optional _audio_frame_rate; Frame _audio_length = 0; std::string _name; @@ -186,7 +188,7 @@ private: bool _has_audio = false; boost::optional _audio_language; /** number of different assets of each type (OCAP/CCAP) */ - int _text_count[static_cast(TextType::COUNT)]; + EnumIndexedVector _text_count; boost::optional _open_subtitle_language; /** the DCPTextTracks for each of our CCAPs */ std::vector _dcp_text_tracks; @@ -204,5 +206,19 @@ private: bool _has_atmos = false; Frame _atmos_length = 0; dcp::Fraction _atmos_edit_rate; - std::vector>> _fonts; + + struct Font + { + Font(int reel_index_, std::string asset_id_, std::shared_ptr font_) + : reel_index(reel_index_) + , asset_id(asset_id_) + , font(font_) + {} + + int reel_index; + std::string asset_id; + std::shared_ptr font; + }; + + std::vector _fonts; }; diff --git a/src/lib/dcp_subtitle_content.cc b/src/lib/dcp_subtitle_content.cc index b111bdb2a..8de5967ef 100644 --- a/src/lib/dcp_subtitle_content.cc +++ b/src/lib/dcp_subtitle_content.cc @@ -21,6 +21,7 @@ #include "font.h" #include "dcp_subtitle_content.h" #include "film.h" +#include "font_id_allocator.h" #include "text_content.h" #include #include @@ -57,12 +58,12 @@ DCPSubtitleContent::examine (shared_ptr film, shared_ptr job) { Content::examine (film, job); - auto sc = load (path(0)); + auto subtitle_asset = load(path(0)); - auto iop = dynamic_pointer_cast(sc); - auto smpte = dynamic_pointer_cast(sc); + auto iop = dynamic_pointer_cast(subtitle_asset); + auto smpte = dynamic_pointer_cast(subtitle_asset); if (smpte) { - set_video_frame_rate (smpte->edit_rate().numerator); + set_video_frame_rate(film, smpte->edit_rate().numerator); } boost::mutex::scoped_lock lm (_mutex); @@ -70,25 +71,46 @@ DCPSubtitleContent::examine (shared_ptr film, shared_ptr job) /* Default to turning these subtitles on */ only_text()->set_use (true); - _length = ContentTime::from_seconds (sc->latest_subtitle_out().as_seconds()); + _length = ContentTime::from_seconds(subtitle_asset->latest_subtitle_out().as_seconds()); - sc->fix_empty_font_ids (); + subtitle_asset->fix_empty_font_ids(); + add_fonts(only_text(), subtitle_asset); +} + + +void +DCPSubtitleContent::add_fonts(shared_ptr content, shared_ptr subtitle_asset) +{ + FontIDAllocator font_id_allocator; + + for (auto node: subtitle_asset->load_font_nodes()) { + font_id_allocator.add_font(0, subtitle_asset->id(), node->id); + } + font_id_allocator.allocate(); - auto font_data = sc->font_data(); - for (auto node: sc->load_font_nodes()) { + auto font_data = subtitle_asset->font_data(); + for (auto node: subtitle_asset->load_font_nodes()) { auto data = font_data.find(node->id); + shared_ptr font; if (data != font_data.end()) { - only_text()->add_font(make_shared(node->id, data->second)); + font = make_shared( + font_id_allocator.font_id(0, subtitle_asset->id(), node->id), + data->second + ); } else { - only_text()->add_font(make_shared(node->id)); + font = make_shared( + font_id_allocator.font_id(0, subtitle_asset->id(), node->id) + ); } + content->add_font(font); } - if (only_text()->fonts().empty()) { - only_text()->add_font(make_shared("")); + if (!font_id_allocator.has_default_font()) { + content->add_font(make_shared(font_id_allocator.default_font_id(), default_font_file())); } } + DCPTime DCPSubtitleContent::full_length (shared_ptr film) const { diff --git a/src/lib/dcp_subtitle_content.h b/src/lib/dcp_subtitle_content.h index 5949f8b0b..89a6f26a2 100644 --- a/src/lib/dcp_subtitle_content.h +++ b/src/lib/dcp_subtitle_content.h @@ -35,5 +35,7 @@ public: dcpomatic::DCPTime approximate_length () const override; private: + void add_fonts(std::shared_ptr content, std::shared_ptr subtitle_asset); + dcpomatic::ContentTime _length; }; diff --git a/src/lib/dcp_subtitle_decoder.cc b/src/lib/dcp_subtitle_decoder.cc index cbfe6fdbe..711dc77f2 100644 --- a/src/lib/dcp_subtitle_decoder.cc +++ b/src/lib/dcp_subtitle_decoder.cc @@ -21,8 +21,10 @@ #include "dcp_subtitle_content.h" #include "dcp_subtitle_decoder.h" +#include "film.h" #include "font.h" #include "text_content.h" +#include "util.h" #include #include @@ -41,19 +43,22 @@ DCPSubtitleDecoder::DCPSubtitleDecoder (shared_ptr film, shared_ptr< : Decoder (film) { /* Load the XML or MXF file */ - auto const asset = load (content->path(0)); - asset->fix_empty_font_ids (); - _subtitles = asset->subtitles (); + _asset = load(content->path(0)); + _asset->fix_empty_font_ids(); + _subtitles = _asset->subtitles(); _next = _subtitles.begin (); - if (dynamic_pointer_cast(asset)) { - _standard = dcp::Standard::INTEROP; - } else { - _standard = dcp::Standard::SMPTE; - } + _subtitle_standard = _asset->subtitle_standard(); text.push_back (make_shared(this, content->only_text())); update_position(); + + FontIDAllocator font_id_allocator; + + for (auto node: _asset->load_font_nodes()) { + _font_id_allocator.add_font(0, _asset->id(), node->id); + } + _font_id_allocator.allocate(); } @@ -93,7 +98,13 @@ DCPSubtitleDecoder::pass () while (_next != _subtitles.end () && content_time_period (*_next) == p) { auto ns = dynamic_pointer_cast(*_next); if (ns) { - s.push_back (*ns); + dcp::SubtitleString ns_copy = *ns; + if (ns_copy.font()) { + ns_copy.set_font(_font_id_allocator.font_id(0, _asset->id(), ns_copy.font().get())); + } else { + ns_copy.set_font(_font_id_allocator.default_font_id()); + } + s.push_back(ns_copy); ++_next; } else { /* XXX: perhaps these image subs should also be collected together like the string ones are; @@ -108,7 +119,7 @@ DCPSubtitleDecoder::pass () } } - only_text()->emit_plain(p, s, _standard); + only_text()->emit_plain(p, s, _subtitle_standard); update_position(); diff --git a/src/lib/dcp_subtitle_decoder.h b/src/lib/dcp_subtitle_decoder.h index 3eed4ad24..9d0851253 100644 --- a/src/lib/dcp_subtitle_decoder.h +++ b/src/lib/dcp_subtitle_decoder.h @@ -19,8 +19,9 @@ */ -#include "text_decoder.h" #include "dcp_subtitle.h" +#include "font_id_allocator.h" +#include "text_decoder.h" class DCPSubtitleContent; @@ -43,5 +44,8 @@ private: std::vector> _subtitles; std::vector>::const_iterator _next; - dcp::Standard _standard; + dcp::SubtitleStandard _subtitle_standard; + + std::shared_ptr _asset; + FontIDAllocator _font_id_allocator; }; diff --git a/src/lib/dcp_video.cc b/src/lib/dcp_video.cc index 5879d6be6..217b72183 100644 --- a/src/lib/dcp_video.cc +++ b/src/lib/dcp_video.cc @@ -71,9 +71,6 @@ using namespace boost::placeholders; #endif -#define DCI_COEFFICENT (48.0 / 52.37) - - /** Construct a DCP video frame. * @param frame Input frame. * @param index Index of the frame within the DCP. @@ -101,7 +98,7 @@ DCPVideo::DCPVideo (shared_ptr frame, shared_ptr -DCPVideo::convert_to_xyz (shared_ptr frame, dcp::NoteHandler note) +DCPVideo::convert_to_xyz (shared_ptr frame) { shared_ptr xyz; @@ -111,8 +108,7 @@ DCPVideo::convert_to_xyz (shared_ptr frame, dcp::NoteHandler image->data()[0], image->size(), image->stride()[0], - frame->colour_conversion().get(), - note + frame->colour_conversion().get() ); } else { xyz = make_shared(image->data()[0], image->size(), image->stride()[0]); @@ -134,7 +130,7 @@ DCPVideo::encode_locally () const int const minimum_size = 16384; LOG_DEBUG_ENCODE("Using minimum frame size %1", minimum_size); - auto xyz = convert_to_xyz (_frame, boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2)); + auto xyz = convert_to_xyz(_frame); int noise_amount = 2; int pixel_skip = 16; while (true) { @@ -159,7 +155,7 @@ DCPVideo::encode_locally () const * convert_to_xyz() again because compress_j2k() corrupts its xyz parameter. */ - xyz = convert_to_xyz (_frame, boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2)); + xyz = convert_to_xyz(_frame); auto size = xyz->size (); auto pixels = size.width * size.height; dcpomatic::RNG rng(42); @@ -229,7 +225,7 @@ DCPVideo::encode_remotely (EncodeServerDescription serv, int timeout) const /* Send XML metadata */ auto xml = doc.write_to_string ("UTF-8"); - socket->write (xml.length() + 1); + socket->write(xml.bytes() + 1); socket->write ((uint8_t *) xml.c_str(), xml.bytes() + 1); /* Send binary data */ diff --git a/src/lib/dcp_video.h b/src/lib/dcp_video.h index 3bd516ccd..bf95ccfe6 100644 --- a/src/lib/dcp_video.h +++ b/src/lib/dcp_video.h @@ -18,18 +18,23 @@ */ -#include "types.h" + #include "encode_server_description.h" +#include "resolution.h" #include #include +#include + /** @file src/dcp_video_frame.h * @brief A single frame of video destined for a DCP. */ + class Log; class PlayerVideo; + /** @class DCPVideo * @brief A single frame of video destined for a DCP. * @@ -59,7 +64,7 @@ public: bool same (std::shared_ptr other) const; - static std::shared_ptr convert_to_xyz (std::shared_ptr frame, dcp::NoteHandler note); + static std::shared_ptr convert_to_xyz(std::shared_ptr frame); private: diff --git a/src/lib/dcpomatic_socket.cc b/src/lib/dcpomatic_socket.cc index 014e498e6..8f8f639cc 100644 --- a/src/lib/dcpomatic_socket.cc +++ b/src/lib/dcpomatic_socket.cc @@ -84,8 +84,6 @@ Socket::connect (boost::asio::ip::tcp::endpoint endpoint) boost::asio::socket_base::send_buffer_size new_size(*_send_buffer_size); _socket.set_option(new_size); - - LOG_GENERAL("Changed socket send buffer size from %1 to %2", old_size.value(), *_send_buffer_size); } } diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h index 9e7191b1e..1b12ea901 100644 --- a/src/lib/dcpomatic_time.h +++ b/src/lib/dcpomatic_time.h @@ -217,11 +217,11 @@ public: HMSF hmsf; hmsf.h = ff / (3600 * r); - ff -= hmsf.h * 3600 * r; + ff -= static_cast(hmsf.h) * 3600 * r; hmsf.m = ff / (60 * r); - ff -= hmsf.m * 60 * r; + ff -= static_cast(hmsf.m) * 60 * r; hmsf.s = ff / r; - ff -= hmsf.s * r; + ff -= static_cast(hmsf.s) * r; hmsf.f = static_cast (ff); return hmsf; diff --git a/src/lib/decoder.h b/src/lib/decoder.h index 0324075a3..7097db88d 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -29,8 +29,6 @@ #include "dcpomatic_time.h" -#include "film.h" -#include "types.h" #include "weak_film.h" #include diff --git a/src/lib/digester.cc b/src/lib/digester.cc index 8542c75a1..09214c3de 100644 --- a/src/lib/digester.cc +++ b/src/lib/digester.cc @@ -67,7 +67,7 @@ Digester::get () const char hex[MD5_DIGEST_SIZE * 2 + 1]; for (int i = 0; i < MD5_DIGEST_SIZE; ++i) { - sprintf(hex + i * 2, "%02x", digest[i]); + snprintf(hex + i * 2, MD5_DIGEST_SIZE * 2 + 1, "%02x", digest[i]); } _digest = hex; diff --git a/src/lib/disk_writer_messages.cc b/src/lib/disk_writer_messages.cc new file mode 100644 index 000000000..7bccdd9fc --- /dev/null +++ b/src/lib/disk_writer_messages.cc @@ -0,0 +1,100 @@ +/* + Copyright (C) 2023 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "dcpomatic_assert.h" +#include "disk_writer_messages.h" +#include "nanomsg.h" +#include + + +using std::string; +using boost::optional; + + +boost::optional +DiskWriterBackEndResponse::read_from_nanomsg(Nanomsg& nanomsg, int timeout) +{ + auto s = nanomsg.receive(timeout); + if (!s) { + return {}; + } + if (*s == DISK_WRITER_OK) { + return DiskWriterBackEndResponse::ok(); + } else if (*s == DISK_WRITER_ERROR) { + auto const m = nanomsg.receive(500); + auto const n = nanomsg.receive(500); + auto const p = nanomsg.receive(500); + return DiskWriterBackEndResponse::error(m.get_value_or(""), dcp::raw_convert(n.get_value_or("0")), dcp::raw_convert(p.get_value_or("0"))); + } else if (*s == DISK_WRITER_PONG) { + return DiskWriterBackEndResponse::pong(); + } else if (*s == DISK_WRITER_FORMAT_PROGRESS) { + auto progress = nanomsg.receive(500); + return DiskWriterBackEndResponse::format_progress(dcp::raw_convert(progress.get_value_or("0"))); + } else if (*s == DISK_WRITER_COPY_PROGRESS) { + auto progress = nanomsg.receive(500); + return DiskWriterBackEndResponse::copy_progress(dcp::raw_convert(progress.get_value_or("0"))); + } else if (*s == DISK_WRITER_VERIFY_PROGRESS) { + auto progress = nanomsg.receive(500); + return DiskWriterBackEndResponse::verify_progress(dcp::raw_convert(progress.get_value_or("0"))); + } else { + DCPOMATIC_ASSERT(false); + } + + return {}; +} + + +/** @return true if the message was sent, false if there was a timeout */ +bool +DiskWriterBackEndResponse::write_to_nanomsg(Nanomsg& nanomsg, int timeout) const +{ + string message; + + switch (_type) + { + case Type::OK: + message = String::compose("%1\n", DISK_WRITER_OK); + break; + case Type::ERROR: + message = String::compose("%1\n%2\n%3\n%4\n", DISK_WRITER_ERROR, _error_message, _ext4_error_number, _platform_error_number); + break; + case Type::PONG: + message = String::compose("%1\n", DISK_WRITER_PONG); + break; + case Type::FORMAT_PROGRESS: + message = String::compose("%1\n", DISK_WRITER_FORMAT_PROGRESS); + message += dcp::raw_convert(_progress) + "\n"; + break; + case Type::COPY_PROGRESS: + message = String::compose("%1\n", DISK_WRITER_COPY_PROGRESS); + message += dcp::raw_convert(_progress) + "\n"; + break; + case Type::VERIFY_PROGRESS: + message = String::compose("%1\n", DISK_WRITER_VERIFY_PROGRESS); + message += dcp::raw_convert(_progress) + "\n"; + break; + } + + + return nanomsg.send(message, timeout); +} + + diff --git a/src/lib/disk_writer_messages.h b/src/lib/disk_writer_messages.h index ab86f083e..2fa225d85 100644 --- a/src/lib/disk_writer_messages.h +++ b/src/lib/disk_writer_messages.h @@ -18,6 +18,14 @@ */ + +#include +#include + + +class Nanomsg; + + /* We have the front-end application dcpomatic2_disk and the back-end * dcpomatic2_disk_writer. The communication is line-based, separated * by \n. @@ -50,6 +58,7 @@ #define DISK_WRITER_ERROR "E" // Error message // Error number +// Additional error number (a platform-specific error from lwext4) // the drive is being formatted, 40% done #define DISK_WRITER_FORMAT_PROGRESS "F" @@ -81,3 +90,86 @@ // or // DISK_WRITER_ERROR + +class DiskWriterBackEndResponse +{ +public: + enum class Type { + OK, + ERROR, + PONG, + FORMAT_PROGRESS, + COPY_PROGRESS, + VERIFY_PROGRESS + }; + + static DiskWriterBackEndResponse ok() { + return DiskWriterBackEndResponse(Type::OK); + } + + static DiskWriterBackEndResponse error(std::string message, int ext4_number, int platform_number) { + auto r = DiskWriterBackEndResponse(Type::ERROR); + r._error_message = message; + r._ext4_error_number = ext4_number; + r._platform_error_number = platform_number; + return r; + } + + static DiskWriterBackEndResponse pong() { + return DiskWriterBackEndResponse(Type::PONG); + } + + static DiskWriterBackEndResponse format_progress(float p) { + auto r = DiskWriterBackEndResponse(Type::FORMAT_PROGRESS); + r._progress = p; + return r; + } + + static DiskWriterBackEndResponse copy_progress(float p) { + auto r = DiskWriterBackEndResponse(Type::COPY_PROGRESS); + r._progress = p; + return r; + } + + static DiskWriterBackEndResponse verify_progress(float p) { + auto r = DiskWriterBackEndResponse(Type::VERIFY_PROGRESS); + r._progress = p; + return r; + } + + static boost::optional read_from_nanomsg(Nanomsg& nanomsg, int timeout); + + bool write_to_nanomsg(Nanomsg& nanomsg, int timeout) const; + + Type type() const { + return _type; + } + + std::string error_message() const { + return _error_message; + } + + int ext4_error_number() const { + return _ext4_error_number; + } + + int platform_error_number() const { + return _platform_error_number; + } + + float progress() const { + return _progress; + } + +private: + DiskWriterBackEndResponse(Type type) + : _type(type) + {} + + Type _type; + std::string _error_message; + int _ext4_error_number = 0; + int _platform_error_number = 0; + float _progress = 0; +}; + diff --git a/src/lib/dkdm_recipient.cc b/src/lib/dkdm_recipient.cc index ff19aa265..c73379bed 100644 --- a/src/lib/dkdm_recipient.cc +++ b/src/lib/dkdm_recipient.cc @@ -19,10 +19,12 @@ */ +#include "config.h" #include "dkdm_recipient.h" #include "film.h" #include "kdm_with_metadata.h" #include +#include using std::make_shared; @@ -71,19 +73,16 @@ kdm_for_dkdm_recipient ( return {}; } - dcp::LocalTime const begin(valid_from, recipient->utc_offset_hour, recipient->utc_offset_minute); - dcp::LocalTime const end (valid_to, recipient->utc_offset_hour, recipient->utc_offset_minute); - - auto const kdm = film->make_kdm ( - recipient->recipient.get(), - vector(), - cpl, - begin, - end, - dcp::Formulation::MODIFIED_TRANSITIONAL_1, - true, - 0 - ); + dcp::LocalTime const begin(valid_from, dcp::UTCOffset(recipient->utc_offset_hour, recipient->utc_offset_minute)); + dcp::LocalTime const end (valid_to, dcp::UTCOffset(recipient->utc_offset_hour, recipient->utc_offset_minute)); + + auto signer = Config::instance()->signer_chain(); + if (!signer->valid()) { + throw InvalidSignerError(); + } + + auto const decrypted_kdm = film->make_kdm(cpl, begin, end); + auto const kdm = decrypted_kdm.encrypt(signer, recipient->recipient.get(), {}, dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, 0); dcp::NameFormat::Map name_values; name_values['f'] = kdm.content_title_text(); diff --git a/src/lib/dkdm_recipient.h b/src/lib/dkdm_recipient.h index 6e9e4dfb1..7a0fa0185 100644 --- a/src/lib/dkdm_recipient.h +++ b/src/lib/dkdm_recipient.h @@ -33,7 +33,7 @@ public: std::string const& name_, std::string const& notes_, boost::optional recipient_, - std::list emails_, + std::vector emails_, int utc_offset_hour_, int utc_offset_minute_ ) @@ -49,7 +49,7 @@ public: void as_xml (xmlpp::Element *) const override; - std::list emails; + std::vector emails; int utc_offset_hour; int utc_offset_minute; }; diff --git a/src/lib/dkdm_wrapper.cc b/src/lib/dkdm_wrapper.cc index 532bbb314..016c77c3f 100644 --- a/src/lib/dkdm_wrapper.cc +++ b/src/lib/dkdm_wrapper.cc @@ -111,3 +111,35 @@ DKDMGroup::remove (shared_ptr child) } } } + + +bool +DKDMGroup::contains(string dkdm_id) const +{ + for (auto child: _children) { + if (auto child_group = dynamic_pointer_cast(child)) { + if (child_group->contains(dkdm_id)) { + return true; + } + } else if (auto child_dkdm = dynamic_pointer_cast(child)) { + if (child_dkdm->dkdm().id() == dkdm_id) { + return true; + } + } + } + + return false; +} + + +bool +DKDMGroup::contains_dkdm() const +{ + for (auto child: _children) { + if (child->contains_dkdm()) { + return true; + } + } + + return false; +} diff --git a/src/lib/dkdm_wrapper.h b/src/lib/dkdm_wrapper.h index 7227fdc86..7182e5e85 100644 --- a/src/lib/dkdm_wrapper.h +++ b/src/lib/dkdm_wrapper.h @@ -38,6 +38,8 @@ public: virtual ~DKDMBase () {} virtual std::string name () const = 0; virtual void as_xml (xmlpp::Element *) const = 0; + /** @return true if this thing is, or contains, any actual DKDM */ + virtual bool contains_dkdm() const = 0; static std::shared_ptr read (cxml::ConstNodePtr node); @@ -63,6 +65,9 @@ public: std::string name () const override; void as_xml (xmlpp::Element *) const override; + bool contains_dkdm() const override { + return true; + } dcp::EncryptedKDM dkdm () const { return _dkdm; @@ -86,6 +91,8 @@ public: void as_xml (xmlpp::Element *) const override; + bool contains_dkdm() const override; + std::list> children () const { return _children; } @@ -93,6 +100,8 @@ public: void add (std::shared_ptr child, std::shared_ptr previous = std::shared_ptr()); void remove (std::shared_ptr child); + bool contains(std::string dkdm_id) const; + private: std::string _name; std::list> _children; diff --git a/src/lib/ecinema_kdm_data.h b/src/lib/ecinema_kdm_data.h deleted file mode 100644 index 9ca3b24d0..000000000 --- a/src/lib/ecinema_kdm_data.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright (C) 2019 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - -/* ECinema KDM data block contains: - - key (16 bytes) - - (optional) not-valid-before time (25 bytes) - - (optional) not-valid-after time (25 bytes) -*/ - -#define ECINEMA_KDM_KEY 0 -#define ECINEMA_KDM_KEY_LENGTH 16 -#define ECINEMA_KDM_NOT_VALID_BEFORE (ECINEMA_KDM_KEY_LENGTH) -#define ECINEMA_KDM_NOT_VALID_BEFORE_LENGTH 25 -#define ECINEMA_KDM_NOT_VALID_AFTER (ECINEMA_KDM_NOT_VALID_BEFORE + ECINEMA_KDM_NOT_VALID_BEFORE_LENGTH) -#define ECINEMA_KDM_NOT_VALID_AFTER_LENGTH 25 diff --git a/src/lib/email.cc b/src/lib/email.cc new file mode 100644 index 000000000..8557b40e0 --- /dev/null +++ b/src/lib/email.cc @@ -0,0 +1,299 @@ +/* + Copyright (C) 2015-2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "compose.hpp" +#include "config.h" +#include "email.h" +#include "exceptions.h" +#include +#include +#include + +#include "i18n.h" + + +using std::cout; +using std::min; +using std::pair; +using std::shared_ptr; +using std::string; +using std::vector; +using dcp::ArrayData; + + +Email::Email(string from, vector to, string subject, string body) + : _from (from) + , _to (to) + , _subject (subject) + , _body (fix (body)) + , _offset (0) +{ + +} + + +string +Email::fix(string s) const +{ + boost::algorithm::replace_all (s, "\n", "\r\n"); + boost::algorithm::replace_all (s, "\0", " "); + return s; +} + + +void +Email::add_cc(string cc) +{ + _cc.push_back (cc); +} + + +void +Email::add_bcc(string bcc) +{ + _bcc.push_back (bcc); +} + + +void +Email::add_attachment(boost::filesystem::path file, string name, string mime_type) +{ + Attachment a; + a.file = dcp::ArrayData(file); + a.name = name; + a.mime_type = mime_type; + _attachments.push_back (a); +} + + +static size_t +curl_data_shim (void* ptr, size_t size, size_t nmemb, void* userp) +{ + return reinterpret_cast(userp)->get_data (ptr, size, nmemb); +} + + +static int +curl_debug_shim (CURL* curl, curl_infotype type, char* data, size_t size, void* userp) +{ + return reinterpret_cast(userp)->debug (curl, type, data, size); +} + + +size_t +Email::get_data(void* ptr, size_t size, size_t nmemb) +{ + size_t const t = min (_email.length() - _offset, size * nmemb); + memcpy (ptr, _email.substr (_offset, t).c_str(), t); + _offset += t; + return t; +} + + +void +Email::send(string server, int port, EmailProtocol protocol, string user, string password) +{ + char date_buffer[128]; + time_t now = time (0); + strftime (date_buffer, sizeof(date_buffer), "%a, %d %b %Y %H:%M:%S ", localtime(&now)); + + auto const utc_now = boost::posix_time::second_clock::universal_time (); + auto const local_now = boost::date_time::c_local_adjustor::utc_to_local (utc_now); + auto offset = local_now - utc_now; + sprintf (date_buffer + strlen(date_buffer), "%s%02d%02d", (offset.hours() >= 0 ? "+" : "-"), int(abs(offset.hours())), int(offset.minutes())); + + _email = "Date: " + string(date_buffer) + "\r\n" + "To: " + address_list (_to) + "\r\n" + "From: " + _from + "\r\n"; + + if (!_cc.empty()) { + _email += "Cc: " + address_list(_cc) + "\r\n"; + } + + if (!_bcc.empty()) { + _email += "Bcc: " + address_list(_bcc) + "\r\n"; + } + + string const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + string boundary; + for (int i = 0; i < 32; ++i) { + boundary += chars[rand() % chars.length()]; + } + + if (!_attachments.empty ()) { + _email += "MIME-Version: 1.0\r\n" + "Content-Type: multipart/mixed; boundary=" + boundary + "\r\n"; + } + + _email += "Subject: " + encode_rfc1342(_subject) + "\r\n" + "User-Agent: DCP-o-matic\r\n" + "\r\n"; + + if (!_attachments.empty ()) { + _email += "--" + boundary + "\r\n" + + "Content-Type: text/plain; charset=utf-8\r\n\r\n"; + } + + _email += _body; + + for (auto i: _attachments) { + _email += "\r\n\r\n--" + boundary + "\r\n" + "Content-Type: " + i.mime_type + "; name=" + encode_rfc1342(i.name) + "\r\n" + "Content-Transfer-Encoding: Base64\r\n" + "Content-Disposition: attachment; filename=" + encode_rfc1342(i.name) + "\r\n\r\n"; + + auto b64 = BIO_new (BIO_f_base64()); + if (!b64) { + throw std::bad_alloc(); + } + + auto bio = BIO_new (BIO_s_mem()); + if (!bio) { + throw std::bad_alloc(); + } + bio = BIO_push (b64, bio); + + BIO_write(bio, i.file.data(), i.file.size()); + (void) BIO_flush (bio); + + char* out; + long int bytes = BIO_get_mem_data (bio, &out); + _email += fix (string (out, bytes)); + + BIO_free_all (b64); + } + + if (!_attachments.empty ()) { + _email += "\r\n--" + boundary + "--\r\n"; + } + + curl_global_init (CURL_GLOBAL_DEFAULT); + + auto curl = curl_easy_init (); + if (!curl) { + throw NetworkError ("Could not initialise libcurl"); + } + + if ((protocol == EmailProtocol::AUTO && port == 465) || protocol == EmailProtocol::SSL) { + /* "SSL" or "Implicit TLS"; I think curl wants us to use smtps here */ + curl_easy_setopt (curl, CURLOPT_URL, String::compose("smtps://%1:%2", server, port).c_str()); + } else { + curl_easy_setopt (curl, CURLOPT_URL, String::compose("smtp://%1:%2", server, port).c_str()); + } + + if (!user.empty ()) { + curl_easy_setopt (curl, CURLOPT_USERNAME, user.c_str ()); + } + if (!password.empty ()) { + curl_easy_setopt (curl, CURLOPT_PASSWORD, password.c_str()); + } + + curl_easy_setopt (curl, CURLOPT_MAIL_FROM, _from.c_str()); + + struct curl_slist* recipients = nullptr; + for (auto i: _to) { + recipients = curl_slist_append (recipients, i.c_str()); + } + for (auto i: _cc) { + recipients = curl_slist_append (recipients, i.c_str()); + } + for (auto i: _bcc) { + recipients = curl_slist_append (recipients, i.c_str()); + } + + curl_easy_setopt (curl, CURLOPT_MAIL_RCPT, recipients); + + curl_easy_setopt (curl, CURLOPT_READFUNCTION, curl_data_shim); + curl_easy_setopt (curl, CURLOPT_READDATA, this); + curl_easy_setopt (curl, CURLOPT_UPLOAD, 1L); + + if (protocol == EmailProtocol::AUTO || protocol == EmailProtocol::STARTTLS) { + curl_easy_setopt (curl, CURLOPT_USE_SSL, (long) CURLUSESSL_TRY); + } + curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt (curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt (curl, CURLOPT_DEBUGFUNCTION, curl_debug_shim); + curl_easy_setopt (curl, CURLOPT_DEBUGDATA, this); + + auto const r = curl_easy_perform (curl); + if (r != CURLE_OK) { + throw NetworkError (_("Failed to send email"), string(curl_easy_strerror(r))); + } + + curl_slist_free_all (recipients); + curl_easy_cleanup (curl); + curl_global_cleanup (); +} + + +string +Email::address_list(vector addresses) +{ + string o; + for (auto i: addresses) { + o += i + ", "; + } + + return o.substr (0, o.length() - 2); +} + + +int +Email::debug(CURL *, curl_infotype type, char* data, size_t size) +{ + if (type == CURLINFO_TEXT) { + _notes += string (data, size); + } else if (type == CURLINFO_HEADER_IN) { + _notes += "<- " + string (data, size); + } else if (type == CURLINFO_HEADER_OUT) { + _notes += "-> " + string (data, size); + } + return 0; +} + + +string +Email::encode_rfc1342(string subject) +{ + auto b64 = BIO_new(BIO_f_base64()); + if (!b64) { + throw std::bad_alloc(); + } + + auto bio = BIO_new(BIO_s_mem()); + if (!bio) { + throw std::bad_alloc(); + } + + bio = BIO_push(b64, bio); + BIO_write(bio, subject.c_str(), subject.length()); + (void) BIO_flush(bio); + + char* out; + long int bytes = BIO_get_mem_data(bio, &out); + string base64_subject(out, bytes); + BIO_free_all(b64); + + boost::algorithm::replace_all(base64_subject, "\n", ""); + return "=?utf-8?B?" + base64_subject + "?="; +} + diff --git a/src/lib/email.h b/src/lib/email.h new file mode 100644 index 000000000..36398bfd8 --- /dev/null +++ b/src/lib/email.h @@ -0,0 +1,74 @@ +/* + Copyright (C) 2015-2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include +#include + + +class Email +{ +public: + Email(std::string from, std::vector to, std::string subject, std::string body); + + void add_cc (std::string cc); + void add_bcc (std::string bcc); + /** Add attachment, copying the contents of the file into memory */ + void add_attachment (boost::filesystem::path file, std::string name, std::string mime_type); + + void send (std::string server, int port, EmailProtocol protocol, std::string user = "", std::string password = ""); + + std::string notes () const { + return _notes; + } + + size_t get_data (void* ptr, size_t size, size_t nmemb); + int debug (CURL* curl, curl_infotype type, char* data, size_t size); + + /** @return full email, after send() has been called */ + std::string email () const { + return _email; + } + + static std::string address_list(std::vector addresses); + +private: + + std::string fix (std::string s) const; + static std::string encode_rfc1342 (std::string subject); + + std::string _from; + std::vector _to; + std::string _subject; + std::string _body; + std::vector _cc; + std::vector _bcc; + + struct Attachment { + dcp::ArrayData file; + std::string name; + std::string mime_type; + }; + + std::vector _attachments; + std::string _email; + size_t _offset; + std::string _notes; +}; diff --git a/src/lib/emailer.cc b/src/lib/emailer.cc deleted file mode 100644 index e21bbfe2d..000000000 --- a/src/lib/emailer.cc +++ /dev/null @@ -1,300 +0,0 @@ -/* - Copyright (C) 2015-2021 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - - -#include "compose.hpp" -#include "config.h" -#include "emailer.h" -#include "exceptions.h" -#include -#include -#include - -#include "i18n.h" - - -using std::cout; -using std::list; -using std::min; -using std::pair; -using std::shared_ptr; -using std::string; -using dcp::ArrayData; - - -Emailer::Emailer (string from, list to, string subject, string body) - : _from (from) - , _to (to) - , _subject (subject) - , _body (fix (body)) - , _offset (0) -{ - -} - - -string -Emailer::fix (string s) const -{ - boost::algorithm::replace_all (s, "\n", "\r\n"); - boost::algorithm::replace_all (s, "\0", " "); - return s; -} - - -void -Emailer::add_cc (string cc) -{ - _cc.push_back (cc); -} - - -void -Emailer::add_bcc (string bcc) -{ - _bcc.push_back (bcc); -} - - -void -Emailer::add_attachment (boost::filesystem::path file, string name, string mime_type) -{ - Attachment a; - a.file = file; - a.name = name; - a.mime_type = mime_type; - _attachments.push_back (a); -} - - -static size_t -curl_data_shim (void* ptr, size_t size, size_t nmemb, void* userp) -{ - return reinterpret_cast(userp)->get_data (ptr, size, nmemb); -} - - -static int -curl_debug_shim (CURL* curl, curl_infotype type, char* data, size_t size, void* userp) -{ - return reinterpret_cast(userp)->debug (curl, type, data, size); -} - - -size_t -Emailer::get_data (void* ptr, size_t size, size_t nmemb) -{ - size_t const t = min (_email.length() - _offset, size * nmemb); - memcpy (ptr, _email.substr (_offset, t).c_str(), t); - _offset += t; - return t; -} - - -void -Emailer::send (string server, int port, EmailProtocol protocol, string user, string password) -{ - char date_buffer[128]; - time_t now = time (0); - strftime (date_buffer, sizeof(date_buffer), "%a, %d %b %Y %H:%M:%S ", localtime(&now)); - - auto const utc_now = boost::posix_time::second_clock::universal_time (); - auto const local_now = boost::date_time::c_local_adjustor::utc_to_local (utc_now); - auto offset = local_now - utc_now; - sprintf (date_buffer + strlen(date_buffer), "%s%02d%02d", (offset.hours() >= 0 ? "+" : "-"), int(abs(offset.hours())), int(offset.minutes())); - - _email = "Date: " + string(date_buffer) + "\r\n" - "To: " + address_list (_to) + "\r\n" - "From: " + _from + "\r\n"; - - if (!_cc.empty()) { - _email += "Cc: " + address_list(_cc) + "\r\n"; - } - - if (!_bcc.empty()) { - _email += "Bcc: " + address_list(_bcc) + "\r\n"; - } - - string const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; - string boundary; - for (int i = 0; i < 32; ++i) { - boundary += chars[rand() % chars.length()]; - } - - if (!_attachments.empty ()) { - _email += "MIME-Version: 1.0\r\n" - "Content-Type: multipart/mixed; boundary=" + boundary + "\r\n"; - } - - _email += "Subject: " + encode_rfc1342(_subject) + "\r\n" - "User-Agent: DCP-o-matic\r\n" - "\r\n"; - - if (!_attachments.empty ()) { - _email += "--" + boundary + "\r\n" - + "Content-Type: text/plain; charset=utf-8\r\n\r\n"; - } - - _email += _body; - - for (auto i: _attachments) { - _email += "\r\n\r\n--" + boundary + "\r\n" - "Content-Type: " + i.mime_type + "; name=" + encode_rfc1342(i.name) + "\r\n" - "Content-Transfer-Encoding: Base64\r\n" - "Content-Disposition: attachment; filename=" + encode_rfc1342(i.name) + "\r\n\r\n"; - - auto b64 = BIO_new (BIO_f_base64()); - if (!b64) { - throw std::bad_alloc(); - } - - auto bio = BIO_new (BIO_s_mem()); - if (!bio) { - throw std::bad_alloc(); - } - bio = BIO_push (b64, bio); - - ArrayData data (i.file); - BIO_write (bio, data.data(), data.size()); - (void) BIO_flush (bio); - - char* out; - long int bytes = BIO_get_mem_data (bio, &out); - _email += fix (string (out, bytes)); - - BIO_free_all (b64); - } - - if (!_attachments.empty ()) { - _email += "\r\n--" + boundary + "--\r\n"; - } - - curl_global_init (CURL_GLOBAL_DEFAULT); - - auto curl = curl_easy_init (); - if (!curl) { - throw NetworkError ("Could not initialise libcurl"); - } - - if ((protocol == EmailProtocol::AUTO && port == 465) || protocol == EmailProtocol::SSL) { - /* "SSL" or "Implicit TLS"; I think curl wants us to use smtps here */ - curl_easy_setopt (curl, CURLOPT_URL, String::compose("smtps://%1:%2", server, port).c_str()); - } else { - curl_easy_setopt (curl, CURLOPT_URL, String::compose("smtp://%1:%2", server, port).c_str()); - } - - if (!user.empty ()) { - curl_easy_setopt (curl, CURLOPT_USERNAME, user.c_str ()); - } - if (!password.empty ()) { - curl_easy_setopt (curl, CURLOPT_PASSWORD, password.c_str()); - } - - curl_easy_setopt (curl, CURLOPT_MAIL_FROM, _from.c_str()); - - struct curl_slist* recipients = nullptr; - for (auto i: _to) { - recipients = curl_slist_append (recipients, i.c_str()); - } - for (auto i: _cc) { - recipients = curl_slist_append (recipients, i.c_str()); - } - for (auto i: _bcc) { - recipients = curl_slist_append (recipients, i.c_str()); - } - - curl_easy_setopt (curl, CURLOPT_MAIL_RCPT, recipients); - - curl_easy_setopt (curl, CURLOPT_READFUNCTION, curl_data_shim); - curl_easy_setopt (curl, CURLOPT_READDATA, this); - curl_easy_setopt (curl, CURLOPT_UPLOAD, 1L); - - if (protocol == EmailProtocol::AUTO || protocol == EmailProtocol::STARTTLS) { - curl_easy_setopt (curl, CURLOPT_USE_SSL, (long) CURLUSESSL_TRY); - } - curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L); - curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L); - curl_easy_setopt (curl, CURLOPT_VERBOSE, 1L); - curl_easy_setopt (curl, CURLOPT_DEBUGFUNCTION, curl_debug_shim); - curl_easy_setopt (curl, CURLOPT_DEBUGDATA, this); - - auto const r = curl_easy_perform (curl); - if (r != CURLE_OK) { - throw NetworkError (_("Failed to send email"), string(curl_easy_strerror(r))); - } - - curl_slist_free_all (recipients); - curl_easy_cleanup (curl); - curl_global_cleanup (); -} - - -string -Emailer::address_list (list addresses) -{ - string o; - for (auto i: addresses) { - o += i + ", "; - } - - return o.substr (0, o.length() - 2); -} - - -int -Emailer::debug (CURL *, curl_infotype type, char* data, size_t size) -{ - if (type == CURLINFO_TEXT) { - _notes += string (data, size); - } else if (type == CURLINFO_HEADER_IN) { - _notes += "<- " + string (data, size); - } else if (type == CURLINFO_HEADER_OUT) { - _notes += "-> " + string (data, size); - } - return 0; -} - - -string -Emailer::encode_rfc1342 (string subject) -{ - auto b64 = BIO_new(BIO_f_base64()); - if (!b64) { - throw std::bad_alloc(); - } - - auto bio = BIO_new(BIO_s_mem()); - if (!bio) { - throw std::bad_alloc(); - } - - bio = BIO_push(b64, bio); - BIO_write(bio, subject.c_str(), subject.length()); - (void) BIO_flush(bio); - - char* out; - long int bytes = BIO_get_mem_data(bio, &out); - string base64_subject(out, bytes); - BIO_free_all(b64); - - boost::algorithm::replace_all(base64_subject, "\n", ""); - return "=?utf-8?B?" + base64_subject + "?="; -} - diff --git a/src/lib/emailer.h b/src/lib/emailer.h deleted file mode 100644 index ef41b0320..000000000 --- a/src/lib/emailer.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - Copyright (C) 2015-2021 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - - -#include -#include - - -class Emailer -{ -public: - Emailer (std::string from, std::list to, std::string subject, std::string body); - - void add_cc (std::string cc); - void add_bcc (std::string bcc); - void add_attachment (boost::filesystem::path file, std::string name, std::string mime_type); - - void send (std::string server, int port, EmailProtocol protocol, std::string user = "", std::string password = ""); - - std::string notes () const { - return _notes; - } - - size_t get_data (void* ptr, size_t size, size_t nmemb); - int debug (CURL* curl, curl_infotype type, char* data, size_t size); - - /** @return full email, after send() has been called */ - std::string email () const { - return _email; - } - - static std::string address_list (std::list addresses); - -private: - - std::string fix (std::string s) const; - static std::string encode_rfc1342 (std::string subject); - - std::string _from; - std::list _to; - std::string _subject; - std::string _body; - std::list _cc; - std::list _bcc; - - struct Attachment { - boost::filesystem::path file; - std::string name; - std::string mime_type; - }; - - std::list _attachments; - std::string _email; - size_t _offset; - std::string _notes; -}; diff --git a/src/lib/empty.cc b/src/lib/empty.cc index 96d036463..a75066395 100644 --- a/src/lib/empty.cc +++ b/src/lib/empty.cc @@ -43,7 +43,7 @@ Empty::Empty (shared_ptr film, shared_ptr playlist, list full; for (auto i: playlist->content()) { if (part(i) && i->paths_valid()) { - full.push_back (DCPTimePeriod(i->position(), i->end(film))); + full.push_back(i->period(film)); } } diff --git a/src/lib/encode_server.cc b/src/lib/encode_server.cc index d326c767b..036ea58a5 100644 --- a/src/lib/encode_server.cc +++ b/src/lib/encode_server.cc @@ -25,18 +25,18 @@ */ -#include "encode_server.h" -#include "util.h" -#include "dcpomatic_socket.h" -#include "image.h" -#include "dcp_video.h" +#include "compose.hpp" #include "config.h" +#include "constants.h" #include "cross.h" -#include "player_video.h" -#include "compose.hpp" -#include "log.h" +#include "dcp_video.h" #include "dcpomatic_log.h" +#include "dcpomatic_socket.h" +#include "encode_server.h" #include "encoded_log_entry.h" +#include "image.h" +#include "log.h" +#include "player_video.h" #include "version.h" #include #include @@ -126,6 +126,10 @@ EncodeServer::process (shared_ptr socket, struct timeval& after_read, st Socket::ReadDigestScope ds (socket); auto length = socket->read_uint32 (); + if (length > 65536) { + throw NetworkError("Malformed encode request (too large)"); + } + scoped_array buffer (new char[length]); socket->read (reinterpret_cast(buffer.get()), length); diff --git a/src/lib/encode_server_description.h b/src/lib/encode_server_description.h index f60051b85..f79b890f7 100644 --- a/src/lib/encode_server_description.h +++ b/src/lib/encode_server_description.h @@ -18,12 +18,15 @@ */ + #ifndef DCPOMATIC_ENCODE_SERVER_DESCRIPTION_H #define DCPOMATIC_ENCODE_SERVER_DESCRIPTION_H + #include "types.h" #include + /** @class EncodeServerDescription * @brief Class to describe a server to which we can send encoding work. */ diff --git a/src/lib/encode_server_finder.cc b/src/lib/encode_server_finder.cc index e01019a8c..1d4ced595 100644 --- a/src/lib/encode_server_finder.cc +++ b/src/lib/encode_server_finder.cc @@ -19,13 +19,13 @@ */ -#include "encode_server_finder.h" -#include "exceptions.h" -#include "util.h" #include "config.h" +#include "constants.h" #include "cross.h" -#include "encode_server_description.h" #include "dcpomatic_socket.h" +#include "encode_server_description.h" +#include "encode_server_finder.h" +#include "exceptions.h" #include #include #include @@ -227,6 +227,11 @@ EncodeServerFinder::handle_accept (boost::system::error_code ec) _accept_socket->read (reinterpret_cast(&length), sizeof(uint32_t)); length = ntohl (length); + if (length > 65536) { + start_accept(); + return; + } + scoped_array buffer(new char[length]); _accept_socket->read (reinterpret_cast(buffer.get()), length); server_available = buffer.get(); diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index 1d688c318..5268d8620 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -41,7 +41,7 @@ Encoder::Encoder (std::shared_ptr film, std::weak_ptr job) : _film (film) , _job (job) - , _player (new Player(film, Image::Alignment::PADDED)) + , _player(film, Image::Alignment::PADDED) { } diff --git a/src/lib/encoder.h b/src/lib/encoder.h index 19c1120b3..9b67720d3 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -23,8 +23,8 @@ #define DCPOMATIC_ENCODER_H +#include "player.h" #include "player_text.h" -#include "types.h" #include @@ -62,7 +62,7 @@ public: protected: std::shared_ptr _film; std::weak_ptr _job; - std::shared_ptr _player; + Player _player; }; diff --git a/src/lib/enum_indexed_vector.h b/src/lib/enum_indexed_vector.h new file mode 100644 index 000000000..0187daba2 --- /dev/null +++ b/src/lib/enum_indexed_vector.h @@ -0,0 +1,79 @@ +/* + Copyright (C) 2022 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#ifndef DCPOMATIC_ENUM_INDEXED_VECTOR_H +#define DCPOMATIC_ENUM_INDEXED_VECTOR_H + + +#include + + +template +class EnumIndexedVector +{ +public: + EnumIndexedVector() + : _data(static_cast(Enum::COUNT)) + {} + + typename std::vector::reference operator[](int index) { + return _data[index]; + } + + typename std::vector::const_reference operator[](int index) const { + return _data[index]; + } + + typename std::vector::reference operator[](Enum index) { + return _data[static_cast(index)]; + } + + typename std::vector::const_reference operator[](Enum index) const { + return _data[static_cast(index)]; + } + + void clear() { + std::fill(_data.begin(), _data.end(), {}); + } + + typename std::vector::const_iterator begin() const { + return _data.begin(); + } + + typename std::vector::const_iterator end() const { + return _data.end(); + } + + typename std::vector::iterator begin() { + return _data.begin(); + } + + typename std::vector::iterator end() { + return _data.end(); + } + +private: + std::vector _data; +}; + + +#endif + diff --git a/src/lib/exceptions.cc b/src/lib/exceptions.cc index 66db9fda7..a51842b80 100644 --- a/src/lib/exceptions.cc +++ b/src/lib/exceptions.cc @@ -148,10 +148,11 @@ GLError::GLError (char const* message) } -CopyError::CopyError (string m, optional n) - : runtime_error (String::compose("%1%2", m, n ? String::compose(" (%1)", *n) : "")) +CopyError::CopyError(string m, optional ext4, optional platform) + : runtime_error(String::compose("%1%2%3", m, ext4 ? String::compose(" (%1)", *ext4) : "", platform ? String::compose(" (%1)", *platform) : "")) , _message (m) - , _number (n) + , _ext4_number(ext4) + , _platform_number(platform) { } diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h index 618a03f43..10231f59a 100644 --- a/src/lib/exceptions.h +++ b/src/lib/exceptions.h @@ -351,8 +351,8 @@ public: explicit InvalidSignerError (std::string reason); }; -class ProgrammingError : public std::runtime_error +class ProgrammingError : public std::runtime_error { public: ProgrammingError (std::string file, int line, std::string message = ""); @@ -407,20 +407,25 @@ public: class CopyError : public std::runtime_error { public: - CopyError (std::string s, boost::optional n = boost::optional()); + CopyError (std::string s, boost::optional ext4_error = boost::optional(), boost::optional platform_error = boost::optional()); virtual ~CopyError () throw () {} std::string message () const { return _message; } - boost::optional number () const { - return _number; + boost::optional ext4_number() const { + return _ext4_number; + } + + boost::optional platform_number() const { + return _platform_number; } private: std::string _message; - boost::optional _number; + boost::optional _ext4_number; + boost::optional _platform_number; }; @@ -461,8 +466,17 @@ class PrivilegeError : public std::runtime_error { public: explicit PrivilegeError (std::string s) - : std::runtime_error (s) - {} + : std::runtime_error (s) + {} +}; + + +class MissingConfigurationError : public std::runtime_error +{ +public: + explicit MissingConfigurationError(std::string s) + : std::runtime_error(s) + {} }; diff --git a/src/lib/ext.cc b/src/lib/ext.cc index 49e63d648..25946df73 100644 --- a/src/lib/ext.cc +++ b/src/lib/ext.cc @@ -28,6 +28,7 @@ #include "ext.h" #include "nanomsg.h" #include +#include #ifdef DCPOMATIC_LINUX #include @@ -83,7 +84,7 @@ count (std::vector dirs, uint64_t& total_bytes) using namespace boost::filesystem; for (auto dir: dirs) { - dir = dcp::fix_long_path(dir); + dir = dcp::filesystem::fix_long_path(dir); for (auto path: directory_iterator(dir)) { if (is_directory(path)) { count({path}, total_bytes); @@ -113,13 +114,13 @@ write (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total ext4_file out; int r = ext4_fopen(&out, to.generic_string().c_str(), "wb"); if (r != EOK) { - throw CopyError (String::compose("Failed to open file %1", to.generic_string()), r); + throw CopyError(String::compose("Failed to open file %1", to.generic_string()), r, ext4_blockdev_errno); } dcp::File in(from, "rb"); if (!in) { ext4_fclose (&out); - throw CopyError (String::compose("Failed to open file %1", from.string()), 0); + throw CopyError(String::compose("Failed to open file %1", from.string()), 0); } std::vector buffer(block_size); @@ -133,7 +134,7 @@ write (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total size_t read = in.read(buffer.data(), 1, this_time); if (read != this_time) { ext4_fclose (&out); - throw CopyError (String::compose("Short read; expected %1 but read %2", this_time, read), 0); + throw CopyError(String::compose("Short read; expected %1 but read %2", this_time, read), 0, ext4_blockdev_errno); } digester.add (buffer.data(), this_time); @@ -142,18 +143,18 @@ write (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total r = ext4_fwrite (&out, buffer.data(), this_time, &written); if (r != EOK) { ext4_fclose (&out); - throw CopyError ("Write failed", r); + throw CopyError("Write failed", r, ext4_blockdev_errno); } if (written != this_time) { ext4_fclose (&out); - throw CopyError (String::compose("Short write; expected %1 but wrote %2", this_time, written), 0); + throw CopyError(String::compose("Short write; expected %1 but wrote %2", this_time, written), 0, ext4_blockdev_errno); } remaining -= this_time; total_remaining -= this_time; ++progress_count; if ((progress_count % progress_frequency) == 0 && nanomsg) { - nanomsg->send(String::compose(DISK_WRITER_COPY_PROGRESS "\n%1\n", (1 - float(total_remaining) / total)), SHORT_TIMEOUT); + DiskWriterBackEndResponse::copy_progress(1 - float(total_remaining) / total).write_to_nanomsg(*nanomsg, SHORT_TIMEOUT); } } @@ -194,7 +195,7 @@ read (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total_ remaining -= this_time; total_remaining -= this_time; if (nanomsg) { - nanomsg->send(String::compose(DISK_WRITER_VERIFY_PROGRESS "\n%1\n", (1 - float(total_remaining) / total)), SHORT_TIMEOUT); + DiskWriterBackEndResponse::verify_progress(1 - float(total_remaining) / total).write_to_nanomsg(*nanomsg, SHORT_TIMEOUT); } } @@ -228,7 +229,7 @@ void copy (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total_remaining, uint64_t total, vector& copied_files, Nanomsg* nanomsg) { LOG_DISK ("Copy %1 -> %2", from.string(), to.generic_string()); - from = dcp::fix_long_path (from); + from = dcp::filesystem::fix_long_path(from); using namespace boost::filesystem; @@ -237,7 +238,7 @@ copy (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total_ if (is_directory(from)) { int r = ext4_dir_mk (cr.generic_string().c_str()); if (r != EOK) { - throw CopyError (String::compose("Failed to create directory %1", cr.generic_string()), r); + throw CopyError(String::compose("Failed to create directory %1", cr.generic_string()), r, ext4_blockdev_errno); } set_timestamps_to_now (cr); @@ -272,7 +273,7 @@ void format_progress (void* context, float progress) { if (context) { - reinterpret_cast(context)->send(String::compose(DISK_WRITER_FORMAT_PROGRESS "\n%1\n", progress), SHORT_TIMEOUT); + DiskWriterBackEndResponse::format_progress(progress).write_to_nanomsg(*reinterpret_cast(context), SHORT_TIMEOUT); } } @@ -317,7 +318,7 @@ try #endif if (!bd) { - throw CopyError ("Failed to open drive", 0); + throw CopyError("Failed to open drive", 0, ext4_blockdev_errno); } LOG_DISK_NC ("Opened drive"); @@ -330,14 +331,14 @@ try /* XXX: not sure if disk_id matters */ int r = ext4_mbr_write (bd, &parts, 0); if (r) { - throw CopyError ("Failed to write MBR", r); + throw CopyError("Failed to write MBR", r, ext4_blockdev_errno); } LOG_DISK_NC ("Wrote MBR"); struct ext4_mbr_bdevs bdevs; r = ext4_mbr_scan (bd, &bdevs); if (r != EOK) { - throw CopyError ("Failed to read MBR", r); + throw CopyError("Failed to read MBR", r, ext4_blockdev_errno); } #ifdef DCPOMATIC_LINUX @@ -369,25 +370,25 @@ try #endif if (!bd) { - throw CopyError ("Failed to open partition", 0); + throw CopyError("Failed to open partition", 0, ext4_blockdev_errno); } LOG_DISK_NC ("Opened partition"); r = ext4_mkfs(&fs, bd, &info, F_SET_EXT2, format_progress, nanomsg); if (r != EOK) { - throw CopyError ("Failed to make filesystem", r); + throw CopyError("Failed to make filesystem", r, ext4_blockdev_errno); } LOG_DISK_NC ("Made filesystem"); r = ext4_device_register(bd, "ext4_fs"); if (r != EOK) { - throw CopyError ("Failed to register device", r); + throw CopyError("Failed to register device", r, ext4_blockdev_errno); } LOG_DISK_NC ("Registered device"); r = ext4_mount("ext4_fs", "/mp/", false); if (r != EOK) { - throw CopyError ("Failed to mount device", r); + throw CopyError("Failed to mount device", r, ext4_blockdev_errno); } LOG_DISK_NC ("Mounted device"); @@ -403,11 +404,11 @@ try /* Unmount and re-mount to make sure the write has finished */ r = ext4_umount("/mp/"); if (r != EOK) { - throw CopyError ("Failed to unmount device", r); + throw CopyError("Failed to unmount device", r, ext4_blockdev_errno); } r = ext4_mount("ext4_fs", "/mp/", false); if (r != EOK) { - throw CopyError ("Failed to mount device", r); + throw CopyError("Failed to mount device", r, ext4_blockdev_errno); } LOG_DISK_NC ("Re-mounted device"); @@ -415,29 +416,29 @@ try r = ext4_umount("/mp/"); if (r != EOK) { - throw CopyError ("Failed to unmount device", r); + throw CopyError("Failed to unmount device", r, ext4_blockdev_errno); } ext4_device_unregister("ext4_fs"); - if (nanomsg && !nanomsg->send(DISK_WRITER_OK "\n", LONG_TIMEOUT)) { + if (nanomsg && !DiskWriterBackEndResponse::ok().write_to_nanomsg(*nanomsg, LONG_TIMEOUT)) { throw CommunicationFailedError (); } disk_write_finished (); } catch (CopyError& e) { - LOG_DISK("CopyError (from write): %1 %2", e.message(), e.number().get_value_or(0)); + LOG_DISK("CopyError (from write): %1 %2 %3", e.message(), e.ext4_number().get_value_or(0), e.platform_number().get_value_or(0)); if (nanomsg) { - nanomsg->send(String::compose(DISK_WRITER_ERROR "\n%1\n%2\n", e.message(), e.number().get_value_or(0)), LONG_TIMEOUT); + DiskWriterBackEndResponse::error(e.message(), e.ext4_number().get_value_or(0), e.platform_number().get_value_or(0)).write_to_nanomsg(*nanomsg, LONG_TIMEOUT); } } catch (VerifyError& e) { LOG_DISK("VerifyError (from write): %1 %2", e.message(), e.number()); if (nanomsg) { - nanomsg->send(String::compose(DISK_WRITER_ERROR "\n%1\n%2\n", e.message(), e.number()), LONG_TIMEOUT); + DiskWriterBackEndResponse::error(e.message(), e.number(), 0).write_to_nanomsg(*nanomsg, LONG_TIMEOUT); } } catch (exception& e) { LOG_DISK("Exception (from write): %1", e.what()); if (nanomsg) { - nanomsg->send(String::compose(DISK_WRITER_ERROR "\n%1\n0\n", e.what()), LONG_TIMEOUT); + DiskWriterBackEndResponse::error(e.what(), 0, 0).write_to_nanomsg(*nanomsg, LONG_TIMEOUT); } } diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc index 6c8509b6c..d9df232df 100644 --- a/src/lib/ffmpeg.cc +++ b/src/lib/ffmpeg.cc @@ -96,30 +96,9 @@ avio_seek_wrapper (void* data, int64_t offset, int whence) } -void -FFmpeg::ffmpeg_log_callback (void* ptr, int level, const char* fmt, va_list vl) -{ - if (level > AV_LOG_WARNING) { - return; - } - - char line[1024]; - static int prefix = 0; - av_log_format_line (ptr, level, fmt, vl, line, sizeof (line), &prefix); - string str (line); - boost::algorithm::trim (str); - dcpomatic_log->log (String::compose ("FFmpeg: %1", str), LogEntry::TYPE_GENERAL); -} - - void FFmpeg::setup_general () { - /* This might not work too well in some cases of multiple FFmpeg decoders, - but it's probably good enough. - */ - av_log_set_callback (FFmpeg::ffmpeg_log_callback); - _file_group.set_paths (_ffmpeg_content->paths ()); _avio_buffer = static_cast (wrapped_av_malloc(_avio_buffer_size)); _avio_context = avio_alloc_context (_avio_buffer, _avio_buffer_size, 0, this, avio_read_wrapper, 0, avio_seek_wrapper); @@ -147,14 +126,14 @@ FFmpeg::setup_general () optional video_stream_undefined_frame_rate; for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { - auto s = _format_context->streams[i]; - if (s->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && avcodec_find_decoder(s->codecpar->codec_id)) { - auto const frame_rate = av_q2d(s->avg_frame_rate); + auto stream = _format_context->streams[i]; + if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && avcodec_find_decoder(stream->codecpar->codec_id) && stream->disposition != AV_DISPOSITION_ATTACHED_PIC) { + auto const frame_rate = av_q2d(stream->avg_frame_rate); if (frame_rate < 1 || frame_rate > 1000) { /* Ignore video streams with crazy frame rates. These are usually things like album art on MP3s. */ continue; } - if (s->avg_frame_rate.num > 0 && s->avg_frame_rate.den > 0) { + if (stream->avg_frame_rate.num > 0 && stream->avg_frame_rate.den > 0) { /* This is definitely our video stream */ _video_stream = i; } else { @@ -273,7 +252,11 @@ FFmpeg::subtitle_codec_context () const int FFmpeg::avio_read (uint8_t* buffer, int const amount) { - return _file_group.read (buffer, amount); + auto result = _file_group.read(buffer, amount); + if (result.eof && result.bytes_read == 0) { + return AVERROR_EOF; + } + return result.bytes_read; } @@ -376,6 +359,5 @@ FFmpeg::audio_frame (shared_ptr stream) _audio_frame[stream] = frame; return frame; - } diff --git a/src/lib/ffmpeg.h b/src/lib/ffmpeg.h index 25d26e813..834c8946c 100644 --- a/src/lib/ffmpeg.h +++ b/src/lib/ffmpeg.h @@ -93,9 +93,6 @@ private: void setup_general (); void setup_decoders (); - static void ffmpeg_log_callback (void* ptr, int level, const char* fmt, va_list vl); - static std::weak_ptr _ffmpeg_log; - /** AVFrames used for decoding audio streams; accessed with audio_frame() */ std::map, AVFrame*> _audio_frame; }; diff --git a/src/lib/ffmpeg_audio_stream.cc b/src/lib/ffmpeg_audio_stream.cc index 05e1a3fc8..9400eb60d 100644 --- a/src/lib/ffmpeg_audio_stream.cc +++ b/src/lib/ffmpeg_audio_stream.cc @@ -39,7 +39,8 @@ FFmpegAudioStream::FFmpegAudioStream (cxml::ConstNodePtr node, int version) , AudioStream ( node->number_child("FrameRate"), node->optional_number_child("Length").get_value_or(0), - AudioMapping (node->node_child("Mapping"), version) + AudioMapping(node->node_child("Mapping"), version), + node->optional_number_child("BitDepth") ) { optional const f = node->optional_number_child("FirstAudio"); @@ -63,4 +64,7 @@ FFmpegAudioStream::as_xml (xmlpp::Node* root) const if (codec_name) { root->add_child("CodecName")->add_child_text(codec_name.get()); } + if (bit_depth()) { + root->add_child("BitDepth")->add_child_text(raw_convert(bit_depth().get())); + } } diff --git a/src/lib/ffmpeg_audio_stream.h b/src/lib/ffmpeg_audio_stream.h index a5ed90c97..aae982f9e 100644 --- a/src/lib/ffmpeg_audio_stream.h +++ b/src/lib/ffmpeg_audio_stream.h @@ -30,20 +30,20 @@ struct ffmpeg_pts_offset_test; class FFmpegAudioStream : public FFmpegStream, public AudioStream { public: - FFmpegAudioStream (std::string name, int id, int frame_rate, Frame length, int channels) + FFmpegAudioStream(std::string name, int id, int frame_rate, Frame length, int channels, int bit_depth) : FFmpegStream (name, id) - , AudioStream (frame_rate, length, channels) + , AudioStream(frame_rate, length, channels, bit_depth) {} - FFmpegAudioStream (std::string name, std::string codec_name_, int id, int frame_rate, Frame length, int channels) + FFmpegAudioStream(std::string name, std::string codec_name_, int id, int frame_rate, Frame length, int channels, int bit_depth) : FFmpegStream (name, id) - , AudioStream (frame_rate, length, channels) + , AudioStream(frame_rate, length, channels, bit_depth) , codec_name (codec_name_) {} - FFmpegAudioStream (std::string name, int id, int frame_rate, Frame length, AudioMapping mapping) + FFmpegAudioStream(std::string name, int id, int frame_rate, Frame length, AudioMapping mapping, int bit_depth) : FFmpegStream (name, id) - , AudioStream (frame_rate, length, mapping) + , AudioStream(frame_rate, length, mapping, bit_depth) {} FFmpegAudioStream (cxml::ConstNodePtr, int); @@ -61,7 +61,7 @@ private: /* Constructor for tests */ FFmpegAudioStream () : FFmpegStream ("", 0) - , AudioStream (0, 0, 0) + , AudioStream(0, 0, 0, 0) {} }; diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index 4bc88e1e4..4a7c87b34 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -19,22 +19,22 @@ */ -#include "ffmpeg_content.h" -#include "video_content.h" #include "audio_content.h" -#include "ffmpeg_examiner.h" -#include "ffmpeg_subtitle_stream.h" -#include "ffmpeg_audio_stream.h" #include "compose.hpp" -#include "job.h" -#include "util.h" -#include "filter.h" -#include "film.h" -#include "log.h" #include "config.h" +#include "constants.h" #include "exceptions.h" +#include "ffmpeg_audio_stream.h" +#include "ffmpeg_content.h" +#include "ffmpeg_examiner.h" +#include "ffmpeg_subtitle_stream.h" +#include "film.h" +#include "filter.h" #include "frame_rate_change.h" +#include "job.h" +#include "log.h" #include "text_content.h" +#include "video_content.h" #include #include extern "C" { @@ -115,9 +115,8 @@ FFmpegContent::FFmpegContent (cxml::ConstNodePtr node, int version, list } for (auto i: node->node_children("Filter")) { - Filter const * f = Filter::from_id(i->content()); - if (f) { - _filters.push_back (f); + if (auto filter = Filter::from_id(i->content())) { + _filters.push_back(*filter); } else { notes.push_back (String::compose (_("DCP-o-matic no longer supports the `%1' filter, so it has been turned off."), i->content())); } @@ -232,7 +231,7 @@ FFmpegContent::as_xml (xmlpp::Node* node, bool with_paths) const } for (auto i: _filters) { - node->add_child("Filter")->add_child_text(i->id()); + node->add_child("Filter")->add_child_text(i.id()); } if (_first_video) { @@ -273,7 +272,7 @@ FFmpegContent::examine (shared_ptr film, shared_ptr job) if (examiner->has_video ()) { video.reset (new VideoContent (this)); - video->take_from_examiner (examiner); + video->take_from_examiner(film, examiner); } auto first_path = path (0); @@ -292,14 +291,19 @@ FFmpegContent::examine (shared_ptr film, shared_ptr job) if (examiner->rotation()) { auto rot = *examiner->rotation (); if (fabs (rot - 180) < 1.0) { - _filters.push_back (Filter::from_id ("vflip")); - _filters.push_back (Filter::from_id ("hflip")); + _filters.push_back(*Filter::from_id("vflip")); + _filters.push_back(*Filter::from_id("hflip")); } else if (fabs (rot - 90) < 1.0) { - _filters.push_back (Filter::from_id ("90clock")); + _filters.push_back(*Filter::from_id("90clock")); + video->rotate_size(); } else if (fabs (rot - 270) < 1.0) { - _filters.push_back (Filter::from_id ("90anticlock")); + _filters.push_back(*Filter::from_id("90anticlock")); + video->rotate_size(); } } + if (examiner->has_alpha()) { + _filters.push_back(*Filter::from_id("premultiply")); + } } if (!examiner->audio_streams().empty()) { @@ -332,7 +336,7 @@ FFmpegContent::examine (shared_ptr film, shared_ptr job) /* FFmpeg has detected this file as 29.97 and the examiner thinks it is using "soft" 2:3 pulldown (telecine). * This means we can treat it as a 23.976fps file. */ - set_video_frame_rate (24000.0 / 1001); + set_video_frame_rate(film, 24000.0 / 1001); video->set_length (video->length() * 24.0 / 30); } } @@ -455,7 +459,7 @@ FFmpegContent::approximate_length () const void -FFmpegContent::set_filters (vector const & filters) +FFmpegContent::set_filters(vector const& filters) { ContentChangeSignaller cc (this, FFmpegContentProperty::FILTERS); @@ -486,7 +490,7 @@ FFmpegContent::identifier () const } for (auto i: _filters) { - s += "_" + i->id(); + s += "_" + i.id(); } return s; @@ -519,7 +523,7 @@ FFmpegContent::set_default_colour_conversion () video->set_colour_conversion (PresetColourConversion::from_id ("rec2020").conversion); break; default: - if (s.width < 1080) { + if (s && s->width < 1080) { video->set_colour_conversion (PresetColourConversion::from_id ("rec601").conversion); } else { video->set_colour_conversion (PresetColourConversion::from_id ("rec709").conversion); @@ -538,10 +542,12 @@ FFmpegContent::add_properties (shared_ptr film, list& video->add_properties (p); if (_bits_per_pixel) { - /* Assuming there's three components, so bits per pixel component is _bits_per_pixel / 3 */ - int const lim_start = pow(2, _bits_per_pixel.get() / 3 - 4); - int const lim_end = 235 * pow(2, _bits_per_pixel.get() / 3 - 8); - int const total = pow(2, _bits_per_pixel.get() / 3); + auto pixel_quanta_product = video->pixel_quanta().x * video->pixel_quanta().y; + auto bits_per_main_pixel = pixel_quanta_product * _bits_per_pixel.get() / (pixel_quanta_product + 2); + + int const lim_start = pow(2, bits_per_main_pixel - 4); + int const lim_end = 235 * pow(2, bits_per_main_pixel - 8); + int const total = pow(2, bits_per_main_pixel); switch (_color_range.get_value_or(AVCOL_RANGE_UNSPECIFIED)) { case AVCOL_RANGE_UNSPECIFIED: @@ -554,14 +560,14 @@ FFmpegContent::add_properties (shared_ptr film, list& /// file is limited, so that not all possible values are valid. p.push_back ( UserProperty ( - UserProperty::VIDEO, _("Colour range"), String::compose(_("Limited (%1-%2)"), lim_start, lim_end) + UserProperty::VIDEO, _("Colour range"), String::compose(_("Limited / video (%1-%2)"), lim_start, lim_end) ) ); break; case AVCOL_RANGE_JPEG: /// TRANSLATORS: this means that the range of pixel values used in this /// file is full, so that all possible pixel values are valid. - p.push_back (UserProperty (UserProperty::VIDEO, _("Colour range"), String::compose (_("Full (0-%1)"), total))); + p.push_back(UserProperty(UserProperty::VIDEO, _("Colour range"), String::compose(_("Full (0-%1)"), total - 1))); break; default: DCPOMATIC_ASSERT (false); diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index ce4a8aa69..a86358b76 100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.h @@ -18,22 +18,28 @@ */ + #ifndef DCPOMATIC_FFMPEG_CONTENT_H #define DCPOMATIC_FFMPEG_CONTENT_H -#include "content.h" + #include "audio_stream.h" +#include "content.h" +#include "filter.h" + struct AVFormatContext; struct AVStream; -class Filter; -class FFmpegSubtitleStream; + class FFmpegAudioStream; +class FFmpegSubtitleStream; +class Filter; class VideoContent; struct ffmpeg_pts_offset_test; struct audio_sampling_rate_test; + class FFmpegContentProperty { public: @@ -44,6 +50,7 @@ public: static int const KDM; }; + class FFmpegContent : public Content { public: @@ -71,7 +78,7 @@ public: void set_default_colour_conversion (); - void set_filters (std::vector const &); + void set_filters(std::vector const&); std::vector> subtitle_streams () const { boost::mutex::scoped_lock lm (_mutex); @@ -85,7 +92,7 @@ public: std::vector> ffmpeg_audio_streams () const; - std::vector filters () const { + std::vector filters() const { boost::mutex::scoped_lock lm (_mutex); return _filters; } @@ -109,7 +116,7 @@ private: std::shared_ptr _subtitle_stream; boost::optional _first_video; /** Video filters that should be used when generating DCPs */ - std::vector _filters; + std::vector _filters; boost::optional _color_range; boost::optional _color_primaries; diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index ba96d71ff..6130d8e5f 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -77,6 +77,7 @@ using namespace dcpomatic; FFmpegDecoder::FFmpegDecoder (shared_ptr film, shared_ptr c, bool fast) : FFmpeg (c) , Decoder (film) + , _filter_graphs(c->filters(), dcp::Fraction(lrint(_ffmpeg_content->video_frame_rate().get_value_or(24) * 1000), 1000)) { if (c->video && c->video->use()) { video = make_shared(this, c); @@ -105,11 +106,41 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr film, shared_ptr(_flush_state)); + + switch (_flush_state) { + case FlushState::CODECS: + if (flush_codecs() == FlushResult::DONE) { + LOG_DEBUG_PLAYER_NC("Finished flushing codecs"); + _flush_state = FlushState::AUDIO_DECODER; + } + break; + case FlushState::AUDIO_DECODER: + if (audio) { + audio->flush(); + } + LOG_DEBUG_PLAYER_NC("Finished flushing audio decoder"); + _flush_state = FlushState::FILL; + break; + case FlushState::FILL: + if (flush_fill() == FlushResult::DONE) { + LOG_DEBUG_PLAYER_NC("Finished flushing fills"); + return FlushResult::DONE; + } + break; + } + return FlushResult::AGAIN; +} + + +/** @return true if we have finished flushing the codecs */ +FFmpegDecoder::FlushResult +FFmpegDecoder::flush_codecs() +{ bool did_something = false; if (video) { if (decode_and_process_video_packet(nullptr)) { @@ -131,48 +162,49 @@ FFmpegDecoder::flush () } } - if (did_something) { - /* We want to be called again */ - return false; - } + return did_something ? FlushResult::AGAIN : FlushResult::DONE; +} + +FFmpegDecoder::FlushResult +FFmpegDecoder::flush_fill() +{ /* Make sure all streams are the same length and round up to the next video frame */ + bool did_something = false; + auto const frc = film()->active_frame_rate_change(_ffmpeg_content->position()); ContentTime full_length (_ffmpeg_content->full_length(film()), frc); full_length = full_length.ceil (frc.source); - if (video) { + if (video && !video->ignore()) { double const vfr = _ffmpeg_content->video_frame_rate().get(); auto const f = full_length.frames_round (vfr); - auto v = video->position(film()).get_value_or(ContentTime()).frames_round(vfr) + 1; - while (v < f) { - video->emit (film(), make_shared(_black_image), v); - ++v; + auto const v = video->position(film()).get_value_or(ContentTime()).frames_round(vfr) + 1; + if (v < f) { + video->emit(film(), make_shared(_black_image), v); + did_something = true; } } - for (auto i: _ffmpeg_content->ffmpeg_audio_streams ()) { - auto a = audio->stream_position(film(), i); - /* Unfortunately if a is 0 that really means that we don't know the stream position since - there has been no data on it since the last seek. In this case we'll just do nothing - here. I'm not sure if that's the right idea. - */ - if (a > ContentTime()) { - while (a < full_length) { + if (audio && !audio->ignore()) { + for (auto i: _ffmpeg_content->ffmpeg_audio_streams ()) { + auto const a = audio->stream_position(film(), i); + /* Unfortunately if a is 0 that really means that we don't know the stream position since + there has been no data on it since the last seek. In this case we'll just do nothing + here. I'm not sure if that's the right idea. + */ + if (a > ContentTime() && a < full_length) { + LOG_DEBUG_PLAYER("Flush inserts silence at %1", to_string(a)); auto to_do = min (full_length - a, ContentTime::from_seconds (0.1)); auto silence = make_shared(i->channels(), to_do.frames_ceil (i->frame_rate())); silence->make_silent (); audio->emit (film(), i, silence, a, true); - a += to_do; + did_something = true; } } } - if (audio) { - audio->flush (); - } - - return true; + return did_something ? FlushResult::AGAIN : FlushResult::DONE; } @@ -189,6 +221,7 @@ FFmpegDecoder::pass () Hence it makes sense to continue here in that case. */ if (r < 0 && r != AVERROR_INVALIDDATA) { + LOG_DEBUG_PLAYER("FFpmegDecoder::pass flushes because av_read_frame returned %1", r); if (r != AVERROR_EOF) { /* Maybe we should fail here, but for now we'll just finish off instead */ char buf[256]; @@ -197,7 +230,7 @@ FFmpegDecoder::pass () } av_packet_free (&packet); - return flush (); + return flush() == FlushResult::DONE; } int const si = packet->stream_index; @@ -219,8 +252,9 @@ FFmpegDecoder::pass () /** @param data pointer to array of pointers to buffers. * Only the first buffer will be used for non-planar data, otherwise there will be one per channel. */ +static shared_ptr -FFmpegDecoder::deinterleave_audio (AVFrame* frame) +deinterleave_audio(AVFrame* frame) { auto format = static_cast(frame->format); @@ -232,6 +266,10 @@ FFmpegDecoder::deinterleave_audio (AVFrame* frame) auto audio = make_shared(channels, frames); auto data = audio->data(); + if (frames == 0) { + return audio; + } + switch (format) { case AV_SAMPLE_FMT_U8: { @@ -326,14 +364,9 @@ FFmpegDecoder::deinterleave_audio (AVFrame* frame) case AV_SAMPLE_FMT_FLTP: { auto p = reinterpret_cast (frame->data); - DCPOMATIC_ASSERT (frame->channels <= channels); - /* Sometimes there aren't as many channels in the frame as in the stream */ - for (int i = 0; i < frame->channels; ++i) { + for (int i = 0; i < channels; ++i) { memcpy (data[i], p[i], frames * sizeof(float)); } - for (int i = frame->channels; i < channels; ++i) { - audio->make_silent (i); - } } break; @@ -400,13 +433,10 @@ FFmpegDecoder::seek (ContentTime time, bool accurate) AVSEEK_FLAG_BACKWARD ); - { - /* Force re-creation of filter graphs to reset them and hence to make sure - they don't have any pre-seek frames knocking about. - */ - boost::mutex::scoped_lock lm (_filter_graphs_mutex); - _filter_graphs.clear (); - } + /* Force re-creation of filter graphs to reset them and hence to make sure + they don't have any pre-seek frames knocking about. + */ + _filter_graphs.clear(); if (video_codec_context ()) { avcodec_flush_buffers (video_codec_context()); @@ -450,7 +480,7 @@ void FFmpegDecoder::process_audio_frame (shared_ptr stream) { auto frame = audio_frame (stream); - auto data = deinterleave_audio (frame); + auto data = deinterleave_audio(frame); auto const time_base = stream->stream(_format_context)->time_base; @@ -579,25 +609,7 @@ FFmpegDecoder::decode_and_process_video_packet (AVPacket* packet) void FFmpegDecoder::process_video_frame () { - boost::mutex::scoped_lock lm (_filter_graphs_mutex); - - shared_ptr graph; - - auto i = _filter_graphs.begin(); - while (i != _filter_graphs.end() && !(*i)->can_process(dcp::Size(_video_frame->width, _video_frame->height), (AVPixelFormat) _video_frame->format)) { - ++i; - } - - if (i == _filter_graphs.end ()) { - dcp::Fraction vfr (lrint(_ffmpeg_content->video_frame_rate().get() * 1000), 1000); - graph = make_shared(dcp::Size(_video_frame->width, _video_frame->height), (AVPixelFormat) _video_frame->format, vfr); - graph->setup (_ffmpeg_content->filters ()); - _filter_graphs.push_back (graph); - LOG_GENERAL (N_("New graph for %1x%2, pixel format %3"), _video_frame->width, _video_frame->height, _video_frame->format); - } else { - graph = *i; - } - + auto graph = _filter_graphs.get(dcp::Size(_video_frame->width, _video_frame->height), static_cast(_video_frame->format)); auto images = graph->process (_video_frame); for (auto const& i: images) { @@ -622,9 +634,14 @@ FFmpegDecoder::process_video_frame () void FFmpegDecoder::decode_and_process_subtitle_packet (AVPacket* packet) { + auto context = subtitle_codec_context(); + if (!context) { + return; + } + int got_subtitle; AVSubtitle sub; - if (avcodec_decode_subtitle2 (subtitle_codec_context(), &sub, &got_subtitle, packet) < 0 || !got_subtitle) { + if (avcodec_decode_subtitle2(context, &sub, &got_subtitle, packet) < 0 || !got_subtitle) { return; } @@ -651,11 +668,11 @@ FFmpegDecoder::decode_and_process_subtitle_packet (AVPacket* packet) */ ContentTime from; from = sub_period.from + _pts_offset; + _have_current_subtitle = true; if (sub_period.to) { _current_subtitle_to = *sub_period.to + _pts_offset; } else { _current_subtitle_to = optional(); - _have_current_subtitle = true; } ContentBitmapText bitmap_text(from); @@ -703,7 +720,7 @@ FFmpegDecoder::process_bitmap_subtitle (AVSubtitleRect const * rect) /* sub_p looks up into a BGRA palette which is at rect->pict.data[1]; (i.e. first byte B, second G, third R, fourth A) */ - auto const palette = rect->pict.data[1]; + auto const* palette = rect->pict.data[1]; #else /* Start of the first line in the subtitle */ auto sub_p = rect->data[0]; @@ -764,11 +781,23 @@ FFmpegDecoder::process_bitmap_subtitle (AVSubtitleRect const * rect) if (target_height == 0 && video_codec_context()) { target_height = video_codec_context()->height; } - DCPOMATIC_ASSERT (target_width); - DCPOMATIC_ASSERT (target_height); + + int x_offset = 0; + int y_offset = 0; + if (_ffmpeg_content->video && _ffmpeg_content->video->use()) { + auto const crop = _ffmpeg_content->video->actual_crop(); + target_width -= crop.left + crop.right; + target_height -= crop.top + crop.bottom; + x_offset = -crop.left; + y_offset = -crop.top; + } + + DCPOMATIC_ASSERT(target_width > 0); + DCPOMATIC_ASSERT(target_height > 0); + dcpomatic::Rect const scaled_rect ( - static_cast(rect->x) / target_width, - static_cast(rect->y) / target_height, + static_cast(rect->x + x_offset) / target_width, + static_cast(rect->y + y_offset) / target_height, static_cast(rect->w) / target_width, static_cast(rect->h) / target_height ); @@ -799,11 +828,14 @@ FFmpegDecoder::process_ass_subtitle (string ass, ContentTime from) } sub::RawSubtitle base; + auto video_size = _ffmpeg_content->video->size(); + DCPOMATIC_ASSERT(video_size); + auto raw = sub::SSAReader::parse_line ( base, text, - _ffmpeg_content->video->size().width, - _ffmpeg_content->video->size().height, + video_size->width, + video_size->height, sub::Colour(1, 1, 1) ); diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index ce2476fb0..bd4b74f88 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -27,7 +27,7 @@ #include "bitmap_text.h" #include "decoder.h" #include "ffmpeg.h" -#include "util.h" +#include "video_filter_graph_set.h" extern "C" { #include } @@ -57,9 +57,12 @@ public: private: friend struct ::ffmpeg_pts_offset_test; - bool flush (); + enum class FlushResult { + DONE, + AGAIN + }; - static std::shared_ptr deinterleave_audio (AVFrame* frame); + FlushResult flush(); AVSampleFormat audio_sample_format (std::shared_ptr stream) const; int bytes_per_audio_sample (std::shared_ptr stream) const; @@ -78,8 +81,10 @@ private: void maybe_add_subtitle (); - std::list> _filter_graphs; - boost::mutex _filter_graphs_mutex; + FlushResult flush_codecs(); + FlushResult flush_fill(); + + VideoFilterGraphSet _filter_graphs; dcpomatic::ContentTime _pts_offset; boost::optional _current_subtitle_to; @@ -89,4 +94,12 @@ private: std::shared_ptr _black_image; std::map, boost::optional> _next_time; + + enum class FlushState { + CODECS, + AUDIO_DECODER, + FILL, + }; + + FlushState _flush_state = FlushState::CODECS; }; diff --git a/src/lib/ffmpeg_encoder.cc b/src/lib/ffmpeg_encoder.cc index 6b31c4201..c1170f098 100644 --- a/src/lib/ffmpeg_encoder.cc +++ b/src/lib/ffmpeg_encoder.cc @@ -59,65 +59,70 @@ FFmpegEncoder::FFmpegEncoder ( int x264_crf ) : Encoder (film, job) + , _output_audio_channels(mixdown_to_stereo ? 2 : (_film->audio_channels() > 8 ? 16 : _film->audio_channels())) , _history (200) , _output (output) , _format (format) , _split_reels (split_reels) , _audio_stream_per_channel (audio_stream_per_channel) , _x264_crf (x264_crf) -{ - _player->set_always_burn_open_subtitles (); - _player->set_play_referenced (); - - int const ch = film->audio_channels (); - - AudioMapping map; - if (mixdown_to_stereo) { - _output_audio_channels = 2; - map = AudioMapping (ch, 2); - float const overall_gain = 2 / (4 + sqrt(2)); - float const minus_3dB = 1 / sqrt(2); - if (ch == 2) { - map.set (dcp::Channel::LEFT, 0, 1); - map.set (dcp::Channel::RIGHT, 1, 1); - } else if (ch == 4) { - map.set (dcp::Channel::LEFT, 0, overall_gain); - map.set (dcp::Channel::RIGHT, 1, overall_gain); - map.set (dcp::Channel::CENTRE, 0, overall_gain * minus_3dB); - map.set (dcp::Channel::CENTRE, 1, overall_gain * minus_3dB); - map.set (dcp::Channel::LS, 0, overall_gain); - } else if (ch >= 6) { - map.set (dcp::Channel::LEFT, 0, overall_gain); - map.set (dcp::Channel::RIGHT, 1, overall_gain); - map.set (dcp::Channel::CENTRE, 0, overall_gain * minus_3dB); - map.set (dcp::Channel::CENTRE, 1, overall_gain * minus_3dB); - map.set (dcp::Channel::LS, 0, overall_gain); - map.set (dcp::Channel::RS, 1, overall_gain); - } - /* XXX: maybe we should do something better for >6 channel DCPs */ - } else { - /* Our encoders don't really want to encode any channel count between 9 and 15 inclusive, - * so let's just use 16 channel exports for any project with more than 8 channels. - */ - _output_audio_channels = ch > 8 ? 16 : ch; - map = AudioMapping (ch, _output_audio_channels); - for (int i = 0; i < ch; ++i) { - map.set (i, i, 1); - } - } - - _butler = std::make_shared( + , _butler( _film, _player, - map, + mixdown_to_stereo ? stereo_map() : many_channel_map(), _output_audio_channels, - bind(&PlayerVideo::force, FFmpegFileEncoder::pixel_format(format)), + boost::bind(&PlayerVideo::force, FFmpegFileEncoder::pixel_format(format)), VideoRange::VIDEO, Image::Alignment::PADDED, false, false, Butler::Audio::ENABLED - ); + ) +{ + _player.set_always_burn_open_subtitles(); + _player.set_play_referenced(); +} + + +AudioMapping +FFmpegEncoder::stereo_map() const +{ + auto map = AudioMapping(_film->audio_channels(), 2); + float const overall_gain = 2 / (4 + sqrt(2)); + float const minus_3dB = 1 / sqrt(2); + switch (_film->audio_channels()) { + case 2: + map.set(dcp::Channel::LEFT, 0, 1); + map.set(dcp::Channel::RIGHT, 1, 1); + break; + case 4: + map.set(dcp::Channel::LEFT, 0, overall_gain); + map.set(dcp::Channel::RIGHT, 1, overall_gain); + map.set(dcp::Channel::CENTRE, 0, overall_gain * minus_3dB); + map.set(dcp::Channel::CENTRE, 1, overall_gain * minus_3dB); + map.set(dcp::Channel::LS, 0, overall_gain); + break; + default: + map.set(dcp::Channel::LEFT, 0, overall_gain); + map.set(dcp::Channel::RIGHT, 1, overall_gain); + map.set(dcp::Channel::CENTRE, 0, overall_gain * minus_3dB); + map.set(dcp::Channel::CENTRE, 1, overall_gain * minus_3dB); + map.set(dcp::Channel::LS, 0, overall_gain); + map.set(dcp::Channel::RS, 1, overall_gain); + break; + } + return map; +} + + +AudioMapping +FFmpegEncoder::many_channel_map() const +{ + auto map = AudioMapping(_film->audio_channels(), _output_audio_channels); + for (int i = 0; i < _film->audio_channels(); ++i) { + map.set(i, i, 1); + } + return map; } @@ -138,8 +143,8 @@ FFmpegEncoder::go () for (int i = 0; i < files; ++i) { boost::filesystem::path filename = _output; - string extension = boost::filesystem::extension (filename); - filename = boost::filesystem::change_extension (filename, ""); + auto extension = dcp::filesystem::extension(filename); + filename = dcp::filesystem::change_extension(filename, ""); if (files > 1) { /// TRANSLATORS: _reel%1 here is to be added to an export filename to indicate @@ -172,9 +177,9 @@ FFmpegEncoder::go () std::vector interleaved(_output_audio_channels * audio_frames); auto deinterleaved = make_shared(_output_audio_channels, audio_frames); int const gets_per_frame = _film->three_d() ? 2 : 1; - for (DCPTime i; i < _film->length(); i += video_frame) { + for (DCPTime time; time < _film->length(); time += video_frame) { - if (file_encoders.size() > 1 && !reel->contains(i)) { + if (file_encoders.size() > 1 && !reel->contains(time)) { /* Next reel and file */ ++reel; ++encoder; @@ -184,12 +189,12 @@ FFmpegEncoder::go () for (int j = 0; j < gets_per_frame; ++j) { Butler::Error e; - auto v = _butler->get_video (Butler::Behaviour::BLOCKING, &e); - _butler->rethrow (); - if (v.first) { - auto fe = encoder->get (v.first->eyes()); + auto video = _butler.get_video(Butler::Behaviour::BLOCKING, &e); + _butler.rethrow(); + if (video.first) { + auto fe = encoder->get(video.first->eyes()); if (fe) { - fe->video(v.first, v.second - reel->from); + fe->video(video.first, video.second - reel->from); } } else { if (e.code != Butler::Error::Code::FINISHED) { @@ -202,17 +207,17 @@ FFmpegEncoder::go () { boost::mutex::scoped_lock lm (_mutex); - _last_time = i; + _last_time = time; } auto job = _job.lock (); if (job) { - job->set_progress (float(i.get()) / _film->length().get()); + job->set_progress(float(time.get()) / _film->length().get()); } waker.nudge (); - _butler->get_audio (Butler::Behaviour::BLOCKING, interleaved.data(), audio_frames); + _butler.get_audio(Butler::Behaviour::BLOCKING, interleaved.data(), audio_frames); /* XXX: inefficient; butler interleaves and we deinterleave again */ float* p = interleaved.data(); for (int j = 0; j < audio_frames; ++j) { @@ -255,14 +260,14 @@ FFmpegEncoder::FileEncoderSet::FileEncoderSet ( ) { if (three_d) { - /// TRANSLATORS: L here is an abbreviation for "left", to indicate the left-eye part of a 3D export _encoders[Eyes::LEFT] = make_shared( video_frame_size, video_frame_rate, audio_frame_rate, channels, format, + // TRANSLATORS: L here is an abbreviation for "left", to indicate the left-eye part of a 3D export audio_stream_per_channel, x264_crf, String::compose("%1_%2%3", output.string(), _("L"), extension) ); - /// TRANSLATORS: R here is an abbreviation for "right", to indicate the right-eye part of a 3D export _encoders[Eyes::RIGHT] = make_shared( video_frame_size, video_frame_rate, audio_frame_rate, channels, format, + // TRANSLATORS: R here is an abbreviation for "right", to indicate the right-eye part of a 3D export audio_stream_per_channel, x264_crf, String::compose("%1_%2%3", output.string(), _("R"), extension) ); } else { diff --git a/src/lib/ffmpeg_encoder.h b/src/lib/ffmpeg_encoder.h index 393a6d72e..2d5c6af87 100644 --- a/src/lib/ffmpeg_encoder.h +++ b/src/lib/ffmpeg_encoder.h @@ -21,12 +21,12 @@ #ifndef DCPOMATIC_FFMPEG_ENCODER_H #define DCPOMATIC_FFMPEG_ENCODER_H +#include "audio_mapping.h" +#include "butler.h" #include "encoder.h" #include "event_history.h" -#include "audio_mapping.h" #include "ffmpeg_file_encoder.h" -class Butler; class FFmpegEncoder : public Encoder { @@ -76,6 +76,9 @@ private: std::map> _encoders; }; + AudioMapping stereo_map() const; + AudioMapping many_channel_map() const; + int _output_audio_channels; mutable boost::mutex _mutex; @@ -89,7 +92,7 @@ private: bool _audio_stream_per_channel; int _x264_crf; - std::shared_ptr _butler; + Butler _butler; }; #endif diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc index fdcacb465..51ade8e89 100644 --- a/src/lib/ffmpeg_examiner.cc +++ b/src/lib/ffmpeg_examiner.cc @@ -91,7 +91,8 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr c, shared_ptrid, s->codecpar->sample_rate, llrint ((double(_format_context->duration) / AV_TIME_BASE) * s->codecpar->sample_rate), - s->codecpar->channels + s->codecpar->channels, + s->codecpar->bits_per_raw_sample ? s->codecpar->bits_per_raw_sample : s->codecpar->bits_per_coded_sample ) ); @@ -181,7 +182,6 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr c, shared_ptrstreams[*_video_stream]; auto rotate_tag = av_dict_get (stream->metadata, "rotate", 0, 0); uint8_t* displaymatrix = av_stream_get_side_data (stream, AV_PKT_DATA_DISPLAYMATRIX, 0); - _rotation = 0; if (rotate_tag && *rotate_tag->value && strcmp(rotate_tag->value, "0")) { char *tail; @@ -195,7 +195,9 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr c, shared_ptr FFmpegExaminer::video_size () const { return dcp::Size (video_codec_context()->width, video_codec_context()->height); @@ -382,6 +384,19 @@ FFmpegExaminer::bits_per_pixel () const } +bool +FFmpegExaminer::has_alpha() const +{ + if (video_codec_context()->pix_fmt == -1) { + return false; + } + + auto const d = av_pix_fmt_desc_get(video_codec_context()->pix_fmt); + DCPOMATIC_ASSERT(d); + return d->flags & AV_PIX_FMT_FLAG_ALPHA; +} + + bool FFmpegExaminer::yuv () const { diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h index 10d060707..45313ec18 100644 --- a/src/lib/ffmpeg_examiner.h +++ b/src/lib/ffmpeg_examiner.h @@ -38,7 +38,7 @@ public: bool has_video () const override; boost::optional video_frame_rate () const override; - dcp::Size video_size () const override; + boost::optional video_size() const override; Frame video_length () const override; boost::optional sample_aspect_ratio () const override; bool yuv () const override; @@ -77,6 +77,8 @@ public: boost::optional bits_per_pixel () const; + bool has_alpha() const; + boost::optional rotation () const { return _rotation; } diff --git a/src/lib/ffmpeg_file_encoder.cc b/src/lib/ffmpeg_file_encoder.cc index 0d5167c49..6d1ad68f7 100644 --- a/src/lib/ffmpeg_file_encoder.cc +++ b/src/lib/ffmpeg_file_encoder.cc @@ -229,16 +229,16 @@ FFmpegFileEncoder::FFmpegFileEncoder ( switch (format) { case ExportFormat::PRORES_4444: - _sample_format = AV_SAMPLE_FMT_S16; + _sample_format = AV_SAMPLE_FMT_S32; _video_codec_name = "prores_ks"; - _audio_codec_name = "pcm_s16le"; + _audio_codec_name = "pcm_s24le"; av_dict_set(&_video_options, "profile", "4", 0); av_dict_set(&_video_options, "threads", "auto", 0); break; case ExportFormat::PRORES_HQ: - _sample_format = AV_SAMPLE_FMT_S16; + _sample_format = AV_SAMPLE_FMT_S32; _video_codec_name = "prores_ks"; - _audio_codec_name = "pcm_s16le"; + _audio_codec_name = "pcm_s24le"; av_dict_set (&_video_options, "profile", "3", 0); av_dict_set (&_video_options, "threads", "auto", 0); break; @@ -281,6 +281,7 @@ FFmpegFileEncoder::~FFmpegFileEncoder () _audio_streams.clear (); avcodec_close (_video_codec_context); avio_close (_format_context->pb); + _format_context->pb = nullptr; avformat_free_context (_format_context); } @@ -325,7 +326,7 @@ FFmpegFileEncoder::setup_video () _video_codec_context->flags |= AV_CODEC_FLAG_QSCALE | AV_CODEC_FLAG_GLOBAL_HEADER; if (avcodec_open2 (_video_codec_context, _video_codec, &_video_options) < 0) { - throw EncodeError (N_("avcodec_open"), N_("FFmpegFileEncoder::setup_video")); + throw EncodeError(N_("avcodec_open2"), N_("FFmpegFileEncoder::setup_video")); } _video_stream = avformat_new_stream (_format_context, _video_codec); @@ -382,18 +383,22 @@ FFmpegFileEncoder::flush () throw EncodeError (N_("avcodec_receive_packet"), N_("FFmpegFileEncoder::flush"), r); } else { packet->stream_index = _video_stream_index; + packet->duration = _video_stream->time_base.den / _video_frame_rate; av_interleaved_write_frame (_format_context, packet.get()); } flushed_audio = true; - for (auto i: _audio_streams) { + for (auto const& i: _audio_streams) { if (!i->flush()) { flushed_audio = false; } } } - av_write_trailer (_format_context); + auto const r = av_write_trailer(_format_context); + if (r) { + throw EncodeError(N_("av_write_trailer"), N_("FFmpegFileEncoder::flush"), r); + } } @@ -410,13 +415,8 @@ FFmpegFileEncoder::video (shared_ptr video, DCPTime time) auto frame = av_frame_alloc (); DCPOMATIC_ASSERT (frame); - { - boost::mutex::scoped_lock lm (_pending_images_mutex); - _pending_images[image->data()[0]] = image; - } - for (int i = 0; i < 3; ++i) { - auto buffer = av_buffer_create(image->data()[i], image->stride()[i] * image->size().height, &buffer_free, this, 0); + auto buffer = _pending_images.create_buffer(image, i); frame->buf[i] = av_buffer_ref (buffer); frame->data[i] = buffer->data; frame->linesize[i] = image->stride()[i]; @@ -441,6 +441,7 @@ FFmpegFileEncoder::video (shared_ptr video, DCPTime time) throw EncodeError (N_("avcodec_receive_packet"), N_("FFmpegFileEncoder::video"), r); } else if (r >= 0) { packet->stream_index = _video_stream_index; + packet->duration = _video_stream->time_base.den / _video_frame_rate; av_interleaved_write_frame (_format_context, packet.get()); } } @@ -490,20 +491,3 @@ FFmpegFileEncoder::subtitle (PlayerText, DCPTimePeriod) { } - - -void -FFmpegFileEncoder::buffer_free (void* opaque, uint8_t* data) -{ - reinterpret_cast(opaque)->buffer_free2(data); -} - - -void -FFmpegFileEncoder::buffer_free2 (uint8_t* data) -{ - boost::mutex::scoped_lock lm (_pending_images_mutex); - if (_pending_images.find(data) != _pending_images.end()) { - _pending_images.erase (data); - } -} diff --git a/src/lib/ffmpeg_file_encoder.h b/src/lib/ffmpeg_file_encoder.h index 5bf501370..78840d6a8 100644 --- a/src/lib/ffmpeg_file_encoder.h +++ b/src/lib/ffmpeg_file_encoder.h @@ -27,6 +27,7 @@ #include "dcpomatic_time.h" #include "encoder.h" #include "event_history.h" +#include "image_store.h" #include "log.h" #include #include @@ -80,9 +81,6 @@ private: void audio_frame (int size); - static void buffer_free(void* opaque, uint8_t* data); - void buffer_free2(uint8_t* data); - AVCodec const * _video_codec = nullptr; AVCodecContext* _video_codec_context = nullptr; std::vector> _audio_streams; @@ -105,11 +103,7 @@ private: std::shared_ptr _pending_audio; - /** Store of shared_ptr to keep them alive whilst raw pointers into - their data have been passed to FFmpeg. - */ - std::map> _pending_images; - boost::mutex _pending_images_mutex; + ImageStore _pending_images; static int _video_stream_index; static int _audio_stream_index_base; diff --git a/src/lib/ffmpeg_image_proxy.cc b/src/lib/ffmpeg_image_proxy.cc index f513eef2d..2fcd486df 100644 --- a/src/lib/ffmpeg_image_proxy.cc +++ b/src/lib/ffmpeg_image_proxy.cc @@ -27,6 +27,7 @@ #include "ffmpeg_image_proxy.h" #include "image.h" #include "memory_util.h" +#include "video_filter_graph.h" #include #include LIBDCP_DISABLE_WARNINGS @@ -206,7 +207,20 @@ FFmpegImageProxy::image (Image::Alignment alignment, optional) const throw DecodeError (N_("avcodec_receive_frame"), name_for_errors, r, *_path); } - _image = make_shared(frame, alignment); + if (av_pix_fmt_desc_get(context->pix_fmt)->flags & AV_PIX_FMT_FLAG_ALPHA) { + /* XXX: this repeated setup of a the filter graph could be really slow + * (haven't measured it though). + */ + VideoFilterGraph graph(dcp::Size(frame->width, frame->height), context->pix_fmt, dcp::Fraction(24, 1)); + auto filter = Filter::from_id("premultiply"); + DCPOMATIC_ASSERT(filter); + graph.setup({*filter}); + auto images = graph.process(frame); + DCPOMATIC_ASSERT(images.size() == 1); + _image = images.front().first; + } else { + _image = make_shared(frame, alignment); + } av_packet_unref (&packet); av_frame_free (&frame); diff --git a/src/lib/ffmpeg_image_proxy.h b/src/lib/ffmpeg_image_proxy.h index b2b0260b5..b567fadee 100644 --- a/src/lib/ffmpeg_image_proxy.h +++ b/src/lib/ffmpeg_image_proxy.h @@ -19,7 +19,6 @@ */ #include "image_proxy.h" -#include "types.h" #include #include #include @@ -51,6 +50,6 @@ private: failed-decode errors can give more detail. */ boost::optional _path; - mutable std::shared_ptr _image; + mutable std::shared_ptr _image; mutable boost::mutex _mutex; }; diff --git a/src/lib/file_group.cc b/src/lib/file_group.cc index 71faacc4c..228faa74d 100644 --- a/src/lib/file_group.cc +++ b/src/lib/file_group.cc @@ -29,6 +29,7 @@ #include "dcpomatic_assert.h" #include "exceptions.h" #include "file_group.h" +#include #include #include @@ -88,7 +89,7 @@ FileGroup::ensure_open_path (size_t p) const if (!_current_file) { throw OpenFileError (_paths[_current_path], errno, OpenFileError::READ); } - _current_size = boost::filesystem::file_size (_paths[_current_path]); + _current_size = dcp::filesystem::file_size(_paths[_current_path]); } @@ -111,7 +112,7 @@ FileGroup::seek (int64_t pos, int whence) const size_t i = 0; int64_t sub_pos = _position; while (i < _paths.size()) { - boost::uintmax_t len = boost::filesystem::file_size (_paths[i]); + boost::uintmax_t len = dcp::filesystem::file_size(_paths[i]); if (sub_pos < int64_t(len)) { break; } @@ -134,9 +135,8 @@ FileGroup::seek (int64_t pos, int whence) const /** Try to read some data from the current position into a buffer. * @param buffer Buffer to write data into. * @param amount Number of bytes to read. - * @return Number of bytes read. */ -int +FileGroup::Result FileGroup::read (uint8_t* buffer, int amount) const { DCPOMATIC_ASSERT (_current_file); @@ -173,13 +173,13 @@ FileGroup::read (uint8_t* buffer, int amount) const if (eof) { /* See if there is another file to use */ if ((_current_path + 1) >= _paths.size()) { - break; + return { read, true }; } ensure_open_path (_current_path + 1); } } - return read; + return { read, false }; } @@ -189,7 +189,7 @@ FileGroup::length () const { int64_t len = 0; for (size_t i = 0; i < _paths.size(); ++i) { - len += boost::filesystem::file_size (_paths[i]); + len += dcp::filesystem::file_size(_paths[i]); } return len; diff --git a/src/lib/file_group.h b/src/lib/file_group.h index 693bf89ba..aac72c228 100644 --- a/src/lib/file_group.h +++ b/src/lib/file_group.h @@ -49,8 +49,18 @@ public: void set_paths (std::vector const &); + struct Result { + Result(int bytes_read_, bool eof_) + : bytes_read(bytes_read_) + , eof(eof_) + {} + + int bytes_read = 0; + bool eof = false; + }; + int64_t seek (int64_t, int) const; - int read (uint8_t*, int) const; + Result read (uint8_t*, int) const; int64_t length () const; private: diff --git a/src/lib/file_log.cc b/src/lib/file_log.cc index adb06b7f0..5265e2a9d 100644 --- a/src/lib/file_log.cc +++ b/src/lib/file_log.cc @@ -23,6 +23,7 @@ #include "cross.h" #include "config.h" #include +#include #include #include #include @@ -70,7 +71,7 @@ FileLog::head_and_tail (int amount) const uintmax_t head_amount = amount; uintmax_t tail_amount = amount; boost::system::error_code ec; - uintmax_t size = boost::filesystem::file_size (_file, ec); + uintmax_t size = dcp::filesystem::file_size(_file, ec); if (size == static_cast(-1)) { return ""; } diff --git a/src/lib/film.cc b/src/lib/film.cc index 4e133a90d..d9ab6e2a3 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -32,6 +32,7 @@ #include "cinema.h" #include "compose.hpp" #include "config.h" +#include "constants.h" #include "cross.h" #include "dcp_content.h" #include "dcp_content_type.h" @@ -56,13 +57,13 @@ #include "text_content.h" #include "transcode_job.h" #include "upload_job.h" -#include "util.h" #include "video_content.h" #include "version.h" #include #include #include #include +#include #include #include #include @@ -156,7 +157,7 @@ Film::Film (optional dir) : _playlist (new Playlist) , _use_isdcf_name (Config::instance()->use_isdcf_name_by_default()) , _dcp_content_type (Config::instance()->default_dcp_content_type ()) - , _container (Config::instance()->default_container ()) + , _container(Ratio::from_id("185")) , _resolution (Resolution::TWO_K) , _encrypted (false) , _context_id (dcp::make_uuid ()) @@ -166,6 +167,7 @@ Film::Film (optional dir) , _three_d (false) , _sequence (true) , _interop (Config::instance()->default_interop ()) + , _limit_to_smpte_bv20(false) , _audio_processor (0) , _reel_type (ReelType::SINGLE) , _reel_length (2000000000) @@ -174,8 +176,10 @@ Film::Film (optional dir) , _user_explicit_container (false) , _user_explicit_resolution (false) , _name_language (dcp::LanguageTag("en-US")) + , _release_territory(Config::instance()->default_territory()) , _version_number (1) , _status (dcp::Status::FINAL) + , _audio_language(Config::instance()->default_audio_language()) , _state_version (current_state_version) , _dirty (false) , _tolerant (false) @@ -202,27 +206,7 @@ Film::Film (optional dir) _playlist_length_change_connection = _playlist->LengthChange.connect (bind(&Film::playlist_length_change, this)); if (dir) { - /* Make state.directory a complete path without ..s (where possible) - (Code swiped from Adam Bowen on stackoverflow) - XXX: couldn't/shouldn't this just be boost::filesystem::canonical? - */ - - boost::filesystem::path p (boost::filesystem::system_complete (dir.get())); - boost::filesystem::path result; - for (auto i: p) { - if (i == "..") { - boost::system::error_code ec; - if (boost::filesystem::is_symlink(result, ec) || result.filename() == "..") { - result /= i; - } else { - result = result.parent_path (); - } - } else if (i != ".") { - result /= i; - } - } - - set_directory (result.make_preferred ()); + set_directory(dcp::filesystem::weakly_canonical(*dir)); } if (_directory) { @@ -267,6 +251,11 @@ Film::video_identifier () const s += "_I"; } else { s += "_S"; + if (_limit_to_smpte_bv20) { + s += "_L20"; + } else { + s += "_L21"; + } } if (_three_d) { @@ -308,13 +297,13 @@ Film::audio_analysis_path (shared_ptr playlist) const auto p = dir ("analysis"); Digester digester; - for (auto i: playlist->content()) { - if (!i->audio) { + for (auto content: playlist->content()) { + if (!content->audio) { continue; } - digester.add (i->digest()); - digester.add (i->audio->mapping().digest()); + digester.add(content->digest()); + digester.add(content->audio->mapping().digest()); if (playlist->content().size() != 1) { /* Analyses should be considered equal regardless of gain if they were made from just one piece of content. This @@ -322,14 +311,14 @@ Film::audio_analysis_path (shared_ptr playlist) const analysis at the plotting stage rather than having to recompute it. */ - digester.add (i->audio->gain()); + digester.add(content->audio->gain()); /* Likewise we only care about position if we're looking at a * whole-project view. */ - digester.add (i->position().get()); - digester.add (i->trim_start().get()); - digester.add (i->trim_end().get()); + digester.add(content->position().get()); + digester.add(content->trim_start().get()); + digester.add(content->trim_end().get()); } } @@ -351,6 +340,7 @@ Film::subtitle_analysis_path (shared_ptr content) const Digester digester; digester.add (content->digest()); + digester.add(_interop ? "1" : "0"); if (!content->text.empty()) { auto tc = content->text.front(); @@ -413,6 +403,7 @@ Film::metadata (bool with_content_paths) const root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0"); root->add_child("Sequence")->add_child_text (_sequence ? "1" : "0"); root->add_child("Interop")->add_child_text (_interop ? "1" : "0"); + root->add_child("LimitToSMPTEBv20")->add_child_text(_limit_to_smpte_bv20 ? "1" : "0"); root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0"); root->add_child("Key")->add_child_text (_key.hex ()); root->add_child("ContextID")->add_child_text (_context_id); @@ -423,10 +414,10 @@ Film::metadata (bool with_content_paths) const root->add_child("ReelLength")->add_child_text (raw_convert (_reel_length)); root->add_child("ReencodeJ2K")->add_child_text (_reencode_j2k ? "1" : "0"); root->add_child("UserExplicitVideoFrameRate")->add_child_text(_user_explicit_video_frame_rate ? "1" : "0"); - for (map::const_iterator i = _markers.begin(); i != _markers.end(); ++i) { + for (auto const& marker: _markers) { auto m = root->add_child("Marker"); - m->set_attribute("Type", dcp::marker_to_string(i->first)); - m->add_child_text(raw_convert(i->second.get())); + m->set_attribute("Type", dcp::marker_to_string(marker.first)); + m->add_child_text(raw_convert(marker.second.get())); } for (auto i: _ratings) { i.as_xml (root->add_child("Rating")); @@ -435,6 +426,7 @@ Film::metadata (bool with_content_paths) const root->add_child("ContentVersion")->add_child_text(i); } root->add_child("NameLanguage")->add_child_text(_name_language.to_string()); + root->add_child("TerritoryType")->add_child_text(territory_type_to_string(_territory_type)); if (_release_territory) { root->add_child("ReleaseTerritory")->add_child_text(_release_territory->subtag()); } @@ -484,8 +476,13 @@ void Film::write_metadata () { DCPOMATIC_ASSERT (directory()); - boost::filesystem::create_directories (directory().get()); - metadata()->write_to_file_formatted(file(metadata_file).string()); + dcp::filesystem::create_directories(directory().get()); + auto const filename = file(metadata_file); + try { + metadata()->write_to_file_formatted(filename.string()); + } catch (xmlpp::exception& e) { + throw FileError(String::compose("Could not write metadata file (%1)", e.what()), filename); + } set_dirty (false); } @@ -493,7 +490,7 @@ Film::write_metadata () void Film::write_template (boost::filesystem::path path) const { - boost::filesystem::create_directories (path.parent_path()); + dcp::filesystem::create_directories(path.parent_path()); shared_ptr doc = metadata (false); metadata(false)->write_to_file_formatted(path.string()); } @@ -505,19 +502,19 @@ list Film::read_metadata (optional path) { if (!path) { - if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file (metadata_file))) { + if (dcp::filesystem::exists(file("metadata")) && !dcp::filesystem::exists(file(metadata_file))) { throw runtime_error (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version. You will need to create a new Film, re-add your content and set it up again. Sorry!")); } path = file (metadata_file); } - if (!boost::filesystem::exists(*path)) { + if (!dcp::filesystem::exists(*path)) { throw FileNotFoundError(*path); } cxml::Document f ("Metadata"); - f.read_file (path.get ()); + f.read_file(dcp::filesystem::fix_long_path(path.get())); _state_version = f.number_child ("Version"); if (_state_version > current_state_version) { @@ -525,9 +522,9 @@ Film::read_metadata (optional path) } else if (_state_version < current_state_version) { /* This is an older version; save a copy (if we haven't already) */ auto const older = path->parent_path() / String::compose("metadata.%1.xml", _state_version); - if (!boost::filesystem::is_regular_file(older)) { + if (!dcp::filesystem::is_regular_file(older)) { try { - boost::filesystem::copy_file(*path, older); + dcp::filesystem::copy_file(*path, older); } catch (...) { /* Never mind; at least we tried */ } @@ -583,6 +580,7 @@ Film::read_metadata (optional path) _three_d = f.bool_child ("ThreeD"); _interop = f.bool_child ("Interop"); + _limit_to_smpte_bv20 = f.optional_bool_child("LimitToSMPTEBv20").get_value_or(false); _key = dcp::Key (f.string_child ("Key")); _context_id = f.optional_string_child("ContextID").get_value_or (dcp::make_uuid ()); @@ -620,6 +618,10 @@ Film::read_metadata (optional path) if (name_language) { _name_language = dcp::LanguageTag (*name_language); } + auto territory_type = f.optional_string_child("TerritoryType"); + if (territory_type) { + _territory_type = string_to_territory_type(*territory_type); + } auto release_territory = f.optional_string_child("ReleaseTerritory"); if (release_territory) { _release_territory = dcp::LanguageTag::RegionSubtag (*release_territory); @@ -630,7 +632,7 @@ Film::read_metadata (optional path) _sign_language_video_language = dcp::LanguageTag(*sign_language_video_language); } - _version_number = f.optional_number_child("VersionNumber").get_value_or(0); + _version_number = f.optional_number_child("VersionNumber").get_value_or(1); auto status = f.optional_string_child("Status"); if (status) { @@ -691,10 +693,10 @@ Film::read_metadata (optional path) } _studio = isdcf->optional_string_child("Studio"); _facility = isdcf->optional_string_child("Facility"); - _temp_version = isdcf->optional_bool_child("TempVersion").get_value_or("false"); - _pre_release = isdcf->optional_bool_child("PreRelease").get_value_or("false"); - _red_band = isdcf->optional_bool_child("RedBand").get_value_or("false"); - _two_d_version_of_three_d = isdcf->optional_bool_child("TwoDVersionOfThreeD").get_value_or("false"); + _temp_version = isdcf->optional_bool_child("TempVersion").get_value_or(false); + _pre_release = isdcf->optional_bool_child("PreRelease").get_value_or(false); + _red_band = isdcf->optional_bool_child("RedBand").get_value_or(false); + _two_d_version_of_three_d = isdcf->optional_bool_child("TwoDVersionOfThreeD").get_value_or(false); _chain = isdcf->optional_string_child("Chain"); } @@ -724,7 +726,7 @@ Film::dir (boost::filesystem::path d, bool create) const p /= d; if (create) { - boost::filesystem::create_directories (p); + dcp::filesystem::create_directories(p); } return p; @@ -742,7 +744,7 @@ Film::file (boost::filesystem::path f) const p /= _directory.get(); p /= f; - boost::filesystem::create_directories (p.parent_path ()); + dcp::filesystem::create_directories(p.parent_path()); return p; } @@ -774,16 +776,27 @@ Film::mapped_audio_channels () const pair, vector> -Film::subtitle_languages () const +Film::subtitle_languages(bool* burnt_in) const { + if (burnt_in) { + *burnt_in = true; + } + pair, vector> result; for (auto i: content()) { - for (auto j: i->text) { - if (j->use() && j->type() == TextType::OPEN_SUBTITLE && j->language()) { - if (j->language_is_additional()) { - result.second.push_back (j->language().get()); - } else { - result.first = j->language().get(); + auto dcp = dynamic_pointer_cast(i); + for (auto const& text: i->text) { + auto const use = text->use() || (dcp && dcp->reference_text(TextType::OPEN_SUBTITLE)); + if (use && text->type() == TextType::OPEN_SUBTITLE) { + if (!text->burn() && burnt_in) { + *burnt_in = false; + } + if (text->language()) { + if (text->language_is_additional()) { + result.second.push_back(text->language().get()); + } else { + result.first = text->language().get(); + } } } } @@ -799,14 +812,35 @@ Film::subtitle_languages () const } +vector +Film::closed_caption_languages() const +{ + vector result; + for (auto i: content()) { + for (auto text: i->text) { + if (text->use() && text->type() == TextType::CLOSED_CAPTION && text->dcp_track() && text->dcp_track()->language) { + result.push_back(*text->dcp_track()->language); + } + } + } + + return result; +} + + /** @return a ISDCF-compliant name for a DCP of this film */ string Film::isdcf_name (bool if_created_now) const { - string d; + string isdcf_name; auto raw_name = name (); + auto to_upper = [](string s) { + transform(s.begin(), s.end(), s.begin(), ::toupper); + return s; + }; + /* Split the raw name up into words */ vector words; split (words, raw_name, is_any_of (" _-")); @@ -842,14 +876,12 @@ Film::isdcf_name (bool if_created_now) const } } - if (fixed_name.length() > 14) { - fixed_name = fixed_name.substr (0, 14); - } + fixed_name = fixed_name.substr(0, Config::instance()->isdcf_name_part_length()); - d += fixed_name; + isdcf_name += fixed_name; if (dcp_content_type()) { - d += "_" + dcp_content_type()->isdcf_name(); + isdcf_name += "_" + dcp_content_type()->isdcf_name(); string version = "1"; if (_interop) { if (!_content_versions.empty()) { @@ -861,59 +893,62 @@ Film::isdcf_name (bool if_created_now) const } else { version = dcp::raw_convert(_version_number); } - d += "-" + version; + isdcf_name += "-" + version; } if (_temp_version) { - d += "-Temp"; + isdcf_name += "-Temp"; } if (_pre_release) { - d += "-Pre"; + isdcf_name += "-Pre"; } if (_red_band) { - d += "-RedBand"; + isdcf_name += "-RedBand"; } if (_chain && !_chain->empty()) { - d += "-" + *_chain; + isdcf_name += "-" + *_chain; } if (three_d ()) { - d += "-3D"; + isdcf_name += "-3D"; } if (_two_d_version_of_three_d) { - d += "-2D"; + isdcf_name += "-2D"; } if (_luminance) { auto fl = _luminance->value_in_foot_lamberts(); char buffer[64]; snprintf (buffer, sizeof(buffer), "%.1f", fl); - d += String::compose("-%1fl", buffer); + isdcf_name += String::compose("-%1fl", buffer); } if (video_frame_rate() != 24) { - d += "-" + raw_convert(video_frame_rate()); + isdcf_name += "-" + raw_convert(video_frame_rate()); } if (container()) { - d += "_" + container()->isdcf_name(); + isdcf_name += "_" + container()->isdcf_name(); } + auto content_list = content(); + /* XXX: this uses the first bit of content only */ /* Interior aspect ratio. The standard says we don't do this for trailers, for some strange reason */ if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::ContentKind::TRAILER) { - auto cl = content(); - auto first_video = std::find_if(cl.begin(), cl.end(), [](shared_ptr c) { return static_cast(c->video); }); - if (first_video != cl.end()) { - auto first_ratio = lrintf((*first_video)->video->scaled_size(frame_size()).ratio() * 100); - auto container_ratio = lrintf(container()->ratio() * 100); - if (first_ratio != container_ratio) { - d += "-" + dcp::raw_convert(first_ratio); + auto first_video = std::find_if(content_list.begin(), content_list.end(), [](shared_ptr c) { return static_cast(c->video); }); + if (first_video != content_list.end()) { + if (auto scaled_size = (*first_video)->video->scaled_size(frame_size())) { + auto first_ratio = lrintf(scaled_size->ratio() * 100); + auto container_ratio = lrintf(container()->ratio() * 100); + if (first_ratio != container_ratio) { + isdcf_name += "-" + dcp::raw_convert(first_ratio); + } } } } @@ -931,25 +966,11 @@ Film::isdcf_name (bool if_created_now) const auto audio_language = _audio_language ? entry_for_language(*_audio_language) : "XX"; - d += "_" + to_upper (audio_language); - - /* I'm not clear on the precise details of the convention for CCAP labelling; - for now I'm just appending -CCAP if we have any closed captions. - */ - - auto burnt_in = true; - auto ccap = false; - for (auto i: content()) { - for (auto j: i->text) { - if (j->type() == TextType::OPEN_SUBTITLE && j->use() && !j->burn()) { - burnt_in = false; - } else if (j->type() == TextType::CLOSED_CAPTION && j->use()) { - ccap = true; - } - } - } + isdcf_name += "_" + to_upper (audio_language); - auto sub_langs = subtitle_languages(); + bool burnt_in; + auto sub_langs = subtitle_languages(&burnt_in); + auto ccap_langs = closed_caption_languages(); if (sub_langs.first && sub_langs.first->language()) { auto lang = entry_for_language(*sub_langs.first); if (burnt_in) { @@ -958,25 +979,26 @@ Film::isdcf_name (bool if_created_now) const lang = to_upper (lang); } - d += "-" + lang; - if (ccap) { - d += "-CCAP"; - } + isdcf_name += "-" + lang; + } else if (!ccap_langs.empty()) { + isdcf_name += "-" + to_upper(entry_for_language(ccap_langs[0])) + "-CCAP"; } else { /* No subtitles */ - d += "-XX"; + isdcf_name += "-XX"; } - if (_release_territory) { + if (_territory_type == TerritoryType::INTERNATIONAL_TEXTED) { + isdcf_name += "_INT-TD"; + } else if (_territory_type == TerritoryType::INTERNATIONAL_TEXTLESS) { + isdcf_name += "_INT-TL"; + } else if (_release_territory) { auto territory = _release_territory->subtag(); - d += "_" + to_upper (territory); - if (_ratings.empty ()) { - d += "-NR"; - } else { + isdcf_name += "_" + to_upper (territory); + if (!_ratings.empty()) { auto label = _ratings[0].label; boost::erase_all(label, "+"); boost::erase_all(label, "-"); - d += "-" + label; + isdcf_name += "-" + label; } } @@ -986,69 +1008,73 @@ Film::isdcf_name (bool if_created_now) const auto ch = audio_channel_types (mapped, audio_channels()); if (!ch.first && !ch.second) { - d += "_MOS"; + isdcf_name += "_MOS"; } else if (ch.first) { - d += String::compose("_%1%2", ch.first, ch.second); + isdcf_name += String::compose("_%1%2", ch.first, ch.second); } if (audio_channels() > static_cast(dcp::Channel::HI) && find(mapped.begin(), mapped.end(), static_cast(dcp::Channel::HI)) != mapped.end()) { - d += "-HI"; + isdcf_name += "-HI"; } if (audio_channels() > static_cast(dcp::Channel::VI) && find(mapped.begin(), mapped.end(), static_cast(dcp::Channel::VI)) != mapped.end()) { - d += "-VI"; + isdcf_name += "-VI"; + } + + if (find_if(content_list.begin(), content_list.end(), [](shared_ptr c) { return static_cast(c->atmos); }) != content_list.end()) { + isdcf_name += "-IAB"; } - d += "_" + resolution_to_string (_resolution); + isdcf_name += "_" + resolution_to_string (_resolution); if (_studio && _studio->length() >= 2) { - d += "_" + to_upper (_studio->substr(0, 4)); + isdcf_name += "_" + to_upper (_studio->substr(0, 4)); } if (if_created_now) { - d += "_" + boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ()); + isdcf_name += "_" + boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ()); } else { - d += "_" + boost::gregorian::to_iso_string (_isdcf_date); + isdcf_name += "_" + boost::gregorian::to_iso_string (_isdcf_date); } if (_facility && _facility->length() >= 3) { - d += "_" + to_upper(_facility->substr(0, 3)); + isdcf_name += "_" + to_upper(_facility->substr(0, 3)); } if (_interop) { - d += "_IOP"; + isdcf_name += "_IOP"; } else { - d += "_SMPTE"; + isdcf_name += "_SMPTE"; } if (three_d ()) { - d += "-3D"; + isdcf_name += "-3D"; } auto vf = false; - for (auto i: content()) { - auto dc = dynamic_pointer_cast(i); - if (!dc) { + for (auto content: content_list) { + auto dcp = dynamic_pointer_cast(content); + if (!dcp) { continue; } bool any_text = false; for (int i = 0; i < static_cast(TextType::COUNT); ++i) { - if (dc->reference_text(static_cast(i))) { + if (dcp->reference_text(static_cast(i))) { any_text = true; } } - if (dc->reference_video() || dc->reference_audio() || any_text) { + if (dcp->reference_video() || dcp->reference_audio() || any_text) { vf = true; } } if (vf) { - d += "_VF"; + isdcf_name += "_VF"; } else { - d += "_OV"; + isdcf_name += "_OV"; } - return d; + return isdcf_name; } /** @return name to give the DCP */ @@ -1073,21 +1099,21 @@ Film::set_directory (boost::filesystem::path d) void Film::set_name (string n) { - FilmChangeSignaller ch (this, Property::NAME); + FilmChangeSignaller ch(this, FilmProperty::NAME); _name = n; } void Film::set_use_isdcf_name (bool u) { - FilmChangeSignaller ch (this, Property::USE_ISDCF_NAME); + FilmChangeSignaller ch(this, FilmProperty::USE_ISDCF_NAME); _use_isdcf_name = u; } void Film::set_dcp_content_type (DCPContentType const * t) { - FilmChangeSignaller ch (this, Property::DCP_CONTENT_TYPE); + FilmChangeSignaller ch(this, FilmProperty::DCP_CONTENT_TYPE); _dcp_content_type = t; } @@ -1099,7 +1125,7 @@ Film::set_dcp_content_type (DCPContentType const * t) void Film::set_container (Ratio const * c, bool explicit_user) { - FilmChangeSignaller ch (this, Property::CONTAINER); + FilmChangeSignaller ch(this, FilmProperty::CONTAINER); _container = c; if (explicit_user) { @@ -1115,7 +1141,7 @@ Film::set_container (Ratio const * c, bool explicit_user) void Film::set_resolution (Resolution r, bool explicit_user) { - FilmChangeSignaller ch (this, Property::RESOLUTION); + FilmChangeSignaller ch(this, FilmProperty::RESOLUTION); _resolution = r; if (explicit_user) { @@ -1127,7 +1153,7 @@ Film::set_resolution (Resolution r, bool explicit_user) void Film::set_j2k_bandwidth (int b) { - FilmChangeSignaller ch (this, Property::J2K_BANDWIDTH); + FilmChangeSignaller ch(this, FilmProperty::J2K_BANDWIDTH); _j2k_bandwidth = b; } @@ -1138,7 +1164,7 @@ Film::set_j2k_bandwidth (int b) void Film::set_video_frame_rate (int f, bool user_explicit) { - FilmChangeSignaller ch (this, Property::VIDEO_FRAME_RATE); + FilmChangeSignaller ch(this, FilmProperty::VIDEO_FRAME_RATE); _video_frame_rate = f; if (user_explicit) { _user_explicit_video_frame_rate = true; @@ -1148,14 +1174,14 @@ Film::set_video_frame_rate (int f, bool user_explicit) void Film::set_audio_channels (int c) { - FilmChangeSignaller ch (this, Property::AUDIO_CHANNELS); + FilmChangeSignaller ch(this, FilmProperty::AUDIO_CHANNELS); _audio_channels = c; } void Film::set_three_d (bool t) { - FilmChangeSignaller ch (this, Property::THREE_D); + FilmChangeSignaller ch(this, FilmProperty::THREE_D); _three_d = t; if (_three_d && _two_d_version_of_three_d) { @@ -1166,22 +1192,31 @@ Film::set_three_d (bool t) void Film::set_interop (bool i) { - FilmChangeSignaller ch (this, Property::INTEROP); + FilmChangeSignaller ch(this, FilmProperty::INTEROP); _interop = i; } + +void +Film::set_limit_to_smpte_bv20(bool limit) +{ + FilmChangeSignaller ch(this, FilmProperty::LIMIT_TO_SMPTE_BV20); + _limit_to_smpte_bv20 = limit; +} + + void Film::set_audio_processor (AudioProcessor const * processor) { - FilmChangeSignaller ch1 (this, Property::AUDIO_PROCESSOR); - FilmChangeSignaller ch2 (this, Property::AUDIO_CHANNELS); + FilmChangeSignaller ch1(this, FilmProperty::AUDIO_PROCESSOR); + FilmChangeSignaller ch2(this, FilmProperty::AUDIO_CHANNELS); _audio_processor = processor; } void Film::set_reel_type (ReelType t) { - FilmChangeSignaller ch (this, Property::REEL_TYPE); + FilmChangeSignaller ch(this, FilmProperty::REEL_TYPE); _reel_type = t; } @@ -1189,30 +1224,30 @@ Film::set_reel_type (ReelType t) void Film::set_reel_length (int64_t r) { - FilmChangeSignaller ch (this, Property::REEL_LENGTH); + FilmChangeSignaller ch(this, FilmProperty::REEL_LENGTH); _reel_length = r; } void Film::set_reencode_j2k (bool r) { - FilmChangeSignaller ch (this, Property::REENCODE_J2K); + FilmChangeSignaller ch(this, FilmProperty::REENCODE_J2K); _reencode_j2k = r; } void Film::signal_change (ChangeType type, int p) { - signal_change (type, static_cast(p)); + signal_change(type, static_cast(p)); } void -Film::signal_change (ChangeType type, Property p) +Film::signal_change(ChangeType type, FilmProperty p) { if (type == ChangeType::DONE) { set_dirty (true); - if (p == Property::CONTENT) { + if (p == FilmProperty::CONTENT) { if (!_user_explicit_video_frame_rate) { set_video_frame_rate (best_video_frame_rate()); } @@ -1220,7 +1255,7 @@ Film::signal_change (ChangeType type, Property p) emit (boost::bind (boost::ref (Change), type, p)); - if (p == Property::VIDEO_FRAME_RATE || p == Property::SEQUENCE) { + if (p == FilmProperty::VIDEO_FRAME_RATE || p == FilmProperty::SEQUENCE) { /* We want to call Playlist::maybe_sequence but this must happen after the main signal emission (since the butler will see that emission and un-suspend itself). */ @@ -1265,12 +1300,6 @@ Film::j2c_path (int reel, Frame frame, Eyes eyes, bool tmp) const return file (p); } -static -bool -cpl_summary_compare (CPLSummary const & a, CPLSummary const & b) -{ - return a.last_write_time > b.last_write_time; -} /** Find all the DCPs in our directory that can be dcp::DCP::read() and return details of their CPLs. * The list will be returned in reverse order of timestamp (i.e. most recent first). @@ -1279,27 +1308,29 @@ vector Film::cpls () const { if (!directory ()) { - return vector (); + return {}; } vector out; auto const dir = directory().get(); - for (auto i: boost::filesystem::directory_iterator(dir)) { + for (auto const& item: dcp::filesystem::directory_iterator(dir)) { if ( - boost::filesystem::is_directory (i) && - i.path().leaf() != "j2c" && i.path().leaf() != "video" && i.path().leaf() != "info" && i.path().leaf() != "analysis" + dcp::filesystem::is_directory(item) && + item.path().filename() != "j2c" && item.path().filename() != "video" && item.path().filename() != "info" && item.path().filename() != "analysis" ) { try { - out.push_back (CPLSummary(i)); + out.push_back(CPLSummary(item)); } catch (...) { } } } - sort (out.begin(), out.end(), cpl_summary_compare); + sort(out.begin(), out.end(), [](CPLSummary const& a, CPLSummary const& b) { + return a.last_write_time > b.last_write_time; + }); return out; } @@ -1307,7 +1338,7 @@ Film::cpls () const void Film::set_encrypted (bool e) { - FilmChangeSignaller ch (this, Property::ENCRYPTED); + FilmChangeSignaller ch(this, FilmProperty::ENCRYPTED); _encrypted = e; } @@ -1383,7 +1414,9 @@ Film::add_content (shared_ptr c) maybe_set_container_and_resolution (); if (c->atmos) { - set_audio_channels (14); + if (_audio_channels < 14) { + set_audio_channels(14); + } set_interop (false); } } @@ -1394,23 +1427,23 @@ Film::maybe_set_container_and_resolution () { /* Get the only piece of video content, if there is only one */ shared_ptr video; - for (auto i: _playlist->content()) { - if (i->video) { + for (auto content: _playlist->content()) { + if (content->video) { if (!video) { - video = i->video; + video = content->video; } else { video.reset (); } } } - if (video) { + if (video && video->size()) { /* This is the only piece of video content in this Film. Use it to make a guess for * DCP container size and resolution, unless the user has already explicitly set these * things. */ if (!_user_explicit_container) { - if (video->size().ratio() > 2.3) { + if (video->size()->ratio() > 2.3) { set_container (Ratio::from_id("239"), false); } else { set_container (Ratio::from_id("185"), false); @@ -1418,7 +1451,7 @@ Film::maybe_set_container_and_resolution () } if (!_user_explicit_resolution) { - if (video->size_after_crop().width > 2048 || video->size_after_crop().height > 1080) { + if (video->size_after_crop()->width > 2048 || video->size_after_crop()->height > 1080) { set_resolution (Resolution::FOUR_K, false); } else { set_resolution (Resolution::TWO_K, false); @@ -1476,9 +1509,9 @@ void Film::playlist_content_change (ChangeType type, weak_ptr c, int p, bool frequent) { if (p == ContentProperty::VIDEO_FRAME_RATE) { - signal_change (type, Property::CONTENT); + signal_change(type, FilmProperty::CONTENT); } else if (p == AudioContentProperty::STREAMS) { - signal_change (type, Property::NAME); + signal_change(type, FilmProperty::NAME); } if (type == ChangeType::DONE) { @@ -1502,8 +1535,8 @@ Film::playlist_length_change () void Film::playlist_change (ChangeType type) { - signal_change (type, Property::CONTENT); - signal_change (type, Property::NAME); + signal_change(type, FilmProperty::CONTENT); + signal_change(type, FilmProperty::NAME); if (type == ChangeType::DONE) { check_settings_consistency (); @@ -1568,7 +1601,7 @@ void Film::playlist_order_changed () { /* XXX: missing PENDING */ - signal_change (ChangeType::DONE, Property::CONTENT_ORDER); + signal_change(ChangeType::DONE, FilmProperty::CONTENT_ORDER); } @@ -1579,7 +1612,7 @@ Film::set_sequence (bool s) return; } - FilmChangeSignaller cc (this, Property::SEQUENCE); + FilmChangeSignaller cc(this, FilmProperty::SEQUENCE); _sequence = s; _playlist->set_sequence (s); } @@ -1616,9 +1649,10 @@ Film::active_area () const for (auto i: content()) { if (i->video) { - dcp::Size s = i->video->scaled_size (frame); - active.width = max(active.width, s.width); - active.height = max(active.height, s.height); + if (auto s = i->video->scaled_size(frame)) { + active.width = max(active.width, s->width); + active.height = max(active.height, s->height); + } } } @@ -1626,37 +1660,18 @@ Film::active_area () const } -/** @param recipient KDM recipient certificate. - * @param trusted_devices Certificate thumbprints of other trusted devices (can be empty). - * @param cpl_file CPL filename. +/* @param cpl_file CPL filename. * @param from KDM from time expressed as a local time with an offset from UTC. * @param until KDM to time expressed as a local time with an offset from UTC. - * @param formulation KDM formulation to use. - * @param disable_forensic_marking_picture true to disable forensic marking of picture. - * @param disable_forensic_marking_audio if not set, don't disable forensic marking of audio. If set to 0, - * disable all forensic marking; if set above 0, disable forensic marking above that channel. */ -dcp::EncryptedKDM -Film::make_kdm ( - dcp::Certificate recipient, - vector trusted_devices, - boost::filesystem::path cpl_file, - dcp::LocalTime from, - dcp::LocalTime until, - dcp::Formulation formulation, - bool disable_forensic_marking_picture, - optional disable_forensic_marking_audio - ) const +dcp::DecryptedKDM +Film::make_kdm(boost::filesystem::path cpl_file, dcp::LocalTime from, dcp::LocalTime until) const { if (!_encrypted) { throw runtime_error (_("Cannot make a KDM as this project is not encrypted.")); } auto cpl = make_shared(cpl_file); - auto signer = Config::instance()->signer_chain(); - if (!signer->valid ()) { - throw InvalidSignerError (); - } /* Find keys that have been added to imported, encrypted DCP content */ list imported_keys; @@ -1671,31 +1686,31 @@ Film::make_kdm ( map, dcp::Key> keys; - for (auto i: cpl->reel_file_assets()) { - if (!i->encrypted()) { + for (auto asset: cpl->reel_file_assets()) { + if (!asset->encrypted()) { continue; } /* Get any imported key for this ID */ bool done = false; - for (auto j: imported_keys) { - if (j.id() == i->key_id().get()) { - LOG_GENERAL ("Using imported key for %1", i->key_id().get()); - keys[i] = j.key(); + for (auto const& k: imported_keys) { + if (k.id() == asset->key_id().get()) { + LOG_GENERAL("Using imported key for %1", asset->key_id().get()); + keys[asset] = k.key(); done = true; } } if (!done) { /* No imported key; it must be an asset that we encrypted */ - LOG_GENERAL ("Using our own key for %1", i->key_id().get()); - keys[i] = key(); + LOG_GENERAL("Using our own key for %1", asset->key_id().get()); + keys[asset] = key(); } } return dcp::DecryptedKDM ( cpl->id(), keys, from, until, cpl->content_title_text(), cpl->content_title_text(), dcp::LocalTime().as_string() - ).encrypt (signer, recipient, trusted_devices, formulation, disable_forensic_marking_picture, disable_forensic_marking_audio); + ); } @@ -1726,15 +1741,15 @@ Film::should_be_enough_disk_space (double& required, double& available, bool& ca if (f) { f.close(); boost::system::error_code ec; - boost::filesystem::create_hard_link (test, test2, ec); + dcp::filesystem::create_hard_link(test, test2, ec); if (ec) { can_hard_link = false; } - boost::filesystem::remove (test); - boost::filesystem::remove (test2); + dcp::filesystem::remove(test); + dcp::filesystem::remove(test2); } - auto s = boost::filesystem::space (internal_video_asset_dir ()); + auto s = dcp::filesystem::space(internal_video_asset_dir()); required = double (required_disk_space ()) / 1073741824.0f; if (!can_hard_link) { required *= 2; @@ -1921,13 +1936,10 @@ Film::references_dcp_audio () const bool Film::contains_atmos_content () const { - for (auto i: _playlist->content()) { - if (i->atmos) { - return true; - } - } - - return false; + auto const content = _playlist->content(); + return std::find_if(content.begin(), content.end(), [](shared_ptr content) { + return static_cast(content->atmos); + }) != content.end(); } @@ -1936,10 +1948,10 @@ Film::closed_caption_tracks () const { list tt; for (auto i: content()) { - for (auto j: i->text) { + for (auto text: i->text) { /* XXX: Empty DCPTextTrack ends up being a magic value here - the "unknown" or "not specified" track */ - auto dtt = j->dcp_track().get_value_or(DCPTextTrack()); - if (j->type() == TextType::CLOSED_CAPTION && find(tt.begin(), tt.end(), dtt) == tt.end()) { + auto dtt = text->dcp_track().get_value_or(DCPTextTrack()); + if (text->type() == TextType::CLOSED_CAPTION && find(tt.begin(), tt.end(), dtt) == tt.end()) { tt.push_back (dtt); } } @@ -1951,7 +1963,7 @@ Film::closed_caption_tracks () const void Film::set_marker (dcp::Marker type, DCPTime time) { - FilmChangeSignaller ch (this, Property::MARKERS); + FilmChangeSignaller ch(this, FilmProperty::MARKERS); _markers[type] = time; } @@ -1959,7 +1971,7 @@ Film::set_marker (dcp::Marker type, DCPTime time) void Film::unset_marker (dcp::Marker type) { - FilmChangeSignaller ch (this, Property::MARKERS); + FilmChangeSignaller ch(this, FilmProperty::MARKERS); _markers.erase (type); } @@ -1967,7 +1979,7 @@ Film::unset_marker (dcp::Marker type) void Film::clear_markers () { - FilmChangeSignaller ch (this, Property::MARKERS); + FilmChangeSignaller ch(this, FilmProperty::MARKERS); _markers.clear (); } @@ -1975,14 +1987,14 @@ Film::clear_markers () void Film::set_ratings (vector r) { - FilmChangeSignaller ch (this, Property::RATINGS); + FilmChangeSignaller ch(this, FilmProperty::RATINGS); _ratings = r; } void Film::set_content_versions (vector v) { - FilmChangeSignaller ch (this, Property::CONTENT_VERSIONS); + FilmChangeSignaller ch(this, FilmProperty::CONTENT_VERSIONS); _content_versions = v; } @@ -1990,7 +2002,7 @@ Film::set_content_versions (vector v) void Film::set_name_language (dcp::LanguageTag lang) { - FilmChangeSignaller ch (this, Property::NAME_LANGUAGE); + FilmChangeSignaller ch(this, FilmProperty::NAME_LANGUAGE); _name_language = lang; } @@ -1998,7 +2010,7 @@ Film::set_name_language (dcp::LanguageTag lang) void Film::set_release_territory (optional region) { - FilmChangeSignaller ch (this, Property::RELEASE_TERRITORY); + FilmChangeSignaller ch(this, FilmProperty::RELEASE_TERRITORY); _release_territory = region; } @@ -2006,7 +2018,7 @@ Film::set_release_territory (optional region) void Film::set_status (dcp::Status s) { - FilmChangeSignaller ch (this, Property::STATUS); + FilmChangeSignaller ch(this, FilmProperty::STATUS); _status = s; } @@ -2014,7 +2026,7 @@ Film::set_status (dcp::Status s) void Film::set_version_number (int v) { - FilmChangeSignaller ch (this, Property::VERSION_NUMBER); + FilmChangeSignaller ch(this, FilmProperty::VERSION_NUMBER); _version_number = v; } @@ -2022,7 +2034,7 @@ Film::set_version_number (int v) void Film::set_chain (optional c) { - FilmChangeSignaller ch (this, Property::CHAIN); + FilmChangeSignaller ch(this, FilmProperty::CHAIN); _chain = c; } @@ -2030,7 +2042,7 @@ Film::set_chain (optional c) void Film::set_distributor (optional d) { - FilmChangeSignaller ch (this, Property::DISTRIBUTOR); + FilmChangeSignaller ch(this, FilmProperty::DISTRIBUTOR); _distributor = d; } @@ -2038,7 +2050,7 @@ Film::set_distributor (optional d) void Film::set_luminance (optional l) { - FilmChangeSignaller ch (this, Property::LUMINANCE); + FilmChangeSignaller ch(this, FilmProperty::LUMINANCE); _luminance = l; } @@ -2046,7 +2058,7 @@ Film::set_luminance (optional l) void Film::set_facility (optional f) { - FilmChangeSignaller ch (this, Property::FACILITY); + FilmChangeSignaller ch(this, FilmProperty::FACILITY); _facility = f; } @@ -2054,7 +2066,7 @@ Film::set_facility (optional f) void Film::set_studio (optional s) { - FilmChangeSignaller ch (this, Property::STUDIO); + FilmChangeSignaller ch(this, FilmProperty::STUDIO); _studio = s; } @@ -2077,10 +2089,10 @@ Film::info_file_handle (DCPTimePeriod period, bool read) const InfoFileHandle::InfoFileHandle (boost::mutex& mutex, boost::filesystem::path path, bool read) : _lock (mutex) - , _file (path, read ? "rb" : (boost::filesystem::exists(path) ? "r+b" : "wb")) + , _file(path, read ? "rb" : (dcp::filesystem::exists(path) ? "r+b" : "wb")) { if (!_file) { - throw OpenFileError (path, errno, read ? OpenFileError::READ : (boost::filesystem::exists(path) ? OpenFileError::READ_WRITE : OpenFileError::WRITE)); + throw OpenFileError(path, errno, read ? OpenFileError::READ : (dcp::filesystem::exists(path) ? OpenFileError::READ_WRITE : OpenFileError::WRITE)); } } @@ -2102,7 +2114,7 @@ Film::add_ffoc_lfoc (Markers& markers) const void Film::set_temp_version (bool t) { - FilmChangeSignaller ch (this, Property::TEMP_VERSION); + FilmChangeSignaller ch(this, FilmProperty::TEMP_VERSION); _temp_version = t; } @@ -2110,7 +2122,7 @@ Film::set_temp_version (bool t) void Film::set_pre_release (bool p) { - FilmChangeSignaller ch (this, Property::PRE_RELEASE); + FilmChangeSignaller ch(this, FilmProperty::PRE_RELEASE); _pre_release = p; } @@ -2118,7 +2130,7 @@ Film::set_pre_release (bool p) void Film::set_red_band (bool r) { - FilmChangeSignaller ch (this, Property::RED_BAND); + FilmChangeSignaller ch(this, FilmProperty::RED_BAND); _red_band = r; } @@ -2126,7 +2138,7 @@ Film::set_red_band (bool r) void Film::set_two_d_version_of_three_d (bool t) { - FilmChangeSignaller ch (this, Property::TWO_D_VERSION_OF_THREE_D); + FilmChangeSignaller ch(this, FilmProperty::TWO_D_VERSION_OF_THREE_D); _two_d_version_of_three_d = t; } @@ -2134,7 +2146,7 @@ Film::set_two_d_version_of_three_d (bool t) void Film::set_audio_language (optional language) { - FilmChangeSignaller ch (this, Property::AUDIO_LANGUAGE); + FilmChangeSignaller ch(this, FilmProperty::AUDIO_LANGUAGE); _audio_language = language; } @@ -2142,7 +2154,7 @@ Film::set_audio_language (optional language) void Film::set_audio_frame_rate (int rate) { - FilmChangeSignaller ch (this, Property::AUDIO_FRAME_RATE); + FilmChangeSignaller ch(this, FilmProperty::AUDIO_FRAME_RATE); _audio_frame_rate = rate; } @@ -2157,7 +2169,7 @@ Film::has_sign_language_video_channel () const void Film::set_sign_language_video_language (optional lang) { - FilmChangeSignaller ch (this, Property::SIGN_LANGUAGE_VIDEO_LANGUAGE); + FilmChangeSignaller ch(this, FilmProperty::SIGN_LANGUAGE_VIDEO_LANGUAGE); _sign_language_video_language = lang; } @@ -2210,3 +2222,11 @@ Film::last_written_by_earlier_than(int major, int minor, int micro) const return our_micro < micro; } + +void +Film::set_territory_type(TerritoryType type) +{ + FilmChangeSignaller ch(this, FilmProperty::TERRITORY_TYPE); + _territory_type = type; +} + diff --git a/src/lib/film.h b/src/lib/film.h index 72d6d5e8d..43a41ad45 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -31,8 +31,13 @@ #include "change_signaller.h" #include "dcp_text_track.h" +#include "dcpomatic_time.h" +#include "film_property.h" #include "frame_rate_change.h" +#include "named_channel.h" +#include "resolution.h" #include "signaller.h" +#include "territory_type.h" #include "transcode_job.h" #include "types.h" #include "util.h" @@ -41,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -58,18 +64,21 @@ namespace dcpomatic { class Screen; } +class AudioContent; +class AudioProcessor; +class Content; class DCPContentType; +class Film; +class Job; class Log; -class Content; class Playlist; -class AudioContent; -class AudioProcessor; class Ratio; -class Job; -class Film; +struct atmos_encrypted_passthrough_test; struct isdcf_name_test; +struct isdcf_name_with_atmos; +struct isdcf_name_with_ccap; +struct ov_subs_in_vf_name; struct recover_test_2d_encrypted; -struct atmos_encrypted_passthrough_test; class InfoFileHandle @@ -163,16 +172,7 @@ public: FrameRateChange active_frame_rate_change (dcpomatic::DCPTime) const; std::pair speed_up_range (int dcp_frame_rate) const; - dcp::EncryptedKDM make_kdm ( - dcp::Certificate recipient, - std::vector trusted_devices, - boost::filesystem::path cpl_file, - dcp::LocalTime from, - dcp::LocalTime until, - dcp::Formulation formulation, - bool disable_forensic_marking_picture, - boost::optional disable_forensic_marking_audio - ) const; + dcp::DecryptedKDM make_kdm(boost::filesystem::path cpl_file, dcp::LocalTime from, dcp::LocalTime until) const; int state_version () const { return _state_version; @@ -193,8 +193,12 @@ public: return _audio_language; } - /** @return pair containing the main subtitle language, and additional languages */ - std::pair, std::vector> subtitle_languages () const; + /** @param burnt_in If non-null, filled with true if all subtitles are burnt in, otherwise false. + * @return pair containing the main subtitle language, and additional languages + */ + std::pair, std::vector> subtitle_languages(bool* burnt_in = nullptr) const; + /** @return all closed caption languages in the film */ + std::vector closed_caption_languages() const; std::string content_summary (dcpomatic::DCPTimePeriod period) const; @@ -212,54 +216,6 @@ public: bool last_written_by_earlier_than(int major, int minor, int micro) const; - /** Identifiers for the parts of our state; - used for signalling changes. - */ - enum class Property { - NONE, - NAME, - USE_ISDCF_NAME, - /** The playlist's content list has changed (i.e. content has been added or removed) */ - CONTENT, - /** The order of content in the playlist has changed */ - CONTENT_ORDER, - DCP_CONTENT_TYPE, - CONTAINER, - RESOLUTION, - ENCRYPTED, - J2K_BANDWIDTH, - VIDEO_FRAME_RATE, - AUDIO_FRAME_RATE, - AUDIO_CHANNELS, - /** The setting of _three_d has changed */ - THREE_D, - SEQUENCE, - INTEROP, - AUDIO_PROCESSOR, - REEL_TYPE, - REEL_LENGTH, - REENCODE_J2K, - MARKERS, - RATINGS, - CONTENT_VERSIONS, - NAME_LANGUAGE, - AUDIO_LANGUAGE, - RELEASE_TERRITORY, - SIGN_LANGUAGE_VIDEO_LANGUAGE, - VERSION_NUMBER, - STATUS, - CHAIN, - DISTRIBUTOR, - FACILITY, - STUDIO, - TEMP_VERSION, - PRE_RELEASE, - RED_BAND, - TWO_D_VERSION_OF_THREE_D, - LUMINANCE, - }; - - /* GET */ boost::optional directory () const { @@ -319,6 +275,10 @@ public: return _interop; } + bool limit_to_smpte_bv20() const { + return _limit_to_smpte_bv20; + } + AudioProcessor const * audio_processor () const { return _audio_processor; } @@ -358,6 +318,10 @@ public: return _name_language; } + TerritoryType territory_type() const { + return _territory_type; + } + boost::optional release_territory () const { return _release_territory; } @@ -440,6 +404,7 @@ public: void set_isdcf_date_today (); void set_sequence (bool); void set_interop (bool); + void set_limit_to_smpte_bv20(bool); void set_audio_processor (AudioProcessor const * processor); void set_reel_type (ReelType); void set_reel_length (int64_t); @@ -450,6 +415,7 @@ public: void set_ratings (std::vector r); void set_content_versions (std::vector v); void set_name_language (dcp::LanguageTag lang); + void set_territory_type(TerritoryType type); void set_release_territory (boost::optional region = boost::none); void set_sign_language_video_language (boost::optional tag); void set_version_number (int v); @@ -469,7 +435,7 @@ public: void add_ffoc_lfoc (Markers& markers) const; /** Emitted when some property has of the Film is about to change or has changed */ - mutable boost::signals2::signal Change; + mutable boost::signals2::signal Change; /** Emitted when some property of our content has changed */ mutable boost::signals2::signal, int, bool)> ContentChange; @@ -490,13 +456,16 @@ public: private: friend struct ::isdcf_name_test; + friend struct ::isdcf_name_with_atmos; + friend struct ::isdcf_name_with_ccap; friend struct ::recover_test_2d_encrypted; friend struct ::atmos_encrypted_passthrough_test; - template friend class ChangeSignaller; + friend struct ::ov_subs_in_vf_name; + template friend class ChangeSignalDespatcher; boost::filesystem::path info_file (dcpomatic::DCPTimePeriod p) const; - void signal_change (ChangeType, Property); + void signal_change (ChangeType, FilmProperty); void signal_change (ChangeType, int); std::string video_identifier () const; void playlist_change (ChangeType); @@ -550,6 +519,7 @@ private: bool _three_d; bool _sequence; bool _interop; + bool _limit_to_smpte_bv20; AudioProcessor const * _audio_processor; ReelType _reel_type; /** Desired reel length in bytes, if _reel_type == REELTYPE_BY_LENGTH */ @@ -563,6 +533,7 @@ private: std::vector _ratings; std::vector _content_versions; dcp::LanguageTag _name_language; + TerritoryType _territory_type = TerritoryType::SPECIFIC; boost::optional _release_territory; boost::optional _sign_language_video_language; int _version_number; @@ -605,7 +576,7 @@ private: }; -typedef ChangeSignaller FilmChangeSignaller; +typedef ChangeSignaller FilmChangeSignaller; #endif diff --git a/src/lib/film_property.h b/src/lib/film_property.h new file mode 100644 index 000000000..c23297965 --- /dev/null +++ b/src/lib/film_property.h @@ -0,0 +1,78 @@ +/* + Copyright (C) 2012-2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#ifndef DCPOMATIC_FILM_PROPERTY_H +#define DCPOMATIC_FILM_PROPERTY_H + + +/** Identifiers for the parts of a Film's state; used for signalling changes. + * This could go in Film but separating it out saves a lot of includes of + * film.h + */ +enum class FilmProperty { + NONE, + NAME, + USE_ISDCF_NAME, + /** The playlist's content list has changed (i.e. content has been added or removed) */ + CONTENT, + /** The order of content in the playlist has changed */ + CONTENT_ORDER, + DCP_CONTENT_TYPE, + CONTAINER, + RESOLUTION, + ENCRYPTED, + J2K_BANDWIDTH, + VIDEO_FRAME_RATE, + AUDIO_FRAME_RATE, + AUDIO_CHANNELS, + /** The setting of _three_d has changed */ + THREE_D, + SEQUENCE, + INTEROP, + LIMIT_TO_SMPTE_BV20, + AUDIO_PROCESSOR, + REEL_TYPE, + REEL_LENGTH, + REENCODE_J2K, + MARKERS, + RATINGS, + CONTENT_VERSIONS, + NAME_LANGUAGE, + AUDIO_LANGUAGE, + RELEASE_TERRITORY, + SIGN_LANGUAGE_VIDEO_LANGUAGE, + VERSION_NUMBER, + STATUS, + CHAIN, + DISTRIBUTOR, + FACILITY, + STUDIO, + TEMP_VERSION, + PRE_RELEASE, + RED_BAND, + TWO_D_VERSION_OF_THREE_D, + LUMINANCE, + TERRITORY_TYPE, +}; + + +#endif + diff --git a/src/lib/film_util.cc b/src/lib/film_util.cc new file mode 100644 index 000000000..a12a3ae89 --- /dev/null +++ b/src/lib/film_util.cc @@ -0,0 +1,47 @@ +/* + Copyright (C) 2023 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "config.h" +#include "film.h" +#include "film_util.h" + + +using std::shared_ptr; +using boost::optional; + + +bool +channel_is_mapped(shared_ptr film, dcp::Channel channel) +{ + auto const mapped = film->mapped_audio_channels(); + return std::find(mapped.begin(), mapped.end(), static_cast(channel)) != mapped.end(); +} + + +optional +add_files_override_path(shared_ptr film) +{ + film->directory(); + return Config::instance()->default_add_file_location() == Config::DefaultAddFileLocation::SAME_AS_PROJECT + ? film->directory()->parent_path() + : boost::optional(); + +} diff --git a/src/lib/film_util.h b/src/lib/film_util.h new file mode 100644 index 000000000..3e5f40644 --- /dev/null +++ b/src/lib/film_util.h @@ -0,0 +1,29 @@ +/* + Copyright (C) 2023 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include + + +class Film; + + +bool channel_is_mapped(std::shared_ptr film, dcp::Channel channel); +boost::optional add_files_override_path(std::shared_ptr film); diff --git a/src/lib/filter.cc b/src/lib/filter.cc index 9158cba5c..9a14795ec 100644 --- a/src/lib/filter.cc +++ b/src/lib/filter.cc @@ -24,6 +24,7 @@ */ +#include "dcpomatic_assert.h" #include "filter.h" #include LIBDCP_DISABLE_WARNINGS @@ -31,40 +32,38 @@ extern "C" { #include } LIBDCP_ENABLE_WARNINGS +#include #include "i18n.h" using namespace std; +using boost::optional; vector Filter::_filters; -/** @param i Our id. - * @param n User-visible name. - * @param c User-visible category. - * @param f String for a FFmpeg filter descriptor. +/** @param id Our id. + * @param name User-visible name. + * @param category User-visible category. + * @param ffmpeg_string String for a FFmpeg filter descriptor. */ -Filter::Filter (string i, string n, string c, string f) - : _id (i) - , _name (n) - , _category (c) - , _ffmpeg (f) +Filter::Filter(string id, string name, string category, string ffmpeg_string) + : _id(id) + , _name(name) + , _category(category) + , _ffmpeg(ffmpeg_string) { } /** @return All available filters */ -vector +vector Filter::all () { - vector raw; - for (auto& filter: _filters) { - raw.push_back (&filter); - } - return raw; + return _filters; } @@ -76,6 +75,19 @@ Filter::setup_filters () { /* Note: "none" is a magic id name, so don't use it here */ + auto maybe_add = [](string id, string name, string category, string ffmpeg) + { + string check_name = ffmpeg; + size_t end = check_name.find("="); + if (end != string::npos) { + check_name = check_name.substr(0, end); + } + + if (avfilter_get_by_name(check_name.c_str())) { + _filters.push_back(Filter(id, name, category, ffmpeg)); + } + }; + maybe_add (N_("vflip"), _("Vertical flip"), _("Orientation"), N_("vflip")); maybe_add (N_("hflip"), _("Horizontal flip"), _("Orientation"), N_("hflip")); maybe_add (N_("90clock"), _("Rotate 90 degrees clockwise"), _("Orientation"), N_("transpose=dir=clock")); @@ -91,21 +103,7 @@ Filter::setup_filters () maybe_add (N_("hqdn3d"), _("High quality 3D denoiser"), _("Noise reduction"), N_("hqdn3d")); maybe_add (N_("telecine"), _("Telecine filter"), _("Misc"), N_("telecine")); maybe_add (N_("ow"), _("Overcomplete wavelet denoiser"), _("Noise reduction"), N_("mp=ow")); -} - - -void -Filter::maybe_add (string i, string n, string c, string f) -{ - string check_name = f; - size_t end = check_name.find("="); - if (end != string::npos) { - check_name = check_name.substr (0, end); - } - - if (avfilter_get_by_name(check_name.c_str())) { - _filters.push_back (Filter(i, n, c, f)); - } + maybe_add (N_("premultiply"), _("Premultiply alpha channel"), _("Misc"), N_("premultiply=inplace=1")); } @@ -113,15 +111,15 @@ Filter::maybe_add (string i, string n, string c, string f) * @return String to pass to FFmpeg for the video filters. */ string -Filter::ffmpeg_string (vector const & filters) +Filter::ffmpeg_string(vector const& filters) { string ff; - for (auto const i: filters) { + for (auto const& i: filters) { if (!ff.empty ()) { ff += N_(","); } - ff += i->ffmpeg (); + ff += i.ffmpeg(); } return ff; @@ -129,19 +127,47 @@ Filter::ffmpeg_string (vector const & filters) /** @param d Our id. - * @return Corresponding Filter, or 0. + * @return Corresponding Filter, if found. */ -Filter const * -Filter::from_id (string d) +optional +Filter::from_id(string id) +{ + auto iter = std::find_if(_filters.begin(), _filters.end(), [id](Filter const& filter) { return filter.id() == id; }); + if (iter == _filters.end()) { + return {}; + } + return *iter; +} + + +bool +operator==(Filter const& a, Filter const& b) +{ + return a.id() == b.id() && a.name() == b.name() && a.category() == b.category() && a.ffmpeg() == b.ffmpeg(); +} + + +bool +operator!=(Filter const& a, Filter const& b) { - auto i = _filters.begin (); - while (i != _filters.end() && i->id() != d) { - ++i; + return a.id() != b.id() || a.name() != b.name() || a.category() != b.category() || a.ffmpeg() != b.ffmpeg(); +} + + +bool +operator<(Filter const& a, Filter const& b) +{ + if (a.id() != b.id()) { + return a.id() < b.id(); + } + + if (a.name() != b.name()) { + return a.name() < b.name(); } - if (i == _filters.end ()) { - return nullptr; + if (a.category() != b.category()) { + return a.category() < b.category(); } - return &(*i); + return a.ffmpeg() < b.ffmpeg(); } diff --git a/src/lib/filter.h b/src/lib/filter.h index f73a95453..bcc40dad7 100644 --- a/src/lib/filter.h +++ b/src/lib/filter.h @@ -28,6 +28,7 @@ #define DCPOMATIC_FILTER_H +#include #include #include @@ -42,7 +43,7 @@ class Filter { public: - Filter (std::string i, std::string n, std::string c, std::string f); + Filter(std::string id, std::string name, std::string category, std::string ffmpeg_string); /** @return our id */ std::string id () const { @@ -63,10 +64,10 @@ public: return _category; } - static std::vector all (); - static Filter const * from_id (std::string d); + static std::vector all (); + static boost::optional from_id(std::string d); static void setup_filters (); - static std::string ffmpeg_string (std::vector const & filters); + static std::string ffmpeg_string(std::vector const& filters); private: @@ -80,8 +81,12 @@ private: /** all available filters */ static std::vector _filters; - static void maybe_add (std::string, std::string, std::string, std::string); }; +bool operator==(Filter const& a, Filter const& b); +bool operator!=(Filter const& a, Filter const& b); +bool operator<(Filter const& a, Filter const& b); + + #endif diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc index fc6b9033a..745d980a4 100644 --- a/src/lib/filter_graph.cc +++ b/src/lib/filter_graph.cc @@ -51,14 +51,15 @@ using dcp::Size; void -FilterGraph::setup (vector filters) +FilterGraph::setup(vector const& filters) { auto const filters_string = Filter::ffmpeg_string (filters); if (filters.empty()) { - _copy = true; return; } + _copy = false; + _frame = av_frame_alloc (); _graph = avfilter_graph_alloc(); diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h index 4019b5863..e474f851f 100644 --- a/src/lib/filter_graph.h +++ b/src/lib/filter_graph.h @@ -28,19 +28,21 @@ #define DCPOMATIC_FILTER_GRAPH_H -#include "util.h" +#include "filter.h" #include LIBDCP_DISABLE_WARNINGS extern "C" { #include } LIBDCP_ENABLE_WARNINGS +#include +#include +class Filter; +class Image; struct AVFilterContext; struct AVFrame; -class Image; -class Filter; /** @class FilterGraph @@ -55,7 +57,7 @@ public: FilterGraph (FilterGraph const&) = delete; FilterGraph& operator== (FilterGraph const&) = delete; - void setup (std::vector); + void setup(std::vector const&); AVFilterContext* get (std::string name); protected: @@ -66,7 +68,7 @@ protected: AVFilterGraph* _graph = nullptr; /** true if this graph has no filters in, so it just copies stuff straight through */ - bool _copy = false; + bool _copy = true; AVFilterContext* _buffer_src_context = nullptr; AVFilterContext* _buffer_sink_context = nullptr; AVFrame* _frame = nullptr; diff --git a/src/lib/find_missing.cc b/src/lib/find_missing.cc index 2234637b3..199ffcc19 100644 --- a/src/lib/find_missing.cc +++ b/src/lib/find_missing.cc @@ -22,7 +22,7 @@ #include "content.h" #include "find_missing.h" #include "util.h" -#include +#include using std::map; @@ -38,16 +38,16 @@ void search (Replacements& replacement_paths, boost::filesystem::path directory, int depth = 0) { boost::system::error_code ec; - for (auto candidate: boost::filesystem::directory_iterator(directory, ec)) { - if (boost::filesystem::is_regular_file(candidate.path())) { + for (auto candidate: dcp::filesystem::directory_iterator(directory, ec)) { + if (dcp::filesystem::is_regular_file(candidate.path())) { for (auto& replacement: replacement_paths) { for (auto& path: replacement.second) { - if (!boost::filesystem::exists(path) && path.filename() == candidate.path().filename()) { + if (!dcp::filesystem::exists(path) && path.filename() == candidate.path().filename()) { path = candidate.path(); } } } - } else if (boost::filesystem::is_directory(candidate.path()) && depth <= 2) { + } else if (dcp::filesystem::is_directory(candidate.path()) && depth <= 2) { search (replacement_paths, candidate, depth + 1); } } diff --git a/src/lib/font.cc b/src/lib/font.cc index 375427d93..955a2ad1c 100644 --- a/src/lib/font.cc +++ b/src/lib/font.cc @@ -38,18 +38,36 @@ Font::Font (cxml::NodePtr node) for (auto i: node->node_children("File")) { string variant = i->optional_string_attribute("Variant").get_value_or("Normal"); if (variant == "Normal") { - _file = i->content(); + _content.file = i->content(); } } } +Font::Font(Font const& other) + : _id(other._id) + , _content(other._content) +{ + +} + + +Font& Font::operator=(Font const& other) +{ + if (&other != this) { + _id = other._id; + _content = other._content; + } + return *this; +} + + void Font::as_xml (xmlpp::Node* node) { node->add_child("Id")->add_child_text(_id); - if (_file) { - node->add_child("File")->add_child_text(_file->string()); + if (_content.file) { + node->add_child("File")->add_child_text(_content.file->string()); } } @@ -61,6 +79,11 @@ dcpomatic::operator== (Font const & a, Font const & b) return false; } + /* XXX: it's dubious that this ignores _data, though I think it's OK for the cases + * where operator== is used. Perhaps we should remove operator== and have a more + * specific comparator. + */ + return a.file() == b.file(); } @@ -75,12 +98,12 @@ dcpomatic::operator!= (Font const & a, Font const & b) optional Font::data () const { - if (_data) { - return _data; + if (_content.data) { + return _content.data; } - if (_file) { - return dcp::ArrayData(*_file); + if (_content.file) { + return dcp::ArrayData(*_content.file); } return {}; diff --git a/src/lib/font.h b/src/lib/font.h index 24d8ed2bb..12a14aba4 100644 --- a/src/lib/font.h +++ b/src/lib/font.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2014-2021 Carl Hetherington + Copyright (C) 2014-2023 Carl Hetherington This file is part of DCP-o-matic. @@ -44,13 +44,18 @@ public: Font (std::string id, boost::filesystem::path file) : _id (id) - , _file (file) - {} + { + _content.file = file; + } Font (std::string id, dcp::ArrayData data) : _id (id) - , _data (data) - {} + { + _content.data = data; + } + + Font(Font const& other); + Font& operator=(Font const& other); void as_xml (xmlpp::Node* node); @@ -63,16 +68,30 @@ public: } boost::optional file () const { - return _file; + return _content.file; } void set_file (boost::filesystem::path file) { - _file = file; + _content.file = file; Changed (); } + /** @return the data set passed to the dcp::ArrayData constructor, + * otherwise the contents of _file, otherwise boost::none. + */ boost::optional data() const; + /** The actual TTF/OTF font data, as either a filename or the raw data itself */ + struct Content + { + boost::optional data; + boost::optional file; + }; + + Content content() const { + return _content; + } + boost::signals2::signal Changed; private: @@ -80,8 +99,7 @@ private: * font family name or an ID from some DCP font XML. */ std::string _id; - boost::optional _data; - boost::optional _file; + Content _content; }; diff --git a/src/lib/font_comparator.h b/src/lib/font_comparator.h new file mode 100644 index 000000000..07ad15b6a --- /dev/null +++ b/src/lib/font_comparator.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2023 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "dcpomatic_assert.h" + + +/** Comparator to allow dcpomatic::Font::Content to be compared for use in a map */ +struct FontComparator +{ + bool operator()(dcpomatic::Font::Content const& a, dcpomatic::Font::Content const& b) const { + auto const a_has_file = static_cast(a.file); + auto const b_has_file = static_cast(b.file); + auto const a_has_data = static_cast(a.data); + auto const b_has_data = static_cast(b.data); + + if (!a_has_file && !a_has_data && !b_has_file && !b_has_data) { + /* Neither font has any font data: a == b */ + return false; + } else if (!a_has_file && !a_has_data) { + /* Fonts with no data are the "lowest": a < b */ + return true; + } else if (!b_has_file && !b_has_data) { + /* ... so here b < a */ + return false; + } else if (a_has_file && !b_has_file) { + /* Fonts with file are lower than fonts with data: a < b */ + return true; + } else if (!a_has_file && b_has_file) { + /* ... so here b < a */ + return false; + } else if (a_has_file && b_has_file) { + /* Compared as "equals" */ + return a.file->string() < b.file->string(); + } else if (a_has_data && b_has_data) { + /* Compared as "equals" */ + auto const a_size = a.data->size(); + auto const b_size = b.data->size(); + if (a_size != b_size) { + return a_size < b_size; + } + return memcmp(a.data->data(), b.data->data(), a_size) < 0; + } + + /* Should never get here */ + DCPOMATIC_ASSERT(false); + return false; + }; +}; + + diff --git a/src/lib/font_config.cc b/src/lib/font_config.cc index 40b9770c7..653c9ba84 100644 --- a/src/lib/font_config.cc +++ b/src/lib/font_config.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2014-2021 Carl Hetherington + Copyright (C) 2014-2023 Carl Hetherington This file is part of DCP-o-matic. @@ -21,12 +21,16 @@ #include "dcpomatic_assert.h" #include "dcpomatic_log.h" +#include "font.h" #include "font_config.h" +#include "util.h" +#include #include #include #include +using std::shared_ptr; using std::string; using boost::optional; @@ -41,14 +45,40 @@ FontConfig::FontConfig() } +FontConfig::~FontConfig() +{ + for (auto file: _temp_files) { + boost::system::error_code ec; + dcp::filesystem::remove(file, ec); + } +} + + string -FontConfig::make_font_available(boost::filesystem::path font_file) +FontConfig::make_font_available(shared_ptr font) { - auto existing = _available_fonts.find(font_file); + DCPOMATIC_ASSERT(font); + + auto existing = _available_fonts.find(font->content()); if (existing != _available_fonts.end()) { return existing->second; } + boost::filesystem::path font_file = default_font_file(); + if (font->file()) { + font_file = *font->file(); + } else if (font->data()) { + /* This font only exists in memory (so far) but FontConfig doesn't have an API to add a font + * from a memory buffer (AFAICS). + * https://gitlab.freedesktop.org/fontconfig/fontconfig/-/issues/12 + * As a workaround, write the font data to a temporary file and use that. + */ + font_file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + _temp_files.push_back(font_file); + font->data()->write(font_file); + } + + /* Make this font available to DCP-o-matic */ optional font_name; FcConfigAppFontAddFile (_config, reinterpret_cast(font_file.string().c_str())); @@ -80,7 +110,10 @@ FontConfig::make_font_available(boost::filesystem::path font_file) DCPOMATIC_ASSERT(font_name); - _available_fonts[font_file] = *font_name; + /* We need to use the font object as the key, as we may be passed the same shared_ptr to a modified + * Font object in the future and in that case we need to load the new font. + */ + _available_fonts[font->content()] = *font_name; FcConfigBuildFonts(_config); return *font_name; @@ -135,3 +168,11 @@ FontConfig::instance() return _instance; } + +void +FontConfig::drop() +{ + delete _instance; + _instance = nullptr; +} + diff --git a/src/lib/font_config.h b/src/lib/font_config.h index 6dadbae8a..57f733b7b 100644 --- a/src/lib/font_config.h +++ b/src/lib/font_config.h @@ -19,6 +19,7 @@ */ +#include "font_comparator.h" #include #include #include @@ -31,14 +32,19 @@ class FontConfig public: static FontConfig* instance(); - std::string make_font_available(boost::filesystem::path font_file); + std::string make_font_available(std::shared_ptr font); boost::optional system_font_with_name(std::string name); + static void drop(); + private: FontConfig(); + ~FontConfig(); FcConfig* _config = nullptr; - std::map _available_fonts; + std::map _available_fonts; + + std::vector _temp_files; static FontConfig* _instance; }; diff --git a/src/lib/font_id_allocator.cc b/src/lib/font_id_allocator.cc new file mode 100644 index 000000000..5263e7f90 --- /dev/null +++ b/src/lib/font_id_allocator.cc @@ -0,0 +1,133 @@ +/* + Copyright (C) 2023 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "compose.hpp" +#include "constants.h" +#include "dcpomatic_assert.h" +#include "font_id_allocator.h" +#include +#include +#include +#include +#include +#include +#include + + +using std::shared_ptr; +using std::set; +using std::string; +using std::vector; + + +void +FontIDAllocator::add_fonts_from_reels(vector> const& reels) +{ + int reel_index = 0; + for (auto reel: reels) { + if (auto sub = reel->main_subtitle()) { + if (sub->asset_ref().resolved()) { + add_fonts_from_asset(reel_index, sub->asset()); + } + } + + for (auto ccap: reel->closed_captions()) { + if (ccap->asset_ref().resolved()) { + add_fonts_from_asset(reel_index, ccap->asset()); + } + } + + ++reel_index; + } +} + + +void +FontIDAllocator::add_fonts_from_asset(int reel_index, shared_ptr asset) +{ + for (auto const& font: asset->font_data()) { + add_font(reel_index, asset->id(), font.first); + } +} + + +void +FontIDAllocator::add_font(int reel_index, string asset_id, string font_id) +{ + auto font = Font(reel_index, asset_id, font_id); + if (!_default_font) { + _default_font = font; + } + _map[font] = 0; +} + + +void +FontIDAllocator::allocate() +{ + /* Last reel index that we have; i.e. the last prefix number that would be used by "old" + * font IDs (i.e. ones before this FontIDAllocator was added and used) + */ + auto const last_reel = std::max_element( + _map.begin(), + _map.end(), + [] (std::pair const& a, std::pair const& b) { + return a.first.reel_index < b.first.reel_index; + })->first.reel_index; + + /* Number of times each ID has been used */ + std::map used_count; + + for (auto& font: _map) { + auto const proposed = String::compose("%1_%2", font.first.reel_index, font.first.font_id); + if (used_count.find(proposed) != used_count.end()) { + /* This ID was already used; we need to disambiguate it. Do so by using + * an ID above last_reel + */ + font.second = last_reel + used_count[proposed]; + ++used_count[proposed]; + } else { + /* This ID was not yet used */ + used_count[proposed] = 1; + font.second = font.first.reel_index; + } + } +} + + +string +FontIDAllocator::font_id(int reel_index, string asset_id, string font_id) const +{ + auto iter = _map.find(Font(reel_index, asset_id, font_id)); + DCPOMATIC_ASSERT(iter != _map.end()); + return String::compose("%1_%2", iter->second, font_id); +} + + +string +FontIDAllocator::default_font_id() const +{ + if (_default_font) { + return font_id(_default_font->reel_index, _default_font->asset_id, _default_font->font_id); + } + + return "default"; +} diff --git a/src/lib/font_id_allocator.h b/src/lib/font_id_allocator.h new file mode 100644 index 000000000..fe4b9ef07 --- /dev/null +++ b/src/lib/font_id_allocator.h @@ -0,0 +1,109 @@ +/* + Copyright (C) 2023 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#ifndef DCPOMATIC_FONT_ID_ALLOCATOR_H +#define DCPOMATIC_FONT_ID_ALLOCATOR_H + + +#include +#include +#include +#include +#include + + +namespace dcp { + class Reel; + class SubtitleAsset; +} + + +/** A class which, given some pairs of (asset-id, font-id) can return a font ID + * which is unique in a piece of content. + * + * When we examine a 2-reel DCP we may have a pair of subtitle assets that + * each have a font with ID "foo". We want to store these in + * TextContent::_fonts in such a way that they are distinguishable. + * + * If TextContent is to carry on storing a list of dcpomatic::Font, we can + * only do this by making each dcpomatic::Font have a different ID (i.e. not + * both "foo"). + * + * Hence when we add the fonts to the TextContent we re-write them to have + * unique IDs. + * + * When we do this, we must do it in a repeatable way so that when the + * DCPDecoder receives the "foo" font IDs it can obtain the same "new" ID given + * "foo" and the asset ID that it came from. + * + * FontIDAllocator can help with that: call add_fonts_from_reels() or add_font(), + * then allocate(), then it will return repeatable unique "new" font IDs from + * an asset map and "old" ID. + */ +class FontIDAllocator +{ +public: + void add_fonts_from_reels(std::vector> const& reels); + void add_font(int reel_index, std::string asset_id, std::string font_id); + + void allocate(); + + std::string font_id(int reel_index, std::string asset_id, std::string font_id) const; + std::string default_font_id() const; + + bool has_default_font() const { + return static_cast(_default_font); + } + +private: + void add_fonts_from_asset(int reel_index, std::shared_ptr asset); + + struct Font + { + Font(int reel_index_, std::string asset_id_, std::string font_id_) + : reel_index(reel_index_) + , asset_id(asset_id_) + , font_id(font_id_) + {} + + bool operator<(Font const& other) const { + if (reel_index != other.reel_index) { + return reel_index < other.reel_index; + } + + if (asset_id != other.asset_id) { + return asset_id < other.asset_id; + } + + return font_id < other.font_id; + } + + int reel_index; + std::string asset_id; + std::string font_id; + }; + + std::map _map; + boost::optional _default_font; +}; + + +#endif diff --git a/src/lib/frame_rate_change.cc b/src/lib/frame_rate_change.cc index 99424a9c1..8372493ef 100644 --- a/src/lib/frame_rate_change.cc +++ b/src/lib/frame_rate_change.cc @@ -23,7 +23,6 @@ #include "content.h" #include "film.h" #include "frame_rate_change.h" -#include "types.h" #include #include "i18n.h" diff --git a/src/lib/guess_crop.h b/src/lib/guess_crop.h index d74912042..f18c2db5e 100644 --- a/src/lib/guess_crop.h +++ b/src/lib/guess_crop.h @@ -19,8 +19,8 @@ */ +#include "crop.h" #include "dcpomatic_time.h" -#include "types.h" #include diff --git a/src/lib/hints.cc b/src/lib/hints.cc index 46296351b..bbd5ae5d5 100644 --- a/src/lib/hints.cc +++ b/src/lib/hints.cc @@ -24,6 +24,7 @@ #include "audio_processor.h" #include "compose.hpp" #include "config.h" +#include "constants.h" #include "content.h" #include "cross.h" #include "dcp_content_type.h" @@ -34,10 +35,10 @@ #include "player.h" #include "ratio.h" #include "text_content.h" -#include "types.h" #include "video_content.h" #include "writer.h" #include +#include #include #include #include @@ -127,8 +128,8 @@ Hints::check_incorrect_container () int narrower_than_scope = 0; int scope = 0; for (auto i: film()->content()) { - if (i->video) { - auto const r = Ratio::nearest_from_ratio(i->video->scaled_size(film()->frame_size()).ratio()); + if (i->video && i->video->size()) { + auto const r = Ratio::nearest_from_ratio(i->video->scaled_size(film()->frame_size())->ratio()); if (r && r->id() == "239") { ++scope; } else if (r && r->id() != "239" && r->id() != "235" && r->id() != "190") { @@ -195,7 +196,7 @@ Hints::check_frame_rate () case 50: case 60: /* You almost certainly want to go to half frame rate */ - hint (String::compose(_("You are set up for a DCP at a frame rate of %1 fps. This frame rate is not supported by all projectors. You are advised to change the DCP frame rate to %2 fps."), f->video_frame_rate(), f->video_frame_rate() / 2)); + hint (String::compose(_("You are set up for a DCP at a frame rate of %1 fps. This frame rate is not supported by all projectors. It is advisable to change the DCP frame rate to %2 fps."), f->video_frame_rate(), f->video_frame_rate() / 2)); break; } } @@ -235,7 +236,7 @@ Hints::check_speed_up () } if (worst_speed_up > 25.5/24.0) { - hint (_("There is a large difference between the frame rate of your DCP and that of some of your content. This will cause your audio to play back at a much lower or higher pitch than it should. You are advised to set your DCP frame rate to one closer to your content, provided that your target projection systems support your chosen DCP rate.")); + hint (_("There is a large difference between the frame rate of your DCP and that of some of your content. This will cause your audio to play back at a much lower or higher pitch than it should. It is advisable to set your DCP frame rate to one closer to your content, provided that your target projection systems support your chosen DCP rate.")); } } @@ -245,7 +246,7 @@ void Hints::check_interop () { if (film()->interop()) { - hint (_("In general it is now advisable to make SMPTE DCPs unless you have a particular reason to use Interop. You are advised to set your DCP to use the SMPTE standard in the \"DCP\" tab.")); + hint (_("In general it is now advisable to make SMPTE DCPs unless you have a particular reason to use Interop. It is advisable to set your DCP to use the SMPTE standard in the \"DCP\" tab.")); } } @@ -259,7 +260,7 @@ Hints::check_big_font_files () for (auto j: i->text) { for (auto k: j->fonts()) { auto const p = k->file (); - if (p && boost::filesystem::file_size(p.get()) >= (MAX_FONT_FILE_SIZE - SIZE_SLACK)) { + if (p && dcp::filesystem::file_size(p.get()) >= (MAX_FONT_FILE_SIZE - SIZE_SLACK)) { big_font_files = true; } } @@ -310,7 +311,7 @@ bool Hints::check_loudness () { auto path = film()->audio_analysis_path(film()->playlist()); - if (!boost::filesystem::exists(path)) { + if (!dcp::filesystem::exists(path)) { return false; } @@ -352,7 +353,7 @@ static bool subtitle_mxf_too_big (shared_ptr asset) { - return asset && asset->file() && boost::filesystem::file_size(*asset->file()) >= (MAX_TEXT_MXF_SIZE - SIZE_SLACK); + return asset && asset->file() && dcp::filesystem::file_size(*asset->file()) >= (MAX_TEXT_MXF_SIZE - SIZE_SLACK); } @@ -369,77 +370,106 @@ Hints::check_out_of_range_markers () void -Hints::thread () -try +Hints::scan_content(shared_ptr film) { - start_of_thread ("Hints"); + auto const check_loudness_done = check_loudness(); - auto film = _film.lock (); - if (!film) { - return; - } + auto content = film->playlist()->content(); + auto iter = std::find_if(content.begin(), content.end(), [](shared_ptr content) { + auto text_iter = std::find_if(content->text.begin(), content->text.end(), [](shared_ptr text) { + return text->use(); + }); + return text_iter != content->text.end(); + }); - auto content = film->content (); + auto const have_text = iter != content.end(); - check_certificates (); - check_interop (); - check_big_font_files (); - check_few_audio_channels (); - check_upmixers (); - check_incorrect_container (); - check_unusual_container (); - check_high_j2k_bandwidth (); - check_frame_rate (); - check_4k_3d (); - check_speed_up (); - check_vob (); - check_3d_in_2d (); - auto const check_loudness_done = check_loudness (); - check_ffec_and_ffmc_in_smpte_feature (); - check_out_of_range_markers (); - check_text_languages (); - check_audio_language (); + if (check_loudness_done && !have_text) { + /* We don't need to check loudness, and we don't have any active text to check, + * so a scan of the content is pointless. + */ + return; + } - if (check_loudness_done) { + if (check_loudness_done && have_text) { emit (bind(boost::ref(Progress), _("Examining subtitles and closed captions"))); + } else if (!check_loudness_done && !have_text) { + emit (bind(boost::ref(Progress), _("Examining audio"))); } else { emit (bind(boost::ref(Progress), _("Examining audio, subtitles and closed captions"))); } auto player = make_shared(film, Image::Alignment::COMPACT); - player->set_ignore_video (); + player->set_ignore_video(); if (check_loudness_done || _disable_audio_analysis) { /* We don't need to analyse audio because we already loaded a suitable analysis */ - player->set_ignore_audio (); + player->set_ignore_audio(); + } else { + /* Send auto to the analyser to check loudness */ + player->Audio.connect(bind(&Hints::audio, this, _1, _2)); } - player->Audio.connect (bind(&Hints::audio, this, _1, _2)); - player->Text.connect (bind(&Hints::text, this, _1, _2, _3, _4)); + player->Text.connect(bind(&Hints::text, this, _1, _2, _3, _4)); struct timeval last_pulse; - gettimeofday (&last_pulse, 0); + gettimeofday(&last_pulse, 0); - _writer->write (player->get_subtitle_fonts()); + _writer->write(player->get_subtitle_fonts()); while (!player->pass()) { struct timeval now; - gettimeofday (&now, 0); + gettimeofday(&now, 0); if ((seconds(now) - seconds(last_pulse)) > 1) { if (_stop) { return; } - emit (bind (boost::ref(Pulse))); + emit(bind(boost::ref(Pulse))); last_pulse = now; } } if (!check_loudness_done) { - _analyser.finish (); + _analyser.finish(); _analyser.get().write(film->audio_analysis_path(film->playlist())); - check_loudness (); + check_loudness(); } +} +void +Hints::thread () +try +{ + start_of_thread ("Hints"); + + auto film = _film.lock (); + if (!film) { + return; + } + + auto content = film->content (); + + check_certificates (); + check_interop (); + check_big_font_files (); + check_few_audio_channels (); + check_upmixers (); + check_incorrect_container (); + check_unusual_container (); + check_high_j2k_bandwidth (); + check_frame_rate (); + check_4k_3d (); + check_speed_up (); + check_vob (); + check_3d_in_2d (); + check_ffec_and_ffmc_in_smpte_feature (); + check_out_of_range_markers (); + check_subtitle_languages(); + check_audio_language (); + check_8_or_16_audio_channels(); + + scan_content(film); + if (_long_subtitle && !_very_long_subtitle) { hint (_("At least one of your subtitle lines has more than 52 characters. It is recommended to make each line 52 characters at most in length.")); } else if (_very_long_subtitle) { @@ -451,7 +481,7 @@ try bool subs_mxf_too_big = false; auto dcp_dir = film->dir("hints") / dcpomatic::get_process_id(); - boost::filesystem::remove_all (dcp_dir); + dcp::filesystem::remove_all(dcp_dir); _writer->finish (film->dir("hints") / dcpomatic::get_process_id()); @@ -483,7 +513,7 @@ try subs_mxf_too_big = true; } } - boost::filesystem::remove_all (dcp_dir); + dcp::filesystem::remove_all(dcp_dir); emit (bind(boost::ref(Finished))); } @@ -584,7 +614,30 @@ Hints::open_subtitle (PlayerText text, DCPTimePeriod period) hint (_("At least one of your subtitles starts less than 2 frames after the previous one. It is advisable to make the gap between subtitles at least 2 frames.")); } - if (text.string.size() > 3 && !_too_many_subtitle_lines) { + struct VPos + { + public: + dcp::VAlign align; + float position; + + bool operator<(VPos const& other) const { + if (static_cast(align) != static_cast(other.align)) { + return static_cast(align) < static_cast(other.align); + } + return position < other.position; + } + }; + + /* This is rather an approximate way to count distinct lines, but I guess it will do; + * to make it better we need to take into account font metrics, and the SMPTE alignment + * debacle, and so on. + */ + std::set lines; + for (auto const& line: text.string) { + lines.insert({ line.v_align(), line.v_position() }); + } + + if (lines.size() > 3 && !_too_many_subtitle_lines) { _too_many_subtitle_lines = true; hint (_("At least one of your subtitles has more than 3 lines. It is advisable to use no more than 3 lines.")); } @@ -624,14 +677,14 @@ Hints::join () void -Hints::check_text_languages () +Hints::check_subtitle_languages() { for (auto i: film()->content()) { for (auto j: i->text) { - if (j->use() && !j->language()) { - hint (_("At least one piece of subtitle or closed caption content has no specified language. " - "It is advisable to set the language for each piece of subtitle or closed caption content " - "in the \"Content→Timed text\", \"Content→Open subtitles\" or \"Content→Closed captions\" tab.")); + if (j->use() && j->type() == TextType::OPEN_SUBTITLE && !j->language()) { + hint (_("At least one piece of subtitle content has no specified language. " + "It is advisable to set the language for each piece of subtitle content " + "in the \"Content→Timed text\" or \"Content→Open subtitles\" tab.")); return; } } @@ -666,14 +719,14 @@ Hints::check_certificates () switch (*bad) { case Config::BAD_SIGNER_UTF8_STRINGS: hint(_("The certificate chain that DCP-o-matic uses for signing DCPs and KDMs contains a small error " - "which will prevent DCPs from being validated correctly on some systems. You are advised to " + "which will prevent DCPs from being validated correctly on some systems. It is advisable to " "re-create the signing certificate chain by clicking the \"Re-make certificates and key...\" " "button in the Keys page of Preferences.")); break; case Config::BAD_SIGNER_VALIDITY_TOO_LONG: hint(_("The certificate chain that DCP-o-matic uses for signing DCPs and KDMs has a validity period " "that is too long. This will cause problems playing back DCPs on some systems. " - "You are advised to re-create the signing certificate chain by clicking the " + "It is advisable to re-create the signing certificate chain by clicking the " "\"Re-make certificates and key...\" button in the Keys page of Preferences.")); break; default: @@ -682,3 +735,13 @@ Hints::check_certificates () } } + +void +Hints::check_8_or_16_audio_channels() +{ + auto const channels = film()->audio_channels(); + if (channels != 8 && channels != 16) { + hint(String::compose(_("Your DCP has %1 audio channels, rather than 8 or 16. This may cause some distributors to raise QC errors when they check your DCP. To avoid this, set the DCP audio channels to 8 or 16."), channels)); + } +} + diff --git a/src/lib/hints.h b/src/lib/hints.h index 985fa1910..0d65edc21 100644 --- a/src/lib/hints.h +++ b/src/lib/hints.h @@ -22,7 +22,6 @@ #include "audio_analyser.h" #include "signaller.h" #include "player_text.h" -#include "types.h" #include "dcp_text_track.h" #include "dcpomatic_time.h" #include "weak_film.h" @@ -59,12 +58,14 @@ private: friend struct hint_subtitle_too_early; void thread (); + void scan_content(std::shared_ptr film); void hint (std::string h); void audio (std::shared_ptr audio, dcpomatic::DCPTime time); void text (PlayerText text, TextType type, boost::optional track, dcpomatic::DCPTimePeriod period); void closed_caption (PlayerText text, dcpomatic::DCPTimePeriod period); void open_subtitle (PlayerText text, dcpomatic::DCPTimePeriod period); + void check_certificates (); void check_interop (); void check_big_font_files (); @@ -81,8 +82,9 @@ private: bool check_loudness (); void check_ffec_and_ffmc_in_smpte_feature (); void check_out_of_range_markers (); - void check_text_languages (); + void check_subtitle_languages(); void check_audio_language (); + void check_8_or_16_audio_channels(); boost::thread _thread; /** This is used to make a partial DCP containing only the subtitles and closed captions that diff --git a/src/lib/image.cc b/src/lib/image.cc index 9aecac834..2167918f8 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -27,6 +27,7 @@ #include "compose.hpp" #include "dcpomatic_assert.h" #include "dcpomatic_socket.h" +#include "enum_indexed_vector.h" #include "exceptions.h" #include "image.h" #include "maths_util.h" @@ -236,10 +237,10 @@ Image::crop_scale_window ( } DCPOMATIC_ASSERT (yuv_to_rgb < dcp::YUVToRGB::COUNT); - int const lut[static_cast(dcp::YUVToRGB::COUNT)] = { - SWS_CS_ITU601, - SWS_CS_ITU709 - }; + EnumIndexedVector lut; + lut[dcp::YUVToRGB::REC601] = SWS_CS_ITU601; + lut[dcp::YUVToRGB::REC709] = SWS_CS_ITU709; + lut[dcp::YUVToRGB::REC2020] = SWS_CS_BT2020; /* The 3rd parameter here is: 0 -> source range MPEG (i.e. "video", 16-235) @@ -254,8 +255,8 @@ Image::crop_scale_window ( */ sws_setColorspaceDetails ( scale_context, - sws_getCoefficients (lut[static_cast(yuv_to_rgb)]), video_range == VideoRange::VIDEO ? 0 : 1, - sws_getCoefficients (lut[static_cast(yuv_to_rgb)]), out_video_range == VideoRange::VIDEO ? 0 : 1, + sws_getCoefficients(lut[yuv_to_rgb]), video_range == VideoRange::VIDEO ? 0 : 1, + sws_getCoefficients(lut[yuv_to_rgb]), out_video_range == VideoRange::VIDEO ? 0 : 1, 0, 1 << 16, 1 << 16 ); @@ -327,7 +328,7 @@ Image::convert_pixel_format (dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_format, /** @param out_size Size to scale to. * @param yuv_to_rgb YUVToRGB transform transform to use, if required. * @param out_format Output pixel format. - * @param out_aligment Output alignment. + * @param out_alignment Output alignment. * @param fast Try to be fast at the possible expense of quality; at present this means using * fast bilinear rather than bicubic scaling. */ @@ -338,6 +339,10 @@ Image::scale (dcp::Size out_size, dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_fo the input image alignment is not PADDED. */ DCPOMATIC_ASSERT (alignment() == Alignment::PADDED); + DCPOMATIC_ASSERT(size().width > 0); + DCPOMATIC_ASSERT(size().height > 0); + DCPOMATIC_ASSERT(out_size.width > 0); + DCPOMATIC_ASSERT(out_size.height > 0); auto scaled = make_shared(out_format, out_size, out_alignment); auto scale_context = sws_getContext ( @@ -346,11 +351,13 @@ Image::scale (dcp::Size out_size, dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_fo (fast ? SWS_FAST_BILINEAR : SWS_BICUBIC) | SWS_ACCURATE_RND, 0, 0, 0 ); + DCPOMATIC_ASSERT(scale_context); + DCPOMATIC_ASSERT (yuv_to_rgb < dcp::YUVToRGB::COUNT); - int const lut[static_cast(dcp::YUVToRGB::COUNT)] = { - SWS_CS_ITU601, - SWS_CS_ITU709 - }; + EnumIndexedVector lut; + lut[dcp::YUVToRGB::REC601] = SWS_CS_ITU601; + lut[dcp::YUVToRGB::REC709] = SWS_CS_ITU709; + lut[dcp::YUVToRGB::REC2020] = SWS_CS_BT2020; /* The 3rd parameter here is: 0 -> source range MPEG (i.e. "video", 16-235) @@ -365,8 +372,8 @@ Image::scale (dcp::Size out_size, dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_fo */ sws_setColorspaceDetails ( scale_context, - sws_getCoefficients (lut[static_cast(yuv_to_rgb)]), 0, - sws_getCoefficients (lut[static_cast(yuv_to_rgb)]), 0, + sws_getCoefficients(lut[yuv_to_rgb]), 0, + sws_getCoefficients(lut[yuv_to_rgb]), 0, 0, 1 << 16, 1 << 16 ); @@ -618,7 +625,7 @@ Image::make_black () void Image::make_transparent () { - if (_pixel_format != AV_PIX_FMT_BGRA && _pixel_format != AV_PIX_FMT_RGBA) { + if (_pixel_format != AV_PIX_FMT_BGRA && _pixel_format != AV_PIX_FMT_RGBA && _pixel_format != AV_PIX_FMT_RGBA64BE) { throw PixelFormatError ("make_transparent()", _pixel_format); } @@ -626,16 +633,333 @@ Image::make_transparent () } +struct TargetParams +{ + int start_x; + int start_y; + dcp::Size size; + uint8_t* const* data; + int const* stride; + int bpp; + + uint8_t* line_pointer(int y) const { + return data[0] + y * stride[0] + start_x * bpp; + } +}; + + +/** Parameters of the other image (the one being blended onto the target) when target and other are RGB */ +struct OtherRGBParams +{ + int start_x; + int start_y; + dcp::Size size; + uint8_t* const* data; + int const* stride; + int bpp; + + uint8_t* line_pointer(int y) const { + return data[0] + y * stride[0]; + } + + float alpha_divisor() const { + return pow(2, bpp * 2) - 1; + } +}; + + +/** Parameters of the other image (the one being blended onto the target) when target and other are YUV */ +struct OtherYUVParams +{ + int start_x; + int start_y; + dcp::Size size; + uint8_t* const* data; + int const* stride; + + uint8_t* const* alpha_data; + int const* alpha_stride; + int alpha_bpp; +}; + + +template +void +alpha_blend_onto_rgb24(TargetParams const& target, OtherRGBParams const& other, int red, int blue, std::function get, int value_divisor) +{ + /* Going onto RGB24. First byte is red, second green, third blue */ + auto const alpha_divisor = other.alpha_divisor(); + for (int ty = target.start_y, oy = other.start_y; ty < target.size.height && oy < other.size.height; ++ty, ++oy) { + auto tp = target.line_pointer(ty); + auto op = reinterpret_cast(other.line_pointer(oy)); + for (int tx = target.start_x, ox = other.start_x; tx < target.size.width && ox < other.size.width; ++tx, ++ox) { + float const alpha = get(op + 3) / alpha_divisor; + tp[0] = (get(op + red) / value_divisor) * alpha + tp[0] * (1 - alpha); + tp[1] = (get(op + 1) / value_divisor) * alpha + tp[1] * (1 - alpha); + tp[2] = (get(op + blue) / value_divisor) * alpha + tp[2] * (1 - alpha); + + tp += target.bpp; + op += other.bpp / sizeof(OtherType); + } + } +} + + +template +void +alpha_blend_onto_bgra(TargetParams const& target, OtherRGBParams const& other, int red, int blue, std::function get, int value_divisor) +{ + auto const alpha_divisor = other.alpha_divisor(); + for (int ty = target.start_y, oy = other.start_y; ty < target.size.height && oy < other.size.height; ++ty, ++oy) { + auto tp = target.line_pointer(ty); + auto op = reinterpret_cast(other.line_pointer(oy)); + for (int tx = target.start_x, ox = other.start_x; tx < target.size.width && ox < other.size.width; ++tx, ++ox) { + float const alpha = get(op + 3) / alpha_divisor; + tp[0] = (get(op + blue) / value_divisor) * alpha + tp[0] * (1 - alpha); + tp[1] = (get(op + 1) / value_divisor) * alpha + tp[1] * (1 - alpha); + tp[2] = (get(op + red) / value_divisor) * alpha + tp[2] * (1 - alpha); + tp[3] = (get(op + 3) / value_divisor) * alpha + tp[3] * (1 - alpha); + + tp += target.bpp; + op += other.bpp / sizeof(OtherType); + } + } +} + + +template +void +alpha_blend_onto_rgba(TargetParams const& target, OtherRGBParams const& other, int red, int blue, std::function get, int value_divisor) +{ + auto const alpha_divisor = other.alpha_divisor(); + for (int ty = target.start_y, oy = other.start_y; ty < target.size.height && oy < other.size.height; ++ty, ++oy) { + auto tp = target.line_pointer(ty); + auto op = reinterpret_cast(other.line_pointer(oy)); + for (int tx = target.start_x, ox = other.start_x; tx < target.size.width && ox < other.size.width; ++tx, ++ox) { + float const alpha = get(op + 3) / alpha_divisor; + tp[0] = (get(op + red) / value_divisor) * alpha + tp[0] * (1 - alpha); + tp[1] = (get(op + 1) / value_divisor) * alpha + tp[1] * (1 - alpha); + tp[2] = (get(op + blue) / value_divisor) * alpha + tp[2] * (1 - alpha); + tp[3] = (get(op + 3) / value_divisor) * alpha + tp[3] * (1 - alpha); + + tp += target.bpp; + op += other.bpp / sizeof(OtherType); + } + } +} + + +template +void +alpha_blend_onto_rgb48le(TargetParams const& target, OtherRGBParams const& other, int red, int blue, std::function get, int value_scale) +{ + auto const alpha_divisor = other.alpha_divisor(); + for (int ty = target.start_y, oy = other.start_y; ty < target.size.height && oy < other.size.height; ++ty, ++oy) { + auto tp = reinterpret_cast(target.line_pointer(ty)); + auto op = reinterpret_cast(other.line_pointer(oy)); + for (int tx = target.start_x, ox = other.start_x; tx < target.size.width && ox < other.size.width; ++tx, ++ox) { + float const alpha = get(op + 3) / alpha_divisor; + tp[0] = get(op + red) * value_scale * alpha + tp[0] * (1 - alpha); + tp[1] = get(op + 1) * value_scale * alpha + tp[1] * (1 - alpha); + tp[2] = get(op + blue) * value_scale * alpha + tp[2] * (1 - alpha); + + tp += target.bpp / 2; + op += other.bpp / sizeof(OtherType); + } + } +} + + +template +void +alpha_blend_onto_xyz12le(TargetParams const& target, OtherRGBParams const& other, int red, int blue, std::function get, int value_divisor) +{ + auto const alpha_divisor = other.alpha_divisor(); + auto conv = dcp::ColourConversion::srgb_to_xyz(); + double fast_matrix[9]; + dcp::combined_rgb_to_xyz(conv, fast_matrix); + auto lut_in = conv.in()->double_lut(0, 1, 8, false); + auto lut_out = conv.out()->int_lut(0, 1, 16, true, 65535); + for (int ty = target.start_y, oy = other.start_y; ty < target.size.height && oy < other.size.height; ++ty, ++oy) { + auto tp = reinterpret_cast(target.data[0] + ty * target.stride[0] + target.start_x * target.bpp); + auto op = reinterpret_cast(other.data[0] + oy * other.stride[0]); + for (int tx = target.start_x, ox = other.start_x; tx < target.size.width && ox < other.size.width; ++tx, ++ox) { + float const alpha = get(op + 3) / alpha_divisor; + + /* Convert sRGB to XYZ; op is BGRA. First, input gamma LUT */ + double const r = lut_in[get(op + red) / value_divisor]; + double const g = lut_in[get(op + 1) / value_divisor]; + double const b = lut_in[get(op + blue) / value_divisor]; + + /* RGB to XYZ, including Bradford transform and DCI companding */ + double const x = max(0.0, min(1.0, r * fast_matrix[0] + g * fast_matrix[1] + b * fast_matrix[2])); + double const y = max(0.0, min(1.0, r * fast_matrix[3] + g * fast_matrix[4] + b * fast_matrix[5])); + double const z = max(0.0, min(1.0, r * fast_matrix[6] + g * fast_matrix[7] + b * fast_matrix[8])); + + /* Out gamma LUT and blend */ + tp[0] = lut_out[lrint(x * 65535)] * alpha + tp[0] * (1 - alpha); + tp[1] = lut_out[lrint(y * 65535)] * alpha + tp[1] * (1 - alpha); + tp[2] = lut_out[lrint(z * 65535)] * alpha + tp[2] * (1 - alpha); + + tp += target.bpp / 2; + op += other.bpp / sizeof(OtherType); + } + } +} + + +static +void +alpha_blend_onto_yuv420p(TargetParams const& target, OtherYUVParams const& other, std::function get_alpha) +{ + auto const ts = target.size; + auto const os = other.size; + for (int ty = target.start_y, oy = other.start_y; ty < ts.height && oy < os.height; ++ty, ++oy) { + int const hty = ty / 2; + int const hoy = oy / 2; + uint8_t* tY = target.data[0] + (ty * target.stride[0]) + target.start_x; + uint8_t* tU = target.data[1] + (hty * target.stride[1]) + target.start_x / 2; + uint8_t* tV = target.data[2] + (hty * target.stride[2]) + target.start_x / 2; + uint8_t* oY = other.data[0] + (oy * other.stride[0]) + other.start_x; + uint8_t* oU = other.data[1] + (hoy * other.stride[1]) + other.start_x / 2; + uint8_t* oV = other.data[2] + (hoy * other.stride[2]) + other.start_x / 2; + uint8_t* alpha = other.alpha_data[0] + (oy * other.alpha_stride[0]) + other.start_x * other.alpha_bpp; + for (int tx = target.start_x, ox = other.start_x; tx < ts.width && ox < os.width; ++tx, ++ox) { + float const a = get_alpha(alpha); + *tY = *oY * a + *tY * (1 - a); + *tU = *oU * a + *tU * (1 - a); + *tV = *oV * a + *tV * (1 - a); + ++tY; + ++oY; + if (tx % 2) { + ++tU; + ++tV; + } + if (ox % 2) { + ++oU; + ++oV; + } + alpha += other.alpha_bpp; + } + } +} + + +static +void +alpha_blend_onto_yuv420p10(TargetParams const& target, OtherYUVParams const& other, std::function get_alpha) +{ + auto const ts = target.size; + auto const os = other.size; + for (int ty = target.start_y, oy = other.start_y; ty < ts.height && oy < os.height; ++ty, ++oy) { + int const hty = ty / 2; + int const hoy = oy / 2; + uint16_t* tY = reinterpret_cast(target.data[0] + (ty * target.stride[0])) + target.start_x; + uint16_t* tU = reinterpret_cast(target.data[1] + (hty * target.stride[1])) + target.start_x / 2; + uint16_t* tV = reinterpret_cast(target.data[2] + (hty * target.stride[2])) + target.start_x / 2; + uint16_t* oY = reinterpret_cast(other.data[0] + (oy * other.stride[0])) + other.start_x; + uint16_t* oU = reinterpret_cast(other.data[1] + (hoy * other.stride[1])) + other.start_x / 2; + uint16_t* oV = reinterpret_cast(other.data[2] + (hoy * other.stride[2])) + other.start_x / 2; + uint8_t* alpha = other.alpha_data[0] + (oy * other.alpha_stride[0]) + other.start_x * other.alpha_bpp; + for (int tx = target.start_x, ox = other.start_x; tx < ts.width && ox < os.width; ++tx, ++ox) { + float const a = get_alpha(alpha); + *tY = *oY * a + *tY * (1 - a); + *tU = *oU * a + *tU * (1 - a); + *tV = *oV * a + *tV * (1 - a); + ++tY; + ++oY; + if (tx % 2) { + ++tU; + ++tV; + } + if (ox % 2) { + ++oU; + ++oV; + } + alpha += other.alpha_bpp; + } + } +} + + +static +void +alpha_blend_onto_yuv422p9or10le(TargetParams const& target, OtherYUVParams const& other, std::function get_alpha) +{ + auto const ts = target.size; + auto const os = other.size; + for (int ty = target.start_y, oy = other.start_y; ty < ts.height && oy < os.height; ++ty, ++oy) { + uint16_t* tY = reinterpret_cast(target.data[0] + (ty * target.stride[0])) + target.start_x; + uint16_t* tU = reinterpret_cast(target.data[1] + (ty * target.stride[1])) + target.start_x / 2; + uint16_t* tV = reinterpret_cast(target.data[2] + (ty * target.stride[2])) + target.start_x / 2; + uint16_t* oY = reinterpret_cast(other.data[0] + (oy * other.stride[0])) + other.start_x; + uint16_t* oU = reinterpret_cast(other.data[1] + (oy * other.stride[1])) + other.start_x / 2; + uint16_t* oV = reinterpret_cast(other.data[2] + (oy * other.stride[2])) + other.start_x / 2; + uint8_t* alpha = other.alpha_data[0] + (oy * other.alpha_stride[0]) + other.start_x * other.alpha_bpp; + for (int tx = target.start_x, ox = other.start_x; tx < ts.width && ox < os.width; ++tx, ++ox) { + float const a = get_alpha(alpha); + *tY = *oY * a + *tY * (1 - a); + *tU = *oU * a + *tU * (1 - a); + *tV = *oV * a + *tV * (1 - a); + ++tY; + ++oY; + if (tx % 2) { + ++tU; + ++tV; + } + if (ox % 2) { + ++oU; + ++oV; + } + alpha += other.alpha_bpp; + } + } +} + + +static +void +alpha_blend_onto_yuv444p9or10le(TargetParams const& target, OtherYUVParams const& other, std::function get_alpha) +{ + auto const ts = target.size; + auto const os = other.size; + for (int ty = target.start_y, oy = other.start_y; ty < ts.height && oy < os.height; ++ty, ++oy) { + uint16_t* tY = reinterpret_cast(target.data[0] + (ty * target.stride[0])) + target.start_x; + uint16_t* tU = reinterpret_cast(target.data[1] + (ty * target.stride[1])) + target.start_x; + uint16_t* tV = reinterpret_cast(target.data[2] + (ty * target.stride[2])) + target.start_x; + uint16_t* oY = reinterpret_cast(other.data[0] + (oy * other.stride[0])) + other.start_x; + uint16_t* oU = reinterpret_cast(other.data[1] + (oy * other.stride[1])) + other.start_x; + uint16_t* oV = reinterpret_cast(other.data[2] + (oy * other.stride[2])) + other.start_x; + uint8_t* alpha = other.alpha_data[0] + (oy * other.alpha_stride[0]) + other.start_x * other.alpha_bpp; + for (int tx = target.start_x, ox = other.start_x; tx < ts.width && ox < os.width; ++tx, ++ox) { + float const a = get_alpha(alpha); + *tY = *oY * a + *tY * (1 - a); + *tU = *oU * a + *tU * (1 - a); + *tV = *oV * a + *tV * (1 - a); + ++tY; + ++oY; + ++tU; + ++tV; + ++oU; + ++oV; + alpha += other.alpha_bpp; + } + } +} + + void Image::alpha_blend (shared_ptr other, Position position) { - /* We're blending RGBA or BGRA images */ - DCPOMATIC_ASSERT (other->pixel_format() == AV_PIX_FMT_BGRA || other->pixel_format() == AV_PIX_FMT_RGBA); + DCPOMATIC_ASSERT( + other->pixel_format() == AV_PIX_FMT_BGRA || + other->pixel_format() == AV_PIX_FMT_RGBA || + other->pixel_format() == AV_PIX_FMT_RGBA64BE + ); + int const blue = other->pixel_format() == AV_PIX_FMT_BGRA ? 0 : 2; int const red = other->pixel_format() == AV_PIX_FMT_BGRA ? 2 : 0; - int const other_bpp = 4; - int start_tx = position.x; int start_ox = 0; @@ -652,218 +976,147 @@ Image::alpha_blend (shared_ptr other, Position position) start_ty = 0; } + TargetParams target_params = { + start_tx, + start_ty, + size(), + data(), + stride(), + 0 + }; + + OtherRGBParams other_rgb_params = { + start_ox, + start_oy, + other->size(), + other->data(), + other->stride(), + other->pixel_format() == AV_PIX_FMT_RGBA64BE ? 8 : 4 + }; + + OtherYUVParams other_yuv_params = { + start_ox, + start_oy, + other->size(), + other->data(), + other->stride(), + nullptr, + nullptr, + other->pixel_format() == AV_PIX_FMT_RGBA64BE ? 8 : 4 + }; + + auto byteswap = [](uint16_t* p) { + return (*p >> 8) | ((*p & 0xff) << 8); + }; + + auto pass = [](uint8_t* p) { + return *p; + }; + + auto get_alpha_64be = [](uint8_t* p) { + return ((static_cast(p[6]) << 8) | p[7]) / 65535.0f; + }; + + auto get_alpha_byte = [](uint8_t* p) { + return p[3] / 255.0f; + }; + switch (_pixel_format) { case AV_PIX_FMT_RGB24: - { - /* Going onto RGB24. First byte is red, second green, third blue */ - int const this_bpp = 3; - for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) { - uint8_t* tp = data()[0] + ty * stride()[0] + start_tx * this_bpp; - uint8_t* op = other->data()[0] + oy * other->stride()[0]; - for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) { - float const alpha = float (op[3]) / 255; - tp[0] = op[red] * alpha + tp[0] * (1 - alpha); - tp[1] = op[1] * alpha + tp[1] * (1 - alpha); - tp[2] = op[blue] * alpha + tp[2] * (1 - alpha); - - tp += this_bpp; - op += other_bpp; - } + target_params.bpp = 3; + if (other->pixel_format() == AV_PIX_FMT_RGBA64BE) { + alpha_blend_onto_rgb24(target_params, other_rgb_params, red, blue, byteswap, 256); + } else { + alpha_blend_onto_rgb24(target_params, other_rgb_params, red, blue, pass, 1); } break; - } case AV_PIX_FMT_BGRA: - { - int const this_bpp = 4; - for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) { - uint8_t* tp = data()[0] + ty * stride()[0] + start_tx * this_bpp; - uint8_t* op = other->data()[0] + oy * other->stride()[0]; - for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) { - float const alpha = float (op[3]) / 255; - tp[0] = op[blue] * alpha + tp[0] * (1 - alpha); - tp[1] = op[1] * alpha + tp[1] * (1 - alpha); - tp[2] = op[red] * alpha + tp[2] * (1 - alpha); - tp[3] = op[3] * alpha + tp[3] * (1 - alpha); - - tp += this_bpp; - op += other_bpp; - } + target_params.bpp = 4; + if (other->pixel_format() == AV_PIX_FMT_RGBA64BE) { + alpha_blend_onto_bgra(target_params, other_rgb_params, red, blue, byteswap, 256); + } else { + alpha_blend_onto_bgra(target_params, other_rgb_params, red, blue, pass, 1); } break; - } case AV_PIX_FMT_RGBA: - { - int const this_bpp = 4; - for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) { - uint8_t* tp = data()[0] + ty * stride()[0] + start_tx * this_bpp; - uint8_t* op = other->data()[0] + oy * other->stride()[0]; - for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) { - float const alpha = float (op[3]) / 255; - tp[0] = op[red] * alpha + tp[0] * (1 - alpha); - tp[1] = op[1] * alpha + tp[1] * (1 - alpha); - tp[2] = op[blue] * alpha + tp[2] * (1 - alpha); - tp[3] = op[3] * alpha + tp[3] * (1 - alpha); - - tp += this_bpp; - op += other_bpp; - } + target_params.bpp = 4; + if (other->pixel_format() == AV_PIX_FMT_RGBA64BE) { + alpha_blend_onto_rgba(target_params, other_rgb_params, red, blue, byteswap, 256); + } else { + alpha_blend_onto_rgba(target_params, other_rgb_params, red, blue, pass, 1); } break; - } case AV_PIX_FMT_RGB48LE: - { - int const this_bpp = 6; - for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) { - uint8_t* tp = data()[0] + ty * stride()[0] + start_tx * this_bpp; - uint8_t* op = other->data()[0] + oy * other->stride()[0]; - for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) { - float const alpha = float (op[3]) / 255; - /* Blend high bytes */ - tp[1] = op[red] * alpha + tp[1] * (1 - alpha); - tp[3] = op[1] * alpha + tp[3] * (1 - alpha); - tp[5] = op[blue] * alpha + tp[5] * (1 - alpha); - - tp += this_bpp; - op += other_bpp; - } + target_params.bpp = 6; + if (other->pixel_format() == AV_PIX_FMT_RGBA64BE) { + alpha_blend_onto_rgb48le(target_params, other_rgb_params, red, blue, byteswap, 1); + } else { + alpha_blend_onto_rgb48le(target_params, other_rgb_params, red, blue, pass, 256); } break; - } case AV_PIX_FMT_XYZ12LE: - { - auto conv = dcp::ColourConversion::srgb_to_xyz(); - double fast_matrix[9]; - dcp::combined_rgb_to_xyz (conv, fast_matrix); - auto lut_in = conv.in()->lut(0, 1, 8, false); - auto lut_out = conv.out()->lut(0, 1, 16, true); - int const this_bpp = 6; - for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) { - uint16_t* tp = reinterpret_cast (data()[0] + ty * stride()[0] + start_tx * this_bpp); - uint8_t* op = other->data()[0] + oy * other->stride()[0]; - for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) { - float const alpha = float (op[3]) / 255; - - /* Convert sRGB to XYZ; op is BGRA. First, input gamma LUT */ - double const r = lut_in[op[red]]; - double const g = lut_in[op[1]]; - double const b = lut_in[op[blue]]; - - /* RGB to XYZ, including Bradford transform and DCI companding */ - double const x = max(0.0, min(1.0, r * fast_matrix[0] + g * fast_matrix[1] + b * fast_matrix[2])); - double const y = max(0.0, min(1.0, r * fast_matrix[3] + g * fast_matrix[4] + b * fast_matrix[5])); - double const z = max(0.0, min(1.0, r * fast_matrix[6] + g * fast_matrix[7] + b * fast_matrix[8])); - - /* Out gamma LUT and blend */ - tp[0] = lrint(lut_out[lrint(x * 65535)] * 65535) * alpha + tp[0] * (1 - alpha); - tp[1] = lrint(lut_out[lrint(y * 65535)] * 65535) * alpha + tp[1] * (1 - alpha); - tp[2] = lrint(lut_out[lrint(z * 65535)] * 65535) * alpha + tp[2] * (1 - alpha); - - tp += this_bpp / 2; - op += other_bpp; - } + target_params.bpp = 6; + if (other->pixel_format() == AV_PIX_FMT_RGBA64BE) { + alpha_blend_onto_xyz12le(target_params, other_rgb_params, red, blue, byteswap, 256); + } else { + alpha_blend_onto_xyz12le(target_params, other_rgb_params, red, blue, pass, 1); } break; - } case AV_PIX_FMT_YUV420P: { auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, Alignment::COMPACT, false); - dcp::Size const ts = size(); - dcp::Size const os = yuv->size(); - for (int ty = start_ty, oy = start_oy; ty < ts.height && oy < os.height; ++ty, ++oy) { - int const hty = ty / 2; - int const hoy = oy / 2; - uint8_t* tY = data()[0] + (ty * stride()[0]) + start_tx; - uint8_t* tU = data()[1] + (hty * stride()[1]) + start_tx / 2; - uint8_t* tV = data()[2] + (hty * stride()[2]) + start_tx / 2; - uint8_t* oY = yuv->data()[0] + (oy * yuv->stride()[0]) + start_ox; - uint8_t* oU = yuv->data()[1] + (hoy * yuv->stride()[1]) + start_ox / 2; - uint8_t* oV = yuv->data()[2] + (hoy * yuv->stride()[2]) + start_ox / 2; - uint8_t* alpha = other->data()[0] + (oy * other->stride()[0]) + start_ox * 4; - for (int tx = start_tx, ox = start_ox; tx < ts.width && ox < os.width; ++tx, ++ox) { - float const a = float(alpha[3]) / 255; - *tY = *oY * a + *tY * (1 - a); - *tU = *oU * a + *tU * (1 - a); - *tV = *oV * a + *tV * (1 - a); - ++tY; - ++oY; - if (tx % 2) { - ++tU; - ++tV; - } - if (ox % 2) { - ++oU; - ++oV; - } - alpha += 4; - } + other_yuv_params.data = yuv->data(); + other_yuv_params.stride = yuv->stride(); + other_yuv_params.alpha_data = other->data(); + other_yuv_params.alpha_stride = other->stride(); + if (other->pixel_format() == AV_PIX_FMT_RGBA64BE) { + alpha_blend_onto_yuv420p(target_params, other_yuv_params, get_alpha_64be); + } else { + alpha_blend_onto_yuv420p(target_params, other_yuv_params, get_alpha_byte); } break; } case AV_PIX_FMT_YUV420P10: { auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, Alignment::COMPACT, false); - dcp::Size const ts = size(); - dcp::Size const os = yuv->size(); - for (int ty = start_ty, oy = start_oy; ty < ts.height && oy < os.height; ++ty, ++oy) { - int const hty = ty / 2; - int const hoy = oy / 2; - uint16_t* tY = ((uint16_t *) (data()[0] + (ty * stride()[0]))) + start_tx; - uint16_t* tU = ((uint16_t *) (data()[1] + (hty * stride()[1]))) + start_tx / 2; - uint16_t* tV = ((uint16_t *) (data()[2] + (hty * stride()[2]))) + start_tx / 2; - uint16_t* oY = ((uint16_t *) (yuv->data()[0] + (oy * yuv->stride()[0]))) + start_ox; - uint16_t* oU = ((uint16_t *) (yuv->data()[1] + (hoy * yuv->stride()[1]))) + start_ox / 2; - uint16_t* oV = ((uint16_t *) (yuv->data()[2] + (hoy * yuv->stride()[2]))) + start_ox / 2; - uint8_t* alpha = other->data()[0] + (oy * other->stride()[0]) + start_ox * 4; - for (int tx = start_tx, ox = start_ox; tx < ts.width && ox < os.width; ++tx, ++ox) { - float const a = float(alpha[3]) / 255; - *tY = *oY * a + *tY * (1 - a); - *tU = *oU * a + *tU * (1 - a); - *tV = *oV * a + *tV * (1 - a); - ++tY; - ++oY; - if (tx % 2) { - ++tU; - ++tV; - } - if (ox % 2) { - ++oU; - ++oV; - } - alpha += 4; - } + other_yuv_params.data = yuv->data(); + other_yuv_params.stride = yuv->stride(); + other_yuv_params.alpha_data = other->data(); + other_yuv_params.alpha_stride = other->stride(); + if (other->pixel_format() == AV_PIX_FMT_RGBA64BE) { + alpha_blend_onto_yuv420p10(target_params, other_yuv_params, get_alpha_64be); + } else { + alpha_blend_onto_yuv420p10(target_params, other_yuv_params, get_alpha_byte); } break; } + case AV_PIX_FMT_YUV422P9LE: case AV_PIX_FMT_YUV422P10LE: { auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, Alignment::COMPACT, false); - dcp::Size const ts = size(); - dcp::Size const os = yuv->size(); - for (int ty = start_ty, oy = start_oy; ty < ts.height && oy < os.height; ++ty, ++oy) { - uint16_t* tY = ((uint16_t *) (data()[0] + (ty * stride()[0]))) + start_tx; - uint16_t* tU = ((uint16_t *) (data()[1] + (ty * stride()[1]))) + start_tx / 2; - uint16_t* tV = ((uint16_t *) (data()[2] + (ty * stride()[2]))) + start_tx / 2; - uint16_t* oY = ((uint16_t *) (yuv->data()[0] + (oy * yuv->stride()[0]))) + start_ox; - uint16_t* oU = ((uint16_t *) (yuv->data()[1] + (oy * yuv->stride()[1]))) + start_ox / 2; - uint16_t* oV = ((uint16_t *) (yuv->data()[2] + (oy * yuv->stride()[2]))) + start_ox / 2; - uint8_t* alpha = other->data()[0] + (oy * other->stride()[0]) + start_ox * 4; - for (int tx = start_tx, ox = start_ox; tx < ts.width && ox < os.width; ++tx, ++ox) { - float const a = float(alpha[3]) / 255; - *tY = *oY * a + *tY * (1 - a); - *tU = *oU * a + *tU * (1 - a); - *tV = *oV * a + *tV * (1 - a); - ++tY; - ++oY; - if (tx % 2) { - ++tU; - ++tV; - } - if (ox % 2) { - ++oU; - ++oV; - } - alpha += 4; - } + other_yuv_params.data = yuv->data(); + other_yuv_params.stride = yuv->stride(); + other_yuv_params.alpha_data = other->data(); + other_yuv_params.alpha_stride = other->stride(); + if (other->pixel_format() == AV_PIX_FMT_RGBA64BE) { + alpha_blend_onto_yuv422p9or10le(target_params, other_yuv_params, get_alpha_64be); + } else { + alpha_blend_onto_yuv422p9or10le(target_params, other_yuv_params, get_alpha_byte); + } + break; + } + case AV_PIX_FMT_YUV444P9LE: + case AV_PIX_FMT_YUV444P10LE: + { + auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, Alignment::COMPACT, false); + other_yuv_params.data = yuv->data(); + other_yuv_params.stride = yuv->stride(); + other_yuv_params.alpha_data = other->data(); + other_yuv_params.alpha_stride = other->stride(); + if (other->pixel_format() == AV_PIX_FMT_RGBA64BE) { + alpha_blend_onto_yuv444p9or10le(target_params, other_yuv_params, get_alpha_64be); + } else { + alpha_blend_onto_yuv444p9or10le(target_params, other_yuv_params, get_alpha_byte); } break; } diff --git a/src/lib/image.h b/src/lib/image.h index 73d08b902..0237b3d1b 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -25,18 +25,22 @@ #ifndef DCPOMATIC_IMAGE_H #define DCPOMATIC_IMAGE_H + +#include "crop.h" #include "position.h" #include "position_image.h" -#include "types.h" +#include "video_range.h" extern "C" { #include } #include #include + struct AVFrame; class Socket; + class Image : public std::enable_shared_from_this { public: diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc index d8f482a3b..1a92c944e 100644 --- a/src/lib/image_content.cc +++ b/src/lib/image_content.cc @@ -27,8 +27,10 @@ #include "image_examiner.h" #include "image_filename_sorter.h" #include "job.h" +#include "util.h" #include "video_content.h" #include +#include #include #include @@ -48,7 +50,7 @@ ImageContent::ImageContent (boost::filesystem::path p) { video = make_shared(this); - if (boost::filesystem::is_regular_file (p) && valid_image_file (p)) { + if (dcp::filesystem::is_regular_file(p) && valid_image_file(p)) { add_path (p); } else { _path_to_scan = p; @@ -115,8 +117,8 @@ ImageContent::examine (shared_ptr film, shared_ptr job) job->sub (_("Scanning image files")); vector paths; int n = 0; - for (auto i: boost::filesystem::directory_iterator(*_path_to_scan)) { - if (boost::filesystem::is_regular_file(i.path()) && valid_image_file (i.path())) { + for (auto i: dcp::filesystem::directory_iterator(*_path_to_scan)) { + if (dcp::filesystem::is_regular_file(i.path()) && valid_image_file(i.path())) { paths.push_back (i.path()); } ++n; @@ -136,7 +138,7 @@ ImageContent::examine (shared_ptr film, shared_ptr job) Content::examine (film, job); auto examiner = make_shared(film, shared_from_this(), job); - video->take_from_examiner (examiner); + video->take_from_examiner(film, examiner); set_default_colour_conversion (); } diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc index 59dc4e873..ce5c8757f 100644 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -27,6 +27,7 @@ #include "image_content.h" #include "image_decoder.h" #include "j2k_image_proxy.h" +#include "util.h" #include "video_content.h" #include "video_decoder.h" #include @@ -72,7 +73,9 @@ ImageDecoder::pass () /* We can't extract image size from a JPEG2000 codestream without decoding it, so pass in the image content's size here. */ - _image = make_shared(path, _image_content->video->size(), pf); + auto size = _image_content->video->size(); + DCPOMATIC_ASSERT(size); + _image = make_shared(path, *size, pf); } else { _image = make_shared(path); } diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h index 067a24720..41924735c 100644 --- a/src/lib/image_decoder.h +++ b/src/lib/image_decoder.h @@ -20,6 +20,7 @@ #include "decoder.h" +#include "types.h" class ImageContent; diff --git a/src/lib/image_examiner.cc b/src/lib/image_examiner.cc index 6e4dea7b7..15a0b043d 100644 --- a/src/lib/image_examiner.cc +++ b/src/lib/image_examiner.cc @@ -31,6 +31,7 @@ #include "job.h" #include #include +#include #include #include @@ -50,7 +51,7 @@ ImageExaminer::ImageExaminer (shared_ptr film, shared_ptrpath(0); if (valid_j2k_file (path)) { - auto size = boost::filesystem::file_size (path); + auto size = dcp::filesystem::file_size(path); dcp::File f(path, "rb"); if (!f) { throw FileError ("Could not open file for reading", path); @@ -76,10 +77,10 @@ ImageExaminer::ImageExaminer (shared_ptr film, shared_ptr ImageExaminer::video_size () const { - return _video_size.get (); + return _video_size; } diff --git a/src/lib/image_examiner.h b/src/lib/image_examiner.h index 53fab327e..54fca7ed1 100644 --- a/src/lib/image_examiner.h +++ b/src/lib/image_examiner.h @@ -31,7 +31,7 @@ public: return true; } boost::optional video_frame_rate () const override; - dcp::Size video_size () const override; + boost::optional video_size() const override; Frame video_length () const override { return _video_length; } diff --git a/src/lib/image_filename_sorter.cc b/src/lib/image_filename_sorter.cc index ab0d298fc..f0370bdcb 100644 --- a/src/lib/image_filename_sorter.cc +++ b/src/lib/image_filename_sorter.cc @@ -54,7 +54,7 @@ string ImageFilenameSorter::extract_numbers (boost::filesystem::path p) { string numbers; - auto const ps = p.leaf().string(); + auto const ps = p.filename().string(); for (size_t i = 0; i < ps.size(); ++i) { if (isdigit (ps[i])) { numbers += ps[i]; diff --git a/src/lib/image_store.cc b/src/lib/image_store.cc new file mode 100644 index 000000000..6c40e34f8 --- /dev/null +++ b/src/lib/image_store.cc @@ -0,0 +1,66 @@ +/* + Copyright (C) 2017-2018 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "image.h" +#include "image_store.h" +extern "C" { +#include +} + + +using std::shared_ptr; + + +void +ImageStore::buffer_free(void* opaque, uint8_t* data) +{ + reinterpret_cast(opaque)->buffer_free2(data); +} + + +void +ImageStore::buffer_free2(uint8_t* data) +{ + boost::mutex::scoped_lock lm(_mutex); + auto iter = _images.find(data); + if (iter != _images.end()) { + iter->second.second--; + if (iter->second.second == 0) { + _images.erase(data); + } + } +} + + +AVBufferRef* +ImageStore::create_buffer(shared_ptr image, int component) +{ + boost::mutex::scoped_lock lm(_mutex); + auto key = image->data()[component]; + auto iter = _images.find(key); + if (iter != _images.end()) { + iter->second.second++; + } else { + _images[key] = { image, 1 }; + } + + return av_buffer_create(image->data()[component], image->stride()[component] * image->size().height, &buffer_free, this, 0); +} diff --git a/src/lib/image_store.h b/src/lib/image_store.h new file mode 100644 index 000000000..365e7e4f6 --- /dev/null +++ b/src/lib/image_store.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2017-2018 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include +#include + + +struct AVBufferRef; +class Image; + + +/** Store of shared_ptr to keep them alive whilst raw pointers into + * their data have been passed to FFmpeg. The second part of the pair is + * a count of how many copies of the same key must be kept. + */ +class ImageStore +{ +public: + ImageStore() {} + + ImageStore(ImageStore const&) = delete; + ImageStore& operator=(ImageStore const&) = delete; + + AVBufferRef* create_buffer(std::shared_ptr, int component); + +private: + + static void buffer_free(void* opaque, uint8_t* data); + void buffer_free2(uint8_t* data); + + std::map, int>> _images; + boost::mutex _mutex; +}; diff --git a/src/lib/internet.cc b/src/lib/internet.cc index a9c30611d..1f280dc5d 100644 --- a/src/lib/internet.cc +++ b/src/lib/internet.cc @@ -75,7 +75,7 @@ ls_url (string url) for (size_t i = 0; i < ls.size(); ++i) { if (ls[i] == '\n') { result.push_back(""); - } else { + } else if (ls[i] != '\r') { result.back() += ls[i]; } } diff --git a/src/lib/j2k_encoder.cc b/src/lib/j2k_encoder.cc index e6dab5828..7c9777c16 100644 --- a/src/lib/j2k_encoder.cc +++ b/src/lib/j2k_encoder.cc @@ -57,17 +57,18 @@ using namespace dcpomatic; /** @param film Film that we are encoding. * @param writer Writer that we are using. */ -J2KEncoder::J2KEncoder (shared_ptr film, shared_ptr writer) +J2KEncoder::J2KEncoder(shared_ptr film, Writer& writer) : _film (film) , _history (200) , _writer (writer) { - servers_list_changed (); } J2KEncoder::~J2KEncoder () { + _server_found_connection.disconnect(); + boost::mutex::scoped_lock lm (_threads_mutex); terminate_threads (); } @@ -76,24 +77,10 @@ J2KEncoder::~J2KEncoder () void J2KEncoder::begin () { - weak_ptr wp = shared_from_this (); - _server_found_connection = EncodeServerFinder::instance()->ServersListChanged.connect ( - boost::bind (&J2KEncoder::call_servers_list_changed, wp) + _server_found_connection = EncodeServerFinder::instance()->ServersListChanged.connect( + boost::bind(&J2KEncoder::servers_list_changed, this) ); -} - - -/* We don't want the servers-list-changed callback trying to do things - during destruction of J2KEncoder, and I think this is the neatest way - to achieve that. -*/ -void -J2KEncoder::call_servers_list_changed (weak_ptr encoder) -{ - auto e = encoder.lock (); - if (e) { - e->servers_list_changed (); - } + servers_list_changed (); } @@ -137,7 +124,7 @@ J2KEncoder::end () for (auto const& i: _queue) { LOG_GENERAL(N_("Encode left-over frame %1"), i.index()); try { - _writer->write ( + _writer.write( make_shared(i.encode_locally()), i.index(), i.eyes() @@ -210,7 +197,7 @@ J2KEncoder::encode (shared_ptr pv, DCPTime time) LOG_TIMING ("decoder-wake queue=%1 threads=%2", _queue.size(), threads); } - _writer->rethrow (); + _writer.rethrow(); /* Re-throw any exception raised by one of our threads. If more than one has thrown an exception, only one will be rethrown, I think; but then, if that happens something has gone badly wrong. @@ -219,19 +206,19 @@ J2KEncoder::encode (shared_ptr pv, DCPTime time) auto const position = time.frames_floor(_film->video_frame_rate()); - if (_writer->can_fake_write (position)) { + if (_writer.can_fake_write(position)) { /* We can fake-write this frame */ LOG_DEBUG_ENCODE("Frame @ %1 FAKE", to_string(time)); - _writer->fake_write (position, pv->eyes ()); + _writer.fake_write(position, pv->eyes ()); frame_done (); } else if (pv->has_j2k() && !_film->reencode_j2k()) { LOG_DEBUG_ENCODE("Frame @ %1 J2K", to_string(time)); /* This frame already has J2K data, so just write it */ - _writer->write (pv->j2k(), position, pv->eyes ()); + _writer.write(pv->j2k(), position, pv->eyes ()); frame_done (); - } else if (_last_player_video[static_cast(pv->eyes())] && _writer->can_repeat(position) && pv->same (_last_player_video[static_cast(pv->eyes())])) { + } else if (_last_player_video[pv->eyes()] && _writer.can_repeat(position) && pv->same(_last_player_video[pv->eyes()])) { LOG_DEBUG_ENCODE("Frame @ %1 REPEAT", to_string(time)); - _writer->repeat (position, pv->eyes ()); + _writer.repeat(position, pv->eyes()); } else { LOG_DEBUG_ENCODE("Frame @ %1 ENCODE", to_string(time)); /* Queue this new frame for encoding */ @@ -250,7 +237,7 @@ J2KEncoder::encode (shared_ptr pv, DCPTime time) _empty_condition.notify_all (); } - _last_player_video[static_cast(pv->eyes())] = pv; + _last_player_video[pv->eyes()] = pv; _last_player_video_time = time; } @@ -357,7 +344,7 @@ try } if (encoded) { - _writer->write (encoded, vf.index(), vf.eyes()); + _writer.write(encoded, vf.index(), vf.eyes()); frame_done (); } else { lock.lock (); @@ -420,5 +407,5 @@ J2KEncoder::servers_list_changed () } } - _writer->set_encoder_threads (_threads->size()); + _writer.set_encoder_threads(_threads->size()); } diff --git a/src/lib/j2k_encoder.h b/src/lib/j2k_encoder.h index ea0a2bef8..63228a6b8 100644 --- a/src/lib/j2k_encoder.h +++ b/src/lib/j2k_encoder.h @@ -28,23 +28,23 @@ */ -#include "util.h" #include "cross.h" +#include "enum_indexed_vector.h" #include "event_history.h" #include "exception_store.h" -#include -#include -#include +#include "writer.h" #include #include +#include +#include +#include #include #include -class Film; -class EncodeServerDescription; class DCPVideo; -class Writer; +class EncodeServerDescription; +class Film; class Job; class PlayerVideo; @@ -55,10 +55,10 @@ class PlayerVideo; * This class keeps a queue of frames to be encoded and distributes * the work around threads and encoding servers. */ -class J2KEncoder : public ExceptionStore, public std::enable_shared_from_this +class J2KEncoder : public ExceptionStore { public: - J2KEncoder (std::shared_ptr film, std::shared_ptr writer); + J2KEncoder(std::shared_ptr film, Writer& writer); ~J2KEncoder (); J2KEncoder (J2KEncoder const&) = delete; @@ -80,8 +80,6 @@ public: private: - static void call_servers_list_changed (std::weak_ptr encoder); - void frame_done (); void encoder_thread (boost::optional); @@ -102,10 +100,10 @@ private: /** condition to manage thread wakeups when we have too much to do */ boost::condition _full_condition; - std::shared_ptr _writer; + Writer& _writer; Waker _waker; - std::shared_ptr _last_player_video[static_cast(Eyes::COUNT)]; + EnumIndexedVector, Eyes> _last_player_video; boost::optional _last_player_video_time; boost::signals2::scoped_connection _server_found_connection; diff --git a/src/lib/job.cc b/src/lib/job.cc index 8ce63ced0..9c9530b7a 100644 --- a/src/lib/job.cc +++ b/src/lib/job.cc @@ -25,6 +25,7 @@ #include "compose.hpp" +#include "constants.h" #include "cross.h" #include "dcpomatic_log.h" #include "exceptions.h" @@ -37,6 +38,7 @@ #include #include #include +#include #include #include "i18n.h" @@ -55,10 +57,9 @@ using namespace dcpomatic; Job::Job (shared_ptr film) : _film (film) , _state (NEW) - , _start_time (0) , _sub_start_time (0) , _progress (0) - , _ran_for (0) + , _rate_limit_progress(true) { } @@ -111,10 +112,10 @@ Job::run_wrapper () } catch (dcp::FileError& e) { - string m = String::compose (_("An error occurred whilst handling the file %1."), boost::filesystem::path (e.filename()).leaf()); + string m = String::compose(_("An error occurred whilst handling the file %1."), e.filename().filename()); try { - auto const s = boost::filesystem::space (e.filename()); + auto const s = dcp::filesystem::space(e.filename()); if (s.available < pow (1024, 3)) { m += N_("\n\n"); m += _("The drive that the film is stored on is low in disc space. Free some more space and try again."); @@ -169,7 +170,7 @@ Job::run_wrapper () String::compose (_("Could not open %1"), e.file().string()), String::compose ( _("DCP-o-matic could not open the file %1 (%2). Perhaps it does not exist or is in an unexpected format."), - boost::filesystem::absolute (e.file()).string(), + dcp::filesystem::absolute(e.file()).string(), e.what() ) ); @@ -184,7 +185,7 @@ Job::run_wrapper () String::compose (_("Could not open %1"), e.path1().string ()), String::compose ( _("DCP-o-matic could not open the file %1 (%2). Perhaps it does not exist or is in an unexpected format."), - boost::filesystem::absolute (e.path1()).string(), + dcp::filesystem::absolute(e.path1()).string(), e.what() ) ); @@ -241,6 +242,12 @@ Job::run_wrapper () set_progress(1); set_state(FINISHED_ERROR); + } catch (MissingConfigurationError& e) { + + set_error(e.what()); + set_progress(1); + set_state(FINISHED_ERROR); + } catch (std::exception& e) { set_error ( @@ -343,22 +350,45 @@ Job::set_state (State s) { boost::mutex::scoped_lock lm (_state_mutex); + if (_state == s) { + return; + } + _state = s; if (_state == FINISHED_OK || _state == FINISHED_ERROR || _state == FINISHED_CANCELLED) { - _ran_for = time(0) - _start_time; + _finish_time = time(nullptr); finished = true; _sub_name.clear (); } } if (finished) { - emit (boost::bind (boost::ref (Finished))); - FinishedImmediate (); + auto const result = state_to_result(s); + emit(boost::bind(boost::ref(Finished), result)); + FinishedImmediate(result); } } +Job::Result +Job::state_to_result(State state) const +{ + switch (state) { + case FINISHED_OK: + return Result::RESULT_OK; + case FINISHED_ERROR: + return Result::RESULT_ERROR; + case FINISHED_CANCELLED: + return Result::RESULT_CANCELLED; + default: + DCPOMATIC_ASSERT(false); + }; + + DCPOMATIC_ASSERT(false); +} + + /** @return DCPTime (in seconds) that this sub-job has been running */ int Job::elapsed_sub_time () const @@ -409,7 +439,7 @@ Job::set_progress (float p, bool force) { check_for_interruption_or_pause (); - if (!force) { + if (!force && _rate_limit_progress) { /* Check for excessively frequent progress reporting */ boost::mutex::scoped_lock lm (_progress_mutex); struct timeval now; @@ -513,6 +543,27 @@ Job::status () const int const t = elapsed_sub_time (); int const r = remaining_time (); + auto day_of_week_to_string = [](boost::gregorian::greg_weekday d) -> std::string { + switch (d.as_enum()) { + case boost::date_time::Sunday: + return _("Sunday"); + case boost::date_time::Monday: + return _("Monday"); + case boost::date_time::Tuesday: + return _("Tuesday"); + case boost::date_time::Wednesday: + return _("Wednesday"); + case boost::date_time::Thursday: + return _("Thursday"); + case boost::date_time::Friday: + return _("Friday"); + case boost::date_time::Saturday: + return _("Saturday"); + } + + return d.as_long_string(); + }; + string s; if (!finished () && p) { int pc = lrintf (p.get() * 100); @@ -544,7 +595,22 @@ Job::status () const ); } } else if (finished_ok ()) { - s = String::compose (_("OK (ran for %1)"), seconds_to_hms (_ran_for)); + auto time_string = [](time_t time) { + auto tm = localtime(&time); + char buffer[8]; + snprintf(buffer, sizeof(buffer), "%02d:%02d", tm->tm_hour, tm->tm_min); + return string(buffer); + }; + auto const duration = _finish_time - _start_time; + if (duration < 10) { + /* It took less than 10 seconds; it doesn't seem worth saying how long it took */ + s = _("OK"); + } else if (duration < 600) { + /* It took less than 10 minutes; it doesn't seem worth saying when it started and finished */ + s = String::compose(_("OK (ran for %1)"), seconds_to_hms(duration)); + } else { + s = String::compose(_("OK (ran for %1 from %2 to %3)"), seconds_to_hms(duration), time_string(_start_time), time_string(_finish_time)); + } } else if (finished_in_error ()) { s = String::compose (_("Error: %1"), error_summary ()); } else if (finished_cancelled ()) { @@ -651,11 +717,11 @@ Job::resume () void -Job::when_finished (boost::signals2::connection& connection, function finished) +Job::when_finished(boost::signals2::connection& connection, function finished) { boost::mutex::scoped_lock lm (_state_mutex); if (_state == FINISHED_OK || _state == FINISHED_ERROR || _state == FINISHED_CANCELLED) { - finished (); + finished(state_to_result(_state)); } else { connection = Finished.connect (finished); } @@ -676,3 +742,11 @@ Job::set_message (string m) boost::mutex::scoped_lock lm (_state_mutex); _message = m; } + + +void +Job::set_rate_limit_progress(bool rate_limit) +{ + _rate_limit_progress = rate_limit; +} + diff --git a/src/lib/job.h b/src/lib/job.h index c8204c280..d4d0f9510 100644 --- a/src/lib/job.h +++ b/src/lib/job.h @@ -18,21 +18,27 @@ */ + /** @file src/job.h * @brief A parent class to represent long-running tasks which are run in their own thread. */ + #ifndef DCPOMATIC_JOB_H #define DCPOMATIC_JOB_H + #include "signaller.h" -#include +#include #include #include +#include #include + class Film; + /** @class Job * @brief A parent class to represent long-running tasks which are run in their own thread. */ @@ -91,13 +97,21 @@ public: return _film; } - void when_finished (boost::signals2::connection& connection, std::function finished); + enum class Result { + RESULT_OK, + RESULT_ERROR, // we can't have plain ERROR on Windows + RESULT_CANCELLED + }; + + void when_finished(boost::signals2::connection& connection, std::function finished); + + void set_rate_limit_progress(bool rate_limit); boost::signals2::signal Progress; /** Emitted from the UI thread when the job is finished */ - boost::signals2::signal Finished; + boost::signals2::signal Finished; /** Emitted from the job thread when the job is finished */ - boost::signals2::signal FinishedImmediate; + boost::signals2::signal FinishedImmediate; protected: @@ -114,6 +128,7 @@ protected: FINISHED_CANCELLED ///< the job was cancelled }; + Result state_to_result(State state) const; void set_state (State); void set_error (std::string s, std::string d = ""); void set_message (std::string m); @@ -123,6 +138,9 @@ protected: std::shared_ptr _film; + time_t _start_time = 0; + time_t _finish_time = 0; + private: void run_wrapper (); @@ -140,8 +158,6 @@ private: /** a message that should be given to the user when the job finishes */ boost::optional _message; - /** time that this job was started */ - time_t _start_time; /** time that this sub-job was started */ time_t _sub_start_time; std::string _sub_name; @@ -151,12 +167,15 @@ private: boost::optional _progress; boost::optional _last_progress_update; + /** true to limit emissions of the progress signal so that they don't + * come too often. + */ + boost::atomic _rate_limit_progress; + /** condition to signal changes to pause/resume so that we know when to wake; this could be a general _state_change if it made more sense. */ boost::condition_variable _pause_changed; - - int _ran_for; }; #endif diff --git a/src/lib/job_manager.cc b/src/lib/job_manager.cc index 608df7ef0..86230db2b 100644 --- a/src/lib/job_manager.cc +++ b/src/lib/job_manager.cc @@ -30,6 +30,7 @@ #include "film.h" #include "job.h" #include "job_manager.h" +#include "util.h" #include @@ -74,7 +75,7 @@ JobManager::~JobManager () { boost::mutex::scoped_lock lm (_mutex); _terminate = true; - _empty_condition.notify_all (); + _schedule_condition.notify_all(); } try { @@ -89,7 +90,7 @@ JobManager::add (shared_ptr j) { boost::mutex::scoped_lock lm (_mutex); _jobs.push_back (j); - _empty_condition.notify_all (); + _schedule_condition.notify_all(); } emit (boost::bind(boost::ref(JobAdded), weak_ptr(j))); @@ -106,7 +107,7 @@ JobManager::add_after (shared_ptr after, shared_ptr j) auto i = find (_jobs.begin(), _jobs.end(), after); DCPOMATIC_ASSERT (i != _jobs.end()); _jobs.insert (i, j); - _empty_condition.notify_all (); + _schedule_condition.notify_all(); } emit (boost::bind(boost::ref(JobAdded), weak_ptr(j))); @@ -165,9 +166,11 @@ JobManager::scheduler () bool have_running = false; for (auto i: _jobs) { - if (have_running && i->running()) { + if ((have_running || _paused) && i->running()) { + /* We already have a running job, or are totally paused, so this job should not be running */ i->pause_by_priority(); - } else if (!have_running && (i->is_new() || i->paused_by_priority())) { + } else if (!have_running && !_paused && (i->is_new() || i->paused_by_priority())) { + /* We don't have a running job, and we should have one, so start/resume this */ if (i->is_new()) { _connections.push_back (i->FinishedImmediate.connect(bind(&JobManager::job_finished, this))); i->start (); @@ -182,7 +185,7 @@ JobManager::scheduler () } } - _empty_condition.wait (lm); + _schedule_condition.wait(lm); } } @@ -196,7 +199,7 @@ JobManager::job_finished () _last_active_job = optional(); } - _empty_condition.notify_all (); + _schedule_condition.notify_all(); } @@ -226,7 +229,7 @@ JobManager::analyse_audio ( shared_ptr playlist, bool from_zero, boost::signals2::connection& connection, - function ready + function ready ) { { @@ -249,7 +252,7 @@ JobManager::analyse_audio ( job = make_shared (film, playlist, from_zero); connection = job->Finished.connect (ready); _jobs.push_back (job); - _empty_condition.notify_all (); + _schedule_condition.notify_all (); } emit (boost::bind (boost::ref (JobAdded), weak_ptr (job))); @@ -261,7 +264,7 @@ JobManager::analyse_subtitles ( shared_ptr film, shared_ptr content, boost::signals2::connection& connection, - function ready + function ready ) { { @@ -269,7 +272,7 @@ JobManager::analyse_subtitles ( for (auto i: _jobs) { auto a = dynamic_pointer_cast (i); - if (a && a->path() == film->subtitle_analysis_path(content)) { + if (a && a->path() == film->subtitle_analysis_path(content) && !i->finished_cancelled()) { i->when_finished (connection, ready); return; } @@ -284,7 +287,7 @@ JobManager::analyse_subtitles ( job = make_shared(film, content); connection = job->Finished.connect (ready); _jobs.push_back (job); - _empty_condition.notify_all (); + _schedule_condition.notify_all (); } emit (boost::bind(boost::ref(JobAdded), weak_ptr(job))); @@ -294,84 +297,52 @@ JobManager::analyse_subtitles ( void JobManager::increase_priority (shared_ptr job) { - bool changed = false; - { boost::mutex::scoped_lock lm (_mutex); - auto last = _jobs.end (); - for (auto i = _jobs.begin(); i != _jobs.end(); ++i) { - if (*i == job && last != _jobs.end()) { - swap (*i, *last); - changed = true; - break; - } - last = i; + auto iter = std::find(_jobs.begin(), _jobs.end(), job); + if (iter == _jobs.begin() || iter == _jobs.end()) { + return; } + swap(*iter, *std::prev(iter)); } - if (changed) { - _empty_condition.notify_all (); - emit (boost::bind(boost::ref(JobsReordered))); - } + _schedule_condition.notify_all(); + emit(boost::bind(boost::ref(JobsReordered))); } void JobManager::decrease_priority (shared_ptr job) { - bool changed = false; - { boost::mutex::scoped_lock lm (_mutex); - for (auto i = _jobs.begin(); i != _jobs.end(); ++i) { - auto next = i; - ++next; - if (*i == job && next != _jobs.end()) { - swap (*i, *next); - changed = true; - break; - } + auto iter = std::find(_jobs.begin(), _jobs.end(), job); + if (iter == _jobs.end() || std::next(iter) == _jobs.end()) { + return; } + swap(*iter, *std::next(iter)); } - if (changed) { - _empty_condition.notify_all (); - emit (boost::bind(boost::ref(JobsReordered))); - } + _schedule_condition.notify_all(); + emit(boost::bind(boost::ref(JobsReordered))); } +/** Pause all job processing */ void JobManager::pause () { boost::mutex::scoped_lock lm (_mutex); - - if (_paused) { - return; - } - - for (auto i: _jobs) { - if (i->pause_by_user()) { - _paused_job = i; - } - } - _paused = true; + _schedule_condition.notify_all(); } +/** Resume processing jobs after a previous pause() */ void JobManager::resume () { boost::mutex::scoped_lock lm (_mutex); - if (!_paused) { - return; - } - - if (_paused_job) { - _paused_job->resume (); - } - - _paused_job.reset (); _paused = false; + _schedule_condition.notify_all(); } diff --git a/src/lib/job_manager.h b/src/lib/job_manager.h index 71db33fd6..c8450bfda 100644 --- a/src/lib/job_manager.h +++ b/src/lib/job_manager.h @@ -24,6 +24,7 @@ */ +#include "job.h" #include "signaller.h" #include #include @@ -32,7 +33,6 @@ #include -class Job; class Film; class Playlist; class Content; @@ -70,14 +70,14 @@ public: std::shared_ptr playlist, bool from_zero, boost::signals2::connection& connection, - std::function ready + std::function ready ); void analyse_subtitles ( std::shared_ptr film, std::shared_ptr content, boost::signals2::connection& connection, - std::function ready + std::function ready ); boost::signals2::signal)> JobAdded; @@ -99,16 +99,17 @@ private: void job_finished (); mutable boost::mutex _mutex; - boost::condition _empty_condition; + boost::condition _schedule_condition; /** List of jobs in the order that they will be executed */ std::list> _jobs; std::list _connections; bool _terminate = false; - bool _paused = false; - std::shared_ptr _paused_job; boost::optional _last_active_job; boost::thread _scheduler; + /** true if all jobs should be paused */ + bool _paused = false; + static JobManager* _instance; }; diff --git a/src/lib/json_server.cc b/src/lib/json_server.cc index 8c626ad1f..92d5c1171 100644 --- a/src/lib/json_server.cc +++ b/src/lib/json_server.cc @@ -24,7 +24,6 @@ #include "job_manager.h" #include "json_server.h" #include "transcode_job.h" -#include "util.h" #include #include #include @@ -152,6 +151,53 @@ JSONServer::handle (shared_ptr socket) } +map +split_get_request(string url) +{ + enum { + AWAITING_QUESTION_MARK, + KEY, + VALUE + } state = AWAITING_QUESTION_MARK; + + map r; + string k; + string v; + for (size_t i = 0; i < url.length(); ++i) { + switch (state) { + case AWAITING_QUESTION_MARK: + if (url[i] == '?') { + state = KEY; + } + break; + case KEY: + if (url[i] == '=') { + v.clear(); + state = VALUE; + } else { + k += url[i]; + } + break; + case VALUE: + if (url[i] == '&') { + r.insert(make_pair(k, v)); + k.clear (); + state = KEY; + } else { + v += url[i]; + } + break; + } + } + + if (state == VALUE) { + r.insert (make_pair (k, v)); + } + + return r; +} + + void JSONServer::request (string url, shared_ptr socket) { diff --git a/src/lib/kdm_cli.cc b/src/lib/kdm_cli.cc index a6656fa0e..2d3a021b5 100644 --- a/src/lib/kdm_cli.cc +++ b/src/lib/kdm_cli.cc @@ -27,7 +27,7 @@ #include "cinema.h" #include "config.h" #include "dkdm_wrapper.h" -#include "emailer.h" +#include "email.h" #include "exceptions.h" #include "film.h" #include "kdm_with_metadata.h" @@ -35,6 +35,7 @@ #include #include #include +#include #include @@ -58,22 +59,23 @@ help (std::function out) { out (String::compose("Syntax: %1 [OPTION] ", program_name)); out (" -h, --help show this help"); - out (" -o, --output output file or directory"); - out (" -K, --filename-format filename format for KDMs"); - out (" -Z, --container-name-format filename format for ZIP containers"); - out (" -f, --valid-from valid from time (in local time zone of the cinema) (e.g. \"2013-09-28 01:41:51\") or \"now\""); - out (" -t, --valid-to valid to time (in local time zone of the cinema) (e.g. \"2014-09-28 01:41:51\")"); - out (" -d, --valid-duration valid duration (e.g. \"1 day\", \"4 hours\", \"2 weeks\")"); - out (" -F, --formulation modified-transitional-1, multiple-modified-transitional-1, dci-any or dci-specific [default modified-transitional-1]"); + out (" -o, --output output file or directory"); + out (" -K, --filename-format filename format for KDMs"); + out (" -Z, --container-name-format filename format for ZIP containers"); + out (" -f, --valid-from