summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--DEVELOP.md12
-rw-r--r--cscript91
-rw-r--r--debian/changelog476
-rwxr-xr-xdebian/rules2
-rw-r--r--doc/design/fonts17
-rw-r--r--doc/design/fonts.svg615
-rw-r--r--graphics/linux/128/dcpomatic2_batch.pngbin22609 -> 24017 bytes
-rw-r--r--graphics/linux/128/dcpomatic2_combiner.pngbin21201 -> 22496 bytes
-rw-r--r--graphics/linux/128/dcpomatic2_disk.pngbin19938 -> 21128 bytes
-rw-r--r--graphics/linux/128/dcpomatic2_editor.pngbin22592 -> 23998 bytes
-rw-r--r--graphics/linux/128/dcpomatic2_kdm.pngbin22251 -> 23698 bytes
-rw-r--r--graphics/linux/128/dcpomatic2_player.pngbin21691 -> 23156 bytes
-rw-r--r--graphics/linux/128/dcpomatic2_playlist.pngbin20975 -> 22268 bytes
-rw-r--r--graphics/linux/128/dcpomatic2_server.pngbin21313 -> 22649 bytes
-rw-r--r--graphics/linux/128/dcpomatic2_verifier.pngbin0 -> 23196 bytes
-rw-r--r--graphics/linux/16/dcpomatic2_batch.pngbin875 -> 955 bytes
-rw-r--r--graphics/linux/16/dcpomatic2_combiner.pngbin858 -> 928 bytes
-rw-r--r--graphics/linux/16/dcpomatic2_disk.pngbin827 -> 905 bytes
-rw-r--r--graphics/linux/16/dcpomatic2_editor.pngbin868 -> 934 bytes
-rw-r--r--graphics/linux/16/dcpomatic2_kdm.pngbin859 -> 948 bytes
-rw-r--r--graphics/linux/16/dcpomatic2_player.pngbin872 -> 934 bytes
-rw-r--r--graphics/linux/16/dcpomatic2_playlist.pngbin869 -> 936 bytes
-rw-r--r--graphics/linux/16/dcpomatic2_server.pngbin874 -> 936 bytes
-rw-r--r--graphics/linux/16/dcpomatic2_verifier.pngbin0 -> 935 bytes
-rw-r--r--graphics/linux/22/dcpomatic2_batch.pngbin1403 -> 1524 bytes
-rw-r--r--graphics/linux/22/dcpomatic2_combiner.pngbin1425 -> 1469 bytes
-rw-r--r--graphics/linux/22/dcpomatic2_disk.pngbin1345 -> 1420 bytes
-rw-r--r--graphics/linux/22/dcpomatic2_editor.pngbin1425 -> 1509 bytes
-rw-r--r--graphics/linux/22/dcpomatic2_kdm.pngbin1392 -> 1512 bytes
-rw-r--r--graphics/linux/22/dcpomatic2_player.pngbin1425 -> 1511 bytes
-rw-r--r--graphics/linux/22/dcpomatic2_playlist.pngbin1415 -> 1511 bytes
-rw-r--r--graphics/linux/22/dcpomatic2_server.pngbin1418 -> 1508 bytes
-rw-r--r--graphics/linux/22/dcpomatic2_verifier.pngbin0 -> 1496 bytes
-rw-r--r--graphics/linux/256/dcpomatic2_batch.pngbin68101 -> 73195 bytes
-rw-r--r--graphics/linux/256/dcpomatic2_combiner.pngbin64533 -> 69398 bytes
-rw-r--r--graphics/linux/256/dcpomatic2_disk.pngbin59000 -> 63256 bytes
-rw-r--r--graphics/linux/256/dcpomatic2_editor.pngbin68131 -> 72771 bytes
-rw-r--r--graphics/linux/256/dcpomatic2_kdm.pngbin68186 -> 73092 bytes
-rw-r--r--graphics/linux/256/dcpomatic2_player.pngbin65636 -> 70474 bytes
-rw-r--r--graphics/linux/256/dcpomatic2_playlist.pngbin62063 -> 66488 bytes
-rw-r--r--graphics/linux/256/dcpomatic2_server.pngbin64026 -> 68543 bytes
-rw-r--r--graphics/linux/256/dcpomatic2_verifier.pngbin0 -> 70647 bytes
-rw-r--r--graphics/linux/32/dcpomatic2_batch.pngbin2569 -> 2805 bytes
-rw-r--r--graphics/linux/32/dcpomatic2_combiner.pngbin2551 -> 2701 bytes
-rw-r--r--graphics/linux/32/dcpomatic2_disk.pngbin2415 -> 2535 bytes
-rw-r--r--graphics/linux/32/dcpomatic2_editor.pngbin2595 -> 2746 bytes
-rw-r--r--graphics/linux/32/dcpomatic2_kdm.pngbin2508 -> 2739 bytes
-rw-r--r--graphics/linux/32/dcpomatic2_player.pngbin2587 -> 2737 bytes
-rw-r--r--graphics/linux/32/dcpomatic2_playlist.pngbin2584 -> 2742 bytes
-rw-r--r--graphics/linux/32/dcpomatic2_server.pngbin2567 -> 2701 bytes
-rw-r--r--graphics/linux/32/dcpomatic2_verifier.pngbin0 -> 2714 bytes
-rw-r--r--graphics/linux/48/dcpomatic2_batch.pngbin5098 -> 5386 bytes
-rw-r--r--graphics/linux/48/dcpomatic2_combiner.pngbin4870 -> 5103 bytes
-rw-r--r--graphics/linux/48/dcpomatic2_disk.pngbin4560 -> 4762 bytes
-rw-r--r--graphics/linux/48/dcpomatic2_editor.pngbin4970 -> 5244 bytes
-rw-r--r--graphics/linux/48/dcpomatic2_kdm.pngbin4919 -> 5225 bytes
-rw-r--r--graphics/linux/48/dcpomatic2_player.pngbin4957 -> 5217 bytes
-rw-r--r--graphics/linux/48/dcpomatic2_playlist.pngbin4875 -> 5139 bytes
-rw-r--r--graphics/linux/48/dcpomatic2_server.pngbin4876 -> 5116 bytes
-rw-r--r--graphics/linux/48/dcpomatic2_verifier.pngbin0 -> 5152 bytes
-rw-r--r--graphics/linux/512/dcpomatic2_batch.pngbin229134 -> 252799 bytes
-rw-r--r--graphics/linux/512/dcpomatic2_combiner.pngbin218347 -> 241424 bytes
-rw-r--r--graphics/linux/512/dcpomatic2_disk.pngbin196768 -> 216895 bytes
-rw-r--r--graphics/linux/512/dcpomatic2_editor.pngbin227202 -> 250659 bytes
-rw-r--r--graphics/linux/512/dcpomatic2_kdm.pngbin228531 -> 252281 bytes
-rw-r--r--graphics/linux/512/dcpomatic2_player.pngbin222254 -> 245391 bytes
-rw-r--r--graphics/linux/512/dcpomatic2_playlist.pngbin206987 -> 228032 bytes
-rw-r--r--graphics/linux/512/dcpomatic2_server.pngbin214908 -> 237737 bytes
-rw-r--r--graphics/linux/512/dcpomatic2_verifier.pngbin0 -> 242957 bytes
-rw-r--r--graphics/linux/64/dcpomatic2_batch.pngbin8041 -> 8486 bytes
-rw-r--r--graphics/linux/64/dcpomatic2_combiner.pngbin7567 -> 7899 bytes
-rw-r--r--graphics/linux/64/dcpomatic2_disk.pngbin7128 -> 7489 bytes
-rw-r--r--graphics/linux/64/dcpomatic2_editor.pngbin7881 -> 8298 bytes
-rw-r--r--graphics/linux/64/dcpomatic2_kdm.pngbin7822 -> 8196 bytes
-rw-r--r--graphics/linux/64/dcpomatic2_player.pngbin7744 -> 8157 bytes
-rw-r--r--graphics/linux/64/dcpomatic2_playlist.pngbin7608 -> 8052 bytes
-rw-r--r--graphics/linux/64/dcpomatic2_server.pngbin7607 -> 8027 bytes
-rw-r--r--graphics/linux/64/dcpomatic2_verifier.pngbin0 -> 8150 bytes
-rw-r--r--graphics/osx/dcpomatic2_batch.icnsbin737755 -> 803841 bytes
-rw-r--r--graphics/osx/dcpomatic2_batch.iconset/icon_128x128.pngbin22609 -> 24017 bytes
-rw-r--r--graphics/osx/dcpomatic2_batch.iconset/icon_128x128@2x.pngbin22609 -> 24017 bytes
-rw-r--r--graphics/osx/dcpomatic2_batch.iconset/icon_16x16.pngbin875 -> 955 bytes
-rw-r--r--graphics/osx/dcpomatic2_batch.iconset/icon_16x16@2x.pngbin875 -> 955 bytes
-rw-r--r--graphics/osx/dcpomatic2_batch.iconset/icon_256x256.pngbin68101 -> 73195 bytes
-rw-r--r--graphics/osx/dcpomatic2_batch.iconset/icon_256x256@2x.pngbin68101 -> 73195 bytes
-rw-r--r--graphics/osx/dcpomatic2_batch.iconset/icon_32x32.pngbin2569 -> 2805 bytes
-rw-r--r--graphics/osx/dcpomatic2_batch.iconset/icon_32x32@2x.pngbin2569 -> 2805 bytes
-rw-r--r--graphics/osx/dcpomatic2_batch.iconset/icon_512x512.pngbin229134 -> 252799 bytes
-rw-r--r--graphics/osx/dcpomatic2_batch.iconset/icon_512x512@2x.pngbin229134 -> 252799 bytes
-rw-r--r--graphics/osx/dcpomatic2_combiner.icnsbin710942 -> 774493 bytes
-rw-r--r--graphics/osx/dcpomatic2_combiner.iconset/icon_128x128.pngbin21201 -> 22496 bytes
-rw-r--r--graphics/osx/dcpomatic2_combiner.iconset/icon_128x128@2x.pngbin21201 -> 22496 bytes
-rw-r--r--graphics/osx/dcpomatic2_combiner.iconset/icon_16x16.pngbin858 -> 928 bytes
-rw-r--r--graphics/osx/dcpomatic2_combiner.iconset/icon_16x16@2x.pngbin858 -> 928 bytes
-rw-r--r--graphics/osx/dcpomatic2_combiner.iconset/icon_256x256.pngbin64533 -> 69398 bytes
-rw-r--r--graphics/osx/dcpomatic2_combiner.iconset/icon_256x256@2x.pngbin64533 -> 69398 bytes
-rw-r--r--graphics/osx/dcpomatic2_combiner.iconset/icon_32x32.pngbin2551 -> 2701 bytes
-rw-r--r--graphics/osx/dcpomatic2_combiner.iconset/icon_32x32@2x.pngbin2551 -> 2701 bytes
-rw-r--r--graphics/osx/dcpomatic2_combiner.iconset/icon_512x512.pngbin218347 -> 241424 bytes
-rw-r--r--graphics/osx/dcpomatic2_combiner.iconset/icon_512x512@2x.pngbin218347 -> 241424 bytes
-rw-r--r--graphics/osx/dcpomatic2_disk.icnsbin640042 -> 695486 bytes
-rw-r--r--graphics/osx/dcpomatic2_disk.iconset/icon_128x128.pngbin19938 -> 21128 bytes
-rw-r--r--graphics/osx/dcpomatic2_disk.iconset/icon_128x128@2x.pngbin19938 -> 21128 bytes
-rw-r--r--graphics/osx/dcpomatic2_disk.iconset/icon_16x16.pngbin827 -> 905 bytes
-rw-r--r--graphics/osx/dcpomatic2_disk.iconset/icon_16x16@2x.pngbin827 -> 905 bytes
-rw-r--r--graphics/osx/dcpomatic2_disk.iconset/icon_256x256.pngbin59000 -> 63256 bytes
-rw-r--r--graphics/osx/dcpomatic2_disk.iconset/icon_256x256@2x.pngbin59000 -> 63256 bytes
-rw-r--r--graphics/osx/dcpomatic2_disk.iconset/icon_32x32.pngbin2415 -> 2535 bytes
-rw-r--r--graphics/osx/dcpomatic2_disk.iconset/icon_32x32@2x.pngbin2415 -> 2535 bytes
-rw-r--r--graphics/osx/dcpomatic2_disk.iconset/icon_512x512.pngbin196768 -> 216895 bytes
-rw-r--r--graphics/osx/dcpomatic2_disk.iconset/icon_512x512@2x.pngbin196768 -> 216895 bytes
-rw-r--r--graphics/osx/dcpomatic2_editor.icnsbin733615 -> 797848 bytes
-rw-r--r--graphics/osx/dcpomatic2_editor.iconset/icon_128x128.pngbin22592 -> 23998 bytes
-rw-r--r--graphics/osx/dcpomatic2_editor.iconset/icon_128x128@2x.pngbin22592 -> 23998 bytes
-rw-r--r--graphics/osx/dcpomatic2_editor.iconset/icon_16x16.pngbin868 -> 934 bytes
-rw-r--r--graphics/osx/dcpomatic2_editor.iconset/icon_16x16@2x.pngbin868 -> 934 bytes
-rw-r--r--graphics/osx/dcpomatic2_editor.iconset/icon_256x256.pngbin68131 -> 72771 bytes
-rw-r--r--graphics/osx/dcpomatic2_editor.iconset/icon_256x256@2x.pngbin68131 -> 72771 bytes
-rw-r--r--graphics/osx/dcpomatic2_editor.iconset/icon_32x32.pngbin2595 -> 2746 bytes
-rw-r--r--graphics/osx/dcpomatic2_editor.iconset/icon_32x32@2x.pngbin2595 -> 2746 bytes
-rw-r--r--graphics/osx/dcpomatic2_editor.iconset/icon_512x512.pngbin227202 -> 250659 bytes
-rw-r--r--graphics/osx/dcpomatic2_editor.iconset/icon_512x512@2x.pngbin227202 -> 250659 bytes
-rw-r--r--graphics/osx/dcpomatic2_kdm.icnsbin740567 -> 806931 bytes
-rw-r--r--graphics/osx/dcpomatic2_kdm.iconset/icon_128x128.pngbin22251 -> 23698 bytes
-rw-r--r--graphics/osx/dcpomatic2_kdm.iconset/icon_128x128@2x.pngbin22251 -> 23698 bytes
-rw-r--r--graphics/osx/dcpomatic2_kdm.iconset/icon_16x16.pngbin859 -> 948 bytes
-rw-r--r--graphics/osx/dcpomatic2_kdm.iconset/icon_16x16@2x.pngbin859 -> 948 bytes
-rw-r--r--graphics/osx/dcpomatic2_kdm.iconset/icon_256x256.pngbin68186 -> 73092 bytes
-rw-r--r--graphics/osx/dcpomatic2_kdm.iconset/icon_256x256@2x.pngbin68186 -> 73092 bytes
-rw-r--r--graphics/osx/dcpomatic2_kdm.iconset/icon_32x32.pngbin2508 -> 2739 bytes
-rw-r--r--graphics/osx/dcpomatic2_kdm.iconset/icon_32x32@2x.pngbin2508 -> 2739 bytes
-rw-r--r--graphics/osx/dcpomatic2_kdm.iconset/icon_512x512.pngbin228531 -> 252281 bytes
-rw-r--r--graphics/osx/dcpomatic2_kdm.iconset/icon_512x512@2x.pngbin228531 -> 252281 bytes
-rw-r--r--graphics/osx/dcpomatic2_player.icnsbin723238 -> 787705 bytes
-rw-r--r--graphics/osx/dcpomatic2_player.iconset/icon_128x128.pngbin21691 -> 23156 bytes
-rw-r--r--graphics/osx/dcpomatic2_player.iconset/icon_128x128@2x.pngbin21691 -> 23156 bytes
-rw-r--r--graphics/osx/dcpomatic2_player.iconset/icon_16x16.pngbin872 -> 934 bytes
-rw-r--r--graphics/osx/dcpomatic2_player.iconset/icon_16x16@2x.pngbin872 -> 934 bytes
-rw-r--r--graphics/osx/dcpomatic2_player.iconset/icon_256x256.pngbin65636 -> 70474 bytes
-rw-r--r--graphics/osx/dcpomatic2_player.iconset/icon_256x256@2x.pngbin65636 -> 70474 bytes
-rw-r--r--graphics/osx/dcpomatic2_player.iconset/icon_32x32.pngbin2587 -> 2737 bytes
-rw-r--r--graphics/osx/dcpomatic2_player.iconset/icon_32x32@2x.pngbin2587 -> 2737 bytes
-rw-r--r--graphics/osx/dcpomatic2_player.iconset/icon_512x512.pngbin222254 -> 245391 bytes
-rw-r--r--graphics/osx/dcpomatic2_player.iconset/icon_512x512@2x.pngbin222254 -> 245391 bytes
-rw-r--r--graphics/osx/dcpomatic2_playlist.icnsbin678287 -> 737981 bytes
-rw-r--r--graphics/osx/dcpomatic2_playlist.iconset/icon_128x128.pngbin20975 -> 22268 bytes
-rw-r--r--graphics/osx/dcpomatic2_playlist.iconset/icon_128x128@2x.pngbin20975 -> 22268 bytes
-rw-r--r--graphics/osx/dcpomatic2_playlist.iconset/icon_16x16.pngbin869 -> 936 bytes
-rw-r--r--graphics/osx/dcpomatic2_playlist.iconset/icon_16x16@2x.pngbin869 -> 936 bytes
-rw-r--r--graphics/osx/dcpomatic2_playlist.iconset/icon_256x256.pngbin62063 -> 66488 bytes
-rw-r--r--graphics/osx/dcpomatic2_playlist.iconset/icon_256x256@2x.pngbin62063 -> 66488 bytes
-rw-r--r--graphics/osx/dcpomatic2_playlist.iconset/icon_32x32.pngbin2584 -> 2742 bytes
-rw-r--r--graphics/osx/dcpomatic2_playlist.iconset/icon_32x32@2x.pngbin2584 -> 2742 bytes
-rw-r--r--graphics/osx/dcpomatic2_playlist.iconset/icon_512x512.pngbin206987 -> 228032 bytes
-rw-r--r--graphics/osx/dcpomatic2_playlist.iconset/icon_512x512@2x.pngbin206987 -> 228032 bytes
-rw-r--r--graphics/osx/dcpomatic2_server.icnsbin700512 -> 762409 bytes
-rw-r--r--graphics/osx/dcpomatic2_server.iconset/icon_128x128.pngbin21313 -> 22649 bytes
-rw-r--r--graphics/osx/dcpomatic2_server.iconset/icon_128x128@2x.pngbin21313 -> 22649 bytes
-rw-r--r--graphics/osx/dcpomatic2_server.iconset/icon_16x16.pngbin874 -> 936 bytes
-rw-r--r--graphics/osx/dcpomatic2_server.iconset/icon_16x16@2x.pngbin874 -> 936 bytes
-rw-r--r--graphics/osx/dcpomatic2_server.iconset/icon_256x256.pngbin64026 -> 68543 bytes
-rw-r--r--graphics/osx/dcpomatic2_server.iconset/icon_256x256@2x.pngbin64026 -> 68543 bytes
-rw-r--r--graphics/osx/dcpomatic2_server.iconset/icon_32x32.pngbin2567 -> 2701 bytes
-rw-r--r--graphics/osx/dcpomatic2_server.iconset/icon_32x32@2x.pngbin2567 -> 2701 bytes
-rw-r--r--graphics/osx/dcpomatic2_server.iconset/icon_512x512.pngbin214908 -> 237737 bytes
-rw-r--r--graphics/osx/dcpomatic2_server.iconset/icon_512x512@2x.pngbin214908 -> 237737 bytes
-rw-r--r--graphics/osx/dcpomatic2_verifier.icnsbin0 -> 778023 bytes
-rw-r--r--graphics/osx/dcpomatic2_verifier.iconset/icon_128x128.pngbin0 -> 23196 bytes
-rw-r--r--graphics/osx/dcpomatic2_verifier.iconset/icon_128x128@2x.pngbin0 -> 23196 bytes
-rw-r--r--graphics/osx/dcpomatic2_verifier.iconset/icon_16x16.pngbin0 -> 935 bytes
-rw-r--r--graphics/osx/dcpomatic2_verifier.iconset/icon_16x16@2x.pngbin0 -> 935 bytes
-rw-r--r--graphics/osx/dcpomatic2_verifier.iconset/icon_256x256.pngbin0 -> 70647 bytes
-rw-r--r--graphics/osx/dcpomatic2_verifier.iconset/icon_256x256@2x.pngbin0 -> 70647 bytes
-rw-r--r--graphics/osx/dcpomatic2_verifier.iconset/icon_32x32.pngbin0 -> 2714 bytes
-rw-r--r--graphics/osx/dcpomatic2_verifier.iconset/icon_32x32@2x.pngbin0 -> 2714 bytes
-rw-r--r--graphics/osx/dcpomatic2_verifier.iconset/icon_512x512.pngbin0 -> 242957 bytes
-rw-r--r--graphics/osx/dcpomatic2_verifier.iconset/icon_512x512@2x.pngbin0 -> 242957 bytes
l---------graphics/src/dcpomatic2.png (renamed from graphics/src/dcpomatic.png)0
-rw-r--r--graphics/src/dcpomatic2_batch.svg87
-rw-r--r--graphics/src/dcpomatic2_combiner.svg135
-rw-r--r--graphics/src/dcpomatic2_disk.svg191
-rw-r--r--graphics/src/dcpomatic2_editor.svg134
-rw-r--r--graphics/src/dcpomatic2_kdm.svg119
-rw-r--r--graphics/src/dcpomatic2_player.svg93
-rw-r--r--graphics/src/dcpomatic2_playlist.svg167
-rw-r--r--graphics/src/dcpomatic2_server.svg187
-rw-r--r--graphics/src/dcpomatic2_verifier.svg248
-rwxr-xr-xgraphics/update19
-rw-r--r--graphics/windows/dcpomatic2_batch.icobin99678 -> 99678 bytes
-rw-r--r--graphics/windows/dcpomatic2_combiner.icobin99678 -> 99678 bytes
-rw-r--r--graphics/windows/dcpomatic2_disk.icobin99678 -> 99678 bytes
-rw-r--r--graphics/windows/dcpomatic2_editor.icobin99678 -> 99678 bytes
-rw-r--r--graphics/windows/dcpomatic2_kdm.icobin99678 -> 99678 bytes
-rw-r--r--graphics/windows/dcpomatic2_player.icobin99678 -> 99678 bytes
-rw-r--r--graphics/windows/dcpomatic2_playlist.icobin99678 -> 99678 bytes
-rw-r--r--graphics/windows/dcpomatic2_server.icobin99678 -> 99678 bytes
-rw-r--r--graphics/windows/dcpomatic2_verifier.icobin0 -> 99678 bytes
-rw-r--r--graphics/wscript3
-rw-r--r--hacks/small_loop9
-rw-r--r--hacks/text.cc6
-rw-r--r--platform/linux/dcpomatic_verifier.desktop.in10
-rw-r--r--platform/linux/wscript1
-rw-r--r--platform/osx/dcpomatic2.Info.plist.in4
-rw-r--r--platform/osx/dcpomatic2_kdm.Info.plist.in4
-rw-r--r--platform/osx/dcpomatic2_player.Info.plist.in4
-rw-r--r--platform/osx/dcpomatic2_verifier.Info.plist.in36
-rw-r--r--platform/osx/make_dmg.sh220
-rw-r--r--platform/osx/wscript19
-rw-r--r--platform/windows/dcpomatic.rc2
-rw-r--r--platform/windows/dcpomatic2_disk_writer.exe.manifest12
-rw-r--r--platform/windows/dcpomatic2_verifier.bat1
-rw-r--r--platform/windows/dcpomatic_batch.rc2
-rw-r--r--platform/windows/dcpomatic_combiner.rc2
-rw-r--r--platform/windows/dcpomatic_disk.rc2
-rw-r--r--platform/windows/dcpomatic_disk_writer.rc2
-rw-r--r--platform/windows/dcpomatic_editor.rc2
-rw-r--r--platform/windows/dcpomatic_kdm.rc2
-rw-r--r--platform/windows/dcpomatic_player.rc2
-rw-r--r--platform/windows/dcpomatic_playlist.rc2
-rw-r--r--platform/windows/dcpomatic_server.rc2
-rw-r--r--platform/windows/wscript189
-rwxr-xr-xrun/dcpomatic_player9
-rwxr-xr-xrun/dcpomatic_playlist8
-rwxr-xr-xrun/dcpomatic_verifier24
-rw-r--r--run/environment3
-rwxr-xr-xrun/tests86
-rw-r--r--run/tests.bat2
-rw-r--r--src/lib/analytics.cc22
-rw-r--r--src/lib/atmos_content.cc6
-rw-r--r--src/lib/atmos_content.h2
-rw-r--r--src/lib/atmos_mxf_content.cc8
-rw-r--r--src/lib/atmos_mxf_content.h2
-rw-r--r--src/lib/audio_analysis.cc33
-rw-r--r--src/lib/audio_content.cc12
-rw-r--r--src/lib/audio_content.h2
-rw-r--r--src/lib/audio_filter_graph.cc18
-rw-r--r--src/lib/audio_filter_graph.h3
-rw-r--r--src/lib/audio_mapping.cc17
-rw-r--r--src/lib/audio_mapping.h2
-rw-r--r--src/lib/audio_point.cc4
-rw-r--r--src/lib/butler.cc2
-rw-r--r--src/lib/check_content_job.cc2
-rw-r--r--src/lib/cinema.cc112
-rw-r--r--src/lib/cinema.h59
-rw-r--r--src/lib/cinema_list.cc466
-rw-r--r--src/lib/cinema_list.h126
-rw-r--r--src/lib/colour_conversion.cc49
-rw-r--r--src/lib/colour_conversion.h2
-rw-r--r--src/lib/config.cc625
-rw-r--r--src/lib/config.h187
-rw-r--r--src/lib/constants.h2
-rw-r--r--src/lib/content.cc14
-rw-r--r--src/lib/content.h2
-rw-r--r--src/lib/content_video.h11
-rw-r--r--src/lib/cpu_j2k_encoder_thread.cc62
-rw-r--r--src/lib/cpu_j2k_encoder_thread.h16
-rw-r--r--src/lib/create_cli.cc21
-rw-r--r--src/lib/create_cli.h2
-rw-r--r--src/lib/crop.cc10
-rw-r--r--src/lib/crop.h2
-rw-r--r--src/lib/cross.h16
-rw-r--r--src/lib/cross_common.cc107
-rw-r--r--src/lib/cross_linux.cc7
-rw-r--r--src/lib/cross_osx.cc120
-rw-r--r--src/lib/dcp_content.cc214
-rw-r--r--src/lib/dcp_content.h19
-rw-r--r--src/lib/dcp_content_type.cc3
-rw-r--r--src/lib/dcp_decoder.cc123
-rw-r--r--src/lib/dcp_decoder.h18
-rw-r--r--src/lib/dcp_digest_file.cc28
-rw-r--r--src/lib/dcp_examiner.cc59
-rw-r--r--src/lib/dcp_examiner.h13
-rw-r--r--src/lib/dcp_film_encoder.cc (renamed from src/lib/dcp_encoder.cc)99
-rw-r--r--src/lib/dcp_film_encoder.h (renamed from src/lib/dcp_encoder.h)19
-rw-r--r--src/lib/dcp_subtitle_content.cc10
-rw-r--r--src/lib/dcp_subtitle_content.h2
-rw-r--r--src/lib/dcp_text_track.cc4
-rw-r--r--src/lib/dcp_video.cc46
-rw-r--r--src/lib/dcp_video.h11
-rw-r--r--src/lib/dcpomatic_log.h2
-rw-r--r--src/lib/dcpomatic_socket.cc32
-rw-r--r--src/lib/dcpomatic_socket.h9
-rw-r--r--src/lib/dcpomatic_time.cc19
-rw-r--r--src/lib/dcpomatic_time.h7
-rw-r--r--src/lib/decoder_factory.cc21
-rw-r--r--src/lib/dkdm_recipient.cc49
-rw-r--r--src/lib/dkdm_recipient.h18
-rw-r--r--src/lib/dkdm_recipient_list.cc243
-rw-r--r--src/lib/dkdm_recipient_list.h90
-rw-r--r--src/lib/dkdm_wrapper.cc16
-rw-r--r--src/lib/email.cc9
-rw-r--r--src/lib/email.h1
-rw-r--r--src/lib/encode_server.cc18
-rw-r--r--src/lib/encode_server.h6
-rw-r--r--src/lib/encode_server_finder.cc4
-rw-r--r--src/lib/encode_server_finder.h3
-rw-r--r--src/lib/environment_info.cc7
-rw-r--r--src/lib/exceptions.h55
-rw-r--r--src/lib/export_config.cc18
-rw-r--r--src/lib/export_config.h2
-rw-r--r--src/lib/ffmpeg_audio_stream.cc14
-rw-r--r--src/lib/ffmpeg_audio_stream.h2
-rw-r--r--src/lib/ffmpeg_content.cc35
-rw-r--r--src/lib/ffmpeg_content.h2
-rw-r--r--src/lib/ffmpeg_decoder.cc9
-rw-r--r--src/lib/ffmpeg_examiner.cc21
-rw-r--r--src/lib/ffmpeg_file_encoder.cc19
-rw-r--r--src/lib/ffmpeg_file_encoder.h5
-rw-r--r--src/lib/ffmpeg_film_encoder.cc (renamed from src/lib/ffmpeg_encoder.cc)24
-rw-r--r--src/lib/ffmpeg_film_encoder.h (renamed from src/lib/ffmpeg_encoder.h)6
-rw-r--r--src/lib/ffmpeg_image_proxy.cc4
-rw-r--r--src/lib/ffmpeg_image_proxy.h4
-rw-r--r--src/lib/ffmpeg_stream.cc6
-rw-r--r--src/lib/ffmpeg_stream.h2
-rw-r--r--src/lib/ffmpeg_subtitle_stream.cc10
-rw-r--r--src/lib/ffmpeg_subtitle_stream.h2
-rw-r--r--src/lib/film.cc418
-rw-r--r--src/lib/film.h49
-rw-r--r--src/lib/film_encoder.cc (renamed from src/lib/encoder.cc)8
-rw-r--r--src/lib/film_encoder.h (renamed from src/lib/encoder.h)19
-rw-r--r--src/lib/film_property.h4
-rw-r--r--src/lib/filter.cc3
-rw-r--r--src/lib/font.cc6
-rw-r--r--src/lib/font.h2
-rw-r--r--src/lib/font_id_allocator.cc30
-rw-r--r--src/lib/font_id_allocator.h2
-rw-r--r--src/lib/frame_info.cc84
-rw-r--r--src/lib/frame_info.h45
-rw-r--r--src/lib/grok/context.h291
-rw-r--r--src/lib/grok/messenger.h906
-rw-r--r--src/lib/grok_j2k_encoder_thread.cc72
-rw-r--r--src/lib/grok_j2k_encoder_thread.h41
-rw-r--r--src/lib/hints.cc62
-rw-r--r--src/lib/hints.h12
-rw-r--r--src/lib/http_server.cc255
-rw-r--r--src/lib/http_server.h91
-rw-r--r--src/lib/id.cc30
-rw-r--r--src/lib/id.h48
-rw-r--r--src/lib/image_content.cc8
-rw-r--r--src/lib/image_content.h2
-rw-r--r--src/lib/image_decoder.cc2
-rw-r--r--src/lib/image_proxy.h4
-rw-r--r--src/lib/internal_player_server.cc48
-rw-r--r--src/lib/internal_player_server.h46
-rw-r--r--src/lib/j2k_encoder.cc416
-rw-r--r--src/lib/j2k_encoder.h52
-rw-r--r--src/lib/j2k_encoder_thread.cc58
-rw-r--r--src/lib/j2k_encoder_thread.h53
-rw-r--r--src/lib/j2k_image_proxy.cc20
-rw-r--r--src/lib/j2k_image_proxy.h10
-rw-r--r--src/lib/j2k_sync_encoder_thread.cc65
-rw-r--r--src/lib/j2k_sync_encoder_thread.h32
-rw-r--r--src/lib/job.cc38
-rw-r--r--src/lib/job.h3
-rw-r--r--src/lib/kdm_cli.cc297
-rw-r--r--src/lib/kdm_recipient.cc8
-rw-r--r--src/lib/kdm_with_metadata.h7
-rw-r--r--src/lib/log_entry.cc1
-rw-r--r--src/lib/log_entry.h1
-rw-r--r--src/lib/make_dcp.cc17
-rw-r--r--src/lib/make_dcp.h2
-rw-r--r--src/lib/map_cli.cc4
-rw-r--r--src/lib/mpeg2_encoder.cc80
-rw-r--r--src/lib/mpeg2_encoder.h42
-rw-r--r--src/lib/overlaps.cc3
-rw-r--r--src/lib/overlaps.h7
-rw-r--r--src/lib/pixel_quanta.cc4
-rw-r--r--src/lib/player.cc274
-rw-r--r--src/lib/player.h11
-rw-r--r--src/lib/player_video.cc45
-rw-r--r--src/lib/player_video.h8
-rw-r--r--src/lib/playlist.cc10
-rw-r--r--src/lib/playlist.h4
-rw-r--r--src/lib/raw_image_proxy.cc10
-rw-r--r--src/lib/raw_image_proxy.h2
-rw-r--r--src/lib/reel_writer.cc261
-rw-r--r--src/lib/reel_writer.h40
-rw-r--r--src/lib/release_notes.cc64
-rw-r--r--src/lib/remembered_asset.cc97
-rw-r--r--src/lib/remembered_asset.h79
-rw-r--r--src/lib/remote_j2k_encoder_thread.cc84
-rw-r--r--src/lib/remote_j2k_encoder_thread.h21
-rw-r--r--src/lib/render_text.cc56
-rw-r--r--src/lib/rgba.cc10
-rw-r--r--src/lib/rgba.h2
-rw-r--r--src/lib/screen.cc58
-rw-r--r--src/lib/screen.h17
-rw-r--r--src/lib/send_problem_report_job.cc11
-rw-r--r--src/lib/server.cc19
-rw-r--r--src/lib/server.h1
-rw-r--r--src/lib/shuffler.cc18
-rw-r--r--src/lib/spl.cc6
-rw-r--r--src/lib/spl_entry.cc2
-rw-r--r--src/lib/sqlite_statement.cc111
-rw-r--r--src/lib/sqlite_statement.h50
-rw-r--r--src/lib/sqlite_table.cc79
-rw-r--r--src/lib/sqlite_table.h54
-rw-r--r--src/lib/sqlite_transaction.cc50
-rw-r--r--src/lib/sqlite_transaction.h40
-rw-r--r--src/lib/state.cc2
-rw-r--r--src/lib/string_text_file_content.cc10
-rw-r--r--src/lib/string_text_file_content.h2
-rw-r--r--src/lib/subtitle_analysis.cc16
-rw-r--r--src/lib/subtitle_film_encoder.cc (renamed from src/lib/subtitle_encoder.cc)14
-rw-r--r--src/lib/subtitle_film_encoder.h (renamed from src/lib/subtitle_encoder.h)8
-rw-r--r--src/lib/text_content.cc80
-rw-r--r--src/lib/text_content.h6
-rw-r--r--src/lib/text_decoder.cc5
-rw-r--r--src/lib/transcode_job.cc28
-rw-r--r--src/lib/transcode_job.h12
-rw-r--r--src/lib/types.h6
-rw-r--r--src/lib/unzipper.cc14
-rw-r--r--src/lib/unzipper.h3
-rw-r--r--src/lib/util.cc84
-rw-r--r--src/lib/util.h19
-rw-r--r--src/lib/variant.cc179
-rw-r--r--src/lib/variant.h56
-rw-r--r--src/lib/verify_dcp_job.cc4
-rw-r--r--src/lib/verify_dcp_job.h6
-rw-r--r--src/lib/video_content.cc58
-rw-r--r--src/lib/video_content.h4
-rw-r--r--src/lib/video_decoder.cc111
-rw-r--r--src/lib/video_decoder.h2
-rw-r--r--src/lib/video_encoder.cc64
-rw-r--r--src/lib/video_encoder.h69
-rw-r--r--src/lib/video_encoding.cc58
-rw-r--r--src/lib/video_encoding.h42
-rw-r--r--src/lib/video_mxf_content.cc16
-rw-r--r--src/lib/video_mxf_content.h2
-rw-r--r--src/lib/video_mxf_decoder.cc18
-rw-r--r--src/lib/video_mxf_decoder.h8
-rw-r--r--src/lib/video_mxf_examiner.cc8
-rw-r--r--src/lib/writer.cc41
-rw-r--r--src/lib/writer.h14
-rw-r--r--src/lib/wscript32
-rw-r--r--src/tools/dcpomatic.cc272
-rw-r--r--src/tools/dcpomatic_batch.cc67
-rw-r--r--src/tools/dcpomatic_cli.cc21
-rw-r--r--src/tools/dcpomatic_combiner.cc25
-rw-r--r--src/tools/dcpomatic_disk.cc60
-rw-r--r--src/tools/dcpomatic_editor.cc27
-rw-r--r--src/tools/dcpomatic_kdm.cc57
-rw-r--r--src/tools/dcpomatic_player.cc186
-rw-r--r--src/tools/dcpomatic_playlist.cc67
-rw-r--r--src/tools/dcpomatic_server.cc15
-rw-r--r--src/tools/dcpomatic_server_cli.cc29
-rw-r--r--src/tools/dcpomatic_verifier.cc251
-rw-r--r--src/tools/wscript89
-rw-r--r--src/wx/about_dialog.cc359
-rw-r--r--src/wx/audio_dialog.cc5
-rw-r--r--src/wx/audio_panel.cc57
-rw-r--r--src/wx/audio_panel.h3
-rw-r--r--src/wx/check_box.cc7
-rw-r--r--src/wx/check_box.h1
-rw-r--r--src/wx/cinema_dialog.cc45
-rw-r--r--src/wx/cinema_dialog.h11
-rw-r--r--src/wx/colours.h25
-rw-r--r--src/wx/config_dialog.cc6
-rw-r--r--src/wx/content_menu.cc51
-rw-r--r--src/wx/content_menu.h2
-rw-r--r--src/wx/content_panel.cc30
-rw-r--r--src/wx/content_panel.h4
-rw-r--r--src/wx/content_sub_panel.cc23
-rw-r--r--src/wx/content_sub_panel.h1
-rw-r--r--src/wx/content_timeline.cc1021
-rw-r--r--src/wx/content_timeline.h149
-rw-r--r--src/wx/content_timeline_atmos_view.cc (renamed from src/wx/timeline_atmos_content_view.cc)15
-rw-r--r--src/wx/content_timeline_atmos_view.h (renamed from src/wx/timeline_atmos_content_view.h)8
-rw-r--r--src/wx/content_timeline_audio_view.cc (renamed from src/wx/timeline_audio_content_view.cc)23
-rw-r--r--src/wx/content_timeline_audio_view.h (renamed from src/wx/timeline_audio_content_view.h)8
-rw-r--r--src/wx/content_timeline_dialog.cc (renamed from src/wx/timeline_dialog.cc)38
-rw-r--r--src/wx/content_timeline_dialog.h (renamed from src/wx/timeline_dialog.h)8
-rw-r--r--src/wx/content_timeline_text_view.cc (renamed from src/wx/timeline_text_content_view.cc)13
-rw-r--r--src/wx/content_timeline_text_view.h (renamed from src/wx/timeline_text_content_view.h)12
-rw-r--r--src/wx/content_timeline_video_view.cc (renamed from src/wx/timeline_video_content_view.cc)18
-rw-r--r--src/wx/content_timeline_video_view.h (renamed from src/wx/timeline_video_content_view.h)8
-rw-r--r--src/wx/content_timeline_view.cc42
-rw-r--r--src/wx/content_timeline_view.h60
-rw-r--r--src/wx/content_view.cc3
-rw-r--r--src/wx/dcp_panel.cc233
-rw-r--r--src/wx/dcp_panel.h17
-rw-r--r--src/wx/dcp_referencing_dialog.cc231
-rw-r--r--src/wx/dcp_referencing_dialog.h70
-rw-r--r--src/wx/dcp_timeline.cc617
-rw-r--r--src/wx/dcp_timeline.h125
-rw-r--r--src/wx/dcp_timeline_dialog.cc78
-rw-r--r--src/wx/dcp_timeline_dialog.h39
-rw-r--r--src/wx/dcp_timeline_reel_marker_view.cc71
-rw-r--r--src/wx/dcp_timeline_reel_marker_view.h59
-rw-r--r--src/wx/dcp_timeline_view.h44
-rw-r--r--src/wx/dcpomatic_choice.cc22
-rw-r--r--src/wx/dcpomatic_choice.h10
-rw-r--r--src/wx/dir_picker_ctrl.cc11
-rw-r--r--src/wx/dir_picker_ctrl.h3
-rw-r--r--src/wx/disk_warning_dialog.cc7
-rw-r--r--src/wx/dkdm_dialog.cc11
-rw-r--r--src/wx/export_video_file_dialog.cc7
-rw-r--r--src/wx/export_video_file_dialog.h2
-rw-r--r--src/wx/file_picker_ctrl.cc4
-rw-r--r--src/wx/film_editor.cc33
-rw-r--r--src/wx/film_editor.h8
-rw-r--r--src/wx/film_name_location_dialog.cc5
-rw-r--r--src/wx/film_viewer.cc32
-rw-r--r--src/wx/film_viewer.h16
-rw-r--r--src/wx/full_config_dialog.cc260
-rw-r--r--src/wx/gl_video_view.cc116
-rw-r--r--src/wx/gl_video_view.h7
-rw-r--r--src/wx/grok/gpu_config_panel.h227
-rw-r--r--src/wx/id.h1
-rw-r--r--src/wx/job_view.cc3
-rw-r--r--src/wx/kdm_cpl_panel.cc7
-rw-r--r--src/wx/kdm_dialog.cc36
-rw-r--r--src/wx/kdm_dialog.h1
-rw-r--r--src/wx/kdm_timing_panel.cc71
-rw-r--r--src/wx/kdm_timing_panel.h27
-rw-r--r--src/wx/load_config_from_zip_dialog.cc59
-rw-r--r--src/wx/load_config_from_zip_dialog.h41
-rw-r--r--src/wx/metadata_dialog.cc10
-rw-r--r--src/wx/optimisation.h37
-rw-r--r--src/wx/player_config_dialog.cc40
-rw-r--r--src/wx/player_information.cc8
-rw-r--r--src/wx/playlist_controls.cc5
-rw-r--r--src/wx/playlist_editor_config_dialog.cc7
-rw-r--r--src/wx/recipient_dialog.cc43
-rw-r--r--src/wx/recipient_dialog.h6
-rw-r--r--src/wx/recipients_panel.cc67
-rw-r--r--src/wx/recipients_panel.h7
-rw-r--r--src/wx/save_template_dialog.cc56
-rw-r--r--src/wx/save_template_dialog.h12
-rw-r--r--src/wx/screens_panel.cc271
-rw-r--r--src/wx/screens_panel.h52
-rw-r--r--src/wx/system_information_dialog.cc5
-rw-r--r--src/wx/text_panel.cc55
-rw-r--r--src/wx/text_panel.h3
-rw-r--r--src/wx/text_view.cc34
-rw-r--r--src/wx/text_view.h10
-rw-r--r--src/wx/timecode.cc2
-rw-r--r--src/wx/timecode.h13
-rw-r--r--src/wx/timeline.cc1001
-rw-r--r--src/wx/timeline.h131
-rw-r--r--src/wx/timeline_content_view.cc7
-rw-r--r--src/wx/timeline_content_view.h6
-rw-r--r--src/wx/timeline_labels_view.cc6
-rw-r--r--src/wx/timeline_labels_view.h6
-rw-r--r--src/wx/timeline_reels_view.cc6
-rw-r--r--src/wx/timeline_reels_view.h6
-rw-r--r--src/wx/timeline_time_axis_view.cc6
-rw-r--r--src/wx/timeline_time_axis_view.h8
-rw-r--r--src/wx/timeline_view.cc69
-rw-r--r--src/wx/timeline_view.h42
-rw-r--r--src/wx/try_unmount_dialog.cc8
-rw-r--r--src/wx/update_dialog.cc5
-rw-r--r--src/wx/verify_dcp_progress_dialog.cc52
-rw-r--r--src/wx/verify_dcp_progress_dialog.h9
-rw-r--r--src/wx/verify_dcp_progress_panel.cc87
-rw-r--r--src/wx/verify_dcp_progress_panel.h44
-rw-r--r--src/wx/verify_dcp_result_dialog.cc47
-rw-r--r--src/wx/verify_dcp_result_dialog.h (renamed from src/wx/verify_dcp_dialog.h)4
-rw-r--r--src/wx/verify_dcp_result_panel.cc (renamed from src/wx/verify_dcp_dialog.cc)236
-rw-r--r--src/wx/verify_dcp_result_panel.h50
-rw-r--r--src/wx/video_panel.cc56
-rw-r--r--src/wx/video_panel.h3
-rw-r--r--src/wx/video_view.h7
-rw-r--r--src/wx/window_metrics.cc45
-rw-r--r--src/wx/window_metrics.h39
-rw-r--r--src/wx/wscript26
-rw-r--r--src/wx/wx_util.cc151
-rw-r--r--src/wx/wx_util.h20
-rw-r--r--src/wx/wx_variant.cc147
-rw-r--r--src/wx/wx_variant.h54
-rw-r--r--test/2536_regression_test.cc6
-rw-r--r--test/4k_test.cc10
-rw-r--r--test/atmos_test.cc14
-rw-r--r--test/audio_analysis_test.cc82
-rw-r--r--test/audio_content_test.cc26
-rw-r--r--test/audio_delay_test.cc2
-rw-r--r--test/audio_processor_test.cc5
-rw-r--r--test/burnt_subtitle_test.cc63
-rw-r--r--test/butler_test.cc12
-rw-r--r--test/bv20_test.cc7
-rw-r--r--test/cinema_list_test.cc226
-rw-r--r--test/client_server_test.cc44
-rw-r--r--test/closed_caption_test.cc4
-rw-r--r--test/config_test.cc445
-rw-r--r--test/content_test.cc35
-rw-r--r--test/cpl_hash_test.cc4
-rw-r--r--test/cpl_metadata_test.cc4
-rw-r--r--test/create_cli_test.cc32
m---------test/data0
-rw-r--r--test/dcp_decoder_test.cc12
-rw-r--r--test/dcp_digest_file_test.cc4
-rw-r--r--test/dcp_examiner_test.cc4
-rw-r--r--test/dcp_metadata_test.cc4
-rw-r--r--test/dcp_playback_test.cc4
-rw-r--r--test/dcp_subtitle_test.cc47
-rw-r--r--test/digest_test.cc8
-rw-r--r--test/disk_writer_test.cc107
-rw-r--r--test/dkdm_recipient_list_test.cc57
-rw-r--r--test/empty_caption_test.cc2
-rw-r--r--test/empty_test.cc8
-rw-r--r--test/encryption_test.cc2
-rw-r--r--test/ffmpeg_audio_only_test.cc6
-rw-r--r--test/ffmpeg_audio_test.cc12
-rw-r--r--test/ffmpeg_dcp_test.cc12
-rw-r--r--test/ffmpeg_decoder_error_test.cc4
-rw-r--r--test/ffmpeg_decoder_seek_test.cc59
-rw-r--r--test/ffmpeg_decoder_sequential_test.cc5
-rw-r--r--test/ffmpeg_encoder_test.cc158
-rw-r--r--test/ffmpeg_examiner_test.cc4
-rw-r--r--test/ffmpeg_properties_test.cc2
-rw-r--r--test/ffmpeg_pts_offset_test.cc4
-rw-r--r--test/file_extension_test.cc4
-rw-r--r--test/file_naming_test.cc23
-rw-r--r--test/film_metadata_test.cc18
-rw-r--r--test/film_test.cc8
-rw-r--r--test/find_missing_test.cc6
-rw-r--r--test/font_id_allocator_test.cc19
-rw-r--r--test/frame_rate_test.cc26
-rw-r--r--test/guess_crop_test.cc6
-rw-r--r--test/hints_test.cc27
-rw-r--r--test/image_content_fade_test.cc2
-rw-r--r--test/image_filename_sorter_test.cc9
-rw-r--r--test/import_dcp_test.cc34
-rw-r--r--test/interrupt_encoder_test.cc8
-rw-r--r--test/isdcf_name_test.cc12
-rw-r--r--test/j2k_encode_threading_test.cc117
-rw-r--r--test/j2k_encoder_test.cc6
-rw-r--r--test/j2k_video_bit_rate_test.cc (renamed from test/j2k_bandwidth_test.cc)19
-rw-r--r--test/kdm_cli_test.cc166
-rw-r--r--test/kdm_naming_test.cc98
-rw-r--r--test/low_bitrate_test.cc3
-rw-r--r--test/map_cli_test.cc36
-rw-r--r--test/markers_test.cc8
-rw-r--r--test/mca_subdescriptors_test.cc2
-rw-r--r--test/no_use_video_test.cc19
-rw-r--r--test/optimise_stills_test.cc19
-rw-r--r--test/overlap_video_test.cc8
-rw-r--r--test/player_test.cc116
-rw-r--r--test/playlist_test.cc2
-rw-r--r--test/pulldown_detect_test.cc2
-rw-r--r--test/recover_test.cc76
-rw-r--r--test/reel_writer_test.cc45
-rw-r--r--test/reels_test.cc123
-rw-r--r--test/release_notes_test.cc6
-rw-r--r--test/remake_id_test.cc6
-rw-r--r--test/remake_video_test.cc6
-rw-r--r--test/remake_with_subtitle_test.cc9
-rw-r--r--test/repeat_frame_test.cc2
-rw-r--r--test/required_disk_space_test.cc13
-rw-r--r--test/scaling_test.cc12
-rw-r--r--test/shuffler_test.cc8
-rw-r--r--test/silence_padding_test.cc4
-rw-r--r--test/skip_frame_test.cc11
-rw-r--r--test/srt_subtitle_test.cc52
-rw-r--r--test/ssa_subtitle_test.cc2
-rw-r--r--test/stream_test.cc36
-rw-r--r--test/subtitle_charset_test.cc4
-rw-r--r--test/subtitle_font_id_change_test.cc12
-rw-r--r--test/subtitle_font_id_test.cc67
-rw-r--r--test/subtitle_language_test.cc6
-rw-r--r--test/subtitle_position_test.cc6
-rw-r--r--test/subtitle_reel_number_test.cc12
-rw-r--r--test/subtitle_reel_test.cc8
-rw-r--r--test/subtitle_timing_test.cc5
-rw-r--r--test/subtitle_trim_test.cc2
-rw-r--r--test/template_test.cc11
-rw-r--r--test/test.cc99
-rw-r--r--test/test.h13
-rw-r--r--test/text_entry_point_test.cc70
-rw-r--r--test/threed_test.cc41
-rw-r--r--test/time_calculation_test.cc8
-rw-r--r--test/torture_test.cc14
-rw-r--r--test/upmixer_a_test.cc10
-rw-r--r--test/vf_kdm_test.cc40
-rw-r--r--test/vf_test.cc127
-rw-r--r--test/video_level_test.cc44
-rw-r--r--test/video_mxf_content_test.cc20
-rw-r--r--test/writer_test.cc16
-rw-r--r--test/wscript8
-rw-r--r--web/index.html90
-rw-r--r--web/wscript5
-rw-r--r--wscript81
682 files changed, 17670 insertions, 8270 deletions
diff --git a/DEVELOP.md b/DEVELOP.md
index 51b63ff5c..5c16c6a18 100644
--- a/DEVELOP.md
+++ b/DEVELOP.md
@@ -118,3 +118,15 @@ The manual PDF looks nice if vector screenshots are used. These can be taken as
- 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`
+
+
+## Adding a new variant
+
+Files to edit:
+- `cscript`
+- `wscript`
+- `src/lib/variant.cc`
+- `src/tools/wscript`
+- `platform/osx/make_dmg.sh`
+- `platform/windows/wscript`
+- `platform/osx/wscript`
diff --git a/cscript b/cscript
index 169598569..dd181b6fc 100644
--- a/cscript
+++ b/cscript
@@ -19,12 +19,19 @@
#
from __future__ import print_function
+import datetime
import glob
import shutil
import os
import copy
import json
+def dmg_prefix(variant):
+ return 'DCP-o-matic'
+
+def debian_name(variant):
+ return 'dcpomatic'
+
deb_build_depends = dict()
deb_build_depends_base = ['debhelper', 'g++', 'pkg-config', 'libsndfile1-dev', 'libgtk2.0-dev', 'libx264-dev']
@@ -353,9 +360,9 @@ def packages(name, packages, f):
s += str(p) + ', '
print(s[:-2], file=f)
-def make_control(debian_version, bits, filename, debug, gui):
+def make_control(debian_version, bits, filename, debug, gui, name):
f = open(filename, 'w')
- print('Source: dcpomatic', file=f)
+ print(f'Source: {name}', file=f)
print('Section: video', file=f)
print('Priority: extra', file=f)
print('Maintainer: Carl Hetherington <carl@dcpomatic.com>', file=f)
@@ -364,9 +371,9 @@ def make_control(debian_version, bits, filename, debug, gui):
print('Homepage: https://dcpomatic.com/', file=f)
print('', file=f)
suffix = '' if gui else '-cli'
- print(f'Package: dcpomatic{suffix}', file=f)
+ print(f'Package: {name}{suffix}', file=f)
if gui:
- print('Replaces: dcpomatic-cli', file=f)
+ print(f'Replaces: {name}-cli', file=f)
if bits == 32:
print('Architecture: i386', file=f)
else:
@@ -387,7 +394,7 @@ def make_control(debian_version, bits, filename, debug, gui):
if debug:
print('', file=f)
- print(f'Package: dcpomatic{suffix}-dbg', file=f)
+ print(f'Package: {name}{suffix}-dbg', file=f)
if bits == 32:
print('Architecture: i386', file=f)
else:
@@ -395,8 +402,8 @@ def make_control(debian_version, bits, filename, debug, gui):
print('Section: debug', file=f)
print('Priority: extra', file=f)
packages('Depends', pkg, f)
- print('Description: debugging symbols for dcpomatic', file=f)
- print(' This package contains the debugging symbols for dcpomatic.', file=f)
+ print(f'Description: debugging symbols for {name}', file=f)
+ print(f' This package contains the debugging symbols for {name}.', file=f)
print('', file=f)
def make_spec(filename, version, target, options, requires=None):
@@ -430,7 +437,8 @@ def make_spec(filename, version, target, options, requires=None):
print('%{_bindir}/dcpomatic2_playlist', file=f)
print('%{_bindir}/dcpomatic2_openssl', file=f)
print('%{_bindir}/dcpomatic2_combiner', file=f)
- print('%{_bindir}/dcpomatic2_verify', file=f)
+ print('%{_bindir}/dcpomatic2_verify_cli', file=f)
+ print('%{_bindir}/dcpomatic2_verifier', file=f)
print('%{_bindir}/dcpomatic2_kdm_inspect', file=f)
print('%{_bindir}/dcpomatic2_map', file=f)
if can_build_disk(target):
@@ -439,6 +447,7 @@ def make_spec(filename, version, target, options, requires=None):
print('%{_datadir}/applications/dcpomatic2.desktop', file=f)
print('%{_datadir}/applications/dcpomatic2_batch.desktop', file=f)
print('%{_datadir}/applications/dcpomatic2_editor.desktop', file=f)
+ print('%{_datadir}/applications/dcpomatic2_verifier.desktop', file=f)
print('%{_datadir}/applications/dcpomatic2_server.desktop', file=f)
print('%{_datadir}/applications/dcpomatic2_kdm.desktop', file=f)
print('%{_datadir}/applications/dcpomatic2_player.desktop', file=f)
@@ -472,6 +481,7 @@ def make_spec(filename, version, target, options, requires=None):
print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2.png' % r, file=f)
print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_batch.png' % r, file=f)
print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_editor.png' % r, file=f)
+ print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_verifier.png' % r, file=f)
print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_kdm.png' % r, file=f)
print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_server.png' % r, file=f)
print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_player.png' % r, file=f)
@@ -506,7 +516,7 @@ def make_spec(filename, version, target, options, requires=None):
print('/bin/cp -r %s/src/libdcp/tags %%{buildroot}/usr/share/libdcp' % target.directory, file=f)
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/dcpverify %%{buildroot}/usr/bin/dcpomatic2_verify_cli' % 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)
@@ -523,19 +533,7 @@ def make_spec(filename, version, target, options, requires=None):
print('/usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :', file=f)
def dependencies(target, options):
-
- if target.platform == 'linux':
- ffmpeg_options = { 'shared': False }
- else:
- ffmpeg_options = {}
-
- if target.platform != 'linux' or target.distro != 'arch':
- deps = [('ffmpeg', '7276e269a93c2ae30e302c34708e8095ac5475e8', ffmpeg_options)]
- else:
- # Use distro-provided FFmpeg on Arch
- deps = []
-
- deps.append(('libdcp', 'v1.8.101'))
+ deps = [('libdcp', 'v1.9.11', {'c++17': target.platform == 'osx'})]
deps.append(('libsub', 'v1.6.49'))
deps.append(('leqm-nrt', '30dcaea1373ac62fba050e02ce5b0c1085797a23'))
deps.append(('rtaudio', 'f619b76'))
@@ -546,7 +544,7 @@ def dependencies(target, options):
deps.append(('openssl', '54298369cacfe0ae01c5aa42ace8a463fd2e7a2e'))
if can_build_disk(target):
deps.append(('lwext4', 'ab082923a791b58478d1d9939d65a0583566ac1f'))
- deps.append(('ffcmp', '53c853d2935de3f2b0d53777529e48c102afd237'))
+ deps.append(('ffcmp', 'd7fabbe7bf85a7fc25963cfbeb9be83eb9edaef3'))
return deps
@@ -578,7 +576,7 @@ def configure_options(target, options, for_package=False):
if not options['gui']:
opt += ' --disable-gui'
- if options['variant'] is not None:
+ if options['variant']:
opt += ' --variant=%s' % options['variant']
# Build Windows debug versions with static linking as I think gdb works better then
@@ -588,8 +586,13 @@ def configure_options(target, options, for_package=False):
if can_build_disk(target):
opt += ' --enable-disk'
- if target.platform == 'osx' and target.arch == 'arm64':
- opt += ' --wx-config=%s/wx-config' % target.bin
+ if target.platform == 'osx':
+ opt += ' --c++17'
+ if target.arch == 'arm64':
+ opt += ' --wx-config=%s/wx-config' % target.bin
+
+ if target.platform == 'linux' and target.distro == 'ubuntu' and target.version in ['22.04']:
+ opt += ' --enable-grok'
return opt
@@ -714,13 +717,11 @@ def build(target, options, for_package):
target.command('./waf install')
def package_windows(target):
- identifier = ''
- if target.version is not None:
- identifier = '%s.' % target.version
- identifier += '%d' % target.bits
+ identifier = '%d' % target.bits
shutil.copyfile('build/platform/windows/installer.%s.nsi' % identifier, 'build/platform/windows/installer2.%s.nsi' % identifier)
target.command('sed -i "s~%%resources%%~%s/platform/windows~g" build/platform/windows/installer2.%s.nsi' % (os.getcwd(), identifier))
target.command('sed -i "s~%%graphics%%~%s/graphics~g" build/platform/windows/installer2.%s.nsi' % (os.getcwd(), identifier))
+ target.command('sed -i "s~%%web%%~%s/web~g" build/platform/windows/installer2.%s.nsi' % (os.getcwd(), identifier))
target.command('sed -i "s~%%static_deps%%~%s~g" build/platform/windows/installer2.%s.nsi' % (target.windows_prefix, identifier))
target.command('sed -i "s~%%cdist_deps%%~%s~g" build/platform/windows/installer2.%s.nsi' % (target.directory, identifier))
target.command('sed -i "s~%%mingw%%~%s~g" build/platform/windows/installer2.%s.nsi' % (target.environment_prefix, identifier))
@@ -730,7 +731,8 @@ def package_windows(target):
return os.path.abspath(glob.glob('build/platform/windows/*%s*.exe' % target.bits)[0])
def package_debian(target, cpu, version, options):
- make_control(target.version, target.bits, 'debian/control', target.debug, options['gui'])
+ name = debian_name(options['variant'])
+ make_control(target.version, target.bits, 'debian/control', target.debug, options['gui'], name)
if target.version != '9' and target.version != '16.04' and options['gui']:
with open('debian/postinst', 'w') as f:
print('#!/bin/sh', file=f)
@@ -739,23 +741,29 @@ def package_debian(target, cpu, version, options):
target.command('./waf dist')
f = open('debian/files', 'w')
suffix = '' if options['gui'] else '-cli'
- print(f'dcpomatic{suffix}_{version}-1_{cpu}.deb video extra', file=f)
+ print(f'{name}{suffix}_{version}-1_{cpu}.deb video extra', file=f)
shutil.rmtree('build/deb', ignore_errors=True)
os.makedirs('build/deb')
os.chdir('build/deb')
- shutil.move('../../dcpomatic-%s.tar.bz2' % version, 'dcpomatic_%s.orig.tar.bz2' % version)
- target.command('tar xjf dcpomatic_%s.orig.tar.bz2' % version)
- os.chdir('dcpomatic-%s' % version)
- target.set('EMAIL', 'carl@dcpomatic.com')
- target.command('dch -b -v %s-1 "New upstream release."' % version)
+ shutil.move(f'../../dcpomatic-{version}.tar.bz2', f'{name}_1+{version}.orig.tar.bz2')
+ target.command(f'tar xjf {name}_1+{version}.orig.tar.bz2')
+ os.chdir(f'dcpomatic-{version}')
+
+ with open('debian/changelog', 'w') as f:
+ print(f'{name} (1+{version}-1) unstable; urgency=medium', file=f)
+ print('', file=f)
+ print(' * New upstream release.', file=f)
+ print('', file=f)
+ print(f" -- Carl Hetherington <carl@dcpomatic.com> {datetime.datetime.now().astimezone().strftime('%a, %d %b %Y %H:%M:%S %z')}", file=f)
+
target.set('CDIST_LINKFLAGS', target.get('LINKFLAGS'))
target.set('CDIST_CXXFLAGS', target.get('CXXFLAGS'))
target.set('CDIST_PKG_CONFIG_PATH', target.get('PKG_CONFIG_PATH'))
target.set('CDIST_DIRECTORY', target.directory)
target.set('CDIST_CONFIGURE', '"' + configure_options(target, options, for_package=True) + '"')
- target.set('CDIST_PACKAGE', f'dcpomatic{suffix}')
+ target.set('CDIST_PACKAGE', f'{name}{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')
@@ -809,7 +817,7 @@ def make_appimage(target, nice_name, internal_name, version, extra_binaries=None
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/dcpverify {appdir}/usr/bin/dcpomatic2_verify_cli')
target.command(f'cp {target.directory}/bin/dcpkdm {appdir}/usr/bin/dcpomatic2_kdm_inspect')
if extra_binaries:
for bin in extra_binaries:
@@ -871,6 +879,7 @@ def package(target, version, options):
out.append(make_appimage(target, 'DCP-o-matic Encode Server', 'dcpomatic2_server', version))
out.append(make_appimage(target, 'DCP-o-matic Combiner', 'dcpomatic2_combiner', version))
out.append(make_appimage(target, 'DCP-o-matic Editor', 'dcpomatic2_editor', version))
+ out.append(make_appimage(target, 'DCP-o-matic Verifier', 'dcpomatic2_verifier', version))
return out
else:
if target.bits == 32:
@@ -887,8 +896,10 @@ def package(target, version, options):
cmd = 'bash platform/osx/make_dmg.sh -e %s -r %s -i %s -p %s %s' % (target.environment_prefix, target.directory, target.apple_id, target.apple_password, archs)
if 'part' in options:
cmd += ' -b ' + options['part']
+ if options['variant']:
+ cmd += ' -v ' + options['variant']
target.command(cmd)
- return glob.glob('build/platform/osx/DCP-o-matic*.dmg')
+ return glob.glob('build/platform/osx/' + dmg_prefix(options['variant']) + '*.dmg')
elif target.platform == 'docker':
shutil.copyfile(target.deb, 'build/platform/docker')
f = open('build/platform/docker/Dockerfile', 'w')
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index fa6591405..000000000
--- a/debian/changelog
+++ /dev/null
@@ -1,476 +0,0 @@
-dcpomatic (2.13.73-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@main.carlh.net> Mon, 26 Nov 2018 01:33:16 +0000
-
-dcpomatic (0.87-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Fri, 26 Apr 2013 09:53:27 +0100
-
-dcpomatic (0.86-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 23 Apr 2013 08:13:13 +0100
-
-dcpomatic (0.85-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 23 Apr 2013 00:08:20 +0100
-
-dcpomatic (0.84-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 21 Apr 2013 17:49:54 +0100
-
-dcpomatic (0.84beta5-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 21 Apr 2013 00:06:12 +0100
-
-dcpomatic (0.84beta4-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Fri, 19 Apr 2013 17:41:58 +0100
-
-dcpomatic (0.84beta3-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Fri, 19 Apr 2013 11:36:37 +0100
-
-dcpomatic (0.84beta2-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Fri, 19 Apr 2013 11:12:09 +0100
-
-dcpomatic (0.84beta1-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 18 Apr 2013 23:32:17 +0100
-
-dcpomatic (0.83-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Wed, 10 Apr 2013 12:48:25 +0100
-
-dcpomatic (0.82-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 09 Apr 2013 23:43:35 +0100
-
-dcpomatic (0.82beta1-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 09 Apr 2013 21:48:56 +0100
-
-dcpomatic (0.81-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 09 Apr 2013 19:48:04 +0100
-
-dcpomatic (0.81beta1-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 09 Apr 2013 15:37:32 +0100
-
-dcpomatic (0.80-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 07 Apr 2013 23:48:12 +0100
-
-dcpomatic (0.80beta4-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 07 Apr 2013 23:08:49 +0100
-
-dcpomatic (0.80beta3-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 07 Apr 2013 22:44:29 +0100
-
-dcpomatic (0.80beta2-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 07 Apr 2013 22:19:34 +0100
-
-dcpomatic (0.80beta1-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 07 Apr 2013 18:21:33 +0100
-
-dcpomatic (0.79-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Mon, 01 Apr 2013 22:37:03 +0100
-
-dcpomatic (0.78-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 31 Mar 2013 02:43:03 +0100
-
-dcpomatic (0.78beta16-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 28 Mar 2013 16:28:05 +0000
-
-dcpomatic (0.78beta15-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 28 Mar 2013 14:25:56 +0000
-
-dcpomatic (0.78beta14-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 28 Mar 2013 10:38:07 +0000
-
-dcpomatic (0.78beta13-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Wed, 27 Mar 2013 12:26:55 +0000
-
-dcpomatic (0.78beta12-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 26 Mar 2013 21:13:54 +0000
-
-dcpomatic (0.78beta11-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 26 Mar 2013 17:34:49 +0000
-
-dcpomatic (0.78beta10-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 26 Mar 2013 11:35:15 +0000
-
-dcpomatic (0.78beta9-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 26 Mar 2013 10:36:05 +0000
-
-dcpomatic (0.78beta8-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 26 Mar 2013 00:59:36 +0000
-
-dcpomatic (0.78beta7-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 26 Mar 2013 00:19:21 +0000
-
-dcpomatic (0.78beta6-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Mon, 25 Mar 2013 00:08:10 +0000
-
-dcpomatic (0.78beta5-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 21 Mar 2013 16:32:21 +0000
-
-dcpomatic (0.78beta4-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Wed, 20 Mar 2013 15:01:10 +0000
-
-dcpomatic (0.78beta3-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Wed, 20 Mar 2013 10:49:17 +0000
-
-dcpomatic (0.78beta2-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 19 Mar 2013 21:35:50 +0000
-
-dcpomatic (0.78beta1-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 19 Mar 2013 20:50:54 +0000
-
-dcpomatic (0.77-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 14 Mar 2013 17:12:03 +0000
-
-dcpomatic (0.77beta2-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 14 Mar 2013 15:50:43 +0000
-
-dcpomatic (0.77beta1-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 14 Mar 2013 15:14:01 +0000
-
-dcpomatic (0.76-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 05 Mar 2013 13:30:28 +0000
-
-dcpomatic (0.76beta3-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 05 Mar 2013 12:47:20 +0000
-
-dcpomatic (0.76beta2-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Fri, 01 Mar 2013 18:32:16 +0000
-
-dcpomatic (0.76beta1-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Fri, 01 Mar 2013 17:36:55 +0000
-
-dcpomatic (0.75-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Wed, 27 Feb 2013 11:03:07 +0000
-
-dcpomatic (0.75beta1-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Wed, 27 Feb 2013 08:20:42 +0000
-
-dcpomatic (0.74-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sat, 23 Feb 2013 22:57:20 +0000
-
-dcpomatic (0.74beta1-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sat, 23 Feb 2013 21:44:22 +0000
-
-dcpomatic (0.73-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 21 Feb 2013 00:43:40 +0000
-
-dcpomatic (0.73beta9-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Wed, 20 Feb 2013 23:40:24 +0000
-
-dcpomatic (0.73beta8-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Mon, 18 Feb 2013 22:35:51 +0000
-
-dcpomatic (0.73beta7-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Mon, 18 Feb 2013 20:38:51 +0000
-
-dcpomatic (0.73beta6-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 17 Feb 2013 23:05:56 +0000
-
-dcpomatic (0.73beta3-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 17 Feb 2013 23:05:05 +0000
-
-dcpomatic (0.73beta2-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sat, 16 Feb 2013 22:42:32 +0000
-
-dcpomatic (0.73beta1-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sat, 16 Feb 2013 21:19:24 +0000
-
-dcpomatic (0.72-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 24 Jan 2013 15:31:57 +0000
-
-dcpomatic (0.71-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 24 Jan 2013 11:36:04 +0000
-
-dcpomatic (0.70-1) UNRELEASED; urgency=low
-
- * New upstream release.
- * New upstream release.
- * New upstream release.
-
- -- Carl Hetherington <cth@carlh.net> Sat, 12 Jan 2013 23:07:15 +0000
-
-dcpomatic (0.70beta3-1) UNRELEASED; urgency=low
-
- * New upstream release.
- * New upstream release.
- * New upstream release.
- * New upstream release.
-
- -- Carl Hetherington <cth@carlh.net> Sun, 06 Jan 2013 23:44:24 +0000
-
-dcpomatic (0.68-1) UNRELEASED; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 23 Dec 2012 01:43:44 +0000
-
-dcpomatic (0.68beta10-1) UNRELEASED; urgency=low
-
- * New upstream release.
- * New upstream release.
- * New upstream release.
- * New upstream release.
- * New upstream release.
-
- -- Carl Hetherington <cth@carlh.net> Sat, 22 Dec 2012 13:27:27 +0000
-
-dcpomatic (0.68beta5-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 20 Dec 2012 07:53:46 +0000
-
-dcpomatic (0.68beta4-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 20 Dec 2012 07:48:45 +0000
-
-dcpomatic (0.68beta3-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 20 Dec 2012 00:35:45 +0000
-
-dcpomatic (0.68beta2-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Wed, 19 Dec 2012 11:22:58 +0000
-
-dcpomatic (0.68beta1-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Wed, 19 Dec 2012 10:11:13 +0000
-
-dcpomatic (0.67-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 18 Dec 2012 23:49:27 +0000
-
-dcpomatic (0.66-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 18 Dec 2012 11:29:04 +0000
-
-dcpomatic (0.65-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 18 Dec 2012 09:24:56 +0000
-
-dcpomatic (0.64-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Thu, 13 Dec 2012 21:52:09 +0000
-
-dcpomatic (0.63pre-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 11 Dec 2012 23:15:52 +0000
-
-dcpomatic (0.60-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Tue, 11 Dec 2012 22:46:04 +0000
-
-dcpomatic (0.59-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Mon, 10 Dec 2012 20:58:19 +0000
-
-dcpomatic (0.59beta5-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 09 Dec 2012 23:51:55 +0000
-
-dcpomatic (0.59beta4-1) unstable; urgency=low
-
- * New upstream release.
-
- -- Carl Hetherington <carl@houllier.lan> Sun, 09 Dec 2012 21:38:00 +0000
-
-dcpomatic (0.59beta1-1) unstable; urgency=low
-
- * Initial release.
-
- -- Carl Hetherington <cth@carlh.net> Sat, 08 Dec 2012 12:41:20 +0000
diff --git a/debian/rules b/debian/rules
index fead4a0b0..7e4197490 100755
--- a/debian/rules
+++ b/debian/rules
@@ -38,7 +38,7 @@ override_dh_auto_install:
mkdir -p debian/$(CDIST_PACKAGE)/usr/share/locale/ru/LC_MESSAGES/
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/dcpverify debian/$(CDIST_PACKAGE)/usr/bin/dcpomatic2_verify_cli
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
diff --git a/doc/design/fonts b/doc/design/fonts
index c431d52e9..5d61f71a3 100644
--- a/doc/design/fonts
+++ b/doc/design/fonts
@@ -47,13 +47,24 @@ 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.
+Gets all fonts, puts them in the FontIDMap using the font's re-written ID.
* Reel Writer
-Gets subtitles, uses font ID map to find the ID from the Font C++ object pointer. Puts this ID in
+Gets subtitles, uses FontIDMap 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.
+
+To put this all another way, we need to:
+
+1. Make some Content-unique ID from a random font ID. Then we can store this in the metadata.xml, and the user can change the font.
+
+2. Then do the same trick later, with the same result, so the decoder can receive the random font ID and go via the Content-unique
+ID to get the actual font TTF from the Content.
+
+In both these situations a string is the easiest thing (to go in some XML, and to go in a dcp::SubtitleString).
+
+This whole problem really is just that a single TextContent represents multiple
+DCP reels, and each reel can have a different font with the same font ID.
diff --git a/doc/design/fonts.svg b/doc/design/fonts.svg
new file mode 100644
index 000000000..515bc1ae3
--- /dev/null
+++ b/doc/design/fonts.svg
@@ -0,0 +1,615 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="210mm"
+ height="297mm"
+ viewBox="0 0 210 297"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
+ sodipodi:docname="fonts.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#ffffff"
+ inkscape:document-units="mm"
+ inkscape:zoom="1.4218013"
+ inkscape:cx="-50.288319"
+ inkscape:cy="514.83986"
+ inkscape:window-width="1920"
+ inkscape:window-height="1043"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1"
+ showborder="false" />
+ <defs
+ id="defs1">
+ <marker
+ style="overflow:visible"
+ id="marker43"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Concave triangle arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.7)"
+ d="M -2,-4 9,0 -2,4 c 2,-2.33 2,-5.66 0,-8 z"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:none"
+ id="path7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ConcaveTriangle"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Concave triangle arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.7)"
+ d="M -2,-4 9,0 -2,4 c 2,-2.33 2,-5.66 0,-8 z"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:none"
+ id="path9" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ConcaveTriangle-2"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Concave triangle arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.7)"
+ d="M -2,-4 9,0 -2,4 c 2,-2.33 2,-5.66 0,-8 z"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:none"
+ id="path9-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ConcaveTriangle-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Concave triangle arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.7)"
+ d="M -2,-4 9,0 -2,4 c 2,-2.33 2,-5.66 0,-8 z"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:none"
+ id="path9-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ConcaveTriangle-0-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Concave triangle arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.7)"
+ d="M -2,-4 9,0 -2,4 c 2,-2.33 2,-5.66 0,-8 z"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:none"
+ id="path9-3-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ConcaveTriangle-0-5-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Concave triangle arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.7)"
+ d="M -2,-4 9,0 -2,4 c 2,-2.33 2,-5.66 0,-8 z"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:none"
+ id="path9-3-4-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ConcaveTriangle-0-5-6-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Concave triangle arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.7)"
+ d="M -2,-4 9,0 -2,4 c 2,-2.33 2,-5.66 0,-8 z"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:none"
+ id="path9-3-4-8-6" />
+ </marker>
+ </defs>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <text
+ xml:space="preserve"
+ style="font-size:5.64444px;fill:#0000ff;stroke:#ff0000;stroke-width:0.264999;stroke-dasharray:none;stroke-opacity:1"
+ x="19.162163"
+ y="23.234892"
+ id="text4"><tspan
+ sodipodi:role="line"
+ id="tspan4"
+ style="stroke-width:0.265"
+ x="19.162163"
+ y="23.234892" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.00157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000;stop-opacity:1"
+ x="67.090012"
+ y="117.13794"
+ id="text6"
+ transform="scale(0.26458333)"><tspan
+ sodipodi:role="line"
+ id="tspan6"
+ x="67.090012"
+ y="117.13794" /></text>
+ <g
+ id="g10"
+ transform="translate(-124.30682,74.671989)">
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="7.4588742"
+ y="11.577716"
+ id="text1"><tspan
+ sodipodi:role="line"
+ id="tspan1"
+ style="font-size:5.64444px;stroke-width:0.264583"
+ x="7.4588742"
+ y="11.577716">DCP</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:4.23333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="21.397272"
+ y="11.577716"
+ id="text2"><tspan
+ sodipodi:role="line"
+ id="tspan2"
+ style="font-size:4.23333px;stroke-width:0.264583"
+ x="21.397272"
+ y="11.577716">imported to project</tspan></text>
+ <rect
+ style="fill:none;stroke:#ff0000;stroke-width:0.48;stroke-dasharray:none;stroke-opacity:1"
+ id="rect2"
+ width="12.201417"
+ height="6.364614"
+ x="7.0392933"
+ y="6.3803439" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:4.58611px;line-height:1.25;font-family:sans-serif;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:0.265;stroke-dasharray:none"
+ x="11.433817"
+ y="20.654835"
+ id="text3"><tspan
+ sodipodi:role="line"
+ id="tspan3"
+ style="font-size:4.58611px;fill:#0000ff;stroke-width:0.265;stroke-dasharray:none"
+ x="11.433817"
+ y="20.654835">Reel #1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:4.58611px;line-height:1.25;font-family:sans-serif;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:0.265;stroke-dasharray:none"
+ x="10.747972"
+ y="40.230225"
+ id="text3-5"><tspan
+ sodipodi:role="line"
+ id="tspan3-3"
+ style="font-size:4.58611px;fill:#0000ff;stroke-width:0.265;stroke-dasharray:none"
+ x="10.747972"
+ y="40.230225">Reel #2</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.58611px;fill:#008000;stroke:none;stroke-width:0.264999;stroke-dasharray:none;stroke-opacity:1"
+ x="14.594666"
+ y="27.475216"
+ id="text5"><tspan
+ sodipodi:role="line"
+ id="tspan5"
+ style="font-size:4.58611px;fill:#008000;stroke:none;stroke-width:0.265"
+ x="14.594666"
+ y="27.475216">Subtitles, font ID &quot;FONT&quot; (asset A)</tspan><tspan
+ sodipodi:role="line"
+ style="font-size:4.58611px;fill:#008000;stroke:none;stroke-width:0.265"
+ x="14.594666"
+ y="33.207855"
+ id="tspan10">Closed captions, font ID &quot;FONT&quot; (asset B)</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.58611px;fill:#008000;stroke:none;stroke-width:0.264999;stroke-dasharray:none;stroke-opacity:1"
+ x="14.594666"
+ y="46.414989"
+ id="text5-1"><tspan
+ sodipodi:role="line"
+ id="tspan5-8"
+ style="font-size:4.58611px;fill:#008000;stroke:none;stroke-width:0.265"
+ x="14.594666"
+ y="46.414989">Subtitles, font ID &quot;FONT&quot; (asset C)</tspan><tspan
+ sodipodi:role="line"
+ style="font-size:4.58611px;fill:#008000;stroke:none;stroke-width:0.265"
+ x="14.594666"
+ y="52.147629"
+ id="tspan10-7">Closed captions, font ID &quot;FONT&quot; (asset D)</tspan></text>
+ <rect
+ style="fill:none;stroke:#ff0000;stroke-width:0.233127;stroke-dasharray:none;stroke-opacity:1"
+ id="rect7"
+ width="101.6746"
+ height="52.954491"
+ x="4.9380584"
+ y="3.8663552" />
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:4.23333px;line-height:normal;font-family:sans-serif;text-decoration-color:#000000;fill:#000000;stroke-width:0.264999;-inkscape-stroke:none;stop-color:#000000"
+ x="40.912861"
+ y="106.03439"
+ id="text11"><tspan
+ sodipodi:role="line"
+ id="tspan11"
+ style="stroke-width:0.265"
+ x="40.912861"
+ y="106.03439"></tspan><tspan
+ sodipodi:role="line"
+ style="stroke-width:0.265"
+ id="tspan12"
+ x="40.912861"
+ y="111.32606" /></text>
+ <g
+ id="g9"
+ transform="translate(-4.5021517,-0.4039482)">
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.00157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000;stop-opacity:1"
+ x="175.30579"
+ y="385.14359"
+ id="text9"
+ transform="scale(0.26458333)"><tspan
+ sodipodi:role="line"
+ id="tspan9"
+ x="175.30579"
+ y="385.14359">Examiner</tspan></text>
+ <rect
+ style="fill:none;stroke:#0000ff;stroke-width:0.431729;stroke-dasharray:none;stroke-opacity:1"
+ id="rect9"
+ width="28.884327"
+ height="7.8319788"
+ x="44.685955"
+ y="95.93483" />
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:4.23333px;line-height:normal;font-family:sans-serif;text-align:center;text-decoration-color:#000000;text-anchor:middle;fill:#000000;stroke-width:0.264999;-inkscape-stroke:none;stop-color:#000000"
+ x="54.733913"
+ y="109.38216"
+ id="text13"><tspan
+ sodipodi:role="line"
+ id="tspan13"
+ style="text-align:center;text-anchor:middle;stroke-width:0.265"
+ x="54.733913"
+ y="109.38216">Allocator prepped with all fonts in the DCP</tspan><tspan
+ sodipodi:role="line"
+ style="text-align:center;text-anchor:middle;stroke-width:0.265"
+ x="54.733913"
+ y="114.67383"
+ id="tspan14">Creates ID for each font</tspan></text>
+ <path
+ style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.465;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;marker-end:url(#ConcaveTriangle);stop-color:#000000;stop-opacity:1"
+ d="m 100.96906,105.01559 h 18.54651"
+ id="path14" />
+ <path
+ style="font-variation-settings:normal;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.465;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;marker-end:url(#ConcaveTriangle-0);stop-color:#000000"
+ d="M -13.193139,105.01559 H 5.3533708"
+ id="path14-1" />
+ <path
+ style="font-variation-settings:normal;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.465;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;marker-end:url(#ConcaveTriangle-0-5);stop-color:#000000"
+ d="m -32.370275,172.28664 h 18.54651"
+ id="path14-1-6" />
+ <path
+ style="font-variation-settings:normal;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.465;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;marker-end:url(#ConcaveTriangle-0-5-6);stop-color:#000000"
+ d="m 67.930036,172.28664 h 18.5465"
+ id="path14-1-6-2" />
+ <path
+ style="font-variation-settings:normal;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.465;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;marker-end:url(#ConcaveTriangle-0-5-6-0);stop-color:#000000"
+ d="m 154.20122,172.28664 h 18.5465"
+ id="path14-1-6-2-5" />
+ <path
+ style="font-variation-settings:normal;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.465;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;marker-end:url(#ConcaveTriangle-2);stop-color:#000000"
+ d="m 172.0024,105.01559 h 18.54651"
+ id="path14-9" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.23333px;line-height:normal;font-family:sans-serif;text-decoration-color:#000000;fill:#000000;stroke-width:0.264999;-inkscape-stroke:none;stop-color:#000000"
+ x="126.92857"
+ y="98.415504"
+ id="text15"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:inconsolata;-inkscape-font-specification:inconsolata;stroke-width:0.265"
+ x="126.92857"
+ y="98.415504"
+ id="tspan17">FONT</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:inconsolata;-inkscape-font-specification:inconsolata;stroke-width:0.265"
+ x="126.92857"
+ y="103.8511"
+ id="tspan16">0_FONT</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:inconsolata;-inkscape-font-specification:inconsolata;stroke-width:0.265"
+ x="126.92857"
+ y="109.2867"
+ id="tspan19">1_FONT</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:inconsolata;-inkscape-font-specification:inconsolata;stroke-width:0.265"
+ x="126.92857"
+ y="114.72229"
+ id="tspan20">2_FONT</tspan><tspan
+ sodipodi:role="line"
+ style="stroke-width:0.265"
+ x="126.92857"
+ y="120.01396"
+ id="tspan18" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.23333px;line-height:1.29;font-family:sans-serif;text-decoration-color:#000000;fill:#008000;stroke-width:0.264999;-inkscape-stroke:none;stop-color:#000000"
+ x="141.02527"
+ y="98.415504"
+ id="text21"><tspan
+ sodipodi:role="line"
+ id="tspan21"
+ style="fill:#008000;stroke-width:0.265;line-height:1.29"
+ x="141.02527"
+ y="98.415504">Subs from A</tspan><tspan
+ sodipodi:role="line"
+ style="fill:#008000;stroke-width:0.265;line-height:1.29"
+ x="141.02527"
+ y="103.8765"
+ id="tspan22">Caps from B</tspan><tspan
+ style="fill:#008000;stroke-width:0.265;line-height:1.29"
+ sodipodi:role="line"
+ id="tspan36"
+ x="141.02527"
+ y="109.33749"><tspan
+ id="tspan38"
+ style="stroke-width:0.264999;line-height:1.29">S</tspan>ubs from C</tspan><tspan
+ style="fill:#008000;stroke-width:0.265;line-height:1.29"
+ sodipodi:role="line"
+ id="tspan37"
+ x="141.02527"
+ y="114.79849">Caps from D</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.23333px;line-height:normal;font-family:sans-serif;text-decoration-color:#000000;fill:#000000;stroke-width:0.264999;-inkscape-stroke:none;stop-color:#000000"
+ x="197.82054"
+ y="103.47042"
+ id="text25"><tspan
+ sodipodi:role="line"
+ id="tspan25"
+ style="stroke-width:0.265"
+ x="197.82054"
+ y="103.47042">Store in <tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:inconsolata;-inkscape-font-specification:inconsolata"
+ id="tspan33">TextContent</tspan></tspan><tspan
+ sodipodi:role="line"
+ style="stroke-width:0.265"
+ x="197.82054"
+ y="108.90601"
+ id="tspan32">Written to <tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:inconsolata;-inkscape-font-specification:inconsolata"
+ id="tspan34">metadata.xml</tspan></tspan><tspan
+ sodipodi:role="line"
+ style="stroke-width:0.265"
+ x="197.82054"
+ y="114.19768"
+ id="tspan26">Then user can change font TTF.</tspan></text>
+ <g
+ id="g28"
+ transform="translate(7.508606,2.5808953)">
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.00157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000"
+ x="175.30579"
+ y="385.14359"
+ id="text9-2"
+ transform="matrix(0.26458333,0,0,0.26458333,-139.28384,58.778076)"><tspan
+ sodipodi:role="line"
+ id="tspan9-9"
+ x="175.30579"
+ y="385.14359">Decoder</tspan></text>
+ <rect
+ style="fill:none;stroke:#0000ff;stroke-width:0.405832;stroke-dasharray:none;stroke-opacity:1"
+ id="rect9-3"
+ width="25.438843"
+ height="7.8578768"
+ x="-94.181831"
+ y="154.63504" />
+ </g>
+ <g
+ id="g45"
+ transform="translate(0,-0.88292437)">
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.00157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000"
+ x="175.30579"
+ y="385.14359"
+ id="text9-2-5"
+ transform="matrix(0.26458333,0,0,0.26458333,-38.448692,62.241907)"><tspan
+ sodipodi:role="line"
+ id="tspan9-9-0"
+ x="175.30579"
+ y="385.14359">DCPDecoder</tspan></text>
+ <rect
+ style="fill:none;stroke:#0000ff;stroke-width:0.481702;stroke-dasharray:none;stroke-opacity:1"
+ id="rect9-3-4"
+ width="36.188915"
+ height="7.7820067"
+ x="6.6912527"
+ y="158.1368" />
+ </g>
+ <g
+ id="g39"
+ transform="translate(-75.313631,-8.3515676)">
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.00157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000"
+ x="175.30579"
+ y="385.14359"
+ id="text9-2-5-7"
+ transform="matrix(0.26458333,0,0,0.26458333,132.90091,69.710541)"><tspan
+ sodipodi:role="line"
+ id="tspan9-9-0-2"
+ x="175.30579"
+ y="385.14359">TextDecoder</tspan></text>
+ <rect
+ style="fill:none;stroke:#0000ff;stroke-width:0.481702;stroke-dasharray:none;stroke-opacity:1"
+ id="rect9-3-4-7"
+ width="36.188915"
+ height="7.7820067"
+ x="178.04085"
+ y="165.60544" />
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:4.23333px;line-height:normal;font-family:sans-serif;text-align:center;text-decoration-color:#000000;text-anchor:middle;fill:#000000;stroke-width:0.264999;-inkscape-stroke:none;stop-color:#000000"
+ x="-73.991905"
+ y="148.05048"
+ id="text13-2"><tspan
+ sodipodi:role="line"
+ id="tspan13-0"
+ style="text-align:center;text-anchor:middle;stroke-width:0.265"
+ x="-73.991905"
+ y="148.05048">Allocator prepped with all fonts in the DCP</tspan><tspan
+ sodipodi:role="line"
+ style="text-align:center;text-anchor:middle;stroke-width:0.265"
+ x="-73.991905"
+ y="153.34213"
+ id="tspan14-6">Creates ID for each font, <tspan
+ style="font-weight:bold"
+ id="tspan29">same IDs as before</tspan></tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.23333px;line-height:normal;font-family:sans-serif;text-decoration-color:#000000;fill:#000000;stroke-width:0.264999;-inkscape-stroke:none;stop-color:#000000"
+ x="-113.201"
+ y="173.88472"
+ id="text28"><tspan
+ sodipodi:role="line"
+ id="tspan28"
+ style="stroke-width:0.265"
+ x="-113.201"
+ y="173.88472">Emits a subtitle with FONT from asset C</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.23333px;line-height:normal;font-family:sans-serif;text-decoration-color:#000000;fill:#000000;stroke-width:0.264999;-inkscape-stroke:none;stop-color:#000000"
+ x="-9.4808464"
+ y="170.79439"
+ id="text30"><tspan
+ sodipodi:role="line"
+ id="tspan30"
+ style="stroke-width:0.265"
+ x="-9.4808464"
+ y="170.79439"><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:inconsolata;-inkscape-font-specification:inconsolata"
+ id="tspan31">dcp::SubtitleString</tspan> re-written to use</tspan><tspan
+ sodipodi:role="line"
+ style="stroke-width:0.265"
+ x="-9.4808464"
+ y="176.22998"
+ id="tspan40">font ID <tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:inconsolata;-inkscape-font-specification:inconsolata"
+ id="tspan39">1_FONT</tspan> (from allocator)</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.23333px;line-height:normal;font-family:sans-serif;text-decoration-color:#000000;fill:#000000;stroke-width:0.264999;-inkscape-stroke:none;stop-color:#000000"
+ x="93.52124"
+ y="171.21983"
+ id="text41"><tspan
+ sodipodi:role="line"
+ id="tspan41"
+ style="stroke-width:0.265"
+ x="93.52124"
+ y="171.21983"><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:inconsolata;-inkscape-font-specification:inconsolata"
+ id="tspan43">StringText</tspan> created with the</tspan><tspan
+ sodipodi:role="line"
+ style="stroke-width:0.265"
+ x="93.52124"
+ y="176.51149"
+ id="tspan42">actual font TTF.</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:0.26458299, 0.52916598;stroke-dashoffset:0;marker-start:url(#marker43);marker-end:url(#ConcaveTriangle-0-5-6)"
+ d="m 192.80332,112.69695 -54.0961,41.53069"
+ id="path43" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333px;line-height:normal;font-family:inconsolata;-inkscape-font-specification:inconsolata;text-decoration-color:#000000;fill:#000000;stroke-width:0.264999;-inkscape-stroke:none;stop-color:#000000"
+ x="17.63851"
+ y="205.46201"
+ id="text44"
+ transform="rotate(-37.518172)"><tspan
+ sodipodi:role="line"
+ id="tspan44"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:inconsolata;-inkscape-font-specification:inconsolata;stroke-width:0.265"
+ x="17.63851"
+ y="205.46201">TextContent::get_font(&quot;1_FONT&quot;)</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.23333px;line-height:normal;font-family:sans-serif;text-decoration-color:#000000;fill:#000000;stroke-width:0.264999;-inkscape-stroke:none;stop-color:#000000"
+ x="178.33331"
+ y="173.31322"
+ id="text45"><tspan
+ sodipodi:role="line"
+ id="tspan45"
+ style="stroke-width:0.265"
+ x="178.33331"
+ y="173.31322">Output DCP</tspan></text>
+ </g>
+</svg>
diff --git a/graphics/linux/128/dcpomatic2_batch.png b/graphics/linux/128/dcpomatic2_batch.png
index 454feb733..97647c7c9 100644
--- a/graphics/linux/128/dcpomatic2_batch.png
+++ b/graphics/linux/128/dcpomatic2_batch.png
Binary files differ
diff --git a/graphics/linux/128/dcpomatic2_combiner.png b/graphics/linux/128/dcpomatic2_combiner.png
index 27ad5d0e7..9436e3ace 100644
--- a/graphics/linux/128/dcpomatic2_combiner.png
+++ b/graphics/linux/128/dcpomatic2_combiner.png
Binary files differ
diff --git a/graphics/linux/128/dcpomatic2_disk.png b/graphics/linux/128/dcpomatic2_disk.png
index 96573fdd8..97de66151 100644
--- a/graphics/linux/128/dcpomatic2_disk.png
+++ b/graphics/linux/128/dcpomatic2_disk.png
Binary files differ
diff --git a/graphics/linux/128/dcpomatic2_editor.png b/graphics/linux/128/dcpomatic2_editor.png
index d05282c43..e93bd5cff 100644
--- a/graphics/linux/128/dcpomatic2_editor.png
+++ b/graphics/linux/128/dcpomatic2_editor.png
Binary files differ
diff --git a/graphics/linux/128/dcpomatic2_kdm.png b/graphics/linux/128/dcpomatic2_kdm.png
index cb725b889..2bbe10f90 100644
--- a/graphics/linux/128/dcpomatic2_kdm.png
+++ b/graphics/linux/128/dcpomatic2_kdm.png
Binary files differ
diff --git a/graphics/linux/128/dcpomatic2_player.png b/graphics/linux/128/dcpomatic2_player.png
index a851d289a..ca4c9bbbb 100644
--- a/graphics/linux/128/dcpomatic2_player.png
+++ b/graphics/linux/128/dcpomatic2_player.png
Binary files differ
diff --git a/graphics/linux/128/dcpomatic2_playlist.png b/graphics/linux/128/dcpomatic2_playlist.png
index 223228580..68cd2458e 100644
--- a/graphics/linux/128/dcpomatic2_playlist.png
+++ b/graphics/linux/128/dcpomatic2_playlist.png
Binary files differ
diff --git a/graphics/linux/128/dcpomatic2_server.png b/graphics/linux/128/dcpomatic2_server.png
index 00038ed60..ac2e6c9f7 100644
--- a/graphics/linux/128/dcpomatic2_server.png
+++ b/graphics/linux/128/dcpomatic2_server.png
Binary files differ
diff --git a/graphics/linux/128/dcpomatic2_verifier.png b/graphics/linux/128/dcpomatic2_verifier.png
new file mode 100644
index 000000000..d28ceb27b
--- /dev/null
+++ b/graphics/linux/128/dcpomatic2_verifier.png
Binary files differ
diff --git a/graphics/linux/16/dcpomatic2_batch.png b/graphics/linux/16/dcpomatic2_batch.png
index a77ed3f78..2268c0395 100644
--- a/graphics/linux/16/dcpomatic2_batch.png
+++ b/graphics/linux/16/dcpomatic2_batch.png
Binary files differ
diff --git a/graphics/linux/16/dcpomatic2_combiner.png b/graphics/linux/16/dcpomatic2_combiner.png
index ae6f698cc..37f3b3697 100644
--- a/graphics/linux/16/dcpomatic2_combiner.png
+++ b/graphics/linux/16/dcpomatic2_combiner.png
Binary files differ
diff --git a/graphics/linux/16/dcpomatic2_disk.png b/graphics/linux/16/dcpomatic2_disk.png
index 29d2c9b63..94d9f001d 100644
--- a/graphics/linux/16/dcpomatic2_disk.png
+++ b/graphics/linux/16/dcpomatic2_disk.png
Binary files differ
diff --git a/graphics/linux/16/dcpomatic2_editor.png b/graphics/linux/16/dcpomatic2_editor.png
index 2ec12f82b..deff7c1af 100644
--- a/graphics/linux/16/dcpomatic2_editor.png
+++ b/graphics/linux/16/dcpomatic2_editor.png
Binary files differ
diff --git a/graphics/linux/16/dcpomatic2_kdm.png b/graphics/linux/16/dcpomatic2_kdm.png
index cbb38e720..6e5ffe946 100644
--- a/graphics/linux/16/dcpomatic2_kdm.png
+++ b/graphics/linux/16/dcpomatic2_kdm.png
Binary files differ
diff --git a/graphics/linux/16/dcpomatic2_player.png b/graphics/linux/16/dcpomatic2_player.png
index 821df7805..42336a67f 100644
--- a/graphics/linux/16/dcpomatic2_player.png
+++ b/graphics/linux/16/dcpomatic2_player.png
Binary files differ
diff --git a/graphics/linux/16/dcpomatic2_playlist.png b/graphics/linux/16/dcpomatic2_playlist.png
index f55396b39..5db3fc873 100644
--- a/graphics/linux/16/dcpomatic2_playlist.png
+++ b/graphics/linux/16/dcpomatic2_playlist.png
Binary files differ
diff --git a/graphics/linux/16/dcpomatic2_server.png b/graphics/linux/16/dcpomatic2_server.png
index 19c4acafc..bbaf9dedd 100644
--- a/graphics/linux/16/dcpomatic2_server.png
+++ b/graphics/linux/16/dcpomatic2_server.png
Binary files differ
diff --git a/graphics/linux/16/dcpomatic2_verifier.png b/graphics/linux/16/dcpomatic2_verifier.png
new file mode 100644
index 000000000..9ad2929dc
--- /dev/null
+++ b/graphics/linux/16/dcpomatic2_verifier.png
Binary files differ
diff --git a/graphics/linux/22/dcpomatic2_batch.png b/graphics/linux/22/dcpomatic2_batch.png
index f5a72a2d2..95ece7f92 100644
--- a/graphics/linux/22/dcpomatic2_batch.png
+++ b/graphics/linux/22/dcpomatic2_batch.png
Binary files differ
diff --git a/graphics/linux/22/dcpomatic2_combiner.png b/graphics/linux/22/dcpomatic2_combiner.png
index 4fa3d3b0a..c545c4b4d 100644
--- a/graphics/linux/22/dcpomatic2_combiner.png
+++ b/graphics/linux/22/dcpomatic2_combiner.png
Binary files differ
diff --git a/graphics/linux/22/dcpomatic2_disk.png b/graphics/linux/22/dcpomatic2_disk.png
index 549e4ff25..c2e05456c 100644
--- a/graphics/linux/22/dcpomatic2_disk.png
+++ b/graphics/linux/22/dcpomatic2_disk.png
Binary files differ
diff --git a/graphics/linux/22/dcpomatic2_editor.png b/graphics/linux/22/dcpomatic2_editor.png
index 3c492a78d..9ba758f90 100644
--- a/graphics/linux/22/dcpomatic2_editor.png
+++ b/graphics/linux/22/dcpomatic2_editor.png
Binary files differ
diff --git a/graphics/linux/22/dcpomatic2_kdm.png b/graphics/linux/22/dcpomatic2_kdm.png
index 678e7828d..327bd5342 100644
--- a/graphics/linux/22/dcpomatic2_kdm.png
+++ b/graphics/linux/22/dcpomatic2_kdm.png
Binary files differ
diff --git a/graphics/linux/22/dcpomatic2_player.png b/graphics/linux/22/dcpomatic2_player.png
index 7b50f591e..f821b0504 100644
--- a/graphics/linux/22/dcpomatic2_player.png
+++ b/graphics/linux/22/dcpomatic2_player.png
Binary files differ
diff --git a/graphics/linux/22/dcpomatic2_playlist.png b/graphics/linux/22/dcpomatic2_playlist.png
index 1e40f8260..4d02aa16a 100644
--- a/graphics/linux/22/dcpomatic2_playlist.png
+++ b/graphics/linux/22/dcpomatic2_playlist.png
Binary files differ
diff --git a/graphics/linux/22/dcpomatic2_server.png b/graphics/linux/22/dcpomatic2_server.png
index d021f847e..3ead2dea4 100644
--- a/graphics/linux/22/dcpomatic2_server.png
+++ b/graphics/linux/22/dcpomatic2_server.png
Binary files differ
diff --git a/graphics/linux/22/dcpomatic2_verifier.png b/graphics/linux/22/dcpomatic2_verifier.png
new file mode 100644
index 000000000..dbe6cbd4d
--- /dev/null
+++ b/graphics/linux/22/dcpomatic2_verifier.png
Binary files differ
diff --git a/graphics/linux/256/dcpomatic2_batch.png b/graphics/linux/256/dcpomatic2_batch.png
index 5fffb3495..c66a71e44 100644
--- a/graphics/linux/256/dcpomatic2_batch.png
+++ b/graphics/linux/256/dcpomatic2_batch.png
Binary files differ
diff --git a/graphics/linux/256/dcpomatic2_combiner.png b/graphics/linux/256/dcpomatic2_combiner.png
index 7388b2ee8..bf7048478 100644
--- a/graphics/linux/256/dcpomatic2_combiner.png
+++ b/graphics/linux/256/dcpomatic2_combiner.png
Binary files differ
diff --git a/graphics/linux/256/dcpomatic2_disk.png b/graphics/linux/256/dcpomatic2_disk.png
index 9baf4a851..4eb22c83a 100644
--- a/graphics/linux/256/dcpomatic2_disk.png
+++ b/graphics/linux/256/dcpomatic2_disk.png
Binary files differ
diff --git a/graphics/linux/256/dcpomatic2_editor.png b/graphics/linux/256/dcpomatic2_editor.png
index d78fcb77c..1ad00f9b2 100644
--- a/graphics/linux/256/dcpomatic2_editor.png
+++ b/graphics/linux/256/dcpomatic2_editor.png
Binary files differ
diff --git a/graphics/linux/256/dcpomatic2_kdm.png b/graphics/linux/256/dcpomatic2_kdm.png
index 5f2de6649..5c54cfcf2 100644
--- a/graphics/linux/256/dcpomatic2_kdm.png
+++ b/graphics/linux/256/dcpomatic2_kdm.png
Binary files differ
diff --git a/graphics/linux/256/dcpomatic2_player.png b/graphics/linux/256/dcpomatic2_player.png
index a5fa0ff3c..140fbc967 100644
--- a/graphics/linux/256/dcpomatic2_player.png
+++ b/graphics/linux/256/dcpomatic2_player.png
Binary files differ
diff --git a/graphics/linux/256/dcpomatic2_playlist.png b/graphics/linux/256/dcpomatic2_playlist.png
index b5d827d09..0b590c576 100644
--- a/graphics/linux/256/dcpomatic2_playlist.png
+++ b/graphics/linux/256/dcpomatic2_playlist.png
Binary files differ
diff --git a/graphics/linux/256/dcpomatic2_server.png b/graphics/linux/256/dcpomatic2_server.png
index b693546e9..bd4d105b9 100644
--- a/graphics/linux/256/dcpomatic2_server.png
+++ b/graphics/linux/256/dcpomatic2_server.png
Binary files differ
diff --git a/graphics/linux/256/dcpomatic2_verifier.png b/graphics/linux/256/dcpomatic2_verifier.png
new file mode 100644
index 000000000..f5198a733
--- /dev/null
+++ b/graphics/linux/256/dcpomatic2_verifier.png
Binary files differ
diff --git a/graphics/linux/32/dcpomatic2_batch.png b/graphics/linux/32/dcpomatic2_batch.png
index 1eb41a879..5100b56d7 100644
--- a/graphics/linux/32/dcpomatic2_batch.png
+++ b/graphics/linux/32/dcpomatic2_batch.png
Binary files differ
diff --git a/graphics/linux/32/dcpomatic2_combiner.png b/graphics/linux/32/dcpomatic2_combiner.png
index e24e0e0e5..63ce67b63 100644
--- a/graphics/linux/32/dcpomatic2_combiner.png
+++ b/graphics/linux/32/dcpomatic2_combiner.png
Binary files differ
diff --git a/graphics/linux/32/dcpomatic2_disk.png b/graphics/linux/32/dcpomatic2_disk.png
index 439e204af..1607cf5f3 100644
--- a/graphics/linux/32/dcpomatic2_disk.png
+++ b/graphics/linux/32/dcpomatic2_disk.png
Binary files differ
diff --git a/graphics/linux/32/dcpomatic2_editor.png b/graphics/linux/32/dcpomatic2_editor.png
index 821af7de0..3c21ecd83 100644
--- a/graphics/linux/32/dcpomatic2_editor.png
+++ b/graphics/linux/32/dcpomatic2_editor.png
Binary files differ
diff --git a/graphics/linux/32/dcpomatic2_kdm.png b/graphics/linux/32/dcpomatic2_kdm.png
index eeebcc334..c8fe47b23 100644
--- a/graphics/linux/32/dcpomatic2_kdm.png
+++ b/graphics/linux/32/dcpomatic2_kdm.png
Binary files differ
diff --git a/graphics/linux/32/dcpomatic2_player.png b/graphics/linux/32/dcpomatic2_player.png
index 5ef8c0d90..7f261158d 100644
--- a/graphics/linux/32/dcpomatic2_player.png
+++ b/graphics/linux/32/dcpomatic2_player.png
Binary files differ
diff --git a/graphics/linux/32/dcpomatic2_playlist.png b/graphics/linux/32/dcpomatic2_playlist.png
index 091b559f4..4bd937653 100644
--- a/graphics/linux/32/dcpomatic2_playlist.png
+++ b/graphics/linux/32/dcpomatic2_playlist.png
Binary files differ
diff --git a/graphics/linux/32/dcpomatic2_server.png b/graphics/linux/32/dcpomatic2_server.png
index 3fa7d1fc3..914da144b 100644
--- a/graphics/linux/32/dcpomatic2_server.png
+++ b/graphics/linux/32/dcpomatic2_server.png
Binary files differ
diff --git a/graphics/linux/32/dcpomatic2_verifier.png b/graphics/linux/32/dcpomatic2_verifier.png
new file mode 100644
index 000000000..61afb5f9f
--- /dev/null
+++ b/graphics/linux/32/dcpomatic2_verifier.png
Binary files differ
diff --git a/graphics/linux/48/dcpomatic2_batch.png b/graphics/linux/48/dcpomatic2_batch.png
index 23f4415e0..80ad503fb 100644
--- a/graphics/linux/48/dcpomatic2_batch.png
+++ b/graphics/linux/48/dcpomatic2_batch.png
Binary files differ
diff --git a/graphics/linux/48/dcpomatic2_combiner.png b/graphics/linux/48/dcpomatic2_combiner.png
index 10fcca296..adfa58909 100644
--- a/graphics/linux/48/dcpomatic2_combiner.png
+++ b/graphics/linux/48/dcpomatic2_combiner.png
Binary files differ
diff --git a/graphics/linux/48/dcpomatic2_disk.png b/graphics/linux/48/dcpomatic2_disk.png
index d6a4d56a3..f3476a39e 100644
--- a/graphics/linux/48/dcpomatic2_disk.png
+++ b/graphics/linux/48/dcpomatic2_disk.png
Binary files differ
diff --git a/graphics/linux/48/dcpomatic2_editor.png b/graphics/linux/48/dcpomatic2_editor.png
index 6d6f03d32..d1ea2e0ab 100644
--- a/graphics/linux/48/dcpomatic2_editor.png
+++ b/graphics/linux/48/dcpomatic2_editor.png
Binary files differ
diff --git a/graphics/linux/48/dcpomatic2_kdm.png b/graphics/linux/48/dcpomatic2_kdm.png
index 3d4b3366d..7bfaa1b59 100644
--- a/graphics/linux/48/dcpomatic2_kdm.png
+++ b/graphics/linux/48/dcpomatic2_kdm.png
Binary files differ
diff --git a/graphics/linux/48/dcpomatic2_player.png b/graphics/linux/48/dcpomatic2_player.png
index b3dfe4126..c4d621418 100644
--- a/graphics/linux/48/dcpomatic2_player.png
+++ b/graphics/linux/48/dcpomatic2_player.png
Binary files differ
diff --git a/graphics/linux/48/dcpomatic2_playlist.png b/graphics/linux/48/dcpomatic2_playlist.png
index f1b98ce88..7168b576c 100644
--- a/graphics/linux/48/dcpomatic2_playlist.png
+++ b/graphics/linux/48/dcpomatic2_playlist.png
Binary files differ
diff --git a/graphics/linux/48/dcpomatic2_server.png b/graphics/linux/48/dcpomatic2_server.png
index 694079e03..96146627b 100644
--- a/graphics/linux/48/dcpomatic2_server.png
+++ b/graphics/linux/48/dcpomatic2_server.png
Binary files differ
diff --git a/graphics/linux/48/dcpomatic2_verifier.png b/graphics/linux/48/dcpomatic2_verifier.png
new file mode 100644
index 000000000..5f5188ea3
--- /dev/null
+++ b/graphics/linux/48/dcpomatic2_verifier.png
Binary files differ
diff --git a/graphics/linux/512/dcpomatic2_batch.png b/graphics/linux/512/dcpomatic2_batch.png
index 03fcd0dac..03647a520 100644
--- a/graphics/linux/512/dcpomatic2_batch.png
+++ b/graphics/linux/512/dcpomatic2_batch.png
Binary files differ
diff --git a/graphics/linux/512/dcpomatic2_combiner.png b/graphics/linux/512/dcpomatic2_combiner.png
index 345166990..41f0f6911 100644
--- a/graphics/linux/512/dcpomatic2_combiner.png
+++ b/graphics/linux/512/dcpomatic2_combiner.png
Binary files differ
diff --git a/graphics/linux/512/dcpomatic2_disk.png b/graphics/linux/512/dcpomatic2_disk.png
index 00570f83f..79ced6e6e 100644
--- a/graphics/linux/512/dcpomatic2_disk.png
+++ b/graphics/linux/512/dcpomatic2_disk.png
Binary files differ
diff --git a/graphics/linux/512/dcpomatic2_editor.png b/graphics/linux/512/dcpomatic2_editor.png
index 53bf93301..10734972e 100644
--- a/graphics/linux/512/dcpomatic2_editor.png
+++ b/graphics/linux/512/dcpomatic2_editor.png
Binary files differ
diff --git a/graphics/linux/512/dcpomatic2_kdm.png b/graphics/linux/512/dcpomatic2_kdm.png
index c85244b41..3a2415611 100644
--- a/graphics/linux/512/dcpomatic2_kdm.png
+++ b/graphics/linux/512/dcpomatic2_kdm.png
Binary files differ
diff --git a/graphics/linux/512/dcpomatic2_player.png b/graphics/linux/512/dcpomatic2_player.png
index 001bf0194..c22d85aad 100644
--- a/graphics/linux/512/dcpomatic2_player.png
+++ b/graphics/linux/512/dcpomatic2_player.png
Binary files differ
diff --git a/graphics/linux/512/dcpomatic2_playlist.png b/graphics/linux/512/dcpomatic2_playlist.png
index bb2e1c79e..453779516 100644
--- a/graphics/linux/512/dcpomatic2_playlist.png
+++ b/graphics/linux/512/dcpomatic2_playlist.png
Binary files differ
diff --git a/graphics/linux/512/dcpomatic2_server.png b/graphics/linux/512/dcpomatic2_server.png
index 6cf7c2a96..40410ed57 100644
--- a/graphics/linux/512/dcpomatic2_server.png
+++ b/graphics/linux/512/dcpomatic2_server.png
Binary files differ
diff --git a/graphics/linux/512/dcpomatic2_verifier.png b/graphics/linux/512/dcpomatic2_verifier.png
new file mode 100644
index 000000000..b8417ed85
--- /dev/null
+++ b/graphics/linux/512/dcpomatic2_verifier.png
Binary files differ
diff --git a/graphics/linux/64/dcpomatic2_batch.png b/graphics/linux/64/dcpomatic2_batch.png
index 155accd18..3ec433e44 100644
--- a/graphics/linux/64/dcpomatic2_batch.png
+++ b/graphics/linux/64/dcpomatic2_batch.png
Binary files differ
diff --git a/graphics/linux/64/dcpomatic2_combiner.png b/graphics/linux/64/dcpomatic2_combiner.png
index 4c52f59c3..7693e810c 100644
--- a/graphics/linux/64/dcpomatic2_combiner.png
+++ b/graphics/linux/64/dcpomatic2_combiner.png
Binary files differ
diff --git a/graphics/linux/64/dcpomatic2_disk.png b/graphics/linux/64/dcpomatic2_disk.png
index fd28c77fe..9cf42cd99 100644
--- a/graphics/linux/64/dcpomatic2_disk.png
+++ b/graphics/linux/64/dcpomatic2_disk.png
Binary files differ
diff --git a/graphics/linux/64/dcpomatic2_editor.png b/graphics/linux/64/dcpomatic2_editor.png
index a1cfa196f..bc31e4047 100644
--- a/graphics/linux/64/dcpomatic2_editor.png
+++ b/graphics/linux/64/dcpomatic2_editor.png
Binary files differ
diff --git a/graphics/linux/64/dcpomatic2_kdm.png b/graphics/linux/64/dcpomatic2_kdm.png
index 8946664ea..23bd925b8 100644
--- a/graphics/linux/64/dcpomatic2_kdm.png
+++ b/graphics/linux/64/dcpomatic2_kdm.png
Binary files differ
diff --git a/graphics/linux/64/dcpomatic2_player.png b/graphics/linux/64/dcpomatic2_player.png
index c64a22003..f26c43481 100644
--- a/graphics/linux/64/dcpomatic2_player.png
+++ b/graphics/linux/64/dcpomatic2_player.png
Binary files differ
diff --git a/graphics/linux/64/dcpomatic2_playlist.png b/graphics/linux/64/dcpomatic2_playlist.png
index 258607386..39e147935 100644
--- a/graphics/linux/64/dcpomatic2_playlist.png
+++ b/graphics/linux/64/dcpomatic2_playlist.png
Binary files differ
diff --git a/graphics/linux/64/dcpomatic2_server.png b/graphics/linux/64/dcpomatic2_server.png
index ab14f0276..7afe5992d 100644
--- a/graphics/linux/64/dcpomatic2_server.png
+++ b/graphics/linux/64/dcpomatic2_server.png
Binary files differ
diff --git a/graphics/linux/64/dcpomatic2_verifier.png b/graphics/linux/64/dcpomatic2_verifier.png
new file mode 100644
index 000000000..b17249b55
--- /dev/null
+++ b/graphics/linux/64/dcpomatic2_verifier.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_batch.icns b/graphics/osx/dcpomatic2_batch.icns
index 73481f96f..438c6b1d3 100644
--- a/graphics/osx/dcpomatic2_batch.icns
+++ b/graphics/osx/dcpomatic2_batch.icns
Binary files differ
diff --git a/graphics/osx/dcpomatic2_batch.iconset/icon_128x128.png b/graphics/osx/dcpomatic2_batch.iconset/icon_128x128.png
index 454feb733..97647c7c9 100644
--- a/graphics/osx/dcpomatic2_batch.iconset/icon_128x128.png
+++ b/graphics/osx/dcpomatic2_batch.iconset/icon_128x128.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_batch.iconset/icon_128x128@2x.png b/graphics/osx/dcpomatic2_batch.iconset/icon_128x128@2x.png
index 454feb733..97647c7c9 100644
--- a/graphics/osx/dcpomatic2_batch.iconset/icon_128x128@2x.png
+++ b/graphics/osx/dcpomatic2_batch.iconset/icon_128x128@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_batch.iconset/icon_16x16.png b/graphics/osx/dcpomatic2_batch.iconset/icon_16x16.png
index a77ed3f78..2268c0395 100644
--- a/graphics/osx/dcpomatic2_batch.iconset/icon_16x16.png
+++ b/graphics/osx/dcpomatic2_batch.iconset/icon_16x16.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_batch.iconset/icon_16x16@2x.png b/graphics/osx/dcpomatic2_batch.iconset/icon_16x16@2x.png
index a77ed3f78..2268c0395 100644
--- a/graphics/osx/dcpomatic2_batch.iconset/icon_16x16@2x.png
+++ b/graphics/osx/dcpomatic2_batch.iconset/icon_16x16@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_batch.iconset/icon_256x256.png b/graphics/osx/dcpomatic2_batch.iconset/icon_256x256.png
index 5fffb3495..c66a71e44 100644
--- a/graphics/osx/dcpomatic2_batch.iconset/icon_256x256.png
+++ b/graphics/osx/dcpomatic2_batch.iconset/icon_256x256.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_batch.iconset/icon_256x256@2x.png b/graphics/osx/dcpomatic2_batch.iconset/icon_256x256@2x.png
index 5fffb3495..c66a71e44 100644
--- a/graphics/osx/dcpomatic2_batch.iconset/icon_256x256@2x.png
+++ b/graphics/osx/dcpomatic2_batch.iconset/icon_256x256@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_batch.iconset/icon_32x32.png b/graphics/osx/dcpomatic2_batch.iconset/icon_32x32.png
index 1eb41a879..5100b56d7 100644
--- a/graphics/osx/dcpomatic2_batch.iconset/icon_32x32.png
+++ b/graphics/osx/dcpomatic2_batch.iconset/icon_32x32.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_batch.iconset/icon_32x32@2x.png b/graphics/osx/dcpomatic2_batch.iconset/icon_32x32@2x.png
index 1eb41a879..5100b56d7 100644
--- a/graphics/osx/dcpomatic2_batch.iconset/icon_32x32@2x.png
+++ b/graphics/osx/dcpomatic2_batch.iconset/icon_32x32@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_batch.iconset/icon_512x512.png b/graphics/osx/dcpomatic2_batch.iconset/icon_512x512.png
index 03fcd0dac..03647a520 100644
--- a/graphics/osx/dcpomatic2_batch.iconset/icon_512x512.png
+++ b/graphics/osx/dcpomatic2_batch.iconset/icon_512x512.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_batch.iconset/icon_512x512@2x.png b/graphics/osx/dcpomatic2_batch.iconset/icon_512x512@2x.png
index 03fcd0dac..03647a520 100644
--- a/graphics/osx/dcpomatic2_batch.iconset/icon_512x512@2x.png
+++ b/graphics/osx/dcpomatic2_batch.iconset/icon_512x512@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_combiner.icns b/graphics/osx/dcpomatic2_combiner.icns
index 8f4f5c2a7..f5b26a366 100644
--- a/graphics/osx/dcpomatic2_combiner.icns
+++ b/graphics/osx/dcpomatic2_combiner.icns
Binary files differ
diff --git a/graphics/osx/dcpomatic2_combiner.iconset/icon_128x128.png b/graphics/osx/dcpomatic2_combiner.iconset/icon_128x128.png
index 27ad5d0e7..9436e3ace 100644
--- a/graphics/osx/dcpomatic2_combiner.iconset/icon_128x128.png
+++ b/graphics/osx/dcpomatic2_combiner.iconset/icon_128x128.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_combiner.iconset/icon_128x128@2x.png b/graphics/osx/dcpomatic2_combiner.iconset/icon_128x128@2x.png
index 27ad5d0e7..9436e3ace 100644
--- a/graphics/osx/dcpomatic2_combiner.iconset/icon_128x128@2x.png
+++ b/graphics/osx/dcpomatic2_combiner.iconset/icon_128x128@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_combiner.iconset/icon_16x16.png b/graphics/osx/dcpomatic2_combiner.iconset/icon_16x16.png
index ae6f698cc..37f3b3697 100644
--- a/graphics/osx/dcpomatic2_combiner.iconset/icon_16x16.png
+++ b/graphics/osx/dcpomatic2_combiner.iconset/icon_16x16.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_combiner.iconset/icon_16x16@2x.png b/graphics/osx/dcpomatic2_combiner.iconset/icon_16x16@2x.png
index ae6f698cc..37f3b3697 100644
--- a/graphics/osx/dcpomatic2_combiner.iconset/icon_16x16@2x.png
+++ b/graphics/osx/dcpomatic2_combiner.iconset/icon_16x16@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_combiner.iconset/icon_256x256.png b/graphics/osx/dcpomatic2_combiner.iconset/icon_256x256.png
index 7388b2ee8..bf7048478 100644
--- a/graphics/osx/dcpomatic2_combiner.iconset/icon_256x256.png
+++ b/graphics/osx/dcpomatic2_combiner.iconset/icon_256x256.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_combiner.iconset/icon_256x256@2x.png b/graphics/osx/dcpomatic2_combiner.iconset/icon_256x256@2x.png
index 7388b2ee8..bf7048478 100644
--- a/graphics/osx/dcpomatic2_combiner.iconset/icon_256x256@2x.png
+++ b/graphics/osx/dcpomatic2_combiner.iconset/icon_256x256@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_combiner.iconset/icon_32x32.png b/graphics/osx/dcpomatic2_combiner.iconset/icon_32x32.png
index e24e0e0e5..63ce67b63 100644
--- a/graphics/osx/dcpomatic2_combiner.iconset/icon_32x32.png
+++ b/graphics/osx/dcpomatic2_combiner.iconset/icon_32x32.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_combiner.iconset/icon_32x32@2x.png b/graphics/osx/dcpomatic2_combiner.iconset/icon_32x32@2x.png
index e24e0e0e5..63ce67b63 100644
--- a/graphics/osx/dcpomatic2_combiner.iconset/icon_32x32@2x.png
+++ b/graphics/osx/dcpomatic2_combiner.iconset/icon_32x32@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_combiner.iconset/icon_512x512.png b/graphics/osx/dcpomatic2_combiner.iconset/icon_512x512.png
index 345166990..41f0f6911 100644
--- a/graphics/osx/dcpomatic2_combiner.iconset/icon_512x512.png
+++ b/graphics/osx/dcpomatic2_combiner.iconset/icon_512x512.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_combiner.iconset/icon_512x512@2x.png b/graphics/osx/dcpomatic2_combiner.iconset/icon_512x512@2x.png
index 345166990..41f0f6911 100644
--- a/graphics/osx/dcpomatic2_combiner.iconset/icon_512x512@2x.png
+++ b/graphics/osx/dcpomatic2_combiner.iconset/icon_512x512@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_disk.icns b/graphics/osx/dcpomatic2_disk.icns
index 1d6beef3c..dd47c9513 100644
--- a/graphics/osx/dcpomatic2_disk.icns
+++ b/graphics/osx/dcpomatic2_disk.icns
Binary files differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_128x128.png b/graphics/osx/dcpomatic2_disk.iconset/icon_128x128.png
index 96573fdd8..97de66151 100644
--- a/graphics/osx/dcpomatic2_disk.iconset/icon_128x128.png
+++ b/graphics/osx/dcpomatic2_disk.iconset/icon_128x128.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_128x128@2x.png b/graphics/osx/dcpomatic2_disk.iconset/icon_128x128@2x.png
index 96573fdd8..97de66151 100644
--- a/graphics/osx/dcpomatic2_disk.iconset/icon_128x128@2x.png
+++ b/graphics/osx/dcpomatic2_disk.iconset/icon_128x128@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_16x16.png b/graphics/osx/dcpomatic2_disk.iconset/icon_16x16.png
index 29d2c9b63..94d9f001d 100644
--- a/graphics/osx/dcpomatic2_disk.iconset/icon_16x16.png
+++ b/graphics/osx/dcpomatic2_disk.iconset/icon_16x16.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_16x16@2x.png b/graphics/osx/dcpomatic2_disk.iconset/icon_16x16@2x.png
index 29d2c9b63..94d9f001d 100644
--- a/graphics/osx/dcpomatic2_disk.iconset/icon_16x16@2x.png
+++ b/graphics/osx/dcpomatic2_disk.iconset/icon_16x16@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_256x256.png b/graphics/osx/dcpomatic2_disk.iconset/icon_256x256.png
index 9baf4a851..4eb22c83a 100644
--- a/graphics/osx/dcpomatic2_disk.iconset/icon_256x256.png
+++ b/graphics/osx/dcpomatic2_disk.iconset/icon_256x256.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_256x256@2x.png b/graphics/osx/dcpomatic2_disk.iconset/icon_256x256@2x.png
index 9baf4a851..4eb22c83a 100644
--- a/graphics/osx/dcpomatic2_disk.iconset/icon_256x256@2x.png
+++ b/graphics/osx/dcpomatic2_disk.iconset/icon_256x256@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_32x32.png b/graphics/osx/dcpomatic2_disk.iconset/icon_32x32.png
index 439e204af..1607cf5f3 100644
--- a/graphics/osx/dcpomatic2_disk.iconset/icon_32x32.png
+++ b/graphics/osx/dcpomatic2_disk.iconset/icon_32x32.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_32x32@2x.png b/graphics/osx/dcpomatic2_disk.iconset/icon_32x32@2x.png
index 439e204af..1607cf5f3 100644
--- a/graphics/osx/dcpomatic2_disk.iconset/icon_32x32@2x.png
+++ b/graphics/osx/dcpomatic2_disk.iconset/icon_32x32@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_512x512.png b/graphics/osx/dcpomatic2_disk.iconset/icon_512x512.png
index 00570f83f..79ced6e6e 100644
--- a/graphics/osx/dcpomatic2_disk.iconset/icon_512x512.png
+++ b/graphics/osx/dcpomatic2_disk.iconset/icon_512x512.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_512x512@2x.png b/graphics/osx/dcpomatic2_disk.iconset/icon_512x512@2x.png
index 00570f83f..79ced6e6e 100644
--- a/graphics/osx/dcpomatic2_disk.iconset/icon_512x512@2x.png
+++ b/graphics/osx/dcpomatic2_disk.iconset/icon_512x512@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_editor.icns b/graphics/osx/dcpomatic2_editor.icns
index 30729b874..0b711fd4a 100644
--- a/graphics/osx/dcpomatic2_editor.icns
+++ b/graphics/osx/dcpomatic2_editor.icns
Binary files differ
diff --git a/graphics/osx/dcpomatic2_editor.iconset/icon_128x128.png b/graphics/osx/dcpomatic2_editor.iconset/icon_128x128.png
index d05282c43..e93bd5cff 100644
--- a/graphics/osx/dcpomatic2_editor.iconset/icon_128x128.png
+++ b/graphics/osx/dcpomatic2_editor.iconset/icon_128x128.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_editor.iconset/icon_128x128@2x.png b/graphics/osx/dcpomatic2_editor.iconset/icon_128x128@2x.png
index d05282c43..e93bd5cff 100644
--- a/graphics/osx/dcpomatic2_editor.iconset/icon_128x128@2x.png
+++ b/graphics/osx/dcpomatic2_editor.iconset/icon_128x128@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_editor.iconset/icon_16x16.png b/graphics/osx/dcpomatic2_editor.iconset/icon_16x16.png
index 2ec12f82b..deff7c1af 100644
--- a/graphics/osx/dcpomatic2_editor.iconset/icon_16x16.png
+++ b/graphics/osx/dcpomatic2_editor.iconset/icon_16x16.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_editor.iconset/icon_16x16@2x.png b/graphics/osx/dcpomatic2_editor.iconset/icon_16x16@2x.png
index 2ec12f82b..deff7c1af 100644
--- a/graphics/osx/dcpomatic2_editor.iconset/icon_16x16@2x.png
+++ b/graphics/osx/dcpomatic2_editor.iconset/icon_16x16@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_editor.iconset/icon_256x256.png b/graphics/osx/dcpomatic2_editor.iconset/icon_256x256.png
index d78fcb77c..1ad00f9b2 100644
--- a/graphics/osx/dcpomatic2_editor.iconset/icon_256x256.png
+++ b/graphics/osx/dcpomatic2_editor.iconset/icon_256x256.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_editor.iconset/icon_256x256@2x.png b/graphics/osx/dcpomatic2_editor.iconset/icon_256x256@2x.png
index d78fcb77c..1ad00f9b2 100644
--- a/graphics/osx/dcpomatic2_editor.iconset/icon_256x256@2x.png
+++ b/graphics/osx/dcpomatic2_editor.iconset/icon_256x256@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_editor.iconset/icon_32x32.png b/graphics/osx/dcpomatic2_editor.iconset/icon_32x32.png
index 821af7de0..3c21ecd83 100644
--- a/graphics/osx/dcpomatic2_editor.iconset/icon_32x32.png
+++ b/graphics/osx/dcpomatic2_editor.iconset/icon_32x32.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_editor.iconset/icon_32x32@2x.png b/graphics/osx/dcpomatic2_editor.iconset/icon_32x32@2x.png
index 821af7de0..3c21ecd83 100644
--- a/graphics/osx/dcpomatic2_editor.iconset/icon_32x32@2x.png
+++ b/graphics/osx/dcpomatic2_editor.iconset/icon_32x32@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_editor.iconset/icon_512x512.png b/graphics/osx/dcpomatic2_editor.iconset/icon_512x512.png
index 53bf93301..10734972e 100644
--- a/graphics/osx/dcpomatic2_editor.iconset/icon_512x512.png
+++ b/graphics/osx/dcpomatic2_editor.iconset/icon_512x512.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_editor.iconset/icon_512x512@2x.png b/graphics/osx/dcpomatic2_editor.iconset/icon_512x512@2x.png
index 53bf93301..10734972e 100644
--- a/graphics/osx/dcpomatic2_editor.iconset/icon_512x512@2x.png
+++ b/graphics/osx/dcpomatic2_editor.iconset/icon_512x512@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_kdm.icns b/graphics/osx/dcpomatic2_kdm.icns
index 3cde61af3..a97b98812 100644
--- a/graphics/osx/dcpomatic2_kdm.icns
+++ b/graphics/osx/dcpomatic2_kdm.icns
Binary files differ
diff --git a/graphics/osx/dcpomatic2_kdm.iconset/icon_128x128.png b/graphics/osx/dcpomatic2_kdm.iconset/icon_128x128.png
index cb725b889..2bbe10f90 100644
--- a/graphics/osx/dcpomatic2_kdm.iconset/icon_128x128.png
+++ b/graphics/osx/dcpomatic2_kdm.iconset/icon_128x128.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_kdm.iconset/icon_128x128@2x.png b/graphics/osx/dcpomatic2_kdm.iconset/icon_128x128@2x.png
index cb725b889..2bbe10f90 100644
--- a/graphics/osx/dcpomatic2_kdm.iconset/icon_128x128@2x.png
+++ b/graphics/osx/dcpomatic2_kdm.iconset/icon_128x128@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_kdm.iconset/icon_16x16.png b/graphics/osx/dcpomatic2_kdm.iconset/icon_16x16.png
index cbb38e720..6e5ffe946 100644
--- a/graphics/osx/dcpomatic2_kdm.iconset/icon_16x16.png
+++ b/graphics/osx/dcpomatic2_kdm.iconset/icon_16x16.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_kdm.iconset/icon_16x16@2x.png b/graphics/osx/dcpomatic2_kdm.iconset/icon_16x16@2x.png
index cbb38e720..6e5ffe946 100644
--- a/graphics/osx/dcpomatic2_kdm.iconset/icon_16x16@2x.png
+++ b/graphics/osx/dcpomatic2_kdm.iconset/icon_16x16@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_kdm.iconset/icon_256x256.png b/graphics/osx/dcpomatic2_kdm.iconset/icon_256x256.png
index 5f2de6649..5c54cfcf2 100644
--- a/graphics/osx/dcpomatic2_kdm.iconset/icon_256x256.png
+++ b/graphics/osx/dcpomatic2_kdm.iconset/icon_256x256.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_kdm.iconset/icon_256x256@2x.png b/graphics/osx/dcpomatic2_kdm.iconset/icon_256x256@2x.png
index 5f2de6649..5c54cfcf2 100644
--- a/graphics/osx/dcpomatic2_kdm.iconset/icon_256x256@2x.png
+++ b/graphics/osx/dcpomatic2_kdm.iconset/icon_256x256@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_kdm.iconset/icon_32x32.png b/graphics/osx/dcpomatic2_kdm.iconset/icon_32x32.png
index eeebcc334..c8fe47b23 100644
--- a/graphics/osx/dcpomatic2_kdm.iconset/icon_32x32.png
+++ b/graphics/osx/dcpomatic2_kdm.iconset/icon_32x32.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_kdm.iconset/icon_32x32@2x.png b/graphics/osx/dcpomatic2_kdm.iconset/icon_32x32@2x.png
index eeebcc334..c8fe47b23 100644
--- a/graphics/osx/dcpomatic2_kdm.iconset/icon_32x32@2x.png
+++ b/graphics/osx/dcpomatic2_kdm.iconset/icon_32x32@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_kdm.iconset/icon_512x512.png b/graphics/osx/dcpomatic2_kdm.iconset/icon_512x512.png
index c85244b41..3a2415611 100644
--- a/graphics/osx/dcpomatic2_kdm.iconset/icon_512x512.png
+++ b/graphics/osx/dcpomatic2_kdm.iconset/icon_512x512.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_kdm.iconset/icon_512x512@2x.png b/graphics/osx/dcpomatic2_kdm.iconset/icon_512x512@2x.png
index c85244b41..3a2415611 100644
--- a/graphics/osx/dcpomatic2_kdm.iconset/icon_512x512@2x.png
+++ b/graphics/osx/dcpomatic2_kdm.iconset/icon_512x512@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_player.icns b/graphics/osx/dcpomatic2_player.icns
index f57bd7eb3..bf43eaf67 100644
--- a/graphics/osx/dcpomatic2_player.icns
+++ b/graphics/osx/dcpomatic2_player.icns
Binary files differ
diff --git a/graphics/osx/dcpomatic2_player.iconset/icon_128x128.png b/graphics/osx/dcpomatic2_player.iconset/icon_128x128.png
index a851d289a..ca4c9bbbb 100644
--- a/graphics/osx/dcpomatic2_player.iconset/icon_128x128.png
+++ b/graphics/osx/dcpomatic2_player.iconset/icon_128x128.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_player.iconset/icon_128x128@2x.png b/graphics/osx/dcpomatic2_player.iconset/icon_128x128@2x.png
index a851d289a..ca4c9bbbb 100644
--- a/graphics/osx/dcpomatic2_player.iconset/icon_128x128@2x.png
+++ b/graphics/osx/dcpomatic2_player.iconset/icon_128x128@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_player.iconset/icon_16x16.png b/graphics/osx/dcpomatic2_player.iconset/icon_16x16.png
index 821df7805..42336a67f 100644
--- a/graphics/osx/dcpomatic2_player.iconset/icon_16x16.png
+++ b/graphics/osx/dcpomatic2_player.iconset/icon_16x16.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_player.iconset/icon_16x16@2x.png b/graphics/osx/dcpomatic2_player.iconset/icon_16x16@2x.png
index 821df7805..42336a67f 100644
--- a/graphics/osx/dcpomatic2_player.iconset/icon_16x16@2x.png
+++ b/graphics/osx/dcpomatic2_player.iconset/icon_16x16@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_player.iconset/icon_256x256.png b/graphics/osx/dcpomatic2_player.iconset/icon_256x256.png
index a5fa0ff3c..140fbc967 100644
--- a/graphics/osx/dcpomatic2_player.iconset/icon_256x256.png
+++ b/graphics/osx/dcpomatic2_player.iconset/icon_256x256.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_player.iconset/icon_256x256@2x.png b/graphics/osx/dcpomatic2_player.iconset/icon_256x256@2x.png
index a5fa0ff3c..140fbc967 100644
--- a/graphics/osx/dcpomatic2_player.iconset/icon_256x256@2x.png
+++ b/graphics/osx/dcpomatic2_player.iconset/icon_256x256@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_player.iconset/icon_32x32.png b/graphics/osx/dcpomatic2_player.iconset/icon_32x32.png
index 5ef8c0d90..7f261158d 100644
--- a/graphics/osx/dcpomatic2_player.iconset/icon_32x32.png
+++ b/graphics/osx/dcpomatic2_player.iconset/icon_32x32.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_player.iconset/icon_32x32@2x.png b/graphics/osx/dcpomatic2_player.iconset/icon_32x32@2x.png
index 5ef8c0d90..7f261158d 100644
--- a/graphics/osx/dcpomatic2_player.iconset/icon_32x32@2x.png
+++ b/graphics/osx/dcpomatic2_player.iconset/icon_32x32@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_player.iconset/icon_512x512.png b/graphics/osx/dcpomatic2_player.iconset/icon_512x512.png
index 001bf0194..c22d85aad 100644
--- a/graphics/osx/dcpomatic2_player.iconset/icon_512x512.png
+++ b/graphics/osx/dcpomatic2_player.iconset/icon_512x512.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_player.iconset/icon_512x512@2x.png b/graphics/osx/dcpomatic2_player.iconset/icon_512x512@2x.png
index 001bf0194..c22d85aad 100644
--- a/graphics/osx/dcpomatic2_player.iconset/icon_512x512@2x.png
+++ b/graphics/osx/dcpomatic2_player.iconset/icon_512x512@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_playlist.icns b/graphics/osx/dcpomatic2_playlist.icns
index 64e971902..82a8ae297 100644
--- a/graphics/osx/dcpomatic2_playlist.icns
+++ b/graphics/osx/dcpomatic2_playlist.icns
Binary files differ
diff --git a/graphics/osx/dcpomatic2_playlist.iconset/icon_128x128.png b/graphics/osx/dcpomatic2_playlist.iconset/icon_128x128.png
index 223228580..68cd2458e 100644
--- a/graphics/osx/dcpomatic2_playlist.iconset/icon_128x128.png
+++ b/graphics/osx/dcpomatic2_playlist.iconset/icon_128x128.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_playlist.iconset/icon_128x128@2x.png b/graphics/osx/dcpomatic2_playlist.iconset/icon_128x128@2x.png
index 223228580..68cd2458e 100644
--- a/graphics/osx/dcpomatic2_playlist.iconset/icon_128x128@2x.png
+++ b/graphics/osx/dcpomatic2_playlist.iconset/icon_128x128@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_playlist.iconset/icon_16x16.png b/graphics/osx/dcpomatic2_playlist.iconset/icon_16x16.png
index f55396b39..5db3fc873 100644
--- a/graphics/osx/dcpomatic2_playlist.iconset/icon_16x16.png
+++ b/graphics/osx/dcpomatic2_playlist.iconset/icon_16x16.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_playlist.iconset/icon_16x16@2x.png b/graphics/osx/dcpomatic2_playlist.iconset/icon_16x16@2x.png
index f55396b39..5db3fc873 100644
--- a/graphics/osx/dcpomatic2_playlist.iconset/icon_16x16@2x.png
+++ b/graphics/osx/dcpomatic2_playlist.iconset/icon_16x16@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_playlist.iconset/icon_256x256.png b/graphics/osx/dcpomatic2_playlist.iconset/icon_256x256.png
index b5d827d09..0b590c576 100644
--- a/graphics/osx/dcpomatic2_playlist.iconset/icon_256x256.png
+++ b/graphics/osx/dcpomatic2_playlist.iconset/icon_256x256.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_playlist.iconset/icon_256x256@2x.png b/graphics/osx/dcpomatic2_playlist.iconset/icon_256x256@2x.png
index b5d827d09..0b590c576 100644
--- a/graphics/osx/dcpomatic2_playlist.iconset/icon_256x256@2x.png
+++ b/graphics/osx/dcpomatic2_playlist.iconset/icon_256x256@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_playlist.iconset/icon_32x32.png b/graphics/osx/dcpomatic2_playlist.iconset/icon_32x32.png
index 091b559f4..4bd937653 100644
--- a/graphics/osx/dcpomatic2_playlist.iconset/icon_32x32.png
+++ b/graphics/osx/dcpomatic2_playlist.iconset/icon_32x32.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_playlist.iconset/icon_32x32@2x.png b/graphics/osx/dcpomatic2_playlist.iconset/icon_32x32@2x.png
index 091b559f4..4bd937653 100644
--- a/graphics/osx/dcpomatic2_playlist.iconset/icon_32x32@2x.png
+++ b/graphics/osx/dcpomatic2_playlist.iconset/icon_32x32@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_playlist.iconset/icon_512x512.png b/graphics/osx/dcpomatic2_playlist.iconset/icon_512x512.png
index bb2e1c79e..453779516 100644
--- a/graphics/osx/dcpomatic2_playlist.iconset/icon_512x512.png
+++ b/graphics/osx/dcpomatic2_playlist.iconset/icon_512x512.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_playlist.iconset/icon_512x512@2x.png b/graphics/osx/dcpomatic2_playlist.iconset/icon_512x512@2x.png
index bb2e1c79e..453779516 100644
--- a/graphics/osx/dcpomatic2_playlist.iconset/icon_512x512@2x.png
+++ b/graphics/osx/dcpomatic2_playlist.iconset/icon_512x512@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_server.icns b/graphics/osx/dcpomatic2_server.icns
index 6e1cba7f2..9a0c8aaaf 100644
--- a/graphics/osx/dcpomatic2_server.icns
+++ b/graphics/osx/dcpomatic2_server.icns
Binary files differ
diff --git a/graphics/osx/dcpomatic2_server.iconset/icon_128x128.png b/graphics/osx/dcpomatic2_server.iconset/icon_128x128.png
index 00038ed60..ac2e6c9f7 100644
--- a/graphics/osx/dcpomatic2_server.iconset/icon_128x128.png
+++ b/graphics/osx/dcpomatic2_server.iconset/icon_128x128.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_server.iconset/icon_128x128@2x.png b/graphics/osx/dcpomatic2_server.iconset/icon_128x128@2x.png
index 00038ed60..ac2e6c9f7 100644
--- a/graphics/osx/dcpomatic2_server.iconset/icon_128x128@2x.png
+++ b/graphics/osx/dcpomatic2_server.iconset/icon_128x128@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_server.iconset/icon_16x16.png b/graphics/osx/dcpomatic2_server.iconset/icon_16x16.png
index 19c4acafc..bbaf9dedd 100644
--- a/graphics/osx/dcpomatic2_server.iconset/icon_16x16.png
+++ b/graphics/osx/dcpomatic2_server.iconset/icon_16x16.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_server.iconset/icon_16x16@2x.png b/graphics/osx/dcpomatic2_server.iconset/icon_16x16@2x.png
index 19c4acafc..bbaf9dedd 100644
--- a/graphics/osx/dcpomatic2_server.iconset/icon_16x16@2x.png
+++ b/graphics/osx/dcpomatic2_server.iconset/icon_16x16@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_server.iconset/icon_256x256.png b/graphics/osx/dcpomatic2_server.iconset/icon_256x256.png
index b693546e9..bd4d105b9 100644
--- a/graphics/osx/dcpomatic2_server.iconset/icon_256x256.png
+++ b/graphics/osx/dcpomatic2_server.iconset/icon_256x256.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_server.iconset/icon_256x256@2x.png b/graphics/osx/dcpomatic2_server.iconset/icon_256x256@2x.png
index b693546e9..bd4d105b9 100644
--- a/graphics/osx/dcpomatic2_server.iconset/icon_256x256@2x.png
+++ b/graphics/osx/dcpomatic2_server.iconset/icon_256x256@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_server.iconset/icon_32x32.png b/graphics/osx/dcpomatic2_server.iconset/icon_32x32.png
index 3fa7d1fc3..914da144b 100644
--- a/graphics/osx/dcpomatic2_server.iconset/icon_32x32.png
+++ b/graphics/osx/dcpomatic2_server.iconset/icon_32x32.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_server.iconset/icon_32x32@2x.png b/graphics/osx/dcpomatic2_server.iconset/icon_32x32@2x.png
index 3fa7d1fc3..914da144b 100644
--- a/graphics/osx/dcpomatic2_server.iconset/icon_32x32@2x.png
+++ b/graphics/osx/dcpomatic2_server.iconset/icon_32x32@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_server.iconset/icon_512x512.png b/graphics/osx/dcpomatic2_server.iconset/icon_512x512.png
index 6cf7c2a96..40410ed57 100644
--- a/graphics/osx/dcpomatic2_server.iconset/icon_512x512.png
+++ b/graphics/osx/dcpomatic2_server.iconset/icon_512x512.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_server.iconset/icon_512x512@2x.png b/graphics/osx/dcpomatic2_server.iconset/icon_512x512@2x.png
index 6cf7c2a96..40410ed57 100644
--- a/graphics/osx/dcpomatic2_server.iconset/icon_512x512@2x.png
+++ b/graphics/osx/dcpomatic2_server.iconset/icon_512x512@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_verifier.icns b/graphics/osx/dcpomatic2_verifier.icns
new file mode 100644
index 000000000..ffd23360c
--- /dev/null
+++ b/graphics/osx/dcpomatic2_verifier.icns
Binary files differ
diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_128x128.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_128x128.png
new file mode 100644
index 000000000..d28ceb27b
--- /dev/null
+++ b/graphics/osx/dcpomatic2_verifier.iconset/icon_128x128.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_128x128@2x.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_128x128@2x.png
new file mode 100644
index 000000000..d28ceb27b
--- /dev/null
+++ b/graphics/osx/dcpomatic2_verifier.iconset/icon_128x128@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_16x16.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_16x16.png
new file mode 100644
index 000000000..9ad2929dc
--- /dev/null
+++ b/graphics/osx/dcpomatic2_verifier.iconset/icon_16x16.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_16x16@2x.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_16x16@2x.png
new file mode 100644
index 000000000..9ad2929dc
--- /dev/null
+++ b/graphics/osx/dcpomatic2_verifier.iconset/icon_16x16@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_256x256.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_256x256.png
new file mode 100644
index 000000000..f5198a733
--- /dev/null
+++ b/graphics/osx/dcpomatic2_verifier.iconset/icon_256x256.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_256x256@2x.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_256x256@2x.png
new file mode 100644
index 000000000..f5198a733
--- /dev/null
+++ b/graphics/osx/dcpomatic2_verifier.iconset/icon_256x256@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_32x32.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_32x32.png
new file mode 100644
index 000000000..61afb5f9f
--- /dev/null
+++ b/graphics/osx/dcpomatic2_verifier.iconset/icon_32x32.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_32x32@2x.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_32x32@2x.png
new file mode 100644
index 000000000..61afb5f9f
--- /dev/null
+++ b/graphics/osx/dcpomatic2_verifier.iconset/icon_32x32@2x.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_512x512.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_512x512.png
new file mode 100644
index 000000000..b8417ed85
--- /dev/null
+++ b/graphics/osx/dcpomatic2_verifier.iconset/icon_512x512.png
Binary files differ
diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_512x512@2x.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_512x512@2x.png
new file mode 100644
index 000000000..b8417ed85
--- /dev/null
+++ b/graphics/osx/dcpomatic2_verifier.iconset/icon_512x512@2x.png
Binary files differ
diff --git a/graphics/src/dcpomatic.png b/graphics/src/dcpomatic2.png
index 95404d5fc..95404d5fc 120000
--- a/graphics/src/dcpomatic.png
+++ b/graphics/src/dcpomatic2.png
diff --git a/graphics/src/dcpomatic2_batch.svg b/graphics/src/dcpomatic2_batch.svg
index fcc174d40..fdb820f45 100644
--- a/graphics/src/dcpomatic2_batch.svg
+++ b/graphics/src/dcpomatic2_batch.svg
@@ -2,21 +2,21 @@
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="1066.6666"
- height="1066.6666"
+ width="1024"
+ height="1024"
id="svg2"
version="1.1"
- inkscape:version="0.92+devel 15537"
+ inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
sodipodi:docname="dcpomatic2_batch.svg"
- viewBox="0 0 1000 1000">
+ viewBox="0 0 960.00004 960.00004"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4">
<linearGradient
@@ -103,17 +103,18 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.5"
- inkscape:cx="793.18353"
- inkscape:cy="448.89208"
+ inkscape:cx="795"
+ inkscape:cy="448"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
- inkscape:window-width="1280"
- inkscape:window-height="987"
- inkscape:window-x="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1048"
+ inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
- inkscape:document-rotation="0" />
+ inkscape:document-rotation="0"
+ inkscape:pagecheckerboard="0" />
<metadata
id="metadata7">
<rdf:RDF>
@@ -122,7 +123,6 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@@ -131,30 +131,33 @@
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-52.362188)">
- <image
- sodipodi:absref="/home/carl/src/dcpomatic/graphics/src/dcpomatic.png"
- xlink:href="dcpomatic.png"
- id="image4646"
- preserveAspectRatio="none"
- height="960.00006"
- width="960.00006"
- x="19.999969"
- y="72.36216" />
<g
- transform="matrix(0,-3.0358402,3.0358402,0,-1935.8423,946.32674)"
- inkscape:label="Layer 1"
- id="layer1-1"
- style="opacity:0.825;fill:#ececec">
- <path
- d="m 80.594,756.96 c -1.4336,0.11093 -3.2466,0.73862 -5.5938,2.0938 -15.022,8.6728 6.6103,12.666 -2.0625,27.688 -8.673,15.02 -22.938,-1.72 -22.938,15.62 0,17.346 14.265,0.60321 22.938,15.625 8.6728,15.022 -12.959,19.015 2.0625,27.688 15.022,8.6728 7.6543,-12.062 25,-12.062 17.3457,0 9.9782,20.735 25,12.062 15.022,-8.6728 -6.6103,-12.666 2.0625,-27.688 8.6728,-15.022 22.938,1.7207 22.938,-15.625 0,-17.3457 -14.265,-0.60321 -22.938,-15.625 -8.6728,-15.022 12.959,-19.015 -2.0625,-27.688 -15.022,-8.6728 -7.6543,12.062 -25,12.062 -14.635,0 -11.665,-14.755 -19.406,-14.156 z M 100,782.36 c 11.046,0 20,8.9543 20,20 0,11.0457 -8.9543,20 -20,20 -11.0457,0 -20,-8.9543 -20,-20 0,-11.0457 8.9543,-20 20,-20 z"
- inkscape:connector-curvature="0"
- style="fill:#ececec"
- id="path2987" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#ececec"
- d="m 145.84,780.15 c -0.88956,0.60965 -1.8317,1.6972 -2.8481,3.4576 -6.5046,11.266 9.0432,5.7478 9.0432,18.757 -1e-5,13.009 -15.544,7.484 -9.039,18.75 6.5046,11.266 9.4914,-4.9574 20.758,1.5472 11.266,6.5046 -1.2868,17.21 11.722,17.21 13.0088,0 0.4482,-10.705 11.715,-17.21 11.266,-6.5046 14.257,9.7262 20.761,-1.5402 6.5046,-11.266 -9.0432,-5.7478 -9.0432,-18.757 1e-5,-13.009 15.544,-7.484 9.039,-18.75 -6.5046,-11.266 -9.4914,4.9574 -20.758,-1.5472 -11.266,-6.5046 1.2868,-17.21 -11.722,-17.21 -13.0088,0 -0.4482,10.705 -11.715,17.21 -9.506,5.4883 -13.11,-5.2096 -17.913,-1.9174 z m 22.132,9.2245 c 7.1744,-4.1421 16.348,-1.684 20.49,5.4904 4.1421,7.1744 1.684,16.348 -5.4904,20.49 -7.1744,4.1421 -16.348,1.684 -20.49,-5.4904 -4.1421,-7.1744 -1.684,-16.348 5.4904,-20.49 z"
- id="path2992" />
+ id="g3097"
+ transform="translate(-19.99998,-19.999982)">
+ <image
+ xlink:href="dcpomatic.png"
+ id="image4646"
+ preserveAspectRatio="none"
+ height="960.00006"
+ width="960.00006"
+ x="19.999969"
+ y="72.36216" />
+ <g
+ transform="matrix(0,-3.0358402,3.0358402,0,-1935.8423,946.32674)"
+ inkscape:label="Layer 1"
+ id="layer1-1"
+ style="opacity:0.825;fill:#ececec">
+ <path
+ d="m 80.594,756.96 c -1.4336,0.11093 -3.2466,0.73862 -5.5938,2.0938 -15.022,8.6728 6.6103,12.666 -2.0625,27.688 -8.673,15.02 -22.938,-1.72 -22.938,15.62 0,17.346 14.265,0.60321 22.938,15.625 8.6728,15.022 -12.959,19.015 2.0625,27.688 15.022,8.6728 7.6543,-12.062 25,-12.062 17.3457,0 9.9782,20.735 25,12.062 15.022,-8.6728 -6.6103,-12.666 2.0625,-27.688 8.6728,-15.022 22.938,1.7207 22.938,-15.625 0,-17.3457 -14.265,-0.60321 -22.938,-15.625 -8.6728,-15.022 12.959,-19.015 -2.0625,-27.688 -15.022,-8.6728 -7.6543,12.062 -25,12.062 -14.635,0 -11.665,-14.755 -19.406,-14.156 z M 100,782.36 c 11.046,0 20,8.9543 20,20 0,11.0457 -8.9543,20 -20,20 -11.0457,0 -20,-8.9543 -20,-20 0,-11.0457 8.9543,-20 20,-20 z"
+ inkscape:connector-curvature="0"
+ style="fill:#ececec"
+ id="path2987" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#ececec"
+ d="m 145.84,780.15 c -0.88956,0.60965 -1.8317,1.6972 -2.8481,3.4576 -6.5046,11.266 9.0432,5.7478 9.0432,18.757 -1e-5,13.009 -15.544,7.484 -9.039,18.75 6.5046,11.266 9.4914,-4.9574 20.758,1.5472 11.266,6.5046 -1.2868,17.21 11.722,17.21 13.0088,0 0.4482,-10.705 11.715,-17.21 11.266,-6.5046 14.257,9.7262 20.761,-1.5402 6.5046,-11.266 -9.0432,-5.7478 -9.0432,-18.757 1e-5,-13.009 15.544,-7.484 9.039,-18.75 -6.5046,-11.266 -9.4914,4.9574 -20.758,-1.5472 -11.266,-6.5046 1.2868,-17.21 -11.722,-17.21 -13.0088,0 -0.4482,10.705 -11.715,17.21 -9.506,5.4883 -13.11,-5.2096 -17.913,-1.9174 z m 22.132,9.2245 c 7.1744,-4.1421 16.348,-1.684 20.49,5.4904 4.1421,7.1744 1.684,16.348 -5.4904,20.49 -7.1744,4.1421 -16.348,1.684 -20.49,-5.4904 -4.1421,-7.1744 -1.684,-16.348 5.4904,-20.49 z"
+ id="path2992" />
+ </g>
</g>
</g>
</svg>
diff --git a/graphics/src/dcpomatic2_combiner.svg b/graphics/src/dcpomatic2_combiner.svg
index 8a150c521..24be9e600 100644
--- a/graphics/src/dcpomatic2_combiner.svg
+++ b/graphics/src/dcpomatic2_combiner.svg
@@ -1,104 +1,104 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- viewBox="0 0 1000 1000"
- sodipodi:docname="dcpomatic2_combine.svg"
- inkscape:version="1.0 (4035a4f, 2020-05-01)"
+ viewBox="0 0 960.00004 960.00004"
+ sodipodi:docname="dcpomatic2_combiner.svg"
+ inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
version="1.1"
id="svg2"
- height="1066.6666"
- width="1066.6666">
+ height="1024"
+ width="1024"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="Arrow1Mstart"
- refX="0.0"
- refY="0.0"
+ refX="0"
+ refY="0"
orient="auto"
inkscape:stockid="Arrow1Mstart">
<path
- transform="scale(0.4) translate(10,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ transform="matrix(0.4,0,0,0.4,4,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path893" />
</marker>
<marker
inkscape:isstock="true"
- style="overflow:visible;"
+ style="overflow:visible"
id="marker1193"
- refX="0.0"
- refY="0.0"
+ refX="0"
+ refY="0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
- transform="scale(0.8) rotate(180) translate(12.5,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path1191" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker1189"
- refX="0.0"
- refY="0.0"
+ refX="0"
+ refY="0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
- transform="scale(0.8) translate(12.5,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path1187" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker1167"
- refX="0.0"
- refY="0.0"
+ refX="0"
+ refY="0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
- transform="scale(0.8) translate(12.5,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path1165" />
</marker>
<marker
inkscape:isstock="true"
- style="overflow:visible;"
+ style="overflow:visible"
id="Arrow1Lend"
- refX="0.0"
- refY="0.0"
+ refX="0"
+ refY="0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
- transform="scale(0.8) rotate(180) translate(12.5,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path890" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="Arrow1Lstart"
- refX="0.0"
- refY="0.0"
+ refX="0"
+ refY="0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
- transform="scale(0.8) translate(12.5,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path887" />
</marker>
<linearGradient
@@ -181,22 +181,23 @@
inkscape:snap-midpoints="true"
inkscape:document-rotation="0"
inkscape:window-maximized="1"
- inkscape:window-y="27"
- inkscape:window-x="0"
- inkscape:window-height="986"
- inkscape:window-width="1680"
+ inkscape:window-y="0"
+ inkscape:window-x="1920"
+ inkscape:window-height="1048"
+ inkscape:window-width="1920"
showgrid="false"
inkscape:current-layer="layer1"
inkscape:document-units="px"
- inkscape:cy="589.03894"
- inkscape:cx="-138.64759"
+ inkscape:cy="589.81402"
+ inkscape:cx="-136.0008"
inkscape:zoom="0.34926264"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
- id="base" />
+ id="base"
+ inkscape:pagecheckerboard="0" />
<metadata
id="metadata7">
<rdf:RDF>
@@ -205,7 +206,6 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@@ -214,18 +214,21 @@
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
- <image
- sodipodi:absref="/home/carl/src/dcpomatic/graphics/src/dcpomatic.png"
- xlink:href="dcpomatic.png"
- id="image4358"
- preserveAspectRatio="none"
- height="960.00006"
- width="960.00006"
- x="10.670144"
- y="80.386467" />
- <path
- d="m 490.67101,280.27778 a 46.083593,46.083593 0 0 0 -46.08414,46.08418 V 514.30317 H 256.6457 a 46.083685,46.083685 0 0 0 -46.08423,46.08417 46.083685,46.083685 0 0 0 46.08423,46.08249 h 187.94117 v 187.94291 a 46.083593,46.083593 0 0 0 46.08414,46.08248 46.083593,46.083593 0 0 0 46.08256,-46.08248 V 606.46983 h 187.94284 a 46.083685,46.083685 0 0 0 46.08247,-46.08249 46.083685,46.083685 0 0 0 -46.08247,-46.08417 H 536.75357 V 326.36196 a 46.083593,46.083593 0 0 0 -46.08256,-46.08418 z"
- style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#5e5e5e;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:15.5608;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;opacity:0.79289843"
- id="path1288" />
+ <g
+ id="g2927"
+ transform="translate(-10.670155,-28.024289)">
+ <image
+ xlink:href="dcpomatic.png"
+ id="image4358"
+ preserveAspectRatio="none"
+ height="960.00006"
+ width="960.00006"
+ x="10.670144"
+ y="80.386467" />
+ <path
+ d="m 490.67101,280.27778 a 46.083593,46.083593 0 0 0 -46.08414,46.08418 V 514.30317 H 256.6457 a 46.083685,46.083685 0 0 0 -46.08423,46.08417 46.083685,46.083685 0 0 0 46.08423,46.08249 h 187.94117 v 187.94291 a 46.083593,46.083593 0 0 0 46.08414,46.08248 46.083593,46.083593 0 0 0 46.08256,-46.08248 V 606.46983 h 187.94284 a 46.083685,46.083685 0 0 0 46.08247,-46.08249 46.083685,46.083685 0 0 0 -46.08247,-46.08417 H 536.75357 V 326.36196 a 46.083593,46.083593 0 0 0 -46.08256,-46.08418 z"
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.792898;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#5e5e5e;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:15.5608;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000"
+ id="path1288" />
+ </g>
</g>
</svg>
diff --git a/graphics/src/dcpomatic2_disk.svg b/graphics/src/dcpomatic2_disk.svg
index d93c8c1f5..6206a349b 100644
--- a/graphics/src/dcpomatic2_disk.svg
+++ b/graphics/src/dcpomatic2_disk.svg
@@ -2,21 +2,21 @@
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="1066.6666"
- height="1066.6666"
+ width="1024"
+ height="1024"
id="svg2"
version="1.1"
- inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
sodipodi:docname="dcpomatic2_disk.svg"
- viewBox="0 0 1000 1000">
+ viewBox="0 0 960.00004 960.00004"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
@@ -24,20 +24,21 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
- inkscape:zoom="0.061741495"
- inkscape:cx="-2231.4864"
- inkscape:cy="438.2963"
+ inkscape:zoom="0.08731566"
+ inkscape:cx="-2468.0567"
+ inkscape:cy="-240.50669"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
- inkscape:window-width="1680"
- inkscape:window-height="995"
- inkscape:window-x="0"
- inkscape:window-y="27"
+ inkscape:window-width="1920"
+ inkscape:window-height="1048"
+ inkscape:window-x="1920"
+ inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:document-rotation="0"
inkscape:snap-global="true"
- inkscape:snap-bbox="true" />
+ inkscape:snap-bbox="true"
+ inkscape:pagecheckerboard="0" />
<defs
id="defs4">
<linearGradient
@@ -99,7 +100,7 @@
inkscape:vp_x="573.17725 : 70.615423 : 0"
inkscape:vp_y="0 : 1048.4799 : 0"
inkscape:vp_z="1196.4109 : -308.5085 : 0"
- inkscape:persp3d-origin="531.47195 : 173.77218 : 1"
+ inkscape:persp3d-origin="531.47195 : 133.77222 : 1"
id="perspective4530" />
<linearGradient
x1="-772.01001"
@@ -187,7 +188,7 @@
x2="280.90915"
y2="487.69684"
gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(1.2956992,0,0,1.1173853,-70.442976,-75.323286)" />
+ gradientTransform="matrix(1.2956992,0,0,1.1173853,-792.0547,-836.605)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient979"
@@ -197,7 +198,7 @@
x2="280.90915"
y2="487.69684"
gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(1.2956992,0,0,1.1173853,346.73201,-93.640862)" />
+ gradientTransform="matrix(1.2956992,0,0,1.1173853,-374.87971,-854.92257)" />
</defs>
<metadata
id="metadata7">
@@ -207,7 +208,6 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@@ -216,84 +216,73 @@
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-52.362188)">
- <rect
- style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:#ececec;stroke-width:15.00000095;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
- id="rect945"
- width="1167.6385"
- height="1151.5332"
- x="1217.4148"
- y="1347.8682"
- rx="33.75"
- ry="33.75" />
- <image
- sodipodi:absref="/home/carl/src/dcpomatic/graphics/src/dcpomatic.png"
- xlink:href="dcpomatic.png"
- style="stroke:#000000;stroke-opacity:1;stroke-width:2.5000001;stroke-miterlimit:4;stroke-dasharray:none;opacity:1"
- id="image4358"
- preserveAspectRatio="none"
- height="960.00006"
- width="960.00006"
- x="10.670144"
- y="80.386467" />
- <rect
- style="opacity:1;vector-effect:none;fill:url(#linearGradient981);fill-opacity:1;stroke:none;stroke-width:15.00000095;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
- id="rect958"
- width="95.643623"
- height="189.7067"
- x="238.2251"
- y="451.96884"
- rx="43.729847"
- ry="36.004051" />
- <rect
- style="opacity:1;vector-effect:none;fill:url(#linearGradient981-5);fill-opacity:1;stroke:none;stroke-width:15.00000095;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
- id="rect958-6"
- width="95.643623"
- height="189.70668"
- x="655.40009"
- y="433.65125"
- rx="43.729847"
- ry="36.004051" />
- <path
- style="fill:#808080;stroke:#ffffff;stroke-width:15.00000095;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 635.01872,349.90824 85.16099,283.02659 H 263.60389 l 87.18344,-281.69086 c 3.82184,-13.96906 23.00671,-28.47528 36.85535,-28.47528 l 213.6276,-0.35249 c 17.09674,0.72772 28.6937,11.26128 33.74844,27.49204 z"
- id="path947"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="ccccccc" />
- <rect
- style="opacity:1;vector-effect:none;fill:#999999;fill-opacity:1;stroke:#ececec;stroke-width:30.00000191;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
- id="rect943"
- width="456.57584"
- height="105.73843"
- x="263.60385"
- y="632.93481"
- ry="17.726633"
- rx="17.773174" />
- <text
- xml:space="preserve"
- style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.50000191px;line-height:1.20000005;font-family:'Bitstream Vera Sans Mono';-inkscape-font-specification:'Bitstream Vera Sans Mono';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.9375;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
- x="-1064.7976"
- y="826.49603"
- id="text1027"><tspan
- sodipodi:role="line"
- id="tspan1025"
- x="-1064.7976"
- y="846.88104"
- style="stroke-width:0.9375;-inkscape-font-specification:'Bitstream Vera Sans Mono';font-family:'Bitstream Vera Sans Mono';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;" /></text>
+ <g
+ id="g2696"
+ transform="translate(710.94158,733.25744)">
+ <image
+ xlink:href="dcpomatic.png"
+ style="stroke:#000000;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="image4358"
+ preserveAspectRatio="none"
+ height="960.00006"
+ width="960.00006"
+ x="-710.94159"
+ y="-680.89526" />
+ <rect
+ style="vector-effect:none;fill:url(#linearGradient981);fill-opacity:1;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
+ id="rect958"
+ width="95.643623"
+ height="189.7067"
+ x="-483.38663"
+ y="-309.31287"
+ rx="43.729847"
+ ry="36.004051" />
+ <rect
+ style="vector-effect:none;fill:url(#linearGradient981-5);fill-opacity:1;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
+ id="rect958-6"
+ width="95.643623"
+ height="189.70668"
+ x="-66.211632"
+ y="-327.63046"
+ rx="43.729847"
+ ry="36.004051" />
+ <path
+ style="fill:#808080;stroke:#ffffff;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m -86.593,-411.37347 85.16099,283.02659 h -456.57582 l 87.18344,-281.69086 c 3.82184,-13.96906 23.00671,-28.47528 36.85535,-28.47528 l 213.6276,-0.35249 c 17.09674,0.72772 28.6937,11.26128 33.74844,27.49204 z"
+ id="path947"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccc" />
+ <rect
+ style="vector-effect:none;fill:#999999;fill-opacity:1;stroke:#ececec;stroke-width:30;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
+ id="rect943"
+ width="456.57584"
+ height="105.73843"
+ x="-458.00787"
+ y="-128.34689"
+ ry="17.726633"
+ rx="17.773174" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:128.486px;line-height:1.2;font-family:'Latin Modern Roman';-inkscape-font-specification:'Latin Modern Roman, Bold';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.9375;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ x="-326.67416"
+ y="-31.555574"
+ id="text1035"><tspan
+ sodipodi:role="line"
+ id="tspan1033"
+ x="-326.67416"
+ y="-31.555574"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Latin Modern Roman';-inkscape-font-specification:'Latin Modern Roman, Bold';stroke-width:0.9375">DCP</tspan></text>
+ <path
+ style="vector-effect:none;fill:#999999;fill-opacity:1;stroke:#ececec;stroke-width:30;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
+ d="m -429.03103,-73.65446 h 37.57918"
+ id="path1037"
+ inkscape:connector-curvature="0" />
+ </g>
<text
xml:space="preserve"
- style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:128.48579407px;line-height:1.20000005;font-family:'Latin Modern Roman';-inkscape-font-specification:'Latin Modern Roman, Bold';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.93749988;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
- x="394.93756"
- y="729.72614"
- id="text1035"><tspan
- sodipodi:role="line"
- id="tspan1033"
- x="394.93756"
- y="729.72614"
- style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Latin Modern Roman';-inkscape-font-specification:'Latin Modern Roman, Bold';stroke-width:0.93749988">DCP</tspan></text>
- <path
- style="opacity:1;vector-effect:none;fill:#999999;fill-opacity:1;stroke:#ececec;stroke-width:30.00000191;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
- d="m 292.58069,687.62725 h 37.57918"
- id="path1037"
- inkscape:connector-curvature="0" />
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.5px;line-height:1.2;font-family:'Bitstream Vera Sans Mono';-inkscape-font-specification:'Bitstream Vera Sans Mono';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.9375;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ x="-1786.4093"
+ y="65.214325"
+ id="text1027" />
</g>
</svg>
diff --git a/graphics/src/dcpomatic2_editor.svg b/graphics/src/dcpomatic2_editor.svg
index e5bb7b135..04a10cee0 100644
--- a/graphics/src/dcpomatic2_editor.svg
+++ b/graphics/src/dcpomatic2_editor.svg
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
- viewBox="0 0 1000 1000"
+ viewBox="0 0 960.00004 960.00004"
sodipodi:docname="dcpomatic2_editor.svg"
inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
version="1.1"
id="svg2"
- height="1066.6666"
- width="1066.6666"
+ height="1024"
+ width="1024"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
@@ -21,84 +21,84 @@
inkscape:isstock="true"
style="overflow:visible"
id="Arrow1Mstart"
- refX="0.0"
- refY="0.0"
+ refX="0"
+ refY="0"
orient="auto"
inkscape:stockid="Arrow1Mstart">
<path
- transform="scale(0.4) translate(10,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ transform="matrix(0.4,0,0,0.4,4,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path893" />
</marker>
<marker
inkscape:isstock="true"
- style="overflow:visible;"
+ style="overflow:visible"
id="marker1193"
- refX="0.0"
- refY="0.0"
+ refX="0"
+ refY="0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
- transform="scale(0.8) rotate(180) translate(12.5,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path1191" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker1189"
- refX="0.0"
- refY="0.0"
+ refX="0"
+ refY="0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
- transform="scale(0.8) translate(12.5,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path1187" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker1167"
- refX="0.0"
- refY="0.0"
+ refX="0"
+ refY="0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
- transform="scale(0.8) translate(12.5,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path1165" />
</marker>
<marker
inkscape:isstock="true"
- style="overflow:visible;"
+ style="overflow:visible"
id="Arrow1Lend"
- refX="0.0"
- refY="0.0"
+ refX="0"
+ refY="0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
- transform="scale(0.8) rotate(180) translate(12.5,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path890" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="Arrow1Lstart"
- refX="0.0"
- refY="0.0"
+ refX="0"
+ refY="0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
- transform="scale(0.8) translate(12.5,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path887" />
</marker>
<linearGradient
@@ -182,14 +182,14 @@
inkscape:document-rotation="0"
inkscape:window-maximized="1"
inkscape:window-y="0"
- inkscape:window-x="0"
- inkscape:window-height="1043"
+ inkscape:window-x="1920"
+ inkscape:window-height="1048"
inkscape:window-width="1920"
showgrid="false"
inkscape:current-layer="layer1"
inkscape:document-units="px"
inkscape:cy="182.21133"
- inkscape:cx="38.466836"
+ inkscape:cx="38.466837"
inkscape:zoom="0.24696598"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
@@ -217,34 +217,38 @@
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
- <image
- xlink:href="dcpomatic.png"
- id="image4358"
- preserveAspectRatio="none"
- height="960.00006"
- width="960.00006"
- x="10.670144"
- y="80.386467" />
<g
- id="g8530"
- transform="rotate(45,1084.1843,1784.932)">
- <rect
- style="font-variation-settings:normal;opacity:0.792898;vector-effect:none;fill:#5e5e5e;fill-opacity:1;stroke:#000000;stroke-width:14.2059;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000;stop-opacity:1"
- id="rect1581"
- width="307.76059"
- height="110.81631"
- x="-350.94226"
- y="1283.3156" />
- <path
- id="path1605"
- style="font-variation-settings:normal;opacity:0.792898;vector-effect:none;fill:#5e5e5e;fill-opacity:1;stroke:#000000;stroke-width:15.5608;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000;stop-opacity:1"
- d="m -12.416849,1368.7621 c 83.822764,-19.6805 86.251983,62.0217 60.827987,62.3855 l -60.841013,0.8707 c -51.520013,0.7373 -93.294775,-41.7695 -93.294755,-93.2948 3e-5,-51.5252 41.770681,-93.6427 93.294755,-93.2947 l 65.466374,0.4422 c 21.731192,0.1468 14.788626,78.6616 -67.922673,59.3129 -41.565783,-9.7235 -40.507802,75.1684 2.469325,63.5782 z"
- sodipodi:nodetypes="csssssscc" />
- <path
- id="path1605-6"
- style="font-variation-settings:normal;opacity:0.792898;vector-effect:none;fill:#5e5e5e;fill-opacity:1;stroke:#000000;stroke-width:15.5608;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000"
- d="m -390.33914,1368.7586 c -83.82277,-19.6805 -86.25198,62.0217 -60.82799,62.3855 l 60.84101,0.8707 c 51.52002,0.7373 93.29478,-41.7695 93.29476,-93.2948 -3e-5,-51.5252 -41.77068,-93.6427 -93.29476,-93.2947 l -65.46637,0.4422 c -21.73119,0.1468 -14.78863,78.6616 67.92267,59.3129 41.56579,-9.7235 40.50781,75.1684 -2.46932,63.5782 z"
- sodipodi:nodetypes="csssssscc" />
+ id="g2379"
+ transform="translate(-10.670155,-28.024289)">
+ <image
+ xlink:href="dcpomatic.png"
+ id="image4358"
+ preserveAspectRatio="none"
+ height="960.00006"
+ width="960.00006"
+ x="10.670144"
+ y="80.386467" />
+ <g
+ id="g8530"
+ transform="rotate(45,1084.1843,1784.932)">
+ <rect
+ style="font-variation-settings:normal;opacity:0.792898;vector-effect:none;fill:#5e5e5e;fill-opacity:1;stroke:#000000;stroke-width:14.2059;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000;stop-opacity:1"
+ id="rect1581"
+ width="307.76059"
+ height="110.81631"
+ x="-350.94226"
+ y="1283.3156" />
+ <path
+ id="path1605"
+ style="font-variation-settings:normal;opacity:0.792898;vector-effect:none;fill:#5e5e5e;fill-opacity:1;stroke:#000000;stroke-width:15.5608;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000;stop-opacity:1"
+ d="m -12.416849,1368.7621 c 83.822764,-19.6805 86.251983,62.0217 60.827987,62.3855 l -60.841013,0.8707 c -51.520013,0.7373 -93.294775,-41.7695 -93.294755,-93.2948 3e-5,-51.5252 41.770681,-93.6427 93.294755,-93.2947 l 65.466374,0.4422 c 21.731192,0.1468 14.788626,78.6616 -67.922673,59.3129 -41.565783,-9.7235 -40.507802,75.1684 2.469325,63.5782 z"
+ sodipodi:nodetypes="csssssscc" />
+ <path
+ id="path1605-6"
+ style="font-variation-settings:normal;opacity:0.792898;vector-effect:none;fill:#5e5e5e;fill-opacity:1;stroke:#000000;stroke-width:15.5608;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000"
+ d="m -390.33914,1368.7586 c -83.82277,-19.6805 -86.25198,62.0217 -60.82799,62.3855 l 60.84101,0.8707 c 51.52002,0.7373 93.29478,-41.7695 93.29476,-93.2948 -3e-5,-51.5252 -41.77068,-93.6427 -93.29476,-93.2947 l -65.46637,0.4422 c -21.73119,0.1468 -14.78863,78.6616 67.92267,59.3129 41.56579,-9.7235 40.50781,75.1684 -2.46932,63.5782 z"
+ sodipodi:nodetypes="csssssscc" />
+ </g>
</g>
</g>
</svg>
diff --git a/graphics/src/dcpomatic2_kdm.svg b/graphics/src/dcpomatic2_kdm.svg
index be300b0e6..6b807dad2 100644
--- a/graphics/src/dcpomatic2_kdm.svg
+++ b/graphics/src/dcpomatic2_kdm.svg
@@ -2,21 +2,21 @@
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="1066.6666"
- height="1066.6666"
+ width="1024"
+ height="1024"
id="svg2"
version="1.1"
- inkscape:version="0.92+devel 15537"
+ inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
sodipodi:docname="dcpomatic2_kdm.svg"
- viewBox="0 0 1000 1000">
+ viewBox="0 0 960.00004 960.00004"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4">
<linearGradient
@@ -103,17 +103,18 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.34926264"
- inkscape:cx="796.00954"
- inkscape:cy="412.05102"
+ inkscape:cx="797.39419"
+ inkscape:cy="412.29718"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
- inkscape:window-width="1280"
- inkscape:window-height="987"
- inkscape:window-x="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1048"
+ inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
- inkscape:document-rotation="0" />
+ inkscape:document-rotation="0"
+ inkscape:pagecheckerboard="0" />
<metadata
id="metadata7">
<rdf:RDF>
@@ -122,7 +123,6 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@@ -131,46 +131,49 @@
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-52.362188)">
- <image
- sodipodi:absref="/home/carl/src/dcpomatic/graphics/src/dcpomatic.png"
- xlink:href="dcpomatic.png"
- id="image5375"
- preserveAspectRatio="none"
- height="960.00006"
- width="960.00006"
- x="19.999969"
- y="72.36216" />
<g
- id="g4313"
- transform="matrix(0.69033116,0.69033116,-0.69033116,0.69033116,790.39537,-160.38132)"
- style="opacity:0.825">
- <path
- id="path6625"
- d="m 149.40259,561.73351 c -46.65,46.65 -46.67,122.4 -0.03,169.04 30.92,30.92 74.6,41.33 114.12,31.27 l 22.3,22.29 39.67,4.86 4.91,39.73 39.68,4.86 4.89,39.7 39.72,4.91 4.86,39.68 70.53,-5.91 5.66,-0.46 1.07,-12.21 5.62,-63.77 -22.07,-22.07 -22.03,-22.03 -22.05,-22.05 -22.03,-22.02 -22.05,-22.05 -22.05,-22.05 -22.05,-22.05 c 12.55,-40.94 2.66,-87.25 -29.71,-119.62 -46.64,-46.64 -122.32,-46.69 -168.96,-0.05 z m 24.21,24.32 c 21.41,-21.41 52.07,-25.54 68.44,-9.17 16.37,16.37 12.26,47.05 -9.14,68.46 -21.41,21.41 -52.07,25.49 -68.44,9.12 -16.37,-16.37 -12.27,-47.01 9.14,-68.41 z"
- style="color:#000000;fill:url(#linearGradient3601);stroke:#000000;stroke-width:12;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- inkscape:connector-curvature="0" />
- <path
- id="path6871"
- d="m 310.63259,723.03351 c -1.68,1.68 -2.88,3.59 -3.69,5.59 -0.74,1.82 -1.28,3.93 -1.28,6.32 0,2.4 0.52,4.53 1.26,6.35 0.77,1.9 2.01,3.91 3.69,5.59 l 163.12,163.12 3.27,3.27 4.62,-0.38 5.68,-0.46 8.39,-0.71 0.75,-8.4 1.06,-12.19 0.4,-4.64 -3.29,-3.3 -160.16,-160.16 c -1.68,-1.68 -3.68,-2.91 -5.59,-3.69 -1.81,-0.73 -3.92,-1.28 -6.32,-1.28 -2.4,0 -4.51,0.55 -6.32,1.28 -1.91,0.78 -3.91,2.01 -5.59,3.69 z"
- style="color:#000000;fill:url(#linearGradient3632);stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- inkscape:connector-curvature="0" />
- <path
- id="path2365"
- d="m 161.64259,576.85351 c -2.29,2.41 -4.43,4.92 -6.43,7.49 2.5,-3.23 5.25,-6.31 8.22,-9.28 -0.6,0.6 -1.21,1.18 -1.79,1.79 z m 3.62,-3.58 c 4.29,-4.07 8.84,-7.67 13.61,-10.83 -4.76,3.15 -9.33,6.77 -13.61,10.83 z m -11.62,13.17 c -0.93,1.27 -1.81,2.53 -2.67,3.82 0.85,-1.29 1.74,-2.56 2.67,-3.82 z m -2.81,4.05 c -0.89,1.35 -1.76,2.74 -2.58,4.13 0.82,-1.4 1.68,-2.77 2.58,-4.13 z m -2.58,4.13 c -1.66,2.8 -3.16,5.65 -4.51,8.57 1.35,-2.91 2.86,-5.78 4.51,-8.57 z m -4.51,8.57 c -1.35,2.92 -2.55,5.9 -3.6,8.91 1.05,-3.01 2.25,-6 3.6,-8.91 z m -3.6,8.91 c -1.05,3 -1.97,6.05 -2.72,9.12 0.75,-3.08 1.67,-6.12 2.72,-9.12 z m -2.72,9.12 c -0.31,1.27 -0.58,2.53 -0.84,3.8 0.26,-1.27 0.53,-2.53 0.84,-3.8 z m 43.53,-60.1 c 1.38,-0.87 2.79,-1.69 4.2,-2.48 -1.41,0.79 -2.82,1.61 -4.2,2.48 z m 8.49,-4.73 c 1.44,-0.71 2.88,-1.37 4.35,-2.01 -1.46,0.63 -2.92,1.3 -4.35,2.01 z m 17.89,-6.76 c 1.53,-0.41 3.06,-0.77 4.6,-1.11 -1.54,0.34 -3.07,0.7 -4.6,1.11 z m 4.62,-1.13 c 1.54,-0.34 3.09,-0.62 4.64,-0.88 -1.55,0.26 -3.1,0.54 -4.64,0.88 z m -75.52,77.34 c -0.16,0.79 -0.29,1.58 -0.42,2.36 0.13,-0.77 0.27,-1.58 0.42,-2.36 z m -0.42,2.36 c -0.13,0.78 -0.27,1.55 -0.38,2.33 0.11,-0.79 0.24,-1.55 0.38,-2.33 z m -0.69,4.67 c -0.09,0.78 -0.17,1.57 -0.24,2.36 0.07,-0.78 0.15,-1.58 0.24,-2.36 z m -0.44,4.73 c -0.06,0.78 -0.1,1.56 -0.13,2.34 0.03,-0.79 0.07,-1.56 0.13,-2.34 z m 93.47,-91.26 c 1.18,-0.06 2.37,-0.1 3.56,-0.12 -1.2,0.02 -2.37,0.06 -3.56,0.12 z m 4.71,-0.12 c 0.78,0 1.57,0.01 2.36,0.03 -0.79,-0.02 -1.57,-0.03 -2.36,-0.03 z m -98.18,105.52 c 0.11,1.58 0.25,3.16 0.44,4.73 -0.19,-1.57 -0.33,-3.16 -0.44,-4.73 z m 109.98,-104.81 c 1.56,0.19 3.12,0.4 4.68,0.66 -1.56,-0.26 -3.12,-0.47 -4.68,-0.66 z m -108.85,114.2 c 0.13,0.78 0.26,1.58 0.42,2.36 -0.15,-0.77 -0.29,-1.58 -0.42,-2.36 z m 0.42,2.36 c 0.14,0.77 0.31,1.54 0.48,2.3 -0.17,-0.77 -0.33,-1.52 -0.48,-2.3 z m 1.61,6.92 c 0.21,0.77 0.41,1.55 0.64,2.32 -0.23,-0.76 -0.44,-1.55 -0.64,-2.32 z m 1.35,4.57 c 0.24,0.76 0.49,1.51 0.75,2.26 -0.27,-0.75 -0.51,-1.5 -0.75,-2.26 z m 0.75,2.26 c 0.25,0.71 0.5,1.43 0.77,2.14 -0.26,-0.7 -0.52,-1.43 -0.77,-2.14 z m 1.7,4.48 c 0.3,0.75 0.61,1.48 0.93,2.21 -0.32,-0.73 -0.63,-1.47 -0.93,-2.21 z m 0.93,2.21 c 0.32,0.74 0.63,1.48 0.97,2.21 -0.34,-0.72 -0.65,-1.47 -0.97,-2.21 z m 0.97,2.21 c 0.34,0.73 0.7,1.45 1.06,2.17 -0.36,-0.72 -0.72,-1.44 -1.06,-2.17 z m 2.14,4.31 c 0.38,0.72 0.78,1.43 1.17,2.15 -0.39,-0.71 -0.79,-1.43 -1.17,-2.15 z m 135.57,-131.54 c 1.12,0.63 2.23,1.28 3.34,1.97 -1.11,-0.69 -2.22,-1.34 -3.34,-1.97 z m -131.92,137.88 c 0.38,0.61 0.77,1.23 1.17,1.84 -0.4,-0.61 -0.79,-1.22 -1.17,-1.84 z m 1.57,2.46 c 0.42,0.62 0.85,1.24 1.28,1.85 -0.43,-0.62 -0.86,-1.23 -1.28,-1.85 z m 2.72,3.86 c 0.95,1.3 1.95,2.57 2.98,3.83 -1.02,-1.25 -2.03,-2.54 -2.98,-3.83 z m 139.45,-136.36 c 1.3,1.01 2.61,2.04 3.87,3.12 -1.27,-1.09 -2.56,-2.1 -3.87,-3.12 z m -126.15,151.04 c 38.64,34.9 98.35,33.74 135.59,-3.49 37.23,-37.24 38.39,-96.96 3.49,-135.59 31.41,35.32 -20.5,44.27 -57.68,81.45 -37.17,37.17 -46.08,89.04 -81.4,57.63 z"
- style="color:#000000;fill:url(#linearGradient3609);stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- inkscape:connector-curvature="0" />
- <path
- id="path6632"
- d="m 161.64259,576.87351 c -36.65,38.56 -36.03,99.58 1.8,137.42 38.44,38.43 46.67,-15.69 85.1,-54.13 38.43,-38.43 92.59,-46.69 54.15,-85.12 -38.43,-38.44 -100.81,-38.41 -139.25,0.03 -0.6,0.6 -1.22,1.19 -1.8,1.8 z m 11.99,9.17 c 21.4,-21.41 52.05,-25.51 68.42,-9.14 16.37,16.37 12.27,47.02 -9.14,68.43 -21.4,21.4 -52.05,25.5 -68.42,9.13 -16.37,-16.37 -12.27,-47.02 9.14,-68.42 z"
- style="color:#000000;fill:url(#linearGradient3594);stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- inkscape:connector-curvature="0" />
- <rect
- y="505.21939"
- x="84.558434"
- height="442.68826"
- width="442.68826"
- id="rect6322"
- style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ff0000;fill-opacity:0;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;marker:none;enable-background:accumulate" />
+ id="g2153"
+ transform="translate(-19.99998,-19.999982)">
+ <image
+ xlink:href="dcpomatic.png"
+ id="image5375"
+ preserveAspectRatio="none"
+ height="960.00006"
+ width="960.00006"
+ x="19.999969"
+ y="72.36216" />
+ <g
+ id="g4313"
+ transform="matrix(0.69033116,0.69033116,-0.69033116,0.69033116,790.39537,-160.38132)"
+ style="opacity:0.825">
+ <path
+ id="path6625"
+ d="m 149.40259,561.73351 c -46.65,46.65 -46.67,122.4 -0.03,169.04 30.92,30.92 74.6,41.33 114.12,31.27 l 22.3,22.29 39.67,4.86 4.91,39.73 39.68,4.86 4.89,39.7 39.72,4.91 4.86,39.68 70.53,-5.91 5.66,-0.46 1.07,-12.21 5.62,-63.77 -22.07,-22.07 -22.03,-22.03 -22.05,-22.05 -22.03,-22.02 -22.05,-22.05 -22.05,-22.05 -22.05,-22.05 c 12.55,-40.94 2.66,-87.25 -29.71,-119.62 -46.64,-46.64 -122.32,-46.69 -168.96,-0.05 z m 24.21,24.32 c 21.41,-21.41 52.07,-25.54 68.44,-9.17 16.37,16.37 12.26,47.05 -9.14,68.46 -21.41,21.41 -52.07,25.49 -68.44,9.12 -16.37,-16.37 -12.27,-47.01 9.14,-68.41 z"
+ style="color:#000000;fill:url(#linearGradient3601);stroke:#000000;stroke-width:12;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path6871"
+ d="m 310.63259,723.03351 c -1.68,1.68 -2.88,3.59 -3.69,5.59 -0.74,1.82 -1.28,3.93 -1.28,6.32 0,2.4 0.52,4.53 1.26,6.35 0.77,1.9 2.01,3.91 3.69,5.59 l 163.12,163.12 3.27,3.27 4.62,-0.38 5.68,-0.46 8.39,-0.71 0.75,-8.4 1.06,-12.19 0.4,-4.64 -3.29,-3.3 -160.16,-160.16 c -1.68,-1.68 -3.68,-2.91 -5.59,-3.69 -1.81,-0.73 -3.92,-1.28 -6.32,-1.28 -2.4,0 -4.51,0.55 -6.32,1.28 -1.91,0.78 -3.91,2.01 -5.59,3.69 z"
+ style="color:#000000;fill:url(#linearGradient3632);stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path2365"
+ d="m 161.64259,576.85351 c -2.29,2.41 -4.43,4.92 -6.43,7.49 2.5,-3.23 5.25,-6.31 8.22,-9.28 -0.6,0.6 -1.21,1.18 -1.79,1.79 z m 3.62,-3.58 c 4.29,-4.07 8.84,-7.67 13.61,-10.83 -4.76,3.15 -9.33,6.77 -13.61,10.83 z m -11.62,13.17 c -0.93,1.27 -1.81,2.53 -2.67,3.82 0.85,-1.29 1.74,-2.56 2.67,-3.82 z m -2.81,4.05 c -0.89,1.35 -1.76,2.74 -2.58,4.13 0.82,-1.4 1.68,-2.77 2.58,-4.13 z m -2.58,4.13 c -1.66,2.8 -3.16,5.65 -4.51,8.57 1.35,-2.91 2.86,-5.78 4.51,-8.57 z m -4.51,8.57 c -1.35,2.92 -2.55,5.9 -3.6,8.91 1.05,-3.01 2.25,-6 3.6,-8.91 z m -3.6,8.91 c -1.05,3 -1.97,6.05 -2.72,9.12 0.75,-3.08 1.67,-6.12 2.72,-9.12 z m -2.72,9.12 c -0.31,1.27 -0.58,2.53 -0.84,3.8 0.26,-1.27 0.53,-2.53 0.84,-3.8 z m 43.53,-60.1 c 1.38,-0.87 2.79,-1.69 4.2,-2.48 -1.41,0.79 -2.82,1.61 -4.2,2.48 z m 8.49,-4.73 c 1.44,-0.71 2.88,-1.37 4.35,-2.01 -1.46,0.63 -2.92,1.3 -4.35,2.01 z m 17.89,-6.76 c 1.53,-0.41 3.06,-0.77 4.6,-1.11 -1.54,0.34 -3.07,0.7 -4.6,1.11 z m 4.62,-1.13 c 1.54,-0.34 3.09,-0.62 4.64,-0.88 -1.55,0.26 -3.1,0.54 -4.64,0.88 z m -75.52,77.34 c -0.16,0.79 -0.29,1.58 -0.42,2.36 0.13,-0.77 0.27,-1.58 0.42,-2.36 z m -0.42,2.36 c -0.13,0.78 -0.27,1.55 -0.38,2.33 0.11,-0.79 0.24,-1.55 0.38,-2.33 z m -0.69,4.67 c -0.09,0.78 -0.17,1.57 -0.24,2.36 0.07,-0.78 0.15,-1.58 0.24,-2.36 z m -0.44,4.73 c -0.06,0.78 -0.1,1.56 -0.13,2.34 0.03,-0.79 0.07,-1.56 0.13,-2.34 z m 93.47,-91.26 c 1.18,-0.06 2.37,-0.1 3.56,-0.12 -1.2,0.02 -2.37,0.06 -3.56,0.12 z m 4.71,-0.12 c 0.78,0 1.57,0.01 2.36,0.03 -0.79,-0.02 -1.57,-0.03 -2.36,-0.03 z m -98.18,105.52 c 0.11,1.58 0.25,3.16 0.44,4.73 -0.19,-1.57 -0.33,-3.16 -0.44,-4.73 z m 109.98,-104.81 c 1.56,0.19 3.12,0.4 4.68,0.66 -1.56,-0.26 -3.12,-0.47 -4.68,-0.66 z m -108.85,114.2 c 0.13,0.78 0.26,1.58 0.42,2.36 -0.15,-0.77 -0.29,-1.58 -0.42,-2.36 z m 0.42,2.36 c 0.14,0.77 0.31,1.54 0.48,2.3 -0.17,-0.77 -0.33,-1.52 -0.48,-2.3 z m 1.61,6.92 c 0.21,0.77 0.41,1.55 0.64,2.32 -0.23,-0.76 -0.44,-1.55 -0.64,-2.32 z m 1.35,4.57 c 0.24,0.76 0.49,1.51 0.75,2.26 -0.27,-0.75 -0.51,-1.5 -0.75,-2.26 z m 0.75,2.26 c 0.25,0.71 0.5,1.43 0.77,2.14 -0.26,-0.7 -0.52,-1.43 -0.77,-2.14 z m 1.7,4.48 c 0.3,0.75 0.61,1.48 0.93,2.21 -0.32,-0.73 -0.63,-1.47 -0.93,-2.21 z m 0.93,2.21 c 0.32,0.74 0.63,1.48 0.97,2.21 -0.34,-0.72 -0.65,-1.47 -0.97,-2.21 z m 0.97,2.21 c 0.34,0.73 0.7,1.45 1.06,2.17 -0.36,-0.72 -0.72,-1.44 -1.06,-2.17 z m 2.14,4.31 c 0.38,0.72 0.78,1.43 1.17,2.15 -0.39,-0.71 -0.79,-1.43 -1.17,-2.15 z m 135.57,-131.54 c 1.12,0.63 2.23,1.28 3.34,1.97 -1.11,-0.69 -2.22,-1.34 -3.34,-1.97 z m -131.92,137.88 c 0.38,0.61 0.77,1.23 1.17,1.84 -0.4,-0.61 -0.79,-1.22 -1.17,-1.84 z m 1.57,2.46 c 0.42,0.62 0.85,1.24 1.28,1.85 -0.43,-0.62 -0.86,-1.23 -1.28,-1.85 z m 2.72,3.86 c 0.95,1.3 1.95,2.57 2.98,3.83 -1.02,-1.25 -2.03,-2.54 -2.98,-3.83 z m 139.45,-136.36 c 1.3,1.01 2.61,2.04 3.87,3.12 -1.27,-1.09 -2.56,-2.1 -3.87,-3.12 z m -126.15,151.04 c 38.64,34.9 98.35,33.74 135.59,-3.49 37.23,-37.24 38.39,-96.96 3.49,-135.59 31.41,35.32 -20.5,44.27 -57.68,81.45 -37.17,37.17 -46.08,89.04 -81.4,57.63 z"
+ style="color:#000000;fill:url(#linearGradient3609);stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path6632"
+ d="m 161.64259,576.87351 c -36.65,38.56 -36.03,99.58 1.8,137.42 38.44,38.43 46.67,-15.69 85.1,-54.13 38.43,-38.43 92.59,-46.69 54.15,-85.12 -38.43,-38.44 -100.81,-38.41 -139.25,0.03 -0.6,0.6 -1.22,1.19 -1.8,1.8 z m 11.99,9.17 c 21.4,-21.41 52.05,-25.51 68.42,-9.14 16.37,16.37 12.27,47.02 -9.14,68.43 -21.4,21.4 -52.05,25.5 -68.42,9.13 -16.37,-16.37 -12.27,-47.02 9.14,-68.42 z"
+ style="color:#000000;fill:url(#linearGradient3594);stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <rect
+ y="505.21939"
+ x="84.558434"
+ height="442.68826"
+ width="442.68826"
+ id="rect6322"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ff0000;fill-opacity:0;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;marker:none;enable-background:accumulate" />
+ </g>
</g>
</g>
</svg>
diff --git a/graphics/src/dcpomatic2_player.svg b/graphics/src/dcpomatic2_player.svg
index 8b68c0d98..5f2ee5b0b 100644
--- a/graphics/src/dcpomatic2_player.svg
+++ b/graphics/src/dcpomatic2_player.svg
@@ -2,21 +2,21 @@
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="1066.6666"
- height="1066.6666"
+ width="1024"
+ height="1024"
id="svg2"
version="1.1"
- inkscape:version="0.91 r13725"
+ inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
sodipodi:docname="dcpomatic2_player.svg"
- viewBox="0 0 1000 1000">
+ viewBox="0 0 960.00004 960.00004"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4">
<linearGradient
@@ -103,17 +103,18 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49393196"
- inkscape:cx="793.18353"
- inkscape:cy="448.89208"
+ inkscape:cx="794.64386"
+ inkscape:cy="449.45462"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
- inkscape:window-width="1280"
- inkscape:window-height="995"
- inkscape:window-x="1280"
+ inkscape:window-width="1920"
+ inkscape:window-height="1048"
+ inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
- inkscape:document-rotation="0" />
+ inkscape:document-rotation="0"
+ inkscape:pagecheckerboard="0" />
<metadata
id="metadata7">
<rdf:RDF>
@@ -122,7 +123,6 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
@@ -131,31 +131,34 @@
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-52.362188)">
- <image
- sodipodi:absref="/home/c.hetherington/src/dcpomatic/graphics/src/dcpomatic.png"
- xlink:href="dcpomatic.png"
- y="80.386467"
- x="10.670144"
- width="960.00006"
- height="960.00006"
- preserveAspectRatio="none"
- id="image4358" />
- <path
- sodipodi:type="star"
- style="opacity:0.80699978;fill:#008000;fill-rule:evenodd;stroke:#999999;stroke-width:34.09389496;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- id="path3635"
- sodipodi:sides="3"
- sodipodi:cx="462.30658"
- sodipodi:cy="552.36218"
- sodipodi:r1="230.56197"
- sodipodi:r2="115.28098"
- sodipodi:arg1="2.0943951"
- sodipodi:arg2="3.1415927"
- inkscape:flatsided="false"
- inkscape:rounded="0"
- inkscape:randomized="0"
- d="m 347.0256,752.0347 0,-199.67252 0,-199.67252 172.92148,99.83627 172.92147,99.83625 -172.92148,99.83627 z"
- inkscape:transform-center-x="-57.640504"
- inkscape:transform-center-y="-2.0391502e-05" />
+ <g
+ id="g1986"
+ transform="translate(-10.670155,-28.024289)">
+ <image
+ xlink:href="dcpomatic.png"
+ y="80.386467"
+ x="10.670144"
+ width="960.00006"
+ height="960.00006"
+ preserveAspectRatio="none"
+ id="image4358" />
+ <path
+ sodipodi:type="star"
+ style="opacity:0.807;fill:#008000;fill-rule:evenodd;stroke:#999999;stroke-width:34.0939;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3635"
+ sodipodi:sides="3"
+ sodipodi:cx="462.30658"
+ sodipodi:cy="552.36218"
+ sodipodi:r1="230.56197"
+ sodipodi:r2="115.28098"
+ sodipodi:arg1="2.0943951"
+ sodipodi:arg2="3.1415927"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 347.0256,752.0347 0,-199.67252 0,-199.67252 172.92148,99.83627 172.92147,99.83625 -172.92148,99.83627 z"
+ inkscape:transform-center-x="-57.640504"
+ inkscape:transform-center-y="-2.0391502e-05" />
+ </g>
</g>
</svg>
diff --git a/graphics/src/dcpomatic2_playlist.svg b/graphics/src/dcpomatic2_playlist.svg
index 92d5a3e07..9dbdd3f2c 100644
--- a/graphics/src/dcpomatic2_playlist.svg
+++ b/graphics/src/dcpomatic2_playlist.svg
@@ -2,21 +2,21 @@
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="1066.6666"
- height="1066.6666"
+ width="1024"
+ height="1024"
id="svg2"
version="1.1"
- inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
sodipodi:docname="dcpomatic2_playlist.svg"
- viewBox="0 0 1000 1000">
+ viewBox="0 0 960.00004 960.00004"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4">
<linearGradient
@@ -103,17 +103,18 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.69852528"
- inkscape:cx="422.74009"
- inkscape:cy="571.24376"
+ inkscape:cx="423.03408"
+ inkscape:cy="571.20338"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
- inkscape:window-width="1680"
- inkscape:window-height="995"
- inkscape:window-x="0"
- inkscape:window-y="27"
+ inkscape:window-width="1920"
+ inkscape:window-height="1048"
+ inkscape:window-x="1920"
+ inkscape:window-y="0"
inkscape:window-maximized="1"
- inkscape:document-rotation="0" />
+ inkscape:document-rotation="0"
+ inkscape:pagecheckerboard="0" />
<metadata
id="metadata7">
<rdf:RDF>
@@ -122,7 +123,6 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@@ -131,67 +131,70 @@
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-52.362188)">
- <image
- sodipodi:absref="/home/carl/src/dcpomatic/graphics/src/dcpomatic.png"
- xlink:href="dcpomatic.png"
- id="image4358"
- preserveAspectRatio="none"
- height="960.00006"
- width="960.00006"
- x="10.670144"
- y="80.386467" />
- <path
- sodipodi:type="star"
- style="opacity:0.80699978;fill:#008000;fill-rule:evenodd;stroke:#999999;stroke-width:34.09389496;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- id="path3635"
- sodipodi:sides="3"
- sodipodi:cx="462.30658"
- sodipodi:cy="552.36218"
- sodipodi:r1="230.56197"
- sodipodi:r2="115.28098"
- sodipodi:arg1="2.0943951"
- sodipodi:arg2="3.1415927"
- inkscape:flatsided="false"
- inkscape:rounded="0"
- inkscape:randomized="0"
- d="m 347.0256,752.0347 0,-199.67252 0,-199.67252 172.92148,99.83627 172.92147,99.83625 -172.92148,99.83627 z"
- inkscape:transform-center-x="-57.640504"
- inkscape:transform-center-y="-2.0391502e-05" />
- <rect
- style="opacity:1;vector-effect:none;fill:#f9f9f9;fill-opacity:0.6350711;stroke:#333333;stroke-width:24.2728138;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none"
- id="rect827"
- width="277.81744"
- height="463.02905"
- x="428.80518"
- y="323.19205" />
- <path
- style="fill:none;stroke:#000000;stroke-width:31.87500122;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
- d="m 481.81866,383.98979 h 171.7905"
- id="path829"
- inkscape:connector-curvature="0" />
- <path
- style="fill:none;stroke:#000000;stroke-width:31.87500191;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 481.81866,466.76958 128.84288,0"
- id="path829-3"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc" />
- <path
- style="fill:none;stroke:#000000;stroke-width:31.87500191;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 481.81866,549.54941 157.02725,0"
- id="path829-6"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc" />
- <path
- style="fill:none;stroke:#000000;stroke-width:31.87500191;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 481.81866,632.32916 107.36906,0"
- id="path829-7"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc" />
- <path
- style="fill:none;stroke:#000000;stroke-width:31.87500191;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 481.81866,715.10896 42.94763,0"
- id="path829-5"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc" />
+ <g
+ id="g1828"
+ transform="translate(-10.670155,-28.024289)">
+ <image
+ xlink:href="dcpomatic.png"
+ id="image4358"
+ preserveAspectRatio="none"
+ height="960.00006"
+ width="960.00006"
+ x="10.670144"
+ y="80.386467" />
+ <path
+ sodipodi:type="star"
+ style="opacity:0.807;fill:#008000;fill-rule:evenodd;stroke:#999999;stroke-width:34.0939;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3635"
+ sodipodi:sides="3"
+ sodipodi:cx="462.30658"
+ sodipodi:cy="552.36218"
+ sodipodi:r1="230.56197"
+ sodipodi:r2="115.28098"
+ sodipodi:arg1="2.0943951"
+ sodipodi:arg2="3.1415927"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 347.0256,752.0347 0,-199.67252 0,-199.67252 172.92148,99.83627 172.92147,99.83625 -172.92148,99.83627 z"
+ inkscape:transform-center-x="-57.640504"
+ inkscape:transform-center-y="-2.0391502e-05" />
+ <rect
+ style="opacity:1;vector-effect:none;fill:#f9f9f9;fill-opacity:0.635071;stroke:#333333;stroke-width:24.2728;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none"
+ id="rect827"
+ width="277.81744"
+ height="463.02905"
+ x="428.80518"
+ y="323.19205" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:31.875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 481.81866,383.98979 h 171.7905"
+ id="path829"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:31.875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 481.81866,466.76958 H 610.66154"
+ id="path829-3"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:31.875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 481.81866,549.54941 H 638.84591"
+ id="path829-6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:31.875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 481.81866,632.32916 H 589.18772"
+ id="path829-7"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:31.875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 481.81866,715.10896 h 42.94763"
+ id="path829-5"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ </g>
</g>
</svg>
diff --git a/graphics/src/dcpomatic2_server.svg b/graphics/src/dcpomatic2_server.svg
index 663a4f5b7..2d1d9c77f 100644
--- a/graphics/src/dcpomatic2_server.svg
+++ b/graphics/src/dcpomatic2_server.svg
@@ -2,21 +2,21 @@
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="1066.6666"
- height="1066.6666"
+ width="1024"
+ height="1024"
id="svg2"
version="1.1"
- inkscape:version="0.92+devel 15537"
+ inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
sodipodi:docname="dcpomatic2_server.svg"
- viewBox="0 0 1000 1000">
+ viewBox="0 0 960.00004 960.00004"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4">
<linearGradient
@@ -103,17 +103,18 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49393196"
- inkscape:cx="793.18353"
- inkscape:cy="448.89208"
+ inkscape:cx="794.64386"
+ inkscape:cy="449.45462"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
- inkscape:window-width="1280"
- inkscape:window-height="987"
- inkscape:window-x="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1048"
+ inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
- inkscape:document-rotation="0" />
+ inkscape:document-rotation="0"
+ inkscape:pagecheckerboard="0" />
<metadata
id="metadata7">
<rdf:RDF>
@@ -122,7 +123,6 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@@ -131,80 +131,83 @@
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-52.362188)">
- <image
- sodipodi:absref="/home/carl/src/dcpomatic/graphics/src/dcpomatic.png"
- xlink:href="dcpomatic.png"
- id="image4358"
- preserveAspectRatio="none"
- height="960.00006"
- width="960.00006"
- x="10.670144"
- y="80.386467" />
<g
- transform="matrix(3.3796256,0,0,3.3796256,-252.84746,-678.75323)"
- inkscape:label="Calque 1"
- id="layer1-3"
- style="image-rendering:auto;opacity:0.825">
- <path
- inkscape:connector-curvature="0"
- style="fill:#e1e1e1;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:3.75;stroke-miterlimit:10"
- d="m 180,314.15 15,-15 h 50 l 15,15 v 120 h -80 z"
- id="path256" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#bdbdbd;fill-rule:evenodd;stroke:#9c9c9c;stroke-width:1.25;stroke-miterlimit:10"
- d="m 187.5,316.65 12.5,-12.5 h 40 l 12.5,12.5 z"
- id="path258" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#bdbdbd;fill-rule:evenodd;stroke:#9c9c9c;stroke-width:1.25;stroke-linejoin:round;stroke-miterlimit:10"
- d="m 188.75,319.15 h 62.5 c 0.6885,0 1.25,0.56152 1.25,1.25 v 107.5 c 0,0.6885 -0.5615,1.25 -1.25,1.25 h -62.5 c -0.69325,0 -1.25,-0.5615 -1.25,-1.25 V 320.4 c 0,-0.68848 0.55675,-1.25 1.25,-1.25"
- id="path260" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:3.75;stroke-linejoin:round;stroke-miterlimit:10"
- d="m 193.75,326.65 h 52.5 c 0.6885,0 1.25,0.56152 1.25,1.25 v 12.5 c 0,0.68847 -0.5615,1.25 -1.25,1.25 h -52.5 c -0.69325,0 -1.25,-0.56153 -1.25,-1.25 v -12.5 c 0,-0.68848 0.55675,-1.25 1.25,-1.25"
- id="path262" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:3.75;stroke-linejoin:round;stroke-miterlimit:10"
- d="m 193.75,349.15 h 52.5 c 0.6885,0 1.25,0.56152 1.25,1.25 v 12.5 c 0,0.68847 -0.5615,1.25 -1.25,1.25 h -52.5 c -0.69325,0 -1.25,-0.56153 -1.25,-1.25 v -12.5 c 0,-0.68848 0.55675,-1.25 1.25,-1.25"
- id="path264" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:3.75;stroke-linejoin:round;stroke-miterlimit:10"
- d="m 216.25,376.65 h 30 c 0.6885,0 1.25,0.56152 1.25,1.25 v 10 c 0,0.69325 -0.5615,1.25 -1.25,1.25 h -30 c -0.69325,0 -1.25,-0.55675 -1.25,-1.25 v -10 c 0,-0.68848 0.55675,-1.25 1.25,-1.25"
- id="path266" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#c51900;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:2.5;stroke-linejoin:round;stroke-miterlimit:10"
- d="m 207.5,382.9 c 0,8.335 -12.5,8.335 -12.5,0 0,-8.3301 12.5,-8.3301 12.5,0"
- id="path268" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:2.5;stroke-miterlimit:10"
- d="m 205,400.46 h 5 v 27.5 h -5 z"
- id="path270" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:2.5;stroke-miterlimit:10"
- d="m 215,400.46 h 5 v 27.5 h -5 z"
- id="path272" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:2.5;stroke-miterlimit:10"
- d="m 225,400.46 h 5 v 27.5 h -5 z"
- id="path274" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:2.5;stroke-miterlimit:10"
- d="m 235,400.46 h 5 v 27.5 h -5 z"
- id="path276" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:2.5;stroke-miterlimit:10"
- d="m 245,400.51 h 5 v 27.5 h -5 z"
- id="path278" />
+ id="g1646"
+ transform="translate(-10.670155,-28.024289)">
+ <image
+ xlink:href="dcpomatic.png"
+ id="image4358"
+ preserveAspectRatio="none"
+ height="960.00006"
+ width="960.00006"
+ x="10.670144"
+ y="80.386467" />
+ <g
+ transform="matrix(3.3796256,0,0,3.3796256,-252.84746,-678.75323)"
+ inkscape:label="Calque 1"
+ id="layer1-3"
+ style="opacity:0.825;image-rendering:auto">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#e1e1e1;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:3.75;stroke-miterlimit:10"
+ d="m 180,314.15 15,-15 h 50 l 15,15 v 120 h -80 z"
+ id="path256" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#bdbdbd;fill-rule:evenodd;stroke:#9c9c9c;stroke-width:1.25;stroke-miterlimit:10"
+ d="m 187.5,316.65 12.5,-12.5 h 40 l 12.5,12.5 z"
+ id="path258" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#bdbdbd;fill-rule:evenodd;stroke:#9c9c9c;stroke-width:1.25;stroke-linejoin:round;stroke-miterlimit:10"
+ d="m 188.75,319.15 h 62.5 c 0.6885,0 1.25,0.56152 1.25,1.25 v 107.5 c 0,0.6885 -0.5615,1.25 -1.25,1.25 h -62.5 c -0.69325,0 -1.25,-0.5615 -1.25,-1.25 V 320.4 c 0,-0.68848 0.55675,-1.25 1.25,-1.25"
+ id="path260" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:3.75;stroke-linejoin:round;stroke-miterlimit:10"
+ d="m 193.75,326.65 h 52.5 c 0.6885,0 1.25,0.56152 1.25,1.25 v 12.5 c 0,0.68847 -0.5615,1.25 -1.25,1.25 h -52.5 c -0.69325,0 -1.25,-0.56153 -1.25,-1.25 v -12.5 c 0,-0.68848 0.55675,-1.25 1.25,-1.25"
+ id="path262" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:3.75;stroke-linejoin:round;stroke-miterlimit:10"
+ d="m 193.75,349.15 h 52.5 c 0.6885,0 1.25,0.56152 1.25,1.25 v 12.5 c 0,0.68847 -0.5615,1.25 -1.25,1.25 h -52.5 c -0.69325,0 -1.25,-0.56153 -1.25,-1.25 v -12.5 c 0,-0.68848 0.55675,-1.25 1.25,-1.25"
+ id="path264" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:3.75;stroke-linejoin:round;stroke-miterlimit:10"
+ d="m 216.25,376.65 h 30 c 0.6885,0 1.25,0.56152 1.25,1.25 v 10 c 0,0.69325 -0.5615,1.25 -1.25,1.25 h -30 c -0.69325,0 -1.25,-0.55675 -1.25,-1.25 v -10 c 0,-0.68848 0.55675,-1.25 1.25,-1.25"
+ id="path266" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#c51900;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:2.5;stroke-linejoin:round;stroke-miterlimit:10"
+ d="m 207.5,382.9 c 0,8.335 -12.5,8.335 -12.5,0 0,-8.3301 12.5,-8.3301 12.5,0"
+ id="path268" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:2.5;stroke-miterlimit:10"
+ d="m 205,400.46 h 5 v 27.5 h -5 z"
+ id="path270" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:2.5;stroke-miterlimit:10"
+ d="m 215,400.46 h 5 v 27.5 h -5 z"
+ id="path272" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:2.5;stroke-miterlimit:10"
+ d="m 225,400.46 h 5 v 27.5 h -5 z"
+ id="path274" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:2.5;stroke-miterlimit:10"
+ d="m 235,400.46 h 5 v 27.5 h -5 z"
+ id="path276" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#9c9c9c;fill-rule:evenodd;stroke:#7d7d7d;stroke-width:2.5;stroke-miterlimit:10"
+ d="m 245,400.51 h 5 v 27.5 h -5 z"
+ id="path278" />
+ </g>
</g>
</g>
</svg>
diff --git a/graphics/src/dcpomatic2_verifier.svg b/graphics/src/dcpomatic2_verifier.svg
new file mode 100644
index 000000000..5572f0a61
--- /dev/null
+++ b/graphics/src/dcpomatic2_verifier.svg
@@ -0,0 +1,248 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ viewBox="0 0 960.00004 960.00004"
+ sodipodi:docname="dcpomatic2_verifier.svg"
+ inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
+ version="1.1"
+ id="svg2"
+ height="1024"
+ width="1024"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs
+ id="defs4">
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="Arrow1Mstart"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Mstart">
+ <path
+ transform="matrix(0.4,0,0,0.4,4,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ id="path893" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker1193"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lend">
+ <path
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ id="path1191" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker1189"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lstart">
+ <path
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ id="path1187" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker1167"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lstart">
+ <path
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ id="path1165" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="Arrow1Lend"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lend">
+ <path
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ id="path890" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="Arrow1Lstart"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lstart">
+ <path
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ id="path887" />
+ </marker>
+ <linearGradient
+ id="linearGradient3594"
+ y2="742.5"
+ gradientUnits="userSpaceOnUse"
+ x2="-886.76001"
+ gradientTransform="matrix(-0.84033,-0.84033,-0.84033,0.84033,136.32259,-691.39649)"
+ y1="742.5"
+ x1="-772.01001">
+ <stop
+ id="stop4687"
+ stop-color="#fff"
+ offset="0" />
+ <stop
+ id="stop4689"
+ stop-color="#fff"
+ stop-opacity="0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3601"
+ y2="613.94"
+ gradientUnits="userSpaceOnUse"
+ x2="385.04001"
+ gradientTransform="matrix(0.70711,-0.70711,0.70711,0.70711,-203.97741,756.21351)"
+ y1="63.870998"
+ x1="386.39001">
+ <stop
+ id="stop3797"
+ stop-color="#ffe800"
+ offset="0" />
+ <stop
+ id="stop3799"
+ stop-color="#dfb300"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3609"
+ y2="161.84"
+ gradientUnits="userSpaceOnUse"
+ x2="212.92999"
+ y1="358.29999"
+ x1="409.38"
+ gradientTransform="translate(-77.797413,384.00351)">
+ <stop
+ id="stop4034"
+ stop-color="#dfb300"
+ offset="0" />
+ <stop
+ id="stop3374"
+ stop-color="#dfb300"
+ offset=".5" />
+ <stop
+ id="stop3376"
+ stop-color="#dfb300"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3632"
+ y2="448.35001"
+ gradientUnits="userSpaceOnUse"
+ x2="382.89999"
+ gradientTransform="matrix(0.70711,-0.70711,0.70711,0.70711,-203.97741,756.21351)"
+ y1="448.35001"
+ x1="403.63">
+ <stop
+ id="stop3636"
+ stop-color="#ffe800"
+ stop-opacity=".39216"
+ offset="0" />
+ <stop
+ id="stop3638"
+ stop-color="#dfb300"
+ stop-opacity=".39216"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ inkscape:snap-midpoints="true"
+ inkscape:document-rotation="0"
+ inkscape:window-maximized="1"
+ inkscape:window-y="0"
+ inkscape:window-x="1920"
+ inkscape:window-height="1048"
+ inkscape:window-width="1920"
+ showgrid="false"
+ inkscape:current-layer="layer1"
+ inkscape:document-units="px"
+ inkscape:cy="799.70529"
+ inkscape:cx="784.52101"
+ inkscape:zoom="0.49393196"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ inkscape:pagecheckerboard="0"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-bbox="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:snap-global="false" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-52.362188)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Layer 1">
+ <g
+ id="g1445"
+ transform="translate(-10.670155,-28.024289)">
+ <image
+ xlink:href="dcpomatic.png"
+ id="image4358"
+ preserveAspectRatio="none"
+ height="960.00006"
+ width="960.00006"
+ x="10.670144"
+ y="80.386467" />
+ <rect
+ style="font-variation-settings:normal;opacity:0.792898;vector-effect:none;fill:#5e5e5e;fill-opacity:1;stroke:#000000;stroke-width:14.2122;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000"
+ id="rect1581"
+ width="232.89954"
+ height="83.860855"
+ x="797.59167"
+ y="-15.120205"
+ transform="matrix(0.69206236,0.72183772,-0.69206236,0.72183772,0,0)" />
+ <circle
+ style="font-variation-settings:normal;opacity:0.792898;fill:#5e5e5e;fill-opacity:1;stroke:#000000;stroke-width:23.4375;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
+ id="path1076"
+ cx="424.06757"
+ cy="478.95279"
+ r="167.43817" />
+ </g>
+ </g>
+</svg>
diff --git a/graphics/update b/graphics/update
index 46964470b..83a332dc1 100755
--- a/graphics/update
+++ b/graphics/update
@@ -25,12 +25,13 @@ function required_font()
fi
}
-svg_apps="dcpomatic2_kdm dcpomatic2_server dcpomatic2_batch dcpomatic2_player dcpomatic2_playlist dcpomatic2_disk dcpomatic2_combiner dcpomatic2_editor"
+png_apps="dcpomatic2"
+svg_apps="dcpomatic2_kdm dcpomatic2_server dcpomatic2_batch dcpomatic2_player dcpomatic2_playlist dcpomatic2_disk dcpomatic2_combiner dcpomatic2_editor dcpomatic2_verifier"
if [ `uname -s` == "Darwin" ]; then
# Convert OS X icons using OS X-only iconutil
- for p in dcpomatic2 $svg_apps; do
+ for p in $png_apps $svg_apps; do
iconutil --convert icns --output osx/$p.icns osx/$p.iconset
done
else
@@ -49,10 +50,10 @@ else
# OS X application icons
mkdir -p osx
for r in 16 32 128 256 512; do
- for p in dcpomatic2; do
+ for p in $png_apps; do
mkdir -p osx/$p.iconset
- convert src/dcpomatic.png -resize $rx$r osx/$p.iconset/icon_${r}x${r}.png
- convert src/dcpomatic.png -resize $rx$r osx/$p.iconset/icon_${r}x${r}@2x.png
+ convert src/$p.png -resize $rx$r osx/$p.iconset/icon_${r}x${r}.png
+ convert src/$p.png -resize $rx$r osx/$p.iconset/icon_${r}x${r}@2x.png
done
for p in $svg_apps; do
mkdir -p osx/$p.iconset
@@ -65,7 +66,9 @@ else
mkdir -p linux
for r in 16 22 32 48 64 128 256 512; do
mkdir -p linux/$r
- convert src/dcpomatic.png -resize $rx$r linux/$r/dcpomatic2.png
+ for p in $png_apps; do
+ convert src/$p.png -resize $rx$r linux/$r/$p.png
+ done
for p in $svg_apps; do
$INKSCAPE_EXPORT --export-filename=linux/$r/$p.png src/$p.svg -w $r -h $r
done
@@ -73,10 +76,10 @@ else
# Windows application icons
mkdir -p windows
- for p in dcpomatic2 $svg_apps; do
+ for p in $png_apps $svg_apps; do
icotool -c -o windows/$p.ico linux/16/$p.png linux/32/$p.png linux/48/$p.png linux/64/$p.png linux/128/$p.png
done
- convert src/dcpomatic.png -resize 400x400 windows/dcpomatic.bmp
+ convert src/dcpomatic2.png -resize 400x400 windows/dcpomatic.bmp
# OS X preferences icons
mkdir -p osx/preferences
diff --git a/graphics/windows/dcpomatic2_batch.ico b/graphics/windows/dcpomatic2_batch.ico
index b98028834..b44b7988c 100644
--- a/graphics/windows/dcpomatic2_batch.ico
+++ b/graphics/windows/dcpomatic2_batch.ico
Binary files differ
diff --git a/graphics/windows/dcpomatic2_combiner.ico b/graphics/windows/dcpomatic2_combiner.ico
index 0db1eb108..84609fb26 100644
--- a/graphics/windows/dcpomatic2_combiner.ico
+++ b/graphics/windows/dcpomatic2_combiner.ico
Binary files differ
diff --git a/graphics/windows/dcpomatic2_disk.ico b/graphics/windows/dcpomatic2_disk.ico
index d17241f37..7b469e845 100644
--- a/graphics/windows/dcpomatic2_disk.ico
+++ b/graphics/windows/dcpomatic2_disk.ico
Binary files differ
diff --git a/graphics/windows/dcpomatic2_editor.ico b/graphics/windows/dcpomatic2_editor.ico
index f07998374..87cca74f9 100644
--- a/graphics/windows/dcpomatic2_editor.ico
+++ b/graphics/windows/dcpomatic2_editor.ico
Binary files differ
diff --git a/graphics/windows/dcpomatic2_kdm.ico b/graphics/windows/dcpomatic2_kdm.ico
index 178fdd01b..44b2d2682 100644
--- a/graphics/windows/dcpomatic2_kdm.ico
+++ b/graphics/windows/dcpomatic2_kdm.ico
Binary files differ
diff --git a/graphics/windows/dcpomatic2_player.ico b/graphics/windows/dcpomatic2_player.ico
index 50514bcd7..eba18654c 100644
--- a/graphics/windows/dcpomatic2_player.ico
+++ b/graphics/windows/dcpomatic2_player.ico
Binary files differ
diff --git a/graphics/windows/dcpomatic2_playlist.ico b/graphics/windows/dcpomatic2_playlist.ico
index 618ecf29d..b869764c2 100644
--- a/graphics/windows/dcpomatic2_playlist.ico
+++ b/graphics/windows/dcpomatic2_playlist.ico
Binary files differ
diff --git a/graphics/windows/dcpomatic2_server.ico b/graphics/windows/dcpomatic2_server.ico
index 787b5b877..c58220ac7 100644
--- a/graphics/windows/dcpomatic2_server.ico
+++ b/graphics/windows/dcpomatic2_server.ico
Binary files differ
diff --git a/graphics/windows/dcpomatic2_verifier.ico b/graphics/windows/dcpomatic2_verifier.ico
new file mode 100644
index 000000000..2a85de433
--- /dev/null
+++ b/graphics/windows/dcpomatic2_verifier.ico
Binary files differ
diff --git a/graphics/wscript b/graphics/wscript
index 663e28287..287a6dec4 100644
--- a/graphics/wscript
+++ b/graphics/wscript
@@ -32,7 +32,8 @@ def build(bld):
'dcpomatic2_playlist',
'dcpomatic2_disk',
'dcpomatic2_combiner',
- 'dcpomatic2_editor']:
+ 'dcpomatic2_editor',
+ 'dcpomatic2_verifier']:
bld.install_files('${PREFIX}/share/icons/hicolor/%dx%d/apps' % (r, r), 'linux/%d/%s.png' % (r, p))
# Install stuff for POSIX systems
diff --git a/hacks/small_loop b/hacks/small_loop
new file mode 100644
index 000000000..20e52962f
--- /dev/null
+++ b/hacks/small_loop
@@ -0,0 +1,9 @@
+SIZE=1G
+
+fallocate drive.img -l $SIZE
+mkfs.ext4 drive.img
+mkdir -p small
+sudo mount -oloop drive.img small
+sudo mkdir small/dcp
+sudo chown $USER small/dcp
+
diff --git a/hacks/text.cc b/hacks/text.cc
index bf391a325..ff5d6f8b8 100644
--- a/hacks/text.cc
+++ b/hacks/text.cc
@@ -19,10 +19,10 @@ int main ()
Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create (
data,
- Cairo::FORMAT_ARGB32,
+ Cairo::ImageSurface::Format::ARGB32,
width, height,
/* Cairo ARGB32 means first byte blue, second byte green, third byte red, fourth byte alpha */
- Cairo::ImageSurface::format_stride_for_width (Cairo::FORMAT_ARGB32, width)
+ Cairo::ImageSurface::format_stride_for_width(Cairo::ImageSurface::Format::ARGB32, width)
);
Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (surface);
@@ -33,7 +33,7 @@ int main ()
context->rectangle (0, 0, width, height);
context->fill ();
- layout->set_alignment (Pango::ALIGN_LEFT);
+ layout->set_alignment (Pango::Alignment::LEFT);
context->set_line_width (1);
// Cairo::FontOptions fo;
diff --git a/platform/linux/dcpomatic_verifier.desktop.in b/platform/linux/dcpomatic_verifier.desktop.in
new file mode 100644
index 000000000..b52a6fc3d
--- /dev/null
+++ b/platform/linux/dcpomatic_verifier.desktop.in
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Encoding=UTF-8
+Version=1.0
+Type=Application
+Terminal=false
+Exec=@INSTALL_PREFIX@/bin/dcpomatic2_verifier
+Name=DCP-o-matic 2 Verifier
+Icon=dcpomatic2_verifier
+Comment=DCP generator
+Categories=AudioVideo;Video
diff --git a/platform/linux/wscript b/platform/linux/wscript
index 98aff905d..4a3516fd0 100644
--- a/platform/linux/wscript
+++ b/platform/linux/wscript
@@ -16,6 +16,7 @@ def build(bld):
desktop(bld, '_playlist'),
desktop(bld, '_combiner'),
desktop(bld, '_editor'),
+ desktop(bld, '_verifier'),
]
if bld.env.ENABLE_DISK:
diff --git a/platform/osx/dcpomatic2.Info.plist.in b/platform/osx/dcpomatic2.Info.plist.in
index 00a7d99aa..a5550572b 100644
--- a/platform/osx/dcpomatic2.Info.plist.in
+++ b/platform/osx/dcpomatic2.Info.plist.in
@@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>dcpomatic2</string>
<key>CFBundleGetInfoString</key>
- <string>DCP-o-matic 2</string>
+ <string>@NAME@</string>
<key>CFBundleIconFile</key>
<string>dcpomatic2.icns</string>
<key>CFBundleIdentifier</key>
@@ -15,7 +15,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
- <string>DCP-o-matic 2</string>
+ <string>@NAME@</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersions</key>
diff --git a/platform/osx/dcpomatic2_kdm.Info.plist.in b/platform/osx/dcpomatic2_kdm.Info.plist.in
index 95016a972..151d20718 100644
--- a/platform/osx/dcpomatic2_kdm.Info.plist.in
+++ b/platform/osx/dcpomatic2_kdm.Info.plist.in
@@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>dcpomatic2_kdm</string>
<key>CFBundleGetInfoString</key>
- <string>DCP-o-matic 2 KDM creator</string>
+ <string>@NAME@</string>
<key>CFBundleIconFile</key>
<string>dcpomatic2_kdm.icns</string>
<key>CFBundleIdentifier</key>
@@ -15,7 +15,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
- <string>DCP-o-matic 2 KDM Creator</string>
+ <string>@NAME@</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersions</key>
diff --git a/platform/osx/dcpomatic2_player.Info.plist.in b/platform/osx/dcpomatic2_player.Info.plist.in
index 0a10e3727..54e688eaf 100644
--- a/platform/osx/dcpomatic2_player.Info.plist.in
+++ b/platform/osx/dcpomatic2_player.Info.plist.in
@@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>dcpomatic2_player</string>
<key>CFBundleGetInfoString</key>
- <string>DCP-o-matic 2 Player</string>
+ <string>@NAME@</string>
<key>CFBundleIconFile</key>
<string>dcpomatic2_player.icns</string>
<key>CFBundleIdentifier</key>
@@ -15,7 +15,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
- <string>DCP-o-matic 2 Player</string>
+ <string>@NAME@</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersions</key>
diff --git a/platform/osx/dcpomatic2_verifier.Info.plist.in b/platform/osx/dcpomatic2_verifier.Info.plist.in
new file mode 100644
index 000000000..28dba8f9b
--- /dev/null
+++ b/platform/osx/dcpomatic2_verifier.Info.plist.in
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>dcpomatic2_verifier</string>
+ <key>CFBundleGetInfoString</key>
+ <string>@NAME@</string>
+ <key>CFBundleIconFile</key>
+ <string>dcpomatic2_verifier.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.dcpomatic.verifier</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>@NAME@</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersions</key>
+ <string>@VERSION@</string>
+ <key>CFBundleSignature</key>
+ <string>DOMC</string>
+ <key>CFBundleVersion</key>
+ <string>@VERSION@</string>
+ <key>CFBundleAllowMixedLocalizations</key>
+ <true/>
+ <key>LSUIElement</key>
+ <string>0</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/platform/osx/make_dmg.sh b/platform/osx/make_dmg.sh
index bebd089bd..da7c38812 100644
--- a/platform/osx/make_dmg.sh
+++ b/platform/osx/make_dmg.sh
@@ -1,14 +1,14 @@
#!/bin/bash
#
-SYNTAX="make_dmg.sh -e <environment> -r <builddir> -i <apple-id> -p <apple-password> -a <arch1> [-a <arch2>] [-b <id>]"
+SYNTAX="make_dmg.sh -e <environment> -r <builddir> -i <apple-id> -p <apple-password> -a <arch1> [-a <arch2>] [-b <id>] [-v <variant>]"
#
# e.g. make_dmg.sh -e /Users/carl/osx-environment -r /Users/carl/cdist -i foo@bar.net -p opensesame -a x86_64/10.10 -a arm64/11.0 [-b dcpomatic2_player]
# Don't set -e here as egrep (used a few times) returns 1 if no matches
# were found.
-BUILD="main kdm server batch player playlist combiner editor disk"
-while getopts "e:r:i:p:a:b:" o; do
+BUILD="main kdm server batch player playlist combiner editor disk verifier"
+while getopts "e:r:i:p:a:b:v:" o; do
case "${o}" in
e)
ENV=${OPTARG}
@@ -28,9 +28,23 @@ while getopts "e:r:i:p:a:b:" o; do
b)
BUILD=${OPTARG}
;;
+ v)
+ VARIANT=${OPTARG}
+ ;;
esac
done
+VOLUME_PREFIX="DCP-o-matic-"
+GENERAL_NAME="DCP-o-matic"
+DCPOMATIC_APP="DCP-o-matic 2.app"
+KDM_CREATOR_NAME="DCP-o-matic KDM Creator"
+KDM_CREATOR_APP="DCP-o-matic 2 KDM Creator.app"
+PLAYER_APP="DCP-o-matic 2 Player.app"
+PLAYER_NAME="DCP-o-matic Player"
+VERIFIER_APP="DCP-o-matic 2 Verifier.app"
+VERIFIER_NAME="DCP-o-matic Verifier"
+SOURCE_NAME="dcpomatic"
+
# Use a tag if what we've built is exactly on one
version=$(git describe --tags --abbrev=0 --match=v2.*.* --exact-match 2> /dev/null)
if [ "$?" == "0" ]; then
@@ -141,8 +155,8 @@ function copy_libs {
local dest="$1"
copy_lib_root libcxml "$dest"
copy_lib_root libdcp-1.0 "$dest"
- copy_lib_root libasdcp-carl "$dest"
- copy_lib_root libkumu-carl "$dest"
+ copy_lib_root libasdcp-dcpomatic "$dest"
+ copy_lib_root libkumu-dcpomatic "$dest"
copy_lib_root libsub "$dest"
copy_lib_root libopenjp2 "$dest"
copy_lib_root libavdevice "$dest"
@@ -156,8 +170,9 @@ function copy_libs {
copy_lib_root liblwext4 "$dest"
copy_lib_root libblockdev "$dest"
copy_lib_root libleqm_nrt "$dest"
- copy $ROOT src/dcpomatic/build/src/lib/libdcpomatic2.dylib "$dest"
- copy $ROOT src/dcpomatic/build/src/wx/libdcpomatic2-wx.dylib "$dest"
+ copy $ROOT src/$SOURCE_NAME/build/src/lib/libdcpomatic2.dylib "$dest"
+ copy $ROOT src/$SOURCE_NAME/build/src/wx/libdcpomatic2-wx.dylib "$dest"
+ copy_lib_env libboost_atomic "$dest"
copy_lib_env libboost_system "$dest"
copy_lib_env libboost_filesystem "$dest"
copy_lib_env libboost_thread "$dest"
@@ -167,7 +182,7 @@ function copy_libs {
copy_lib_env libxml++ "$dest"
copy_lib_env libxslt "$dest"
copy_lib_env libxml2 "$dest"
- copy_lib_env libglibmm-2.4 "$dest"
+ copy_lib_env libglibmm "$dest"
copy_lib_env libgobject "$dest"
copy_lib_env libgthread "$dest"
copy_lib_env libgmodule "$dest"
@@ -205,6 +220,7 @@ function copy_libs {
copy_lib_env libgio "$dest"
copy_lib_env libz "$dest"
copy_lib_env libdav1d "$dest"
+ copy_lib_env libsqlite "$dest"
}
# @param #1 directory to copy to
@@ -215,45 +231,48 @@ function copy_resources {
else
local prefix=$ROOT/$ARCH1
fi
- cp $prefix/src/dcpomatic/graphics/osx/dcpomatic_small_white.png "$dest"
- cp $prefix/src/dcpomatic/graphics/osx/dcpomatic_small_black.png "$dest"
- cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2.icns "$dest"
- cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_kdm.icns "$dest"
- cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_server.icns "$dest"
- cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_player.icns "$dest"
- cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_batch.icns "$dest"
- cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_playlist.icns "$dest"
- 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/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/me.jpg "$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"
+ source=$prefix/src/$SOURCE_NAME
+ cp $source/graphics/osx/dcpomatic_small_white.png "$dest"
+ cp $source/graphics/osx/dcpomatic_small_black.png "$dest"
+ cp $source/graphics/osx/dcpomatic2.icns "$dest"
+ cp $source/graphics/osx/dcpomatic2_kdm.icns "$dest"
+ cp $source/graphics/osx/dcpomatic2_server.icns "$dest"
+ cp $source/graphics/osx/dcpomatic2_player.icns "$dest"
+ cp $source/graphics/osx/dcpomatic2_batch.icns "$dest"
+ cp $source/graphics/osx/dcpomatic2_playlist.icns "$dest"
+ cp $source/graphics/osx/dcpomatic2_disk.icns "$dest"
+ cp $source/graphics/osx/dcpomatic2_combiner.icns "$dest"
+ cp $source/graphics/osx/dcpomatic2_editor.icns "$dest"
+ cp $source/graphics/osx/dcpomatic2_verifier.icns "$dest"
+ cp $source/graphics/osx/preferences/defaults*.png "$dest"
+ cp $source/graphics/osx/preferences/kdm_email*.png "$dest"
+ cp $source/graphics/osx/preferences/email*.png "$dest"
+ cp $source/graphics/osx/preferences/servers*.png "$dest"
+ cp $source/graphics/osx/preferences/tms*.png "$dest"
+ cp $source/graphics/osx/preferences/keys*.png "$dest"
+ cp $source/graphics/osx/preferences/cover_sheet*.png "$dest"
+ cp $source/graphics/osx/preferences/notifications*.png "$dest"
+ cp $source/graphics/osx/preferences/sound*.png "$dest"
+ cp $source/graphics/osx/preferences/identifiers*.png "$dest"
+ cp $source/graphics/osx/preferences/general*.png "$dest"
+ cp $source/graphics/osx/preferences/advanced*.png "$dest"
+ cp $source/graphics/osx/preferences/locations*.png "$dest"
+ cp $source/graphics/osx/preferences/non_standard*.png "$dest"
+ cp $source/fonts/LiberationSans-Regular.ttf "$dest"
+ cp $source/fonts/LiberationSans-Italic.ttf "$dest"
+ cp $source/fonts/LiberationSans-Bold.ttf "$dest"
+ cp $source/fonts/fonts.conf.osx "$dest"/fonts.conf
+ cp $source/graphics/splash.png "$dest"
+ cp $source/graphics/zoom*.png "$dest"
+ cp $source/graphics/zoom_all*.png "$dest"
+ cp $source/graphics/select*.png "$dest"
+ cp $source/graphics/snap*.png "$dest"
+ cp $source/graphics/sequence*.png "$dest"
+ cp $source/graphics/me.jpg "$dest"
+ cp $source/graphics/link*.png "$dest"
+ cp $source/graphics/add*.png "$dest"
+ cp $source/graphics/pause*.png "$dest"
+ cp -r $source/web "$dest"
cp -r $prefix/share/libdcp/xsd "$dest"
cp -r $prefix/share/libdcp/tags "$dest"
cp -r $prefix/share/libdcp/ratings "$dest"
@@ -261,15 +280,15 @@ function copy_resources {
# 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 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"
- cp $prefix/src/dcpomatic/build/src/tools/mo/$lang/*.mo "$dest/$lang/LC_MESSAGES"
+ cp $source/build/src/lib/mo/$lang/*.mo "$dest/$lang/LC_MESSAGES"
+ cp $source/build/src/wx/mo/$lang/*.mo "$dest/$lang/LC_MESSAGES"
+ cp $source/build/src/tools/mo/$lang/*.mo "$dest/$lang/LC_MESSAGES"
done
# i18n: wxWidgets .mo files
for lang in de es fr it sv nl ru pl da cs sl; do
mkdir "$dest/$lang"
- cp $ENV/$ARCH1/share/locale/$lang/LC_MESSAGES/wxstd.mo "$dest/$lang"
+ cp $ENV/$ARCH1/share/locale/$lang/LC_MESSAGES/wxstd*.mo "$dest/$lang"
done
}
@@ -341,7 +360,7 @@ function make_dmg {
else
dmg="$full_name $version macOS10.10+.dmg"
fi
- vol_name=DCP-o-matic-$version
+ vol_name=$VOLUME_PREFIX$version
find "$appdir/Contents/Frameworks" -iname "*.dylib" -type f -print0 | while IFS= read -r -d '' f; do
sign "$f"
@@ -366,17 +385,17 @@ function make_dmg {
fi
ln -s /Applications "$vol_name/Applications"
cat<<EOF > "$vol_name/READ ME.txt"
-Welcome to DCP-o-matic! The first time you run the program there may be
+Welcome to $GENERAL_NAME The first time you run the program there may be
a long (several-minute) delay while macOS checks the code for viruses and
other malware. Please be patient!
EOF
cat<<EOF > "$vol_name/READ ME.de_DE.txt"
-Beim erstmaligen Start der DCP-o-matic Anwendungen kann ein längerer
+Beim erstmaligen Start der $GENERAL_NAME Anwendungen kann ein längerer
Verifikationsvorgang auftreten. Dies ist von der macOS Sicherheitsumgebung
'Gatekeeper' verursacht. Dieser je nach Rechner teils minutenlange
Verifikationsvorgang ist gegenwärtig normal und nicht zu umgehen,
es ist kein Programmfehler. Warten sie die Verifikation für jede der
-DCP-o-matic Anwendungen ab, bei weiteren Programmstarts wird sie nicht
+$GENERAL_NAME Anwendungen ab, bei weiteren Programmstarts wird sie nicht
mehr auftreten.
EOF
@@ -463,8 +482,8 @@ function setup {
function copy_verify {
copy $ROOT src/libdcp/build/tools/dcpverify "$approot/MacOS"
- mv "$approot/MacOS/dcpverify" "$approot/MacOS/dcpomatic2_verify"
- rl=("$approot/MacOS/dcpomatic2_verify" "$approot/Frameworks/"*.dylib)
+ mv "$approot/MacOS/dcpverify" "$approot/MacOS/dcpomatic2_verify_cli"
+ rl=("$approot/MacOS/dcpomatic2_verify_cli" "$approot/Frameworks/"*.dylib)
relink_relative "${rl[@]}"
}
@@ -483,123 +502,136 @@ fi
if [[ "$BUILD" == *main* ]]; then
# DCP-o-matic main
- setup "DCP-o-matic 2.app"
- 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"
+ setup "$DCPOMATIC_APP"
+ copy $ROOT src/$SOURCE_NAME/build/src/tools/dcpomatic2 "$approot/MacOS"
+ copy $ROOT src/$SOURCE_NAME/build/src/tools/dcpomatic2_cli "$approot/MacOS"
+ copy $ROOT src/$SOURCE_NAME/build/src/tools/dcpomatic2_create "$approot/MacOS"
+ copy $ROOT src/$SOURCE_NAME/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"
+ cp $prefix/src/$SOURCE_NAME/build/platform/osx/dcpomatic2.Info.plist "$approot/Info.plist"
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 dcpomatic2_kdm_inspect openssl ffprobe dcpomatic2_cli dcpomatic2_create dcpomatic2_map dcpomatic2"
+ make_dmg "$appdir" "" "$GENERAL_NAME" "dcpomatic2_verify_cli dcpomatic2_kdm_inspect openssl ffprobe dcpomatic2_cli dcpomatic2_create dcpomatic2_map dcpomatic2"
fi
if [[ "$BUILD" == *kdm* ]]; then
# DCP-o-matic KDM Creator
- setup "DCP-o-matic 2 KDM Creator.app"
- copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_kdm "$approot/MacOS"
- copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_kdm_cli "$approot/MacOS"
+ setup "$KDM_CREATOR_APP"
+ copy $ROOT src/$SOURCE_NAME/build/src/tools/dcpomatic2_kdm "$approot/MacOS"
+ copy $ROOT src/$SOURCE_NAME/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"
+ cp $prefix/src/$SOURCE_NAME/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 dcpomatic2_kdm_inspect openssl dcpomatic2_kdm_cli dcpomatic2_kdm"
+ make_dmg "$appdir" "" "$KDM_CREATOR_NAME" "dcpomatic2_verify_cli dcpomatic2_kdm_inspect openssl dcpomatic2_kdm_cli dcpomatic2_kdm"
fi
if [[ "$BUILD" == *server* ]]; then
# DCP-o-matic Encode Server
setup "DCP-o-matic 2 Encode Server.app"
- copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_server "$approot/MacOS"
- copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_server_cli "$approot/MacOS"
+ copy $ROOT src/$SOURCE_NAME/build/src/tools/dcpomatic2_server "$approot/MacOS"
+ copy $ROOT src/$SOURCE_NAME/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"
+ cp $prefix/src/$SOURCE_NAME/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 dcpomatic2_kdm_inspect openssl dcpomatic2_server_cli dcpomatic2_server"
+ make_dmg "$appdir" "" "DCP-o-matic Encode Server" "dcpomatic2_verify_cli dcpomatic2_kdm_inspect openssl dcpomatic2_server_cli dcpomatic2_server"
fi
if [[ "$BUILD" == *batch* ]]; then
# DCP-o-matic Batch Converter
setup "DCP-o-matic 2 Batch converter.app"
- copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_batch "$approot/MacOS"
+ copy $ROOT src/$SOURCE_NAME/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"
+ cp $prefix/src/$SOURCE_NAME/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 dcpomatic2_kdm_inspect openssl dcpomatic2_batch"
+ make_dmg "$appdir" "" "DCP-o-matic Batch Converter" "dcpomatic2_verify_cli dcpomatic2_kdm_inspect openssl dcpomatic2_batch"
fi
if [[ "$BUILD" == *player* ]]; then
# DCP-o-matic Player
- setup "DCP-o-matic 2 Player.app"
- copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_player "$approot/MacOS"
+ setup "$PLAYER_APP"
+ copy $ROOT src/$SOURCE_NAME/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"
+ cp $prefix/src/$SOURCE_NAME/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 dcpomatic2_kdm_inspect openssl dcpomatic2_player"
+ make_dmg "$appdir" "" "$PLAYER_NAME" "dcpomatic2_verify_cli dcpomatic2_kdm_inspect openssl dcpomatic2_player"
fi
if [[ "$BUILD" == *playlist* ]]; then
# DCP-o-matic Playlist Editor
setup "DCP-o-matic 2 Playlist Editor.app"
- copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_playlist "$approot/MacOS"
+ copy $ROOT src/$SOURCE_NAME/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"
+ cp $prefix/src/$SOURCE_NAME/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 dcpomatic2_kdm_inspect openssl dcpomatic2_playlist"
+ make_dmg "$appdir" "" "DCP-o-matic Playlist Editor" "dcpomatic2_verify_cli dcpomatic2_kdm_inspect openssl dcpomatic2_playlist"
fi
if [[ "$BUILD" == *combiner* ]]; then
# DCP-o-matic Combiner
setup "DCP-o-matic 2 Combiner.app"
- copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_combiner "$approot/MacOS"
+ copy $ROOT src/$SOURCE_NAME/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"
+ cp $prefix/src/$SOURCE_NAME/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 dcpomatic2_kdm_inspect openssl dcpomatic2_combiner"
+ make_dmg "$appdir" "" "DCP-o-matic Combiner" "dcpomatic2_verify_cli dcpomatic2_kdm_inspect openssl dcpomatic2_combiner"
fi
if [[ "$BUILD" == *editor* ]]; then
# DCP-o-matic Editor
setup "DCP-o-matic 2 Editor.app"
- copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_editor "$approot/MacOS"
+ copy $ROOT src/$SOURCE_NAME/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"
+ cp $prefix/src/$SOURCE_NAME/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 dcpomatic2_kdm_inspect openssl dcpomatic2_editor"
+ make_dmg "$appdir" "" "DCP-o-matic Editor" "dcpomatic2_verify_cli dcpomatic2_kdm_inspect openssl dcpomatic2_editor"
+fi
+
+if [[ "$BUILD" == *verifier* ]]; then
+ # DCP-o-matic Verifier
+ setup "$VERIFIER_APP"
+ copy $ROOT src/$SOURCE_NAME/build/src/tools/dcpomatic2_verifier "$approot/MacOS"
+ copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
+ copy_verify
+ copy_kdm
+ cp $prefix/src/$SOURCE_NAME/build/platform/osx/dcpomatic2_verifier.Info.plist "$approot/Info.plist"
+ rl=("$approot/MacOS/dcpomatic2_verifier" "$approot/Frameworks/"*.dylib)
+ relink_relative "${rl[@]}"
+ make_dmg "$appdir" "" "$VERIFIER_NAME" "dcpomatic2_verify_cli dcpomatic2_kdm_inspect openssl dcpomatic2_verifier"
fi
if [[ "$BUILD" == *disk* ]]; then
# DCP-o-matic Disk Writer .app
setup "DCP-o-matic 2 Disk Writer.app"
- copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_disk "$approot/MacOS"
+ copy $ROOT src/$SOURCE_NAME/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"
+ cp $prefix/src/$SOURCE_NAME/platform/osx/uninstall_disk.applescript "$approot/Resources"
+ cp $prefix/src/$SOURCE_NAME/build/platform/osx/dcpomatic2_disk.Info.plist "$approot/Info.plist"
rl=("$approot/MacOS/dcpomatic2_disk" "$approot/Frameworks/"*.dylib)
relink_relative "${rl[@]}"
@@ -651,7 +683,7 @@ EOF
# place with spaces in the filename to avoid some of the pain of escaping
mkdir $pkgbin
- copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_disk_writer "$pkgbin"
+ copy $ROOT src/$SOURCE_NAME/build/src/tools/dcpomatic2_disk_writer "$pkgbin"
copy_libs "$pkgbin"
rl=("$pkgbin/dcpomatic2_disk_writer" "$pkgbin/"*.dylib)
@@ -675,6 +707,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 dcpomatic2_kdm_inspect openssl dcpomatic2_disk"
+ make_dmg "$appdir" "DCP-o-matic Disk Writer.pkg" "DCP-o-matic Disk Writer" "dcpomatic2_verify_cli dcpomatic2_kdm_inspect openssl dcpomatic2_disk"
fi
diff --git a/platform/osx/wscript b/platform/osx/wscript
index fd6733a66..39e8a5815 100644
--- a/platform/osx/wscript
+++ b/platform/osx/wscript
@@ -1,10 +1,11 @@
def build(bld):
- obj = bld(features='subst', source='dcpomatic2.Info.plist.in', target='dcpomatic2.Info.plist', version=bld.env.VERSION)
- obj = bld(features='subst', source='dcpomatic2_kdm.Info.plist.in', target='dcpomatic2_kdm.Info.plist', version=bld.env.VERSION)
- obj = bld(features='subst', source='dcpomatic2_server.Info.plist.in', target='dcpomatic2_server.Info.plist', version=bld.env.VERSION)
- obj = bld(features='subst', source='dcpomatic2_batch.Info.plist.in', target='dcpomatic2_batch.Info.plist', version=bld.env.VERSION)
- obj = bld(features='subst', source='dcpomatic2_player.Info.plist.in', target='dcpomatic2_player.Info.plist', version=bld.env.VERSION)
- obj = bld(features='subst', source='dcpomatic2_playlist.Info.plist.in', target='dcpomatic2_playlist.Info.plist', version=bld.env.VERSION)
- obj = bld(features='subst', source='dcpomatic2_disk.Info.plist.in', target='dcpomatic2_disk.Info.plist', version=bld.env.VERSION)
- obj = bld(features='subst', source='dcpomatic2_combiner.Info.plist.in', target='dcpomatic2_combiner.Info.plist', version=bld.env.VERSION)
- obj = bld(features='subst', source='dcpomatic2_editor.Info.plist.in', target='dcpomatic2_editor.Info.plist', version=bld.env.VERSION)
+ bld(features='subst', source='dcpomatic2.Info.plist.in', target='dcpomatic2.Info.plist', version=bld.env.VERSION, NAME="DCP-o-matic 2")
+ bld(features='subst', source='dcpomatic2_kdm.Info.plist.in', target='dcpomatic2_kdm.Info.plist', version=bld.env.VERSION, NAME="DCP-o-matic 2 KDM Creator")
+ bld(features='subst', source='dcpomatic2_server.Info.plist.in', target='dcpomatic2_server.Info.plist', version=bld.env.VERSION)
+ bld(features='subst', source='dcpomatic2_batch.Info.plist.in', target='dcpomatic2_batch.Info.plist', version=bld.env.VERSION)
+ bld(features='subst', source='dcpomatic2_player.Info.plist.in', target='dcpomatic2_player.Info.plist', version=bld.env.VERSION, NAME="DCP-o-matic 2 Player")
+ bld(features='subst', source='dcpomatic2_playlist.Info.plist.in', target='dcpomatic2_playlist.Info.plist', version=bld.env.VERSION)
+ bld(features='subst', source='dcpomatic2_disk.Info.plist.in', target='dcpomatic2_disk.Info.plist', version=bld.env.VERSION)
+ bld(features='subst', source='dcpomatic2_combiner.Info.plist.in', target='dcpomatic2_combiner.Info.plist', version=bld.env.VERSION)
+ bld(features='subst', source='dcpomatic2_editor.Info.plist.in', target='dcpomatic2_editor.Info.plist', version=bld.env.VERSION)
+ bld(features='subst', source='dcpomatic2_verifier.Info.plist.in', target='dcpomatic2_verifier.Info.plist', version=bld.env.VERSION, NAME="DCP-o-matic 2 Verifier")
diff --git a/platform/windows/dcpomatic.rc b/platform/windows/dcpomatic.rc
deleted file mode 100644
index e58e81b7f..000000000
--- a/platform/windows/dcpomatic.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-id ICON "../../graphics/windows/dcpomatic2.ico"
-#include "wx-3.1/wx/msw/wx.rc"
diff --git a/platform/windows/dcpomatic2_disk_writer.exe.manifest b/platform/windows/dcpomatic2_disk_writer.exe.manifest
deleted file mode 100644
index 7d922a0db..000000000
--- a/platform/windows/dcpomatic2_disk_writer.exe.manifest
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
- <assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="dcpomatic2_disk_writer" type="win32"/>
- <description>DCP-o-matic Disk Writer</description>
- <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
- <security>
- <requestedPrivileges>
- <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
- </requestedPrivileges>
- </security>
- </trustInfo>
-</assembly>
diff --git a/platform/windows/dcpomatic2_verifier.bat b/platform/windows/dcpomatic2_verifier.bat
new file mode 100644
index 000000000..d8850d9fe
--- /dev/null
+++ b/platform/windows/dcpomatic2_verifier.bat
@@ -0,0 +1 @@
+gdb.exe -x gdb_script dcpomatic2_verifier.exe > %HOMEPATH%/Documents/dcpomatic_debug_log.txt
diff --git a/platform/windows/dcpomatic_batch.rc b/platform/windows/dcpomatic_batch.rc
deleted file mode 100644
index 9aea38110..000000000
--- a/platform/windows/dcpomatic_batch.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-id ICON "../../graphics/windows/dcpomatic2_batch.ico"
-#include "wx-3.1/wx/msw/wx.rc"
diff --git a/platform/windows/dcpomatic_combiner.rc b/platform/windows/dcpomatic_combiner.rc
deleted file mode 100644
index 700d85a8b..000000000
--- a/platform/windows/dcpomatic_combiner.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-id ICON "../../graphics/windows/dcpomatic2_combiner.ico"
-#include "wx-3.1/wx/msw/wx.rc"
diff --git a/platform/windows/dcpomatic_disk.rc b/platform/windows/dcpomatic_disk.rc
deleted file mode 100644
index eac76a0d5..000000000
--- a/platform/windows/dcpomatic_disk.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-id ICON "../../graphics/windows/dcpomatic2_disk.ico"
-#include "wx-3.1/wx/msw/wx.rc"
diff --git a/platform/windows/dcpomatic_disk_writer.rc b/platform/windows/dcpomatic_disk_writer.rc
deleted file mode 100644
index 70138ed12..000000000
--- a/platform/windows/dcpomatic_disk_writer.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "winuser.h"
-1 RT_MANIFEST "dcpomatic2_disk_writer.exe.manifest"
diff --git a/platform/windows/dcpomatic_editor.rc b/platform/windows/dcpomatic_editor.rc
deleted file mode 100644
index 865590f51..000000000
--- a/platform/windows/dcpomatic_editor.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-id ICON "../../graphics/windows/dcpomatic2_editor.ico"
-#include "wx-3.1/wx/msw/wx.rc"
diff --git a/platform/windows/dcpomatic_kdm.rc b/platform/windows/dcpomatic_kdm.rc
deleted file mode 100644
index 057a42c01..000000000
--- a/platform/windows/dcpomatic_kdm.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-id ICON "../../graphics/windows/dcpomatic2_kdm.ico"
-#include "wx-3.1/wx/msw/wx.rc"
diff --git a/platform/windows/dcpomatic_player.rc b/platform/windows/dcpomatic_player.rc
deleted file mode 100644
index dd5e0d1fe..000000000
--- a/platform/windows/dcpomatic_player.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-id ICON "../../graphics/windows/dcpomatic2_player.ico"
-#include "wx-3.1/wx/msw/wx.rc"
diff --git a/platform/windows/dcpomatic_playlist.rc b/platform/windows/dcpomatic_playlist.rc
deleted file mode 100644
index 2bb457371..000000000
--- a/platform/windows/dcpomatic_playlist.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-id ICON "../../graphics/windows/dcpomatic2_playlist.ico"
-#include "wx-3.1/wx/msw/wx.rc"
diff --git a/platform/windows/dcpomatic_server.rc b/platform/windows/dcpomatic_server.rc
deleted file mode 100644
index fb9f4e811..000000000
--- a/platform/windows/dcpomatic_server.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-id ICON "../../graphics/windows/dcpomatic2_server.ico"
-#include "wx-3.1/wx/msw/wx.rc"
diff --git a/platform/windows/wscript b/platform/windows/wscript
index 17965d54c..f64923b2c 100644
--- a/platform/windows/wscript
+++ b/platform/windows/wscript
@@ -2,20 +2,52 @@ from __future__ import print_function
import os
-def start_menu_shortcut(file, link, target, debug=False):
+def start_menu_shortcut_folder(variant):
+ return 'DCP-o-matic 2'
+
+def long_name(variant):
+ return 'DCP-o-matic 2'
+
+def short_name(variant):
+ return 'DCP-o-matic'
+
+def kdm_creator_name(variant):
+ return 'KDM Creator'
+
+def player_name(variant):
+ return 'Player'
+
+def verifier_name(variant):
+ return 'Verifier'
+
+def tool_name(variant, debug, tool):
+ name = 'DCP-o-matic 2 %s' % tool
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)
+ name += ' debug'
+ return name
+
+
+
+def start_menu_shortcut(file, link, target, variant, debug=False):
+ folder = start_menu_shortcut_folder(variant)
+ if debug:
+ folder += ' debug'
+ print('CreateShortCut "$SMPROGRAMS\\%s\\%s.lnk" "$INSTDIR\\%s"' % (folder, link, target), file=file)
-def write_installer(bits, dcpomatic_version, debug, disk):
+def write_installer(bits, dcpomatic_version, debug, disk, variant):
+ long_name_with_debug = long_name(variant)
+ short_name_with_debug = short_name(variant)
+ if debug:
+ long_name_with_debug += ' debug'
+ short_name_with_debug += ' debug'
tools = [
+ ('kdm', kdm_creator_name(variant)),
+ ('player', player_name(variant)),
+ ('verifier', verifier_name(variant)),
('batch', 'Batch Converter'),
- ('kdm', 'KDM Creator'),
('kdm_cli', 'KDM Creator CLI'),
- ('player', 'Player'),
('cli', 'CLI'),
('create', 'Creator'),
('playlist', 'Playlist Editor'),
@@ -40,19 +72,11 @@ def write_installer(bits, dcpomatic_version, debug, disk):
if bits == 64:
print('!include "x64.nsh"', file=f)
- if debug:
- print('Name "DCP-o-matic debug"', file=f)
- else:
- print('Name "DCP-o-matic"', file=f)
-
+ print('Name "%s"' % short_name_with_debug, file=f)
print('RequestExecutionLevel admin', file=f)
print('Unicode true', file=f)
- outfile = 'DCP-o-matic '
- if debug:
- outfile += 'Debug '
- outfile += '%s %d-bit Installer.exe' % (dcpomatic_version, bits)
-
+ outfile = '%s %s %d-bit Installer.exe' % (short_name_with_debug, dcpomatic_version, bits)
print('outFile "%s"' % outfile, file=f)
print("""
@@ -67,10 +91,7 @@ def write_installer(bits, dcpomatic_version, debug, disk):
else:
program_files = "$PROGRAMFILES"
- if debug:
- print('InstallDir "%s\\DCP-o-matic 2 debug"' % program_files, file=f)
- else:
- print('InstallDir "%s\\DCP-o-matic 2"' % program_files, file=f)
+ print('InstallDir "%s\\%s"' % (program_files, long_name_with_debug), file=f)
print("""
!insertmacro MUI_PAGE_WELCOME
@@ -116,14 +137,14 @@ File "%static_deps%/bin/libssh.dll"
File "%static_deps%/bin/libstdc++-6.dll"
File "%static_deps%/bin/zlib1.dll"
File "%static_deps%/bin/libjpeg-9.dll"
-File "%static_deps%/bin/wxbase314u_gcc_custom.dll"
-File "%static_deps%/bin/wxmsw314u_core_gcc_custom.dll"
-File "%static_deps%/bin/wxmsw314u_adv_gcc_custom.dll"
-File "%static_deps%/bin/wxmsw314u_richtext_gcc_custom.dll"
-File "%static_deps%/bin/wxmsw314u_html_gcc_custom.dll"
-File "%static_deps%/bin/wxmsw314u_gl_gcc_custom.dll"
-File "%static_deps%/bin/wxmsw314u_propgrid_gcc_custom.dll"
-File "%static_deps%/bin/wxbase314u_xml_gcc_custom.dll"
+File "%static_deps%/bin/wxbase317u_gcc_custom.dll"
+File "%static_deps%/bin/wxmsw317u_core_gcc_custom.dll"
+File "%static_deps%/bin/wxmsw317u_adv_gcc_custom.dll"
+File "%static_deps%/bin/wxmsw317u_richtext_gcc_custom.dll"
+File "%static_deps%/bin/wxmsw317u_html_gcc_custom.dll"
+File "%static_deps%/bin/wxmsw317u_gl_gcc_custom.dll"
+File "%static_deps%/bin/wxmsw317u_propgrid_gcc_custom.dll"
+File "%static_deps%/bin/wxbase317u_xml_gcc_custom.dll"
File "%static_deps%/bin/libcairo-2.dll"
File "%static_deps%/bin/libfreetype-6.dll"
File "%static_deps%/bin/libgthread-2.0-0.dll"
@@ -131,7 +152,8 @@ File "%static_deps%/bin/libpango-1.0-0.dll"
File "%static_deps%/bin/libgmodule-2.0-0.dll"
File "%static_deps%/bin/libpangocairo-1.0-0.dll"
File "%static_deps%/bin/libpangowin32-1.0-0.dll"
-File "%static_deps%/bin/libtiff-5.dll"
+File "%static_deps%/bin/libpangoft2-1.0-0.dll"
+File "%static_deps%/bin/libtiff-6.dll"
File "%static_deps%/bin/libglibmm-2.4-1.dll"
File "%static_deps%/bin/libxml++-2.6-2.dll"
File "%static_deps%/bin/libxml2-2.dll"
@@ -143,7 +165,7 @@ File "%static_deps%/bin/libxmlsec1.dll"
File "%static_deps%/bin/libxmlsec1-openssl.dll"
File "%static_deps%/bin/libexslt-0.dll"
File "%static_deps%/bin/libxslt-1.dll"
-File "%static_deps%/bin/libffi-6.dll"
+File "%static_deps%/bin/libffi-7.dll"
File "%static_deps%/bin/openssl.exe"
File "%static_deps%/bin/libcurl-4.dll"
File "%static_deps%/bin/libzip.dll"
@@ -166,11 +188,15 @@ File "%static_deps%/bin/libunistring-2.dll"
File "%static_deps%/bin/libssh2-1.dll"
File "%static_deps%/bin/libgcrypt-20.dll"
File "%static_deps%/bin/libgpg-error-0.dll"
-File "%static_deps%/bin/libpangoft2-1.0-0.dll"
File "%static_deps%/bin/libx264-155.dll"
File "%static_deps%/bin/libwebp-7.dll"
File "%static_deps%/bin/GLEW.dll"
File "%static_deps%/bin/libdav1d.dll"
+File "%static_deps%/bin/libbrotlidec.dll"
+File "%static_deps%/bin/libbrotlicommon.dll"
+File "%static_deps%/bin/libfribidi-0.dll"
+File "%static_deps%/bin/libsharpyuv-0.dll"
+File "%static_deps%/bin/libsqlite3-0.dll"
""", file=f)
if bits == 32:
@@ -181,11 +207,11 @@ File "%static_deps%/bin/libdav1d.dll"
print("""
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_verify_cli.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"
+File "%cdist_deps%/bin/asdcp-dcpomatic.dll"
+File "%cdist_deps%/bin/kumu-dcpomatic.dll"
""", file=f)
if disk:
@@ -196,14 +222,14 @@ File "%cdist_deps%/lib/liblwext4.dll"
""", file=f)
print("""
-File "%cdist_deps%/bin/avcodec-58.dll"
-File "%cdist_deps%/bin/avfilter-7.dll"
-File "%cdist_deps%/bin/avformat-58.dll"
-File "%cdist_deps%/bin/avutil-56.dll"
-File "%cdist_deps%/bin/avdevice-58.dll"
-File "%cdist_deps%/bin/postproc-55.dll"
-File "%cdist_deps%/bin/swresample-3.dll"
-File "%cdist_deps%/bin/swscale-5.dll"
+File "%cdist_deps%/bin/avcodec-60.dll"
+File "%cdist_deps%/bin/avfilter-9.dll"
+File "%cdist_deps%/bin/avformat-60.dll"
+File "%cdist_deps%/bin/avutil-58.dll"
+File "%cdist_deps%/bin/avdevice-60.dll"
+File "%cdist_deps%/bin/postproc-57.dll"
+File "%cdist_deps%/bin/swresample-4.dll"
+File "%cdist_deps%/bin/swscale-7.dll"
File "%cdist_deps%/bin/dcp-1.0.dll"
File "%cdist_deps%/bin/cxml-0.dll"
File "%cdist_deps%/bin/sub-1.0.dll"
@@ -232,47 +258,47 @@ SetOutPath "$INSTDIR\\locale\\fr\\LC_MESSAGES"
File "%binaries%/src/lib/mo/fr_FR/libdcpomatic2.mo"
File "%binaries%/src/wx/mo/fr_FR/libdcpomatic2-wx.mo"
File "%binaries%/src/tools/mo/fr_FR/dcpomatic2.mo"
-File "%static_deps%/share/locale/fr/LC_MESSAGES/wxstd.mo"
+File "%static_deps%/share/locale/fr/LC_MESSAGES/wxstd-3.1.mo"
SetOutPath "$INSTDIR\\locale\\it\\LC_MESSAGES"
File "%binaries%/src/lib/mo/it_IT/libdcpomatic2.mo"
File "%binaries%/src/wx/mo/it_IT/libdcpomatic2-wx.mo"
File "%binaries%/src/tools/mo/it_IT/dcpomatic2.mo"
-File "%static_deps%/share/locale/it/LC_MESSAGES/wxstd.mo"
+File "%static_deps%/share/locale/it/LC_MESSAGES/wxstd-3.1.mo"
SetOutPath "$INSTDIR\\locale\\es\\LC_MESSAGES"
File "%binaries%/src/lib/mo/es_ES/libdcpomatic2.mo"
File "%binaries%/src/wx/mo/es_ES/libdcpomatic2-wx.mo"
File "%binaries%/src/tools/mo/es_ES/dcpomatic2.mo"
-File "%static_deps%/share/locale/es/LC_MESSAGES/wxstd.mo"
+File "%static_deps%/share/locale/es/LC_MESSAGES/wxstd-3.1.mo"
SetOutPath "$INSTDIR\\locale\\sv\\LC_MESSAGES"
File "%binaries%/src/lib/mo/sv_SE/libdcpomatic2.mo"
File "%binaries%/src/wx/mo/sv_SE/libdcpomatic2-wx.mo"
File "%binaries%/src/tools/mo/sv_SE/dcpomatic2.mo"
-File "%static_deps%/share/locale/sv/LC_MESSAGES/wxstd.mo"
+File "%static_deps%/share/locale/sv/LC_MESSAGES/wxstd-3.1.mo"
SetOutPath "$INSTDIR\\locale\\de\\LC_MESSAGES"
File "%binaries%/src/lib/mo/de_DE/libdcpomatic2.mo"
File "%binaries%/src/wx/mo/de_DE/libdcpomatic2-wx.mo"
File "%binaries%/src/tools/mo/de_DE/dcpomatic2.mo"
-File "%static_deps%/share/locale/de/LC_MESSAGES/wxstd.mo"
+File "%static_deps%/share/locale/de/LC_MESSAGES/wxstd-3.1.mo"
SetOutPath "$INSTDIR\\locale\\nl\\LC_MESSAGES"
File "%binaries%/src/lib/mo/nl_NL/libdcpomatic2.mo"
File "%binaries%/src/wx/mo/nl_NL/libdcpomatic2-wx.mo"
File "%binaries%/src/tools/mo/nl_NL/dcpomatic2.mo"
-File "%static_deps%/share/locale/nl/LC_MESSAGES/wxstd.mo"
+File "%static_deps%/share/locale/nl/LC_MESSAGES/wxstd-3.1.mo"
SetOutPath "$INSTDIR\\locale\\ru\\LC_MESSAGES"
File "%binaries%/src/lib/mo/ru_RU/libdcpomatic2.mo"
File "%binaries%/src/wx/mo/ru_RU/libdcpomatic2-wx.mo"
File "%binaries%/src/tools/mo/ru_RU/dcpomatic2.mo"
-File "%static_deps%/share/locale/ru/LC_MESSAGES/wxstd.mo"
+File "%static_deps%/share/locale/ru/LC_MESSAGES/wxstd-3.1.mo"
SetOutPath "$INSTDIR\\locale\\pl\\LC_MESSAGES"
File "%binaries%/src/lib/mo/pl_PL/libdcpomatic2.mo"
File "%binaries%/src/wx/mo/pl_PL/libdcpomatic2-wx.mo"
File "%binaries%/src/tools/mo/pl_PL/dcpomatic2.mo"
-File "%static_deps%/share/locale/pl/LC_MESSAGES/wxstd.mo"
+File "%static_deps%/share/locale/pl/LC_MESSAGES/wxstd-3.1.mo"
SetOutPath "$INSTDIR\\locale\\da\\LC_MESSAGES"
File "%binaries%/src/lib/mo/da_DK/libdcpomatic2.mo"
File "%binaries%/src/wx/mo/da_DK/libdcpomatic2-wx.mo"
File "%binaries%/src/tools/mo/da_DK/dcpomatic2.mo"
-File "%static_deps%/share/locale/da/LC_MESSAGES/wxstd.mo"
+File "%static_deps%/share/locale/da/LC_MESSAGES/wxstd-3.1.mo"
SetOutPath "$INSTDIR\\locale\\pt_PT\\LC_MESSAGES"
File "%binaries%/src/lib/mo/pt_PT/libdcpomatic2.mo"
File "%binaries%/src/wx/mo/pt_PT/libdcpomatic2-wx.mo"
@@ -289,7 +315,7 @@ SetOutPath "$INSTDIR\\locale\\cs\\LC_MESSAGES"
File "%binaries%/src/lib/mo/cs_CZ/libdcpomatic2.mo"
File "%binaries%/src/wx/mo/cs_CZ/libdcpomatic2-wx.mo"
File "%binaries%/src/tools/mo/cs_CZ/dcpomatic2.mo"
-File "%static_deps%/share/locale/cs/LC_MESSAGES/wxstd.mo"
+File "%static_deps%/share/locale/cs/LC_MESSAGES/wxstd-3.1.mo"
SetOutPath "$INSTDIR\\locale\\uk\\LC_MESSAGES"
File "%binaries%/src/lib/mo/uk_UA/libdcpomatic2.mo"
File "%binaries%/src/wx/mo/uk_UA/libdcpomatic2-wx.mo"
@@ -342,6 +368,8 @@ File "%graphics%/add_black.png"
File "%graphics%/add_white.png"
File "%graphics%/pause_black.png"
File "%graphics%/pause_white.png"
+SetOutPath "$INSTDIR\\web"
+File "%web%/index.html"
SetOutPath "$INSTDIR\\xsd"
File "%cdist_deps%/share/libdcp/xsd/DCDMSubtitle-2010.xsd"
File "%cdist_deps%/share/libdcp/xsd/DCDMSubtitle-2014.xsd"
@@ -379,18 +407,12 @@ File "%cdist_deps%/share/libdcp/ratings"
SectionEnd
""", file=f)
- if debug:
- print('Section "DCP-o-matic 2 debug" SEC_MASTER', file=f)
- else:
- print('Section "DCP-o-matic 2" SEC_MASTER', file=f)
+ print('Section "%s" SEC_MASTER' % long_name_with_debug, 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)
- else:
- print('CreateDirectory "$SMPROGRAMS\\DCP-o-matic 2"', file=f)
+ print('CreateDirectory "$SMPROGRAMS\\%s"' % long_name_with_debug, file=f)
print('File "%binaries%/src/tools/dcpomatic2.exe"', file=f)
for s, l in tools:
@@ -399,36 +421,23 @@ SectionEnd
if disk:
print("""
File "%binaries%/src/tools/dcpomatic2_disk_writer.exe"
-File "%resources%/dcpomatic2_disk_writer.exe.manifest"
+File "%binaries%/src/tools/dcpomatic2_disk_writer.exe.manifest"
""", file=f)
- if debug:
- start_menu_shortcut(f, 'DCP-o-matic 2 debug', 'bin\\dcpomatic2_debug.bat', debug=True)
- for s, l in tools:
- 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:
- start_menu_shortcut(f, 'DCP-o-matic 2', 'bin\\dcpomatic2.exe')
- for s, l in tools:
- 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)
+ suffix = '_debug.bat' if debug else '.exe'
+ start_menu_shortcut(f, long_name_with_debug, 'bin\\dcpomatic2%s' % suffix, variant, debug=True)
+ for s, l in tools:
+ start_menu_shortcut(f, tool_name(variant, debug, l), 'bin\\dcpomatic2_%s%s' % (s, suffix), variant, debug=True)
+ start_menu_shortcut(f, 'Uninstall %s' % long_name_with_debug, 'Uninstall.exe', variant, debug=True)
+ print('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s" "DisplayName" "%s (remove only)"' % (long_name_with_debug, long_name_with_debug), file=f)
+ print('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s" "UninstallString" "$INSTDIR\\Uninstall.exe"' % long_name_with_debug, file=f)
print("SectionEnd", file=f)
- if debug:
- print('Section "DCP-o-matic 2 debug desktop shortcuts" SEC_MASTER_DESKTOP', file=f)
- print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 debug.lnk" "$INSTDIR\\bin\\dcpomatic2_debug.bat" ""', file=f)
- for s, l in tools:
- print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 %s debug.lnk" "$INSTDIR\\bin\\dcpomatic2_%s_debug.bat" ""' % (l, s), file=f)
- else:
- print('Section "DCP-o-matic 2 desktop shortcuts" SEC_MASTER_DESKTOP', file=f)
- print('CreateShortCut "$DESKTOP\\DCP-o-matic 2.lnk" "$INSTDIR\\bin\\dcpomatic2.exe" ""', file=f)
- for s, l in tools:
- print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 %s.lnk" "$INSTDIR\\bin\\dcpomatic2_%s.exe"' % (l, s), file=f)
+ print('Section "%s desktop shortcuts" SEC_MASTER_DESKTOP' % long_name_with_debug, file=f)
+ print('CreateShortCut "$DESKTOP\\%s.lnk" "$INSTDIR\\bin\\dcpomatic2%s" ""' % (long_name_with_debug, suffix), file=f)
+ for s, l in tools:
+ print('CreateShortCut "$DESKTOP\\%s.lnk" "$INSTDIR\\bin\\dcpomatic2_%s%s" ""' % (tool_name(variant, debug, l), s, suffix), file=f)
print("SectionEnd", file=f)
@@ -440,8 +449,8 @@ File "%binaries%/src/tools/dcpomatic2_server_cli.exe"
File "%binaries%/src/tools/dcpomatic2_server.exe"
""", 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')
+ start_menu_shortcut(f, 'DCP-o-matic 2 Encode Server', 'bin\\dcpomatic2_server.exe', variant)
+ start_menu_shortcut(f, 'Uninstall DCP-o-matic 2', 'Uninstall.exe', variant)
print("""
SectionEnd
@@ -512,5 +521,5 @@ DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\U
def build(bld):
- write_installer(32, bld.env.VERSION, bld.env.DEBUG, bld.env.ENABLE_DISK)
- write_installer(64, bld.env.VERSION, bld.env.DEBUG, bld.env.ENABLE_DISK)
+ write_installer(32, bld.env.VERSION, bld.env.DEBUG, bld.env.ENABLE_DISK, bld.env.VARIANT)
+ write_installer(64, bld.env.VERSION, bld.env.DEBUG, bld.env.ENABLE_DISK, bld.env.VARIANT)
diff --git a/run/dcpomatic_player b/run/dcpomatic_player
index 0c565c251..23d2dc3d8 100755
--- a/run/dcpomatic_player
+++ b/run/dcpomatic_player
@@ -4,6 +4,15 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source $DIR/environment
binary=build/src/tools/dcpomatic2_player
+if [[ "$(uname -m)" == arm64 ]]; then
+ env=arm64/11.0
+else
+ env=x86_64/10.10
+fi
+
+echo $env
+export DYLD_LIBRARY_PATH=/Users/cah/osx-environment/$env/lib:/usr/local/lib
+
if [ "$1" == "--debug" ]; then
shift
gdb --args build/src/tools/dcpomatic2_player $*
diff --git a/run/dcpomatic_playlist b/run/dcpomatic_playlist
index a23c45766..134a1047e 100755
--- a/run/dcpomatic_playlist
+++ b/run/dcpomatic_playlist
@@ -4,6 +4,14 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source $DIR/environment
binary=build/src/tools/dcpomatic2_playlist
+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 $binary $*
diff --git a/run/dcpomatic_verifier b/run/dcpomatic_verifier
new file mode 100755
index 000000000..a87c3d4dc
--- /dev/null
+++ b/run/dcpomatic_verifier
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+source $DIR/environment
+binary=$build/src/tools/dcpomatic2_verifier
+
+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
+ if [[ "$(uname)" == Darwin ]]; then
+ /Applications/Xcode.app/Contents/Developer/usr/bin/lldb $binary $*
+ else
+ gdb --args $binary $*
+ fi
+else
+ $binary $* 2> >(grep -v Gtk-CRITICAL | grep -v Gtk-WARNING)
+fi
diff --git a/run/environment b/run/environment
index dac7bef55..822e0603a 100644
--- a/run/environment
+++ b/run/environment
@@ -1,6 +1,7 @@
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
build=$DIR/../build
-export LD_LIBRARY_PATH=$build/src/lib:$build/src/wx:/usr/local/lib64:/usr/local/lib:$LD_LIBRARY_PATH
+env=$DIR/../../..
+export LD_LIBRARY_PATH=$build/src/lib:$build/src/wx:$env/lib:/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
diff --git a/run/tests b/run/tests
index 5f356add3..4e38dbfce 100755
--- a/run/tests
+++ b/run/tests
@@ -5,6 +5,9 @@ set -e
PRIVATE_GIT="881c48805e352dfe150993814757ca974282be18"
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+source $DIR/environment
+
type=""
check=0
while [[ $# -gt 0 ]]; do
@@ -53,52 +56,55 @@ while [[ $# -gt 0 ]]; do
done
if [ "$(uname)" == "Linux" ]; then
- export LD_LIBRARY_PATH=build/src/lib:../../lib:/usr/local/lib:/usr/local/lib64:$LD_LIBRARY_PATH
- rm -f build/test/dcpomatic2_openssl
- # This must be our patched openssl or tests will fail
- 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
+ rm -f build/test/dcpomatic2_openssl
+ mkdir -p build/test
+ # This must be our patched openssl or tests will fail
+ 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
- resources=build/Resources
- rm -rf $resources
- mkdir -p $resources
- cp fonts/*.ttf $resources
- cp -r ../libdcp/tags $resources
- cp -r ../libdcp/xsd $resources
- cp ../libdcp/ratings $resources
- rm -f build/test/openssl
- ln -s ../../../openssl/apps/openssl build/test/openssl
- # SIP stops this being passed in from the caller's environment
- export DYLD_LIBRARY_PATH=$environment/x86_64/10.10/lib:$HOME/workspace/lib
- # We need to find ffcmp in here
- export PATH=$PATH:$HOME/workspace/bin
+ resources=build/Resources
+ rm -rf $resources
+ mkdir -p $resources
+ cp fonts/*.ttf $resources
+ cp -r ../libdcp/tags $resources
+ cp -r ../libdcp/xsd $resources
+ cp ../libdcp/ratings $resources
+ rm -f build/test/openssl
+ ln -s ../../../openssl/apps/openssl build/test/openssl
+ # We need to find ffcmp in here
+ export PATH=$PATH:$HOME/workspace/bin
+ if [ -d "$environment/$(uname -m)/11.0" ]; then
+ export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$environment/$(uname -m)/11.0/lib
+ else
+ export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$environment/$(uname -m)/10.10/lib
+ fi
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
+ 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 [ "$type" == "debug" ]; then
diff --git a/run/tests.bat b/run/tests.bat
index a9d921788..92030e61c 100644
--- a/run/tests.bat
+++ b/run/tests.bat
@@ -1,4 +1,4 @@
-set PATH=%PATH%;c:\users\ci\bin;c:\users\ci\workspace\dcpomatic\bin;c:\users\ci\workspace\dcpomatic\lib
+set PATH=%PATH%;c:\users\ci\bin_v2.17.x;c:\users\ci\workspace\dcpomatic\bin;c:\users\ci\workspace\dcpomatic\lib
set DCPOMATIC_TEST_PRIVATE=c:\users\ci\dcpomatic-test-private
xcopy ..\libdcp\tags build\tags\
copy ..\libdcp\ratings build\
diff --git a/src/lib/analytics.cc b/src/lib/analytics.cc
index 836051fe5..638f59f71 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 "variant.h"
#include <dcp/filesystem.h>
#include <dcp/raw_convert.h>
#include <dcp/warnings.h>
@@ -61,17 +62,17 @@ Analytics::successful_dcp_encode ()
boost::bind(
boost::ref(Message),
_("Congratulations!"),
- String::compose (_(
- "<h2>You have made %1 DCPs with DCP-o-matic!</h2>"
+ String::compose(_(
+ "<h2>You have made %1 DCPs with %2!</h2>"
"<img width=\"20%%\" src=\"memory:me.jpg\" align=\"center\">"
"<p>Hello. I'm Carl and I'm the "
- "developer of DCP-o-matic. I work on it in my spare time (with the help "
+ "developer of %3. I work on it in my spare time (with the help "
"of a fine volunteer team of testers and translators) and I release it "
"as free software."
- "<p>If you find DCP-o-matic useful, please consider a donation to the "
+ "<p>If you find %4 useful, please consider a donation to the "
"project. Financial support will help me to spend more "
- "time developing DCP-o-matic and making it better!"
+ "time developing %5 and making it better!"
"<p><ul>"
"<li><a href=\"https://dcpomatic.com/donate_amount?amount=40\">Go to Paypal to donate €40</a>"
@@ -79,7 +80,12 @@ Analytics::successful_dcp_encode ()
"<li><a href=\"https://dcpomatic.com/donate_amount?amount=10\">Go to Paypal to donate €10</a>"
"</ul>"
- "<p>Thank you!"), _successful_dcp_encodes
+ "<p>Thank you!"),
+ _successful_dcp_encodes,
+ variant::dcpomatic(),
+ variant::dcpomatic(),
+ variant::dcpomatic(),
+ variant::dcpomatic()
)
)
);
@@ -93,8 +99,8 @@ Analytics::write () const
xmlpp::Document doc;
auto root = doc.create_root_node ("Analytics");
- root->add_child("Version")->add_child_text(raw_convert<string>(_current_version));
- root->add_child("SuccessfulDCPEncodes")->add_child_text(raw_convert<string>(_successful_dcp_encodes));
+ cxml::add_text_child(root, "Version", raw_convert<string>(_current_version));
+ cxml::add_text_child(root, "SuccessfulDCPEncodes", raw_convert<string>(_successful_dcp_encodes));
try {
doc.write_to_file_formatted(write_path("analytics.xml").string());
diff --git a/src/lib/atmos_content.cc b/src/lib/atmos_content.cc
index ada304dad..22bd431a2 100644
--- a/src/lib/atmos_content.cc
+++ b/src/lib/atmos_content.cc
@@ -63,10 +63,10 @@ AtmosContent::from_xml (Content* parent, cxml::ConstNodePtr node)
void
-AtmosContent::as_xml (xmlpp::Node* node) const
+AtmosContent::as_xml(xmlpp::Element* element) const
{
- node->add_child("AtmosLength")->add_child_text(dcp::raw_convert<string>(_length));
- node->add_child("AtmosEditRate")->add_child_text(_edit_rate.as_string());
+ cxml::add_text_child(element, "AtmosLength", dcp::raw_convert<string>(_length));
+ cxml::add_text_child(element, "AtmosEditRate", _edit_rate.as_string());
}
diff --git a/src/lib/atmos_content.h b/src/lib/atmos_content.h
index afa3c75a2..ffa181497 100644
--- a/src/lib/atmos_content.h
+++ b/src/lib/atmos_content.h
@@ -42,7 +42,7 @@ public:
static std::shared_ptr<AtmosContent> from_xml (Content* parent, cxml::ConstNodePtr node);
- void as_xml (xmlpp::Node* node) const;
+ void as_xml(xmlpp::Element* element) const;
void set_length (Frame len);
diff --git a/src/lib/atmos_mxf_content.cc b/src/lib/atmos_mxf_content.cc
index 82c20e88f..1283961ee 100644
--- a/src/lib/atmos_mxf_content.cc
+++ b/src/lib/atmos_mxf_content.cc
@@ -98,11 +98,11 @@ AtmosMXFContent::summary () const
void
-AtmosMXFContent::as_xml (xmlpp::Node* node, bool with_paths) const
+AtmosMXFContent::as_xml(xmlpp::Element* element, bool with_paths) const
{
- node->add_child("Type")->add_child_text("AtmosMXF");
- Content::as_xml (node, with_paths);
- atmos->as_xml (node);
+ cxml::add_text_child(element, "Type", "AtmosMXF");
+ Content::as_xml(element, with_paths);
+ atmos->as_xml(element);
}
diff --git a/src/lib/atmos_mxf_content.h b/src/lib/atmos_mxf_content.h
index 57f041774..e6225a355 100644
--- a/src/lib/atmos_mxf_content.h
+++ b/src/lib/atmos_mxf_content.h
@@ -39,7 +39,7 @@ public:
void examine (std::shared_ptr<const Film> film, std::shared_ptr<Job> job) override;
std::string summary () const override;
- void as_xml (xmlpp::Node* node, bool with_path) const override;
+ void as_xml(xmlpp::Element* element, bool with_path) const override;
dcpomatic::DCPTime full_length (std::shared_ptr<const Film> film) const override;
dcpomatic::DCPTime approximate_length () const override;
diff --git a/src/lib/audio_analysis.cc b/src/lib/audio_analysis.cc
index b8c2e072d..ac990ee18 100644
--- a/src/lib/audio_analysis.cc
+++ b/src/lib/audio_analysis.cc
@@ -84,11 +84,8 @@ AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
}
for (auto i: f.node_children ("SamplePeak")) {
- _sample_peak.push_back (
- PeakTime(
- dcp::raw_convert<float>(i->content()), DCPTime(i->number_attribute<Frame>("Time"))
- )
- );
+ auto const time = number_attribute<Frame>(i, "Time", "time");
+ _sample_peak.push_back(PeakTime(dcp::raw_convert<float>(i->content()), DCPTime(time)));
}
for (auto i: f.node_children("TruePeak")) {
@@ -141,44 +138,44 @@ void
AudioAnalysis::write (boost::filesystem::path filename)
{
auto doc = make_shared<xmlpp::Document>();
- xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
+ auto root = doc->create_root_node("AudioAnalysis");
- root->add_child("Version")->add_child_text(raw_convert<string>(_current_state_version));
+ cxml::add_text_child(root, "Version", raw_convert<string>(_current_state_version));
for (auto& i: _data) {
- auto channel = root->add_child ("Channel");
+ auto channel = cxml::add_child(root, "Channel");
for (auto& j: i) {
- j.as_xml (channel->add_child ("Point"));
+ j.as_xml(cxml::add_child(channel, "Point"));
}
}
for (size_t i = 0; i < _sample_peak.size(); ++i) {
- auto n = root->add_child("SamplePeak");
+ auto n = cxml::add_child(root, "SamplePeak");
n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
- n->set_attribute ("Time", raw_convert<string> (_sample_peak[i].time.get()));
+ n->set_attribute("time", raw_convert<string> (_sample_peak[i].time.get()));
}
for (auto i: _true_peak) {
- root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
+ cxml::add_text_child(root, "TruePeak", raw_convert<string>(i));
}
if (_integrated_loudness) {
- root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
+ cxml::add_text_child(root, "IntegratedLoudness", raw_convert<string>(_integrated_loudness.get()));
}
if (_loudness_range) {
- root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
+ cxml::add_text_child(root, "LoudnessRange", raw_convert<string>(_loudness_range.get()));
}
if (_analysis_gain) {
- root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
+ cxml::add_text_child(root, "AnalysisGain", raw_convert<string>(_analysis_gain.get()));
}
- root->add_child("SamplesPerPoint")->add_child_text (raw_convert<string> (_samples_per_point));
- root->add_child("SampleRate")->add_child_text (raw_convert<string> (_sample_rate));
+ cxml::add_text_child(root, "SamplesPerPoint", raw_convert<string>(_samples_per_point));
+ cxml::add_text_child(root, "SampleRate", raw_convert<string>(_sample_rate));
if (_leqm) {
- root->add_child("Leqm")->add_child_text(raw_convert<string>(*_leqm));
+ cxml::add_text_child(root, "Leqm", raw_convert<string>(*_leqm));
}
doc->write_to_file_formatted (filename.string ());
diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc
index 16f2bd5f1..2569e1cf0 100644
--- a/src/lib/audio_content.cc
+++ b/src/lib/audio_content.cc
@@ -124,14 +124,14 @@ AudioContent::AudioContent (Content* parent, vector<shared_ptr<Content>> c)
void
-AudioContent::as_xml (xmlpp::Node* node) const
+AudioContent::as_xml(xmlpp::Element* element) const
{
boost::mutex::scoped_lock lm (_mutex);
- node->add_child("AudioGain")->add_child_text(raw_convert<string>(_gain));
- node->add_child("AudioDelay")->add_child_text(raw_convert<string>(_delay));
- node->add_child("AudioFadeIn")->add_child_text(raw_convert<string>(_fade_in.get()));
- node->add_child("AudioFadeOut")->add_child_text(raw_convert<string>(_fade_out.get()));
- node->add_child("AudioUseSameFadesAsVideo")->add_child_text(_use_same_fades_as_video ? "1" : "0");
+ cxml::add_text_child(element, "AudioGain", raw_convert<string>(_gain));
+ cxml::add_text_child(element, "AudioDelay", raw_convert<string>(_delay));
+ cxml::add_text_child(element, "AudioFadeIn", raw_convert<string>(_fade_in.get()));
+ cxml::add_text_child(element, "AudioFadeOut", raw_convert<string>(_fade_out.get()));
+ cxml::add_text_child(element, "AudioUseSameFadesAsVideo", _use_same_fades_as_video ? "1" : "0");
}
diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h
index 084871c8b..2a140b3e4 100644
--- a/src/lib/audio_content.h
+++ b/src/lib/audio_content.h
@@ -56,7 +56,7 @@ public:
AudioContent (Content* parent, std::vector<std::shared_ptr<Content>>);
AudioContent (Content* parent, cxml::ConstNodePtr);
- void as_xml (xmlpp::Node *) const;
+ void as_xml(xmlpp::Element*) const;
std::string technical_summary () const;
void take_settings_from (std::shared_ptr<const AudioContent> c);
diff --git a/src/lib/audio_filter_graph.cc b/src/lib/audio_filter_graph.cc
index 4e3052d57..55a26cdb2 100644
--- a/src/lib/audio_filter_graph.cc
+++ b/src/lib/audio_filter_graph.cc
@@ -48,11 +48,7 @@ AudioFilterGraph::AudioFilterGraph (int sample_rate, int channels)
/* FFmpeg doesn't know any channel layouts for any counts between 8 and 16,
so we need to tell it we're using 16 channels if we are using more than 8.
*/
- if (_channels > 8) {
- _channel_layout = av_get_default_channel_layout (16);
- } else {
- _channel_layout = av_get_default_channel_layout (_channels);
- }
+ av_channel_layout_default(&_channel_layout, _channels > 8 ? 16 : _channels);
_in_frame = av_frame_alloc ();
if (_in_frame == nullptr) {
@@ -69,7 +65,7 @@ string
AudioFilterGraph::src_parameters () const
{
char layout[64];
- av_get_channel_layout_string (layout, sizeof(layout), 0, _channel_layout);
+ av_channel_layout_describe(&_channel_layout, layout, sizeof(layout));
char buffer[256];
snprintf (
@@ -88,8 +84,9 @@ AudioFilterGraph::set_parameters (AVFilterContext* context) const
int r = av_opt_set_int_list (context, "sample_fmts", sample_fmts, AV_SAMPLE_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
DCPOMATIC_ASSERT (r >= 0);
- int64_t channel_layouts[] = { _channel_layout, -1 };
- r = av_opt_set_int_list (context, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN);
+ char ch_layout[64];
+ av_channel_layout_describe(&_channel_layout, ch_layout, sizeof(ch_layout));
+ r = av_opt_set(context, "ch_layouts", ch_layout, AV_OPT_SEARCH_CHILDREN);
DCPOMATIC_ASSERT (r >= 0);
int sample_rates[] = { _sample_rate, -1 };
@@ -114,7 +111,7 @@ void
AudioFilterGraph::process (shared_ptr<AudioBuffers> buffers)
{
DCPOMATIC_ASSERT (buffers->frames() > 0);
- int const process_channels = av_get_channel_layout_nb_channels (_channel_layout);
+ int const process_channels = _channel_layout.nb_channels;
DCPOMATIC_ASSERT (process_channels >= buffers->channels());
if (buffers->channels() < process_channels) {
@@ -144,8 +141,7 @@ AudioFilterGraph::process (shared_ptr<AudioBuffers> buffers)
_in_frame->nb_samples = buffers->frames ();
_in_frame->format = AV_SAMPLE_FMT_FLTP;
_in_frame->sample_rate = _sample_rate;
- _in_frame->channel_layout = _channel_layout;
- _in_frame->channels = process_channels;
+ _in_frame->ch_layout = _channel_layout;
int r = av_buffersrc_write_frame (_buffer_src_context, _in_frame);
diff --git a/src/lib/audio_filter_graph.h b/src/lib/audio_filter_graph.h
index e5c55fa27..6d09f15be 100644
--- a/src/lib/audio_filter_graph.h
+++ b/src/lib/audio_filter_graph.h
@@ -26,6 +26,7 @@
#include "filter_graph.h"
extern "C" {
#include <libavfilter/buffersink.h>
+#include <libavutil/channel_layout.h>
}
class AudioBuffers;
@@ -47,7 +48,7 @@ protected:
private:
int _sample_rate;
int _channels;
- int64_t _channel_layout;
+ AVChannelLayout _channel_layout;
AVFrame* _in_frame;
};
diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc
index b8aa6249f..37929001d 100644
--- a/src/lib/audio_mapping.cc
+++ b/src/lib/audio_mapping.cc
@@ -24,6 +24,7 @@
#include "constants.h"
#include "dcpomatic_assert.h"
#include "digester.h"
+#include "util.h"
#include <dcp/raw_convert.h>
#include <dcp/warnings.h>
#include <libcxml/cxml.h>
@@ -169,8 +170,8 @@ AudioMapping::AudioMapping (cxml::ConstNodePtr node, int state_version)
);
} else {
set (
- i->number_attribute<int>("Input"),
- i->number_attribute<int>("Output"),
+ number_attribute<int>(i, "Input", "input"),
+ number_attribute<int>(i, "Output", "output"),
raw_convert<float>(i->content())
);
}
@@ -219,19 +220,19 @@ AudioMapping::get (int input_channel, int output_channel) const
void
-AudioMapping::as_xml (xmlpp::Node* node) const
+AudioMapping::as_xml(xmlpp::Element* element) const
{
auto const input = input_channels();
auto const output = output_channels();
- node->add_child("InputChannels")->add_child_text(raw_convert<string>(input));
- node->add_child("OutputChannels")->add_child_text(raw_convert<string>(output));
+ cxml::add_text_child(element, "InputChannels", raw_convert<string>(input));
+ cxml::add_text_child(element, "OutputChannels", raw_convert<string>(output));
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<string> (c));
- t->set_attribute ("Output", raw_convert<string> (d));
+ auto t = cxml::add_child(element, "Gain");
+ t->set_attribute("input", raw_convert<string>(c));
+ t->set_attribute("output", raw_convert<string>(d));
t->add_child_text (raw_convert<string> (get (c, d)));
}
}
diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h
index fe9a79789..68487d908 100644
--- a/src/lib/audio_mapping.h
+++ b/src/lib/audio_mapping.h
@@ -52,7 +52,7 @@ public:
/* Default copy constructor is fine */
- void as_xml (xmlpp::Node *) const;
+ void as_xml(xmlpp::Element*) const;
void make_zero ();
void make_default (AudioProcessor const * processor, boost::optional<boost::filesystem::path> filename = boost::optional<boost::filesystem::path>());
diff --git a/src/lib/audio_point.cc b/src/lib/audio_point.cc
index 1f32d25fc..036904520 100644
--- a/src/lib/audio_point.cc
+++ b/src/lib/audio_point.cc
@@ -72,6 +72,6 @@ AudioPoint::operator= (AudioPoint const & other)
void
AudioPoint::as_xml (xmlpp::Element* parent) const
{
- parent->add_child("Peak")->add_child_text(raw_convert<string>(_data[PEAK]));
- parent->add_child("RMS")->add_child_text(raw_convert<string>(_data[RMS]));
+ cxml::add_text_child(parent, "Peak", raw_convert<string>(_data[PEAK]));
+ cxml::add_text_child(parent, "RMS", raw_convert<string>(_data[RMS]));
}
diff --git a/src/lib/butler.cc b/src/lib/butler.cc
index b2fbc6c60..dd9874587 100644
--- a/src/lib/butler.cc
+++ b/src/lib/butler.cc
@@ -138,7 +138,7 @@ Butler::should_run () const
{
if (_video.size() >= MAXIMUM_VIDEO_READAHEAD * 10) {
/* This is way too big */
- optional<DCPTime> pos = _audio.peek();
+ auto pos = _audio.peek();
if (pos) {
throw ProgrammingError
(__FILE__, __LINE__, String::compose ("Butler video buffers reached %1 frames (audio is %2 at %3)", _video.size(), _audio.size(), pos->get()));
diff --git a/src/lib/check_content_job.cc b/src/lib/check_content_job.cc
index f37890abf..2028b01ac 100644
--- a/src/lib/check_content_job.cc
+++ b/src/lib/check_content_job.cc
@@ -70,7 +70,7 @@ CheckContentJob::run ()
std::vector<shared_ptr<Content>> changed;
std::copy_if (content.begin(), content.end(), std::back_inserter(changed), [](shared_ptr<Content> c) { return c->changed(); });
- if (_film->last_written_by_earlier_than(2, 16, 15)) {
+ if (_film->last_written_by_earlier_than(2, 17, 17)) {
for (auto c: content) {
if (auto stf = dynamic_pointer_cast<StringTextFileContent>(c)) {
stf->check_font_ids();
diff --git a/src/lib/cinema.cc b/src/lib/cinema.cc
deleted file mode 100644
index 3b4b9d7b6..000000000
--- a/src/lib/cinema.cc
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
-
- 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 <http://www.gnu.org/licenses/>.
-
-*/
-
-
-#include "cinema.h"
-#include "screen.h"
-#include "dcpomatic_assert.h"
-#include <libcxml/cxml.h>
-#include <dcp/raw_convert.h>
-#include <libxml++/libxml++.h>
-
-
-using std::make_shared;
-using std::shared_ptr;
-using std::string;
-using dcp::raw_convert;
-using dcpomatic::Screen;
-
-
-Cinema::Cinema (cxml::ConstNodePtr node)
- : name (node->string_child ("Name"))
- , notes (node->optional_string_child("Notes").get_value_or(""))
-{
- for (auto i: node->node_children("Email")) {
- emails.push_back (i->content ());
- }
-
- if (node->optional_number_child<int>("UTCOffset")) {
- _utc_offset_hour = node->number_child<int>("UTCOffset");
- } else {
- _utc_offset_hour = node->optional_number_child<int>("UTCOffsetHour").get_value_or (0);
- }
-
- _utc_offset_minute = node->optional_number_child<int>("UTCOffsetMinute").get_value_or (0);
-}
-
-/* This is necessary so that we can use shared_from_this in add_screen (which cannot be done from
- a constructor)
-*/
-void
-Cinema::read_screens (cxml::ConstNodePtr node)
-{
- for (auto i: node->node_children("Screen")) {
- add_screen (make_shared<Screen>(i));
- }
-}
-
-void
-Cinema::as_xml (xmlpp::Element* parent) const
-{
- parent->add_child("Name")->add_child_text (name);
-
- for (auto i: emails) {
- parent->add_child("Email")->add_child_text (i);
- }
-
- parent->add_child("Notes")->add_child_text (notes);
-
- parent->add_child("UTCOffsetHour")->add_child_text (raw_convert<string> (_utc_offset_hour));
- parent->add_child("UTCOffsetMinute")->add_child_text (raw_convert<string> (_utc_offset_minute));
-
- for (auto i: _screens) {
- i->as_xml (parent->add_child ("Screen"));
- }
-}
-
-void
-Cinema::add_screen (shared_ptr<Screen> s)
-{
- s->cinema = shared_from_this ();
- _screens.push_back (s);
-}
-
-void
-Cinema::remove_screen (shared_ptr<Screen> s)
-{
- auto iter = std::find(_screens.begin(), _screens.end(), s);
- if (iter != _screens.end()) {
- _screens.erase(iter);
- }
-}
-
-void
-Cinema::set_utc_offset_hour (int h)
-{
- DCPOMATIC_ASSERT (h >= -11 && h <= 12);
- _utc_offset_hour = h;
-}
-
-void
-Cinema::set_utc_offset_minute (int m)
-{
- DCPOMATIC_ASSERT (m >= 0 && m <= 59);
- _utc_offset_minute = m;
-}
diff --git a/src/lib/cinema.h b/src/lib/cinema.h
index 6c202a7bf..44f232f91 100644
--- a/src/lib/cinema.h
+++ b/src/lib/cinema.h
@@ -18,76 +18,35 @@
*/
+
/** @file src/lib/cinema.h
* @brief Cinema class.
*/
-#include <libcxml/cxml.h>
+#include <dcp/utc_offset.h>
#include <memory>
+#include <string>
+#include <vector>
-namespace xmlpp {
- class Element;
-}
-
-namespace dcpomatic {
- class Screen;
-}
-
/** @class Cinema
* @brief A description of a Cinema for KDM generation.
*
- * This is a cinema name, some metadata and a list of
- * Screen objects.
+ * This is a cinema name and some metadata.
*/
-class Cinema : public std::enable_shared_from_this<Cinema>
+class Cinema
{
public:
- Cinema(std::string const & name_, std::vector<std::string> const & e, std::string notes_, int utc_offset_hour, int utc_offset_minute)
+ Cinema(std::string const & name_, std::vector<std::string> const & e, std::string notes_, dcp::UTCOffset utc_offset_)
: name (name_)
, emails (e)
, notes (notes_)
- , _utc_offset_hour (utc_offset_hour)
- , _utc_offset_minute (utc_offset_minute)
+ , utc_offset(std::move(utc_offset_))
{}
- explicit Cinema (cxml::ConstNodePtr);
-
- void read_screens (cxml::ConstNodePtr);
-
- void as_xml (xmlpp::Element *) const;
-
- void add_screen (std::shared_ptr<dcpomatic::Screen>);
- void remove_screen (std::shared_ptr<dcpomatic::Screen>);
-
- void set_utc_offset_hour (int h);
- void set_utc_offset_minute (int m);
-
std::string name;
std::vector<std::string> emails;
std::string notes;
-
- int utc_offset_hour () const {
- return _utc_offset_hour;
- }
-
- int utc_offset_minute () const {
- return _utc_offset_minute;
- }
-
- std::vector<std::shared_ptr<dcpomatic::Screen>> screens() const {
- return _screens;
- }
-
-private:
- std::vector<std::shared_ptr<dcpomatic::Screen>> _screens;
- /** Offset such that the equivalent time in UTC can be determined
- by subtracting the offset from the local time.
- */
- int _utc_offset_hour;
- /** Additional minutes to add to _utc_offset_hour if _utc_offset_hour is
- positive, or to subtract if _utc_offset_hour is negative.
- */
- int _utc_offset_minute;
+ dcp::UTCOffset utc_offset;
};
diff --git a/src/lib/cinema_list.cc b/src/lib/cinema_list.cc
new file mode 100644
index 000000000..41b9dbab3
--- /dev/null
+++ b/src/lib/cinema_list.cc
@@ -0,0 +1,466 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "cinema.h"
+#include "cinema_list.h"
+#include "config.h"
+#include "dcpomatic_assert.h"
+#include "exceptions.h"
+#include "screen.h"
+#include "sqlite_statement.h"
+#include "sqlite_transaction.h"
+#include "util.h"
+#include <dcp/certificate.h>
+#include <sqlite3.h>
+#include <boost/algorithm/string.hpp>
+#include <iostream>
+#include <numeric>
+
+
+using std::pair;
+using std::make_pair;
+using std::string;
+using std::vector;
+using boost::optional;
+
+
+CinemaList::CinemaList()
+ : _cinemas("cinemas")
+ , _screens("screens")
+ , _trusted_devices("trusted_devices")
+{
+ setup_tables();
+ setup(Config::instance()->cinemas_file());
+}
+
+
+CinemaList::CinemaList(boost::filesystem::path db_file)
+ : _cinemas("cinemas")
+ , _screens("screens")
+ , _trusted_devices("trusted_devices")
+{
+ setup_tables();
+ setup(db_file);
+}
+
+
+void
+CinemaList::setup_tables()
+{
+ _cinemas.add_column("name", "TEXT");
+ _cinemas.add_column("emails", "TEXT");
+ _cinemas.add_column("notes", "TEXT");
+ _cinemas.add_column("utc_offset_hour", "INTEGER");
+ _cinemas.add_column("utc_offset_minute", "INTEGER");
+
+ _screens.add_column("cinema", "INTEGER");
+ _screens.add_column("name", "TEXT");
+ _screens.add_column("notes", "TEXT");
+ _screens.add_column("recipient", "TEXT");
+ _screens.add_column("recipient_file", "TEXT");
+
+ _trusted_devices.add_column("screen", "INTEGER");
+ _trusted_devices.add_column("certificate_or_thumbprint", "TEXT");
+}
+
+
+void
+CinemaList::read_legacy_file(boost::filesystem::path xml_file)
+{
+ cxml::Document doc("Cinemas");
+ doc.read_file(xml_file);
+ read_legacy_document(doc);
+}
+
+
+void
+CinemaList::read_legacy_string(std::string const& xml)
+{
+ cxml::Document doc("Cinemas");
+ doc.read_string(xml);
+ read_legacy_document(doc);
+}
+
+
+void
+CinemaList::read_legacy_document(cxml::Document const& doc)
+{
+ for (auto cinema_node: doc.node_children("Cinema")) {
+ vector<string> emails;
+ for (auto email_node: cinema_node->node_children("Email")) {
+ emails.push_back(email_node->content());
+ }
+
+ int hour = 0;
+ if (cinema_node->optional_number_child<int>("UTCOffset")) {
+ hour = cinema_node->number_child<int>("UTCOffset");
+ } else {
+ hour = cinema_node->optional_number_child<int>("UTCOffsetHour").get_value_or(0);
+ }
+
+ int minute = cinema_node->optional_number_child<int>("UTCOffsetMinute").get_value_or(0);
+
+ Cinema cinema(
+ cinema_node->string_child("Name"),
+ emails,
+ cinema_node->string_child("Notes"),
+ dcp::UTCOffset(hour, minute)
+ );
+
+ auto cinema_id = add_cinema(cinema);
+
+ for (auto screen_node: cinema_node->node_children("Screen")) {
+ optional<dcp::Certificate> recipient;
+ if (auto recipient_string = screen_node->optional_string_child("Recipient")) {
+ recipient = dcp::Certificate(*recipient_string);
+ }
+ vector<TrustedDevice> trusted_devices;
+ for (auto trusted_device_node: screen_node->node_children("TrustedDevice")) {
+ trusted_devices.push_back(TrustedDevice(trusted_device_node->content()));
+ }
+ dcpomatic::Screen screen(
+ screen_node->string_child("Name"),
+ screen_node->string_child("Notes"),
+ recipient,
+ screen_node->optional_string_child("RecipientFile"),
+ trusted_devices
+ );
+ add_screen(cinema_id, screen);
+ }
+ }
+}
+
+
+void
+CinemaList::clear()
+{
+ for (auto table: { "cinemas", "screens", "trusted_devices" }) {
+ SQLiteStatement sql(_db, String::compose("DELETE FROM %1", table));
+ sql.execute();
+ }
+}
+
+
+void
+CinemaList::setup(boost::filesystem::path db_file)
+{
+#ifdef DCPOMATIC_WINDOWS
+ auto rc = sqlite3_open16(db_file.c_str(), &_db);
+#else
+ auto rc = sqlite3_open(db_file.c_str(), &_db);
+#endif
+ if (rc != SQLITE_OK) {
+ throw FileError("Could not open SQLite database", db_file);
+ }
+
+ sqlite3_busy_timeout(_db, 500);
+
+ SQLiteStatement cinemas(_db, _cinemas.create());
+ cinemas.execute();
+
+ SQLiteStatement screens(_db, _screens.create());
+ screens.execute();
+
+ SQLiteStatement devices(_db, _trusted_devices.create());
+ devices.execute();
+}
+
+
+CinemaList::CinemaList(CinemaList&& other)
+ : _db(other._db)
+ , _cinemas(std::move(other._cinemas))
+ , _screens(std::move(other._screens))
+ , _trusted_devices(std::move(other._trusted_devices))
+{
+ other._db = nullptr;
+}
+
+
+CinemaList&
+CinemaList::operator=(CinemaList&& other)
+{
+ if (this != &other) {
+ _db = other._db;
+ other._db = nullptr;
+ }
+ return *this;
+}
+
+
+CinemaID
+CinemaList::add_cinema(Cinema const& cinema)
+{
+ SQLiteStatement statement(_db, _cinemas.insert());
+
+ statement.bind_text(1, cinema.name);
+ statement.bind_text(2, join_strings(cinema.emails));
+ statement.bind_text(3, cinema.notes);
+ statement.bind_int64(4, cinema.utc_offset.hour());
+ statement.bind_int64(5, cinema.utc_offset.minute());
+
+ statement.execute();
+
+ return sqlite3_last_insert_rowid(_db);
+}
+
+
+void
+CinemaList::update_cinema(CinemaID id, Cinema const& cinema)
+{
+ SQLiteStatement statement(_db, _cinemas.update("WHERE id=?"));
+
+ statement.bind_text(1, cinema.name);
+ statement.bind_text(2, join_strings(cinema.emails));
+ statement.bind_text(3, cinema.notes);
+ statement.bind_int64(4, cinema.utc_offset.hour());
+ statement.bind_int64(5, cinema.utc_offset.minute());
+ statement.bind_int64(6, id.get());
+
+ statement.execute();
+}
+
+
+void
+CinemaList::remove_cinema(CinemaID id)
+{
+ SQLiteStatement statement(_db, "DELETE FROM cinemas WHERE ID=?");
+ statement.bind_int64(1, id.get());
+ statement.execute();
+}
+
+
+CinemaList::~CinemaList()
+{
+ if (_db) {
+ sqlite3_close(_db);
+ }
+}
+
+
+static
+vector<pair<CinemaID, Cinema>>
+cinemas_from_result(SQLiteStatement& statement)
+{
+ vector<pair<CinemaID, Cinema>> output;
+
+ statement.execute([&output](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 6);
+ CinemaID const id = statement.column_int64(0);
+ auto const name = statement.column_text(1);
+ auto const join_strings = statement.column_text(2);
+ vector<string> emails;
+ boost::algorithm::split(emails, join_strings, boost::is_any_of(" "));
+ auto const notes = statement.column_text(3);
+ auto const utc_offset_hour = static_cast<int>(statement.column_int64(4));
+ auto const utc_offset_minute = static_cast<int>(statement.column_int64(5));
+ output.push_back(make_pair(id, Cinema(name, { emails }, notes, dcp::UTCOffset{utc_offset_hour, utc_offset_minute})));
+ });
+
+ return output;
+}
+
+
+vector<pair<CinemaID, Cinema>>
+CinemaList::cinemas() const
+{
+ SQLiteStatement statement(_db, _cinemas.select("ORDER BY name ASC"));
+ return cinemas_from_result(statement);
+}
+
+
+optional<Cinema>
+CinemaList::cinema(CinemaID id) const
+{
+ SQLiteStatement statement(_db, _cinemas.select("WHERE id=?"));
+ statement.bind_int64(1, id.get());
+ auto result = cinemas_from_result(statement);
+ if (result.empty()) {
+ return {};
+ }
+ return result[0].second;
+}
+
+ScreenID
+CinemaList::add_screen(CinemaID cinema_id, dcpomatic::Screen const& screen)
+{
+ SQLiteTransaction transaction(_db);
+
+ SQLiteStatement add_screen(_db, _screens.insert());
+
+ add_screen.bind_int64(1, cinema_id.get());
+ add_screen.bind_text(2, screen.name);
+ add_screen.bind_text(3, screen.notes);
+ add_screen.bind_text(4, screen.recipient->certificate(true));
+ add_screen.bind_text(5, screen.recipient_file.get_value_or(""));
+
+ add_screen.execute();
+
+ auto const screen_id = sqlite3_last_insert_rowid(_db);
+
+ for (auto device: screen.trusted_devices) {
+ SQLiteStatement add_device(_db, _trusted_devices.insert());
+ add_device.bind_int64(1, screen_id);
+ add_device.bind_text(2, device.as_string());
+ }
+
+ transaction.commit();
+
+ return screen_id;
+}
+
+
+dcpomatic::Screen
+CinemaList::screen_from_result(SQLiteStatement& statement, ScreenID screen_id) const
+{
+ auto certificate_string = statement.column_text(4);
+ optional<dcp::Certificate> certificate = certificate_string.empty() ? optional<dcp::Certificate>() : dcp::Certificate(certificate_string);
+ auto recipient_file_string = statement.column_text(5);
+ optional<string> recipient_file = recipient_file_string.empty() ? optional<string>() : recipient_file_string;
+
+ SQLiteStatement trusted_devices_statement(_db, _trusted_devices.select("WHERE screen=?"));
+ trusted_devices_statement.bind_int64(1, screen_id.get());
+ vector<TrustedDevice> trusted_devices;
+ trusted_devices_statement.execute([&trusted_devices](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 1);
+ auto description = statement.column_text(1);
+ if (boost::algorithm::starts_with(description, "-----BEGIN CERTIFICATE")) {
+ trusted_devices.push_back(TrustedDevice(dcp::Certificate(description)));
+ } else {
+ trusted_devices.push_back(TrustedDevice(description));
+ }
+ });
+
+ return dcpomatic::Screen(statement.column_text(2), statement.column_text(3), certificate, recipient_file, trusted_devices);
+}
+
+
+optional<dcpomatic::Screen>
+CinemaList::screen(ScreenID screen_id) const
+{
+ SQLiteStatement statement(_db, _screens.select("WHERE id=?"));
+ statement.bind_int64(1, screen_id.get());
+
+ optional<dcpomatic::Screen> output;
+
+ statement.execute([this, &output, screen_id](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 6);
+ output = screen_from_result(statement, screen_id);
+ });
+
+ return output;
+}
+
+
+
+vector<pair<ScreenID, dcpomatic::Screen>>
+CinemaList::screens_from_result(SQLiteStatement& statement) const
+{
+ vector<pair<ScreenID, dcpomatic::Screen>> output;
+
+ statement.execute([this, &output](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 6);
+ ScreenID const screen_id = statement.column_int64(0);
+ output.push_back({screen_id, screen_from_result(statement, screen_id)});
+ });
+
+ return output;
+}
+
+
+vector<pair<ScreenID, dcpomatic::Screen>>
+CinemaList::screens(CinemaID cinema_id) const
+{
+ SQLiteStatement statement(_db, _screens.select("WHERE cinema=?"));
+ statement.bind_int64(1, cinema_id.get());
+ return screens_from_result(statement);
+}
+
+
+vector<pair<ScreenID, dcpomatic::Screen>>
+CinemaList::screens_by_cinema_and_name(CinemaID id, std::string const& name) const
+{
+ SQLiteStatement statement(_db, _screens.select("WHERE cinema=? AND name=?"));
+ statement.bind_int64(1, id.get());
+ statement.bind_text(2, name);
+ return screens_from_result(statement);
+}
+
+
+optional<std::pair<CinemaID, Cinema>>
+CinemaList::cinema_by_name_or_email(std::string const& text) const
+{
+ SQLiteStatement statement(_db, _cinemas.select("WHERE name LIKE ? OR EMAILS LIKE ?"));
+ auto const wildcard = string("%") + text + "%";
+ statement.bind_text(1, wildcard);
+ statement.bind_text(2, wildcard);
+
+ auto all = cinemas_from_result(statement);
+ if (all.empty()) {
+ return {};
+ }
+ return all[0];
+}
+
+
+void
+CinemaList::update_screen(ScreenID id, dcpomatic::Screen const& screen)
+{
+ SQLiteStatement statement(_db, _screens.update("WHERE id=?"));
+
+ statement.bind_text(1, screen.name);
+ statement.bind_text(2, screen.notes);
+ statement.bind_text(3, screen.recipient->certificate(true));
+ statement.bind_text(4, screen.recipient_file.get_value_or(""));
+ statement.bind_int64(5, id.get());
+
+ statement.execute();
+}
+
+
+void
+CinemaList::remove_screen(ScreenID id)
+{
+ SQLiteStatement statement(_db, "DELETE FROM screens WHERE ID=?");
+ statement.bind_int64(1, id.get());
+ statement.execute();
+}
+
+
+optional<dcp::UTCOffset>
+CinemaList::unique_utc_offset(std::set<CinemaID> const& cinemas_to_check)
+{
+ optional<dcp::UTCOffset> offset;
+
+ for (auto const& cinema: cinemas()) {
+ if (cinemas_to_check.find(cinema.first) == cinemas_to_check.end()) {
+ continue;
+ }
+
+ if (!offset) {
+ offset = cinema.second.utc_offset;
+ } else if (cinema.second.utc_offset != *offset) {
+ return dcp::UTCOffset();
+ }
+ }
+
+ return offset;
+}
+
diff --git a/src/lib/cinema_list.h b/src/lib/cinema_list.h
new file mode 100644
index 000000000..c91f29476
--- /dev/null
+++ b/src/lib/cinema_list.h
@@ -0,0 +1,126 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_CINEMA_LIST_H
+#define DCPOMATIC_CINEMA_LIST_H
+
+
+#include "id.h"
+#include "sqlite_table.h"
+#include <libcxml/cxml.h>
+#include <dcp/utc_offset.h>
+#include <boost/filesystem.hpp>
+#include <boost/optional.hpp>
+#include <sqlite3.h>
+#include <set>
+
+
+class Cinema;
+namespace dcpomatic {
+ class Screen;
+}
+class SQLiteStatement;
+
+
+class CinemaID : public ID
+{
+public:
+ CinemaID(sqlite3_int64 id)
+ : ID(id) {}
+
+ bool operator<(CinemaID const& other) const {
+ return get() < other.get();
+ }
+};
+
+
+class ScreenID : public ID
+{
+public:
+ ScreenID(sqlite3_int64 id)
+ : ID(id) {}
+
+ bool operator==(ScreenID const& other) const {
+ return get() == other.get();
+ }
+
+ bool operator!=(ScreenID const& other) const {
+ return get() != other.get();
+ }
+
+ bool operator<(ScreenID const& other) const {
+ return get() < other.get();
+ }
+};
+
+
+class CinemaList
+{
+public:
+ CinemaList();
+ CinemaList(boost::filesystem::path db_file);
+ ~CinemaList();
+
+ CinemaList(CinemaList const&) = delete;
+ CinemaList& operator=(CinemaList const&) = delete;
+
+ CinemaList(CinemaList&& other);
+ CinemaList& operator=(CinemaList&& other);
+
+ void read_legacy_file(boost::filesystem::path xml_file);
+ void read_legacy_string(std::string const& xml);
+
+ void clear();
+
+ CinemaID add_cinema(Cinema const& cinema);
+ void update_cinema(CinemaID id, Cinema const& cinema);
+ void remove_cinema(CinemaID id);
+ std::vector<std::pair<CinemaID, Cinema>> cinemas() const;
+ boost::optional<Cinema> cinema(CinemaID id) const;
+ boost::optional<std::pair<CinemaID, Cinema>> cinema_by_partial_name(std::string const& text) const;
+ boost::optional<std::pair<CinemaID, Cinema>> cinema_by_name_or_email(std::string const& text) const;
+
+ ScreenID add_screen(CinemaID cinema_id, dcpomatic::Screen const& screen);
+ void update_screen(ScreenID id, dcpomatic::Screen const& screen);
+ void remove_screen(ScreenID id);
+ boost::optional<dcpomatic::Screen> screen(ScreenID screen_id) const;
+ std::vector<std::pair<ScreenID, dcpomatic::Screen>> screens(CinemaID cinema_id) const;
+ std::vector<std::pair<ScreenID, dcpomatic::Screen>> screens_by_cinema_and_name(CinemaID id, std::string const& name) const;
+
+ boost::optional<dcp::UTCOffset> unique_utc_offset(std::set<CinemaID> const& cinemas);
+
+private:
+ dcpomatic::Screen screen_from_result(SQLiteStatement& statement, ScreenID screen_id) const;
+ std::vector<std::pair<ScreenID, dcpomatic::Screen>> screens_from_result(SQLiteStatement& statement) const;
+ void setup_tables();
+ void setup(boost::filesystem::path db_file);
+ void read_legacy_document(cxml::Document const& doc);
+
+ sqlite3* _db = nullptr;
+ SQLiteTable _cinemas;
+ SQLiteTable _screens;
+ SQLiteTable _trusted_devices;
+};
+
+
+
+#endif
+
diff --git a/src/lib/colour_conversion.cc b/src/lib/colour_conversion.cc
index bd1b47cb1..f1e625812 100644
--- a/src/lib/colour_conversion.cc
+++ b/src/lib/colour_conversion.cc
@@ -151,41 +151,40 @@ ColourConversion::from_xml (cxml::NodePtr node, int version)
}
void
-ColourConversion::as_xml (xmlpp::Node* node) const
+ColourConversion::as_xml(xmlpp::Element* element) const
{
- auto in_node = node->add_child ("InputTransferFunction");
+ auto in_node = cxml::add_child(element, "InputTransferFunction");
if (dynamic_pointer_cast<const dcp::GammaTransferFunction> (_in)) {
auto tf = dynamic_pointer_cast<const dcp::GammaTransferFunction> (_in);
- in_node->add_child("Type")->add_child_text ("Gamma");
- in_node->add_child("Gamma")->add_child_text (raw_convert<string> (tf->gamma ()));
+ cxml::add_text_child(in_node, "Type", "Gamma");
+ cxml::add_text_child(in_node, "Gamma", raw_convert<string>(tf->gamma()));
} else if (dynamic_pointer_cast<const dcp::ModifiedGammaTransferFunction> (_in)) {
auto tf = dynamic_pointer_cast<const dcp::ModifiedGammaTransferFunction> (_in);
- in_node->add_child("Type")->add_child_text ("ModifiedGamma");
- in_node->add_child("Power")->add_child_text (raw_convert<string> (tf->power ()));
- in_node->add_child("Threshold")->add_child_text (raw_convert<string> (tf->threshold ()));
- in_node->add_child("A")->add_child_text (raw_convert<string> (tf->A ()));
- in_node->add_child("B")->add_child_text (raw_convert<string> (tf->B ()));
- } else if (dynamic_pointer_cast<const dcp::SGamut3TransferFunction> (_in)) {
- in_node->add_child("Type")->add_child_text ("SGamut3");
+ cxml::add_text_child(in_node, "Type", "ModifiedGamma");
+ cxml::add_text_child(in_node, "Power", raw_convert<string>(tf->power ()));
+ cxml::add_text_child(in_node, "Threshold", raw_convert<string>(tf->threshold ()));
+ cxml::add_text_child(in_node, "A", raw_convert<string>(tf->A()));
+ cxml::add_text_child(in_node, "B", raw_convert<string>(tf->B()));
+ } else if (dynamic_pointer_cast<const dcp::SGamut3TransferFunction>(_in)) {
+ cxml::add_text_child(in_node, "Type", "SGamut3");
}
- node->add_child("YUVToRGB")->add_child_text (raw_convert<string> (static_cast<int> (_yuv_to_rgb)));
- node->add_child("RedX")->add_child_text (raw_convert<string> (_red.x));
- node->add_child("RedY")->add_child_text (raw_convert<string> (_red.y));
- node->add_child("GreenX")->add_child_text (raw_convert<string> (_green.x));
- node->add_child("GreenY")->add_child_text (raw_convert<string> (_green.y));
- node->add_child("BlueX")->add_child_text (raw_convert<string> (_blue.x));
- node->add_child("BlueY")->add_child_text (raw_convert<string> (_blue.y));
- node->add_child("WhiteX")->add_child_text (raw_convert<string> (_white.x));
- node->add_child("WhiteY")->add_child_text (raw_convert<string> (_white.y));
+ cxml::add_text_child(element, "YUVToRGB", raw_convert<string>(static_cast<int>(_yuv_to_rgb)));
+ cxml::add_text_child(element, "RedX", raw_convert<string>(_red.x));
+ cxml::add_text_child(element, "RedY", raw_convert<string>(_red.y));
+ cxml::add_text_child(element, "GreenX", raw_convert<string>(_green.x));
+ cxml::add_text_child(element, "GreenY", raw_convert<string>(_green.y));
+ cxml::add_text_child(element, "BlueX", raw_convert<string>(_blue.x));
+ cxml::add_text_child(element, "BlueY", raw_convert<string>(_blue.y));
+ cxml::add_text_child(element, "WhiteX", raw_convert<string>(_white.x));
+ cxml::add_text_child(element, "WhiteY", raw_convert<string>(_white.y));
if (_adjusted_white) {
- node->add_child("AdjustedWhiteX")->add_child_text (raw_convert<string> (_adjusted_white.get().x));
- node->add_child("AdjustedWhiteY")->add_child_text (raw_convert<string> (_adjusted_white.get().y));
+ cxml::add_text_child(element, "AdjustedWhiteX", raw_convert<string>(_adjusted_white.get().x));
+ cxml::add_text_child(element, "AdjustedWhiteY", raw_convert<string>(_adjusted_white.get().y));
}
- if (dynamic_pointer_cast<const dcp::GammaTransferFunction> (_out)) {
- shared_ptr<const dcp::GammaTransferFunction> gf = dynamic_pointer_cast<const dcp::GammaTransferFunction> (_out);
- node->add_child("OutputGamma")->add_child_text (raw_convert<string> (gf->gamma ()));
+ if (auto gf = dynamic_pointer_cast<const dcp::GammaTransferFunction>(_out)) {
+ cxml::add_text_child(element, "OutputGamma", raw_convert<string>(gf->gamma()));
}
}
diff --git a/src/lib/colour_conversion.h b/src/lib/colour_conversion.h
index 73b6ad23c..0c07ddbac 100644
--- a/src/lib/colour_conversion.h
+++ b/src/lib/colour_conversion.h
@@ -41,7 +41,7 @@ public:
ColourConversion (cxml::NodePtr, int version);
virtual ~ColourConversion () {}
- virtual void as_xml (xmlpp::Node *) const;
+ virtual void as_xml(xmlpp::Element*) const;
std::string identifier () const;
boost::optional<size_t> preset () const;
diff --git a/src/lib/config.cc b/src/lib/config.cc
index 384db5cde..180ce5c55 100644
--- a/src/lib/config.cc
+++ b/src/lib/config.cc
@@ -19,20 +19,21 @@
*/
-#include "cinema.h"
+#include "cinema_list.h"
#include "colour_conversion.h"
#include "compose.hpp"
#include "config.h"
#include "constants.h"
#include "cross.h"
#include "dcp_content_type.h"
-#include "dkdm_recipient.h"
+#include "dkdm_recipient_list.h"
#include "dkdm_wrapper.h"
#include "film.h"
#include "filter.h"
#include "log.h"
#include "ratio.h"
#include "unzipper.h"
+#include "variant.h"
#include "zipper.h"
#include <dcp/certificate_chain.h>
#include <dcp/name_format.h>
@@ -107,7 +108,8 @@ Config::set_defaults ()
_default_still_length = 10;
_default_dcp_content_type = DCPContentType::from_isdcf_name ("FTR");
_default_dcp_audio_channels = 8;
- _default_j2k_bandwidth = 150000000;
+ _default_video_bit_rate[VideoEncoding::JPEG2000] = 150000000;
+ _default_video_bit_rate[VideoEncoding::MPEG2] = 5000000;
_default_audio_delay = 0;
_default_interop = false;
_default_metadata.clear ();
@@ -126,7 +128,8 @@ Config::set_defaults ()
_notification_bcc = "";
_check_for_updates = false;
_check_for_test_updates = false;
- _maximum_j2k_bandwidth = 250000000;
+ _maximum_video_bit_rate[VideoEncoding::JPEG2000] = 250000000;
+ _maximum_video_bit_rate[VideoEncoding::MPEG2] = 50000000;
_log_types = LogEntry::TYPE_GENERAL | LogEntry::TYPE_WARNING | LogEntry::TYPE_ERROR | LogEntry::TYPE_DISK;
_analyse_ebur128 = true;
_automatic_audio_analysis = false;
@@ -136,8 +139,8 @@ Config::set_defaults ()
/* At the moment we don't write these files anywhere new after a version change, so they will be read from
* ~/.config/dcpomatic2 (or equivalent) and written back there.
*/
- _cinemas_file = read_path ("cinemas.xml");
- _dkdm_recipients_file = read_path ("dkdm_recipients.xml");
+ _cinemas_file = read_path("cinemas.sqlite3");
+ _dkdm_recipients_file = read_path("dkdm_recipients.sqlite3");
_show_hints_before_make_dcp = true;
_confirm_kdm_email = true;
_kdm_container_name_format = dcp::NameFormat("KDM_%f_%c");
@@ -177,6 +180,8 @@ Config::set_defaults ()
_gdc_username = optional<string>();
_gdc_password = optional<string>();
_player_mode = PLAYER_MODE_WINDOW;
+ _player_restricted_menus = false;
+ _playlist_editor_restricted_menus = false;
_image_display = 0;
_video_view_type = VIDEO_VIEW_SIMPLE;
_respect_kdm_validity_periods = true;
@@ -198,6 +203,8 @@ Config::set_defaults ()
_initial_paths["CinemaDatabasePath"] = boost::none;
_initial_paths["ConfigFilePath"] = boost::none;
_initial_paths["Preferences"] = boost::none;
+ _initial_paths["SaveVerificationReport"] = boost::none;
+ _initial_paths["CopySettingsPath"] = boost::none;
_use_isdcf_name_by_default = true;
_write_kdms_to_disk = true;
_email_kdms = false;
@@ -207,6 +214,8 @@ Config::set_defaults ()
_last_release_notes_version = boost::none;
_allow_smpte_bv20 = false;
_isdcf_name_part_length = 14;
+ _enable_player_http_server = false;
+ _player_http_server_port = 8080;
_allowed_dcp_frame_rates.clear ();
_allowed_dcp_frame_rates.push_back (24);
@@ -220,6 +229,10 @@ Config::set_defaults ()
set_notification_email_to_default ();
set_cover_sheet_to_default ();
+#ifdef DCPOMATIC_GROK
+ _grok = boost::none;
+#endif
+
_main_divider_sash_position = {};
_main_content_divider_sash_position = {};
@@ -266,7 +279,7 @@ Config::backup ()
copy_file(path_to_copy, add_number(path_to_copy, n), ec);
};
- /* Make a backup copy of any config.xml, cinemas.xml, dkdm_recipients.xml that we might be about
+ /* Make a backup copy of any config.xml, cinemas.sqlite3, dkdm_recipients.sqlite3 that we might be about
* to write over. This is more intended for the situation where we have a corrupted config.xml,
* and decide to overwrite it with a new one (possibly losing important details in the corrupted
* file). But we might as well back up the other files while we're about it.
@@ -286,15 +299,6 @@ Config::backup ()
void
Config::read ()
-{
- read_config();
- read_cinemas();
- read_dkdm_recipients();
-}
-
-
-void
-Config::read_config()
try
{
cxml::Document f ("Config");
@@ -367,7 +371,12 @@ try
_dcp_j2k_comment = f.optional_string_child("DCPJ2KComment").get_value_or("");
_default_still_length = f.optional_number_child<int>("DefaultStillLength").get_value_or (10);
- _default_j2k_bandwidth = f.optional_number_child<int>("DefaultJ2KBandwidth").get_value_or (200000000);
+ if (auto j2k = f.optional_number_child<int>("DefaultJ2KBandwidth")) {
+ _default_video_bit_rate[VideoEncoding::JPEG2000] = *j2k;
+ } else {
+ _default_video_bit_rate[VideoEncoding::JPEG2000] = f.optional_number_child<int64_t>("DefaultJ2KVideoBitRate").get_value_or(200000000);
+ }
+ _default_video_bit_rate[VideoEncoding::MPEG2] = f.optional_number_child<int64_t>("DefaultMPEG2VideoBitRate").get_value_or(5000000);
_default_audio_delay = f.optional_number_child<int>("DefaultAudioDelay").get_value_or (0);
_default_interop = f.optional_bool_child("DefaultInterop").get_value_or (false);
@@ -391,11 +400,6 @@ try
_default_kdm_directory = f.optional_string_child("DefaultKDMDirectory");
- /* Read any cinemas that are still lying around in the config file
- * from an old version.
- */
- read_cinemas (f);
-
_mail_server = f.string_child ("MailServer");
_mail_port = f.optional_number_child<int> ("MailPort").get_value_or (25);
@@ -426,7 +430,7 @@ try
_kdm_bcc = f.optional_string_child ("KDMBCC").get_value_or ("");
_kdm_email = f.string_child ("KDMEmail");
- _notification_subject = f.optional_string_child("NotificationSubject").get_value_or(_("DCP-o-matic notification"));
+ _notification_subject = f.optional_string_child("NotificationSubject").get_value_or(variant::insert_dcpomatic(_("%1 notification")));
_notification_from = f.optional_string_child("NotificationFrom").get_value_or("");
_notification_to = f.optional_string_child("NotificationTo").get_value_or("");
for (auto i: f.node_children("NotificationCC")) {
@@ -442,7 +446,12 @@ try
_check_for_updates = f.optional_bool_child("CheckForUpdates").get_value_or (false);
_check_for_test_updates = f.optional_bool_child("CheckForTestUpdates").get_value_or (false);
- _maximum_j2k_bandwidth = f.optional_number_child<int> ("MaximumJ2KBandwidth").get_value_or (250000000);
+ if (auto j2k = f.optional_number_child<int>("MaximumJ2KBandwidth")) {
+ _maximum_video_bit_rate[VideoEncoding::JPEG2000] = *j2k;
+ } else {
+ _maximum_video_bit_rate[VideoEncoding::JPEG2000] = f.optional_number_child<int64_t>("MaximumJ2KVideoBitRate").get_value_or(250000000);
+ }
+ _maximum_video_bit_rate[VideoEncoding::MPEG2] = f.optional_number_child<int64_t>("MaximumMPEG2VideoBitRate").get_value_or(50000000);
_allow_any_dcp_frame_rate = f.optional_bool_child ("AllowAnyDCPFrameRate").get_value_or (false);
_allow_any_container = f.optional_bool_child ("AllowAnyContainer").get_value_or (false);
_allow_96khz_audio = f.optional_bool_child("Allow96kHzAudio").get_value_or(false);
@@ -494,7 +503,7 @@ try
of the nags.
*/
for (auto i: f.node_children("Nagged")) {
- auto const id = i->number_attribute<int>("Id");
+ auto const id = number_attribute<int>(i, "Id", "id");
if (id >= 0 && id < NAG_COUNT) {
_nagged[id] = raw_convert<int>(i->content());
}
@@ -528,8 +537,8 @@ try
_dkdms->add (DKDMBase::read (i));
}
}
- _cinemas_file = f.optional_string_child("CinemasFile").get_value_or(read_path("cinemas.xml").string());
- _dkdm_recipients_file = f.optional_string_child("DKDMRecipientsFile").get_value_or(read_path("dkdm_recipients.xml").string());
+ _cinemas_file = f.optional_string_child("CinemasFile").get_value_or(read_path("cinemas.sqlite3").string());
+ _dkdm_recipients_file = f.optional_string_child("DKDMRecipientsFile").get_value_or(read_path("dkdm_recipients.sqlite3").string());
_show_hints_before_make_dcp = f.optional_bool_child("ShowHintsBeforeMakeDCP").get_value_or (true);
_confirm_kdm_email = f.optional_bool_child("ConfirmKDMEmail").get_value_or (true);
_kdm_container_name_format = dcp::NameFormat (f.optional_string_child("KDMContainerNameFormat").get_value_or ("KDM %f %c"));
@@ -566,7 +575,7 @@ try
_default_notify = f.optional_bool_child("DefaultNotify").get_value_or(false);
for (auto i: f.node_children("Notification")) {
- int const id = i->number_attribute<int>("Id");
+ int const id = number_attribute<int>(i, "Id", "id");
if (id >= 0 && id < NOTIFICATION_COUNT) {
_notification[id] = raw_convert<int>(i->content());
}
@@ -588,6 +597,9 @@ try
_player_mode = PLAYER_MODE_DUAL;
}
+ _player_restricted_menus = f.optional_bool_child("PlayerRestrictedMenus").get_value_or(false);
+ _playlist_editor_restricted_menus = f.optional_bool_child("PlaylistEditorRestrictedMenus").get_value_or(false);
+
_image_display = f.optional_number_child<int>("ImageDisplay").get_value_or(0);
auto vc = f.optional_string_child("VideoViewType");
if (vc && *vc == "opengl") {
@@ -641,6 +653,14 @@ try
_allow_smpte_bv20 = f.optional_bool_child("AllowSMPTEBv20").get_value_or(false);
_isdcf_name_part_length = f.optional_number_child<int>("ISDCFNamePartLength").get_value_or(14);
+ _enable_player_http_server = f.optional_bool_child("EnablePlayerHTTPServer").get_value_or(false);
+ _player_http_server_port = f.optional_number_child<int>("PlayerHTTPServerPort").get_value_or(8080);
+
+#ifdef DCPOMATIC_GROK
+ if (auto grok = f.optional_node_child("Grok")) {
+ _grok = Grok(grok);
+ }
+#endif
_export.read(f.optional_node_child("Export"));
}
@@ -659,40 +679,6 @@ catch (...) {
}
-void
-Config::read_cinemas()
-{
- if (dcp::filesystem::exists(_cinemas_file)) {
- try {
- cxml::Document f("Cinemas");
- f.read_file(dcp::filesystem::fix_long_path(_cinemas_file));
- read_cinemas(f);
- } catch (...) {
- backup();
- FailedToLoad(LoadFailure::CINEMAS);
- write_cinemas();
- }
- }
-}
-
-
-void
-Config::read_dkdm_recipients()
-{
- if (dcp::filesystem::exists(_dkdm_recipients_file)) {
- try {
- cxml::Document f("DKDMRecipients");
- f.read_file(dcp::filesystem::fix_long_path(_dkdm_recipients_file));
- read_dkdm_recipients(f);
- } catch (...) {
- backup();
- FailedToLoad(LoadFailure::DKDM_RECIPIENTS);
- write_dkdm_recipients();
- }
- }
-}
-
-
/** @return Singleton instance */
Config *
Config::instance ()
@@ -700,6 +686,32 @@ Config::instance ()
if (_instance == nullptr) {
_instance = new Config;
_instance->read ();
+
+ auto cinemas_file = _instance->cinemas_file();
+ if (cinemas_file.extension() == ".xml") {
+ auto sqlite = cinemas_file;
+ sqlite.replace_extension(".sqlite3");
+ bool const had_sqlite = dcp::filesystem::exists(sqlite);
+
+ _instance->set_cinemas_file(sqlite);
+
+ if (dcp::filesystem::exists(cinemas_file) && !had_sqlite) {
+ CinemaList cinemas;
+ cinemas.read_legacy_file(cinemas_file);
+ }
+ }
+
+ auto dkdm_recipients_file = _instance->dkdm_recipients_file();
+ if (dkdm_recipients_file.extension() == ".xml") {
+ auto sqlite = dkdm_recipients_file;
+ sqlite.replace_extension(".sqlite3");
+
+ if (dcp::filesystem::exists(dkdm_recipients_file) && !dcp::filesystem::exists(sqlite)) {
+ _instance->set_dkdm_recipients_file(sqlite);
+ DKDMRecipientList recipients;
+ recipients.read_legacy_file(dkdm_recipients_file);
+ }
+ }
}
return _instance;
@@ -710,8 +722,6 @@ void
Config::write () const
{
write_config ();
- write_cinemas ();
- write_dkdm_recipients ();
}
void
@@ -721,218 +731,199 @@ Config::write_config () const
auto root = doc.create_root_node ("Config");
/* [XML] Version The version number of the configuration file format. */
- root->add_child("Version")->add_child_text (raw_convert<string>(_current_version));
+ cxml::add_text_child(root, "Version", raw_convert<string>(_current_version));
/* [XML] MasterEncodingThreads Number of encoding threads to use when running as master. */
- root->add_child("MasterEncodingThreads")->add_child_text (raw_convert<string> (_master_encoding_threads));
+ cxml::add_text_child(root, "MasterEncodingThreads", raw_convert<string>(_master_encoding_threads));
/* [XML] ServerEncodingThreads Number of encoding threads to use when running as server. */
- root->add_child("ServerEncodingThreads")->add_child_text (raw_convert<string> (_server_encoding_threads));
+ cxml::add_text_child(root, "ServerEncodingThreads", raw_convert<string>(_server_encoding_threads));
if (_default_directory) {
/* [XML:opt] DefaultDirectory Default directory when creating a new film in the GUI. */
- root->add_child("DefaultDirectory")->add_child_text (_default_directory->string ());
+ cxml::add_text_child(root, "DefaultDirectory", _default_directory->string());
}
/* [XML] ServerPortBase Port number to use for frame encoding requests. <code>ServerPortBase</code> + 1 and
<code>ServerPortBase</code> + 2 are used for querying servers. <code>ServerPortBase</code> + 3 is used
by the batch converter to listen for job requests.
*/
- root->add_child("ServerPortBase")->add_child_text (raw_convert<string> (_server_port_base));
+ cxml::add_text_child(root, "ServerPortBase", raw_convert<string>(_server_port_base));
/* [XML] UseAnyServers 1 to broadcast to look for encoding servers to use, 0 to use only those configured. */
- root->add_child("UseAnyServers")->add_child_text (_use_any_servers ? "1" : "0");
+ cxml::add_text_child(root, "UseAnyServers", _use_any_servers ? "1" : "0");
for (auto i: _servers) {
/* [XML:opt] Server IP address or hostname of an encoding server to use; you can use as many of these tags
as you like.
*/
- root->add_child("Server")->add_child_text (i);
+ cxml::add_text_child(root, "Server", i);
}
/* [XML] OnlyServersEncode 1 to set the master to do decoding of source content no JPEG2000 encoding; all encoding
is done by the encoding servers. 0 to set the master to do some encoding as well as coordinating the job.
*/
- root->add_child("OnlyServersEncode")->add_child_text (_only_servers_encode ? "1" : "0");
+ cxml::add_text_child(root, "OnlyServersEncode", _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<string> (static_cast<int> (_tms_protocol)));
+ cxml::add_text_child(root, "TMSProtocol", raw_convert<string>(static_cast<int>(_tms_protocol)));
/* [XML] TMSPassive True to use PASV mode with TMS FTP connections. */
- root->add_child("TMSPassive")->add_child_text(_tms_passive ? "1" : "0");
+ cxml::add_text_child(root, "TMSPassive", _tms_passive ? "1" : "0");
/* [XML] TMSIP IP address of TMS. */
- root->add_child("TMSIP")->add_child_text (_tms_ip);
+ cxml::add_text_child(root, "TMSIP", _tms_ip);
/* [XML] TMSPath Path on the TMS to copy files to. */
- root->add_child("TMSPath")->add_child_text (_tms_path);
+ cxml::add_text_child(root, "TMSPath", _tms_path);
/* [XML] TMSUser Username to log into the TMS with. */
- root->add_child("TMSUser")->add_child_text (_tms_user);
+ cxml::add_text_child(root, "TMSUser", _tms_user);
/* [XML] TMSPassword Password to log into the TMS with. */
- root->add_child("TMSPassword")->add_child_text (_tms_password);
+ cxml::add_text_child(root, "TMSPassword", _tms_password);
if (_language) {
/* [XML:opt] Language Language to use in the GUI e.g. <code>fr_FR</code>. */
- root->add_child("Language")->add_child_text (_language.get());
+ cxml::add_text_child(root, "Language", _language.get());
}
- if (_default_dcp_content_type) {
- /* [XML:opt] DefaultDCPContentType Default content type to use when creating new films (<code>FTR</code>, <code>SHR</code>,
- <code>TLR</code>, <code>TST</code>, <code>XSN</code>, <code>RTG</code>, <code>TSR</code>, <code>POL</code>,
- <code>PSA</code> or <code>ADV</code>). */
- root->add_child("DefaultDCPContentType")->add_child_text (_default_dcp_content_type->isdcf_name ());
- }
- /* [XML] DefaultDCPAudioChannels Default number of audio channels to use when creating new films. */
- root->add_child("DefaultDCPAudioChannels")->add_child_text (raw_convert<string> (_default_dcp_audio_channels));
/* [XML] DCPIssuer Issuer text to write into CPL files. */
- root->add_child("DCPIssuer")->add_child_text (_dcp_issuer);
+ cxml::add_text_child(root, "DCPIssuer", _dcp_issuer);
/* [XML] DCPCreator Creator text to write into CPL files. */
- root->add_child("DCPCreator")->add_child_text (_dcp_creator);
+ cxml::add_text_child(root, "DCPCreator", _dcp_creator);
/* [XML] Company name to write into MXF files. */
- root->add_child("DCPCompanyName")->add_child_text (_dcp_company_name);
+ cxml::add_text_child(root, "DCPCompanyName", _dcp_company_name);
/* [XML] Product name to write into MXF files. */
- root->add_child("DCPProductName")->add_child_text (_dcp_product_name);
+ cxml::add_text_child(root, "DCPProductName", _dcp_product_name);
/* [XML] Product version to write into MXF files. */
- root->add_child("DCPProductVersion")->add_child_text (_dcp_product_version);
+ cxml::add_text_child(root, "DCPProductVersion", _dcp_product_version);
/* [XML] Comment to write into JPEG2000 data. */
- root->add_child("DCPJ2KComment")->add_child_text (_dcp_j2k_comment);
+ cxml::add_text_child(root, "DCPJ2KComment", _dcp_j2k_comment);
/* [XML] UploadAfterMakeDCP 1 to upload to a TMS after making a DCP, 0 for no upload. */
- root->add_child("UploadAfterMakeDCP")->add_child_text (_upload_after_make_dcp ? "1" : "0");
+ cxml::add_text_child(root, "UploadAfterMakeDCP", _upload_after_make_dcp ? "1" : "0");
/* [XML] DefaultStillLength Default length (in seconds) for still images in new films. */
- root->add_child("DefaultStillLength")->add_child_text (raw_convert<string> (_default_still_length));
- /* [XML] DefaultJ2KBandwidth Default bitrate (in bits per second) for JPEG2000 data in new films. */
- root->add_child("DefaultJ2KBandwidth")->add_child_text (raw_convert<string> (_default_j2k_bandwidth));
+ cxml::add_text_child(root, "DefaultStillLength", raw_convert<string>(_default_still_length));
/* [XML] DefaultAudioDelay Default delay to apply to audio (positive moves audio later) in milliseconds. */
- root->add_child("DefaultAudioDelay")->add_child_text (raw_convert<string> (_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");
+ cxml::add_text_child(root, "DefaultAudioDelay", raw_convert<string>(_default_audio_delay));
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);
- c->add_child_text(i.second);
+ cxml::add_text_child(root, "DefaultAudioLanguage", _default_audio_language->to_string());
}
if (_default_kdm_directory) {
/* [XML:opt] DefaultKDMDirectory Default directory to write KDMs to. */
- root->add_child("DefaultKDMDirectory")->add_child_text (_default_kdm_directory->string ());
+ cxml::add_text_child(root, "DefaultKDMDirectory", _default_kdm_directory->string ());
}
- _default_kdm_duration.as_xml(root->add_child("DefaultKDMDuration"));
+ _default_kdm_duration.as_xml(cxml::add_child(root, "DefaultKDMDuration"));
/* [XML] MailServer Hostname of SMTP server to use. */
- root->add_child("MailServer")->add_child_text (_mail_server);
+ cxml::add_text_child(root, "MailServer", _mail_server);
/* [XML] MailPort Port number to use on SMTP server. */
- root->add_child("MailPort")->add_child_text (raw_convert<string> (_mail_port));
+ cxml::add_text_child(root, "MailPort", raw_convert<string>(_mail_port));
/* [XML] MailProtocol Protocol to use on SMTP server (Auto, Plain, STARTTLS or SSL) */
switch (_mail_protocol) {
case EmailProtocol::AUTO:
- root->add_child("MailProtocol")->add_child_text("Auto");
+ cxml::add_text_child(root, "MailProtocol", "Auto");
break;
case EmailProtocol::PLAIN:
- root->add_child("MailProtocol")->add_child_text("Plain");
+ cxml::add_text_child(root, "MailProtocol", "Plain");
break;
case EmailProtocol::STARTTLS:
- root->add_child("MailProtocol")->add_child_text("STARTTLS");
+ cxml::add_text_child(root, "MailProtocol", "STARTTLS");
break;
case EmailProtocol::SSL:
- root->add_child("MailProtocol")->add_child_text("SSL");
+ cxml::add_text_child(root, "MailProtocol", "SSL");
break;
}
/* [XML] MailUser Username to use on SMTP server. */
- root->add_child("MailUser")->add_child_text (_mail_user);
+ cxml::add_text_child(root, "MailUser", _mail_user);
/* [XML] MailPassword Password to use on SMTP server. */
- root->add_child("MailPassword")->add_child_text (_mail_password);
+ cxml::add_text_child(root, "MailPassword", _mail_password);
/* [XML] KDMSubject Subject to use for KDM emails. */
- root->add_child("KDMSubject")->add_child_text (_kdm_subject);
+ cxml::add_text_child(root, "KDMSubject", _kdm_subject);
/* [XML] KDMFrom From address to use for KDM emails. */
- root->add_child("KDMFrom")->add_child_text (_kdm_from);
+ cxml::add_text_child(root, "KDMFrom", _kdm_from);
for (auto i: _kdm_cc) {
/* [XML] KDMCC CC address to use for KDM emails; you can use as many of these tags as you like. */
- root->add_child("KDMCC")->add_child_text (i);
+ cxml::add_text_child(root, "KDMCC", i);
}
/* [XML] KDMBCC BCC address to use for KDM emails. */
- root->add_child("KDMBCC")->add_child_text (_kdm_bcc);
+ cxml::add_text_child(root, "KDMBCC", _kdm_bcc);
/* [XML] KDMEmail Text of KDM email. */
- root->add_child("KDMEmail")->add_child_text (_kdm_email);
+ cxml::add_text_child(root, "KDMEmail", _kdm_email);
/* [XML] NotificationSubject Subject to use for notification emails. */
- root->add_child("NotificationSubject")->add_child_text (_notification_subject);
+ cxml::add_text_child(root, "NotificationSubject", _notification_subject);
/* [XML] NotificationFrom From address to use for notification emails. */
- root->add_child("NotificationFrom")->add_child_text (_notification_from);
+ cxml::add_text_child(root, "NotificationFrom", _notification_from);
/* [XML] NotificationFrom To address to use for notification emails. */
- root->add_child("NotificationTo")->add_child_text (_notification_to);
+ cxml::add_text_child(root, "NotificationTo", _notification_to);
for (auto i: _notification_cc) {
/* [XML] NotificationCC CC address to use for notification emails; you can use as many of these tags as you like. */
- root->add_child("NotificationCC")->add_child_text (i);
+ cxml::add_text_child(root, "NotificationCC", i);
}
/* [XML] NotificationBCC BCC address to use for notification emails. */
- root->add_child("NotificationBCC")->add_child_text (_notification_bcc);
+ cxml::add_text_child(root, "NotificationBCC", _notification_bcc);
/* [XML] NotificationEmail Text of notification email. */
- root->add_child("NotificationEmail")->add_child_text (_notification_email);
+ cxml::add_text_child(root, "NotificationEmail", _notification_email);
/* [XML] CheckForUpdates 1 to check dcpomatic.com for new versions, 0 to check only on request. */
- root->add_child("CheckForUpdates")->add_child_text (_check_for_updates ? "1" : "0");
+ cxml::add_text_child(root, "CheckForUpdates", _check_for_updates ? "1" : "0");
/* [XML] CheckForUpdates 1 to check dcpomatic.com for new text versions, 0 to check only on request. */
- root->add_child("CheckForTestUpdates")->add_child_text (_check_for_test_updates ? "1" : "0");
+ cxml::add_text_child(root, "CheckForTestUpdates", _check_for_test_updates ? "1" : "0");
- /* [XML] MaximumJ2KBandwidth Maximum J2K bandwidth (in bits per second) that can be specified in the GUI. */
- root->add_child("MaximumJ2KBandwidth")->add_child_text (raw_convert<string> (_maximum_j2k_bandwidth));
+ /* [XML] MaximumJ2KVideoBitRate Maximum video bit rate (in bits per second) that can be specified in the GUI for JPEG2000 encodes. */
+ cxml::add_text_child(root, "MaximumJ2KVideoBitRate", raw_convert<string>(_maximum_video_bit_rate[VideoEncoding::JPEG2000]));
+ /* [XML] MaximumMPEG2VideoBitRate Maximum video bit rate (in bits per second) that can be specified in the GUI for MPEG2 encodes. */
+ cxml::add_text_child(root, "MaximumMPEG2VideoBitRate", raw_convert<string>(_maximum_video_bit_rate[VideoEncoding::MPEG2]));
/* [XML] AllowAnyDCPFrameRate 1 to allow users to specify any frame rate when creating DCPs, 0 to limit the GUI to standard rates. */
- root->add_child("AllowAnyDCPFrameRate")->add_child_text (_allow_any_dcp_frame_rate ? "1" : "0");
+ cxml::add_text_child(root, "AllowAnyDCPFrameRate", _allow_any_dcp_frame_rate ? "1" : "0");
/* [XML] AllowAnyContainer 1 to allow users to user any container ratio for their DCP, 0 to limit the GUI to DCI Flat/Scope */
- root->add_child("AllowAnyContainer")->add_child_text (_allow_any_container ? "1" : "0");
+ cxml::add_text_child(root, "AllowAnyContainer", _allow_any_container ? "1" : "0");
/* [XML] Allow96kHzAudio 1 to allow users to make DCPs with 96kHz audio, 0 to always make 48kHz DCPs */
- root->add_child("Allow96kHzAudio")->add_child_text(_allow_96khz_audio ? "1" : "0");
+ cxml::add_text_child(root, "Allow96kHzAudio", _allow_96khz_audio ? "1" : "0");
/* [XML] UseAllAudioChannels 1 to allow users to map audio to all 16 DCP channels, 0 to limit to the channels used in standard DCPs */
- root->add_child("UseAllAudioChannels")->add_child_text(_use_all_audio_channels ? "1" : "0");
+ cxml::add_text_child(root, "UseAllAudioChannels", _use_all_audio_channels ? "1" : "0");
/* [XML] ShowExperimentalAudioProcessors 1 to offer users the (experimental) audio upmixer processors, 0 to hide them */
- root->add_child("ShowExperimentalAudioProcessors")->add_child_text (_show_experimental_audio_processors ? "1" : "0");
+ cxml::add_text_child(root, "ShowExperimentalAudioProcessors", _show_experimental_audio_processors ? "1" : "0");
/* [XML] LogTypes Types of logging to write; a bitfield where 1 is general notes, 2 warnings, 4 errors, 8 debug information related
to 3D, 16 debug information related to encoding, 32 debug information for timing purposes, 64 debug information related
to sending email, 128 debug information related to the video view, 256 information about disk writing, 512 debug information
related to the player, 1024 debug information related to audio analyses.
*/
- root->add_child("LogTypes")->add_child_text (raw_convert<string> (_log_types));
+ cxml::add_text_child(root, "LogTypes", raw_convert<string> (_log_types));
/* [XML] AnalyseEBUR128 1 to do EBUR128 analyses when analysing audio, otherwise 0. */
- root->add_child("AnalyseEBUR128")->add_child_text (_analyse_ebur128 ? "1" : "0");
+ cxml::add_text_child(root, "AnalyseEBUR128", _analyse_ebur128 ? "1" : "0");
/* [XML] AutomaticAudioAnalysis 1 to run audio analysis automatically when audio content is added to the film, otherwise 0. */
- root->add_child("AutomaticAudioAnalysis")->add_child_text (_automatic_audio_analysis ? "1" : "0");
+ cxml::add_text_child(root, "AutomaticAudioAnalysis", _automatic_audio_analysis ? "1" : "0");
#ifdef DCPOMATIC_WINDOWS
if (_win32_console) {
/* [XML] Win32Console 1 to open a console when running on Windows, otherwise 0.
* We only write this if it's true, which is a bit of a hack to allow unit tests to work
* more easily on Windows (without a platform-specific reference in config_write_utf8_test)
*/
- root->add_child("Win32Console")->add_child_text ("1");
+ cxml::add_text_child(root, "Win32Console", "1");
}
#endif
/* [XML] Signer Certificate chain and private key to use when signing DCPs and KDMs. Should contain <code>&lt;Certificate&gt;</code>
tags in order and a <code>&lt;PrivateKey&gt;</code> tag all containing PEM-encoded certificates or private keys as appropriate.
*/
- auto signer = root->add_child ("Signer");
+ auto signer = cxml::add_child(root, "Signer");
DCPOMATIC_ASSERT (_signer_chain);
for (auto const& i: _signer_chain->unordered()) {
- signer->add_child("Certificate")->add_child_text (i.certificate (true));
+ cxml::add_text_child(signer, "Certificate", i.certificate (true));
}
- signer->add_child("PrivateKey")->add_child_text (_signer_chain->key().get ());
+ cxml::add_text_child(signer, "PrivateKey", _signer_chain->key().get ());
/* [XML] Decryption Certificate chain and private key to use when decrypting KDMs */
- auto decryption = root->add_child ("Decryption");
+ auto decryption = cxml::add_child(root, "Decryption");
DCPOMATIC_ASSERT (_decryption_chain);
for (auto const& i: _decryption_chain->unordered()) {
- decryption->add_child("Certificate")->add_child_text (i.certificate (true));
+ cxml::add_text_child(decryption, "Certificate", i.certificate (true));
}
- decryption->add_child("PrivateKey")->add_child_text (_decryption_chain->key().get ());
+ cxml::add_text_child(decryption, "PrivateKey", _decryption_chain->key().get());
/* [XML] History Filename of DCP to present in the <guilabel>File</guilabel> menu of the GUI; there can be more than one
of these tags.
*/
for (auto i: _history) {
- root->add_child("History")->add_child_text (i.string ());
+ cxml::add_text_child(root, "History", i.string());
}
/* [XML] History Filename of DCP to present in the <guilabel>File</guilabel> menu of the player; there can be more than one
of these tags.
*/
for (auto i: _player_history) {
- root->add_child("PlayerHistory")->add_child_text (i.string ());
+ cxml::add_text_child(root, "PlayerHistory", i.string());
}
/* [XML] DKDMGroup A group of DKDMs, each with a <code>Name</code> attribute, containing other <code>&lt;DKDMGroup&gt;</code>
@@ -942,53 +933,53 @@ Config::write_config () const
_dkdms->as_xml (root);
/* [XML] CinemasFile Filename of cinemas list file. */
- root->add_child("CinemasFile")->add_child_text (_cinemas_file.string());
+ cxml::add_text_child(root, "CinemasFile", _cinemas_file.string());
/* [XML] DKDMRecipientsFile Filename of DKDM recipients list file. */
- root->add_child("DKDMRecipientsFile")->add_child_text (_dkdm_recipients_file.string());
+ cxml::add_text_child(root, "DKDMRecipientsFile", _dkdm_recipients_file.string());
/* [XML] ShowHintsBeforeMakeDCP 1 to show hints in the GUI before making a DCP, otherwise 0. */
- root->add_child("ShowHintsBeforeMakeDCP")->add_child_text (_show_hints_before_make_dcp ? "1" : "0");
+ cxml::add_text_child(root, "ShowHintsBeforeMakeDCP", _show_hints_before_make_dcp ? "1" : "0");
/* [XML] ConfirmKDMEmail 1 to confirm before sending KDM emails in the GUI, otherwise 0. */
- root->add_child("ConfirmKDMEmail")->add_child_text (_confirm_kdm_email ? "1" : "0");
+ cxml::add_text_child(root, "ConfirmKDMEmail", _confirm_kdm_email ? "1" : "0");
/* [XML] KDMFilenameFormat Format for KDM filenames. */
- root->add_child("KDMFilenameFormat")->add_child_text (_kdm_filename_format.specification ());
+ cxml::add_text_child(root, "KDMFilenameFormat", _kdm_filename_format.specification());
/* [XML] KDMFilenameFormat Format for DKDM filenames. */
- root->add_child("DKDMFilenameFormat")->add_child_text(_dkdm_filename_format.specification());
+ cxml::add_text_child(root, "DKDMFilenameFormat", _dkdm_filename_format.specification());
/* [XML] KDMContainerNameFormat Format for KDM containers (directories or ZIP files). */
- root->add_child("KDMContainerNameFormat")->add_child_text (_kdm_container_name_format.specification ());
+ cxml::add_text_child(root, "KDMContainerNameFormat", _kdm_container_name_format.specification());
/* [XML] DCPMetadataFilenameFormat Format for DCP metadata filenames. */
- root->add_child("DCPMetadataFilenameFormat")->add_child_text (_dcp_metadata_filename_format.specification ());
+ cxml::add_text_child(root, "DCPMetadataFilenameFormat", _dcp_metadata_filename_format.specification());
/* [XML] DCPAssetFilenameFormat Format for DCP asset filenames. */
- root->add_child("DCPAssetFilenameFormat")->add_child_text (_dcp_asset_filename_format.specification ());
+ cxml::add_text_child(root, "DCPAssetFilenameFormat", _dcp_asset_filename_format.specification());
/* [XML] JumpToSelected 1 to make the GUI jump to the start of content when it is selected, otherwise 0. */
- root->add_child("JumpToSelected")->add_child_text (_jump_to_selected ? "1" : "0");
+ cxml::add_text_child(root, "JumpToSelected", _jump_to_selected ? "1" : "0");
/* [XML] Nagged 1 if a particular nag screen has been shown and should not be shown again, otherwise 0. */
for (int i = 0; i < NAG_COUNT; ++i) {
- xmlpp::Element* e = root->add_child ("Nagged");
- e->set_attribute ("Id", raw_convert<string>(i));
+ auto e = cxml::add_child(root, "Nagged");
+ e->set_attribute("id", raw_convert<string>(i));
e->add_child_text (_nagged[i] ? "1" : "0");
}
/* [XML] PreviewSound 1 to use sound in the GUI preview and player, otherwise 0. */
- root->add_child("PreviewSound")->add_child_text (_sound ? "1" : "0");
+ cxml::add_text_child(root, "PreviewSound", _sound ? "1" : "0");
if (_sound_output) {
/* [XML:opt] PreviewSoundOutput Name of the audio output to use. */
- root->add_child("PreviewSoundOutput")->add_child_text (_sound_output.get());
+ cxml::add_text_child(root, "PreviewSoundOutput", _sound_output.get());
}
/* [XML] CoverSheet Text of the cover sheet to write when making DCPs. */
- root->add_child("CoverSheet")->add_child_text (_cover_sheet);
+ cxml::add_text_child(root, "CoverSheet", _cover_sheet);
if (_last_player_load_directory) {
- root->add_child("LastPlayerLoadDirectory")->add_child_text(_last_player_load_directory->string());
+ cxml::add_text_child(root, "LastPlayerLoadDirectory", _last_player_load_directory->string());
}
/* [XML] LastKDMWriteType Last type of KDM-write: <code>flat</code> for a flat file, <code>folder</code> for a folder or <code>zip</code> for a ZIP file. */
if (_last_kdm_write_type) {
switch (_last_kdm_write_type.get()) {
case KDM_WRITE_FLAT:
- root->add_child("LastKDMWriteType")->add_child_text("flat");
+ cxml::add_text_child(root, "LastKDMWriteType", "flat");
break;
case KDM_WRITE_FOLDER:
- root->add_child("LastKDMWriteType")->add_child_text("folder");
+ cxml::add_text_child(root, "LastKDMWriteType", "folder");
break;
case KDM_WRITE_ZIP:
- root->add_child("LastKDMWriteType")->add_child_text("zip");
+ cxml::add_text_child(root, "LastKDMWriteType", "zip");
break;
}
}
@@ -996,58 +987,58 @@ Config::write_config () const
if (_last_dkdm_write_type) {
switch (_last_dkdm_write_type.get()) {
case DKDM_WRITE_INTERNAL:
- root->add_child("LastDKDMWriteType")->add_child_text("internal");
+ cxml::add_text_child(root, "LastDKDMWriteType", "internal");
break;
case DKDM_WRITE_FILE:
- root->add_child("LastDKDMWriteType")->add_child_text("file");
+ cxml::add_text_child(root, "LastDKDMWriteType", "file");
break;
}
}
/* [XML] FramesInMemoryMultiplier value to multiply the encoding threads count by to get the maximum number of
frames to be held in memory at once.
*/
- root->add_child("FramesInMemoryMultiplier")->add_child_text(raw_convert<string>(_frames_in_memory_multiplier));
+ cxml::add_text_child(root, "FramesInMemoryMultiplier", raw_convert<string>(_frames_in_memory_multiplier));
/* [XML] DecodeReduction power of 2 to reduce DCP images by before decoding in the player. */
if (_decode_reduction) {
- root->add_child("DecodeReduction")->add_child_text(raw_convert<string>(_decode_reduction.get()));
+ cxml::add_text_child(root, "DecodeReduction", raw_convert<string>(_decode_reduction.get()));
}
/* [XML] DefaultNotify 1 to default jobs to notify when complete, otherwise 0. */
- root->add_child("DefaultNotify")->add_child_text(_default_notify ? "1" : "0");
+ cxml::add_text_child(root, "DefaultNotify", _default_notify ? "1" : "0");
/* [XML] Notification 1 if a notification type is enabled, otherwise 0. */
for (int i = 0; i < NOTIFICATION_COUNT; ++i) {
- xmlpp::Element* e = root->add_child ("Notification");
- e->set_attribute ("Id", raw_convert<string>(i));
+ auto e = cxml::add_child(root, "Notification");
+ e->set_attribute ("id", raw_convert<string>(i));
e->add_child_text (_notification[i] ? "1" : "0");
}
if (_barco_username) {
/* [XML] BarcoUsername Username for logging into Barco's servers when downloading server certificates. */
- root->add_child("BarcoUsername")->add_child_text(*_barco_username);
+ cxml::add_text_child(root, "BarcoUsername", *_barco_username);
}
if (_barco_password) {
/* [XML] BarcoPassword Password for logging into Barco's servers when downloading server certificates. */
- root->add_child("BarcoPassword")->add_child_text(*_barco_password);
+ cxml::add_text_child(root, "BarcoPassword", *_barco_password);
}
if (_christie_username) {
/* [XML] ChristieUsername Username for logging into Christie's servers when downloading server certificates. */
- root->add_child("ChristieUsername")->add_child_text(*_christie_username);
+ cxml::add_text_child(root, "ChristieUsername", *_christie_username);
}
if (_christie_password) {
/* [XML] ChristiePassword Password for logging into Christie's servers when downloading server certificates. */
- root->add_child("ChristiePassword")->add_child_text(*_christie_password);
+ cxml::add_text_child(root, "ChristiePassword", *_christie_password);
}
if (_gdc_username) {
/* [XML] GDCUsername Username for logging into GDC's servers when downloading server certificates. */
- root->add_child("GDCUsername")->add_child_text(*_gdc_username);
+ cxml::add_text_child(root, "GDCUsername", *_gdc_username);
}
if (_gdc_password) {
/* [XML] GDCPassword Password for logging into GDC's servers when downloading server certificates. */
- root->add_child("GDCPassword")->add_child_text(*_gdc_password);
+ cxml::add_text_child(root, "GDCPassword", *_gdc_password);
}
/* [XML] PlayerMode <code>window</code> for a single window, <code>full</code> for full-screen and <code>dual</code> for full screen playback
@@ -1055,80 +1046,98 @@ Config::write_config () const
*/
switch (_player_mode) {
case PLAYER_MODE_WINDOW:
- root->add_child("PlayerMode")->add_child_text("window");
+ cxml::add_text_child(root, "PlayerMode", "window");
break;
case PLAYER_MODE_FULL:
- root->add_child("PlayerMode")->add_child_text("full");
+ cxml::add_text_child(root, "PlayerMode", "full");
break;
case PLAYER_MODE_DUAL:
- root->add_child("PlayerMode")->add_child_text("dual");
+ cxml::add_text_child(root, "PlayerMode", "dual");
break;
}
+ if (_player_restricted_menus) {
+ cxml::add_text_child(root, "PlayerRestrictedMenus", "1");
+ }
+
+ if (_playlist_editor_restricted_menus) {
+ cxml::add_text_child(root, "PlaylistEditorRestrictedMenus", "1");
+ }
+
/* [XML] ImageDisplay Screen number to put image on in dual-screen player mode. */
- root->add_child("ImageDisplay")->add_child_text(raw_convert<string>(_image_display));
+ cxml::add_text_child(root, "ImageDisplay", raw_convert<string>(_image_display));
switch (_video_view_type) {
case VIDEO_VIEW_SIMPLE:
- root->add_child("VideoViewType")->add_child_text("simple");
+ cxml::add_text_child(root, "VideoViewType", "simple");
break;
case VIDEO_VIEW_OPENGL:
- root->add_child("VideoViewType")->add_child_text("opengl");
+ cxml::add_text_child(root, "VideoViewType", "opengl");
break;
}
/* [XML] RespectKDMValidityPeriods 1 to refuse to use KDMs that are out of date, 0 to ignore KDM dates. */
- root->add_child("RespectKDMValidityPeriods")->add_child_text(_respect_kdm_validity_periods ? "1" : "0");
+ cxml::add_text_child(root, "RespectKDMValidityPeriods", _respect_kdm_validity_periods ? "1" : "0");
if (_player_debug_log_file) {
/* [XML] PlayerLogFile Filename to use for player debug logs. */
- root->add_child("PlayerDebugLogFile")->add_child_text(_player_debug_log_file->string());
+ cxml::add_text_child(root, "PlayerDebugLogFile", _player_debug_log_file->string());
}
if (_player_content_directory) {
/* [XML] PlayerContentDirectory Directory to use for player content in the dual-screen mode. */
- root->add_child("PlayerContentDirectory")->add_child_text(_player_content_directory->string());
+ cxml::add_text_child(root, "PlayerContentDirectory", _player_content_directory->string());
}
if (_player_playlist_directory) {
/* [XML] PlayerPlaylistDirectory Directory to use for player playlists in the dual-screen mode. */
- root->add_child("PlayerPlaylistDirectory")->add_child_text(_player_playlist_directory->string());
+ cxml::add_text_child(root, "PlayerPlaylistDirectory", _player_playlist_directory->string());
}
if (_player_kdm_directory) {
/* [XML] PlayerKDMDirectory Directory to use for player KDMs in the dual-screen mode. */
- root->add_child("PlayerKDMDirectory")->add_child_text(_player_kdm_directory->string());
+ cxml::add_text_child(root, "PlayerKDMDirectory", _player_kdm_directory->string());
}
if (_audio_mapping) {
- _audio_mapping->as_xml (root->add_child("AudioMapping"));
+ _audio_mapping->as_xml(cxml::add_child(root, "AudioMapping"));
}
for (auto const& i: _custom_languages) {
- root->add_child("CustomLanguage")->add_child_text(i.to_string());
+ cxml::add_text_child(root, "CustomLanguage", i.to_string());
}
for (auto const& initial: _initial_paths) {
if (initial.second) {
- root->add_child(initial.first)->add_child_text(initial.second->string());
+ cxml::add_text_child(root, initial.first, 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");
- root->add_child("EmailKDMs")->add_child_text(_email_kdms ? "1" : "0");
- root->add_child("DefaultKDMType")->add_child_text(dcp::formulation_to_string(_default_kdm_type));
- root->add_child("AutoCropThreshold")->add_child_text(raw_convert<string>(_auto_crop_threshold));
+ cxml::add_text_child(root, "UseISDCFNameByDefault", _use_isdcf_name_by_default ? "1" : "0");
+ cxml::add_text_child(root, "WriteKDMsToDisk", _write_kdms_to_disk ? "1" : "0");
+ cxml::add_text_child(root, "EmailKDMs", _email_kdms ? "1" : "0");
+ cxml::add_text_child(root, "DefaultKDMType", dcp::formulation_to_string(_default_kdm_type));
+ cxml::add_text_child(root, "AutoCropThreshold", raw_convert<string>(_auto_crop_threshold));
if (_last_release_notes_version) {
- root->add_child("LastReleaseNotesVersion")->add_child_text(*_last_release_notes_version);
+ cxml::add_text_child(root, "LastReleaseNotesVersion", *_last_release_notes_version);
}
if (_main_divider_sash_position) {
- root->add_child("MainDividerSashPosition")->add_child_text(raw_convert<string>(*_main_divider_sash_position));
+ cxml::add_text_child(root, "MainDividerSashPosition", raw_convert<string>(*_main_divider_sash_position));
}
if (_main_content_divider_sash_position) {
- root->add_child("MainContentDividerSashPosition")->add_child_text(raw_convert<string>(*_main_content_divider_sash_position));
+ cxml::add_text_child(root, "MainContentDividerSashPosition", raw_convert<string>(*_main_content_divider_sash_position));
}
- root->add_child("DefaultAddFileLocation")->add_child_text(
+ cxml::add_text_child(root, "DefaultAddFileLocation",
_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");
+ cxml::add_text_child(root, "AllowSMPTEBv20", _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<string>(_isdcf_name_part_length));
+ cxml::add_text_child(root, "ISDCFNamePartLength", raw_convert<string>(_isdcf_name_part_length));
+ /* [XML] EnablePlayerHTTPServer 1 to enable a HTTP server to control the player, otherwise 0 */
+ cxml::add_text_child(root, "EnablePlayerHTTPServer", _enable_player_http_server ? "1" : "0");
+ /* [XML] PlayerHTTPServerPort Port to use for player HTTP server (if enabled) */
+ cxml::add_text_child(root, "PlayerHTTPServerPort", raw_convert<string>(_player_http_server_port));
+
+#ifdef DCPOMATIC_GROK
+ if (_grok) {
+ _grok->as_xml(cxml::add_child(root, "Grok"));
+ }
+#endif
- _export.write(root->add_child("Export"));
+ _export.write(cxml::add_child(root, "Export"));
auto target = config_write_file();
@@ -1157,10 +1166,10 @@ write_file (string root_node, string node, string version, list<shared_ptr<T>> t
{
xmlpp::Document doc;
auto root = doc.create_root_node (root_node);
- root->add_child("Version")->add_child_text(version);
+ cxml::add_text_child(root, "Version", version);
for (auto i: things) {
- i->as_xml (root->add_child(node));
+ i->as_xml(cxml::add_child(root, node));
}
try {
@@ -1175,20 +1184,6 @@ write_file (string root_node, string node, string version, list<shared_ptr<T>> t
}
-void
-Config::write_cinemas () const
-{
- write_file ("Cinemas", "Cinema", "1", _cinemas, _cinemas_file);
-}
-
-
-void
-Config::write_dkdm_recipients () const
-{
- write_file ("DKDMRecipients", "DKDMRecipient", "1", _dkdm_recipients, _dkdm_recipients_file);
-}
-
-
boost::filesystem::path
Config::default_directory_or (boost::filesystem::path a) const
{
@@ -1235,20 +1230,20 @@ Config::set_kdm_email_to_default ()
{
_kdm_subject = _("KDM delivery: $CPL_NAME");
- _kdm_email = _(
+ _kdm_email = variant::insert_dcpomatic(_(
"Dear Projectionist\n\n"
"Please find attached KDMs for $CPL_NAME.\n\n"
"Cinema: $CINEMA_NAME\n"
"Screen(s): $SCREENS\n\n"
"The KDMs are valid from $START_TIME until $END_TIME.\n\n"
- "Best regards,\nDCP-o-matic"
- );
+ "Best regards,\n%1"
+ ));
}
void
Config::set_notification_email_to_default ()
{
- _notification_subject = _("DCP-o-matic notification");
+ _notification_subject = variant::insert_dcpomatic(_("%1 notification"));
_notification_email = _(
"$JOB_NAME: $JOB_STATUS"
@@ -1319,7 +1314,7 @@ Config::add_to_history_internal (vector<boost::filesystem::path>& h, boost::file
h.insert (h.begin (), p);
if (h.size() > HISTORY_SIZE) {
- h.pop_back ();
+ h.resize(HISTORY_SIZE);
}
changed (HISTORY);
@@ -1350,20 +1345,6 @@ Config::have_existing (string file)
void
-Config::read_cinemas (cxml::Document const & f)
-{
- _cinemas.clear ();
- for (auto i: f.node_children("Cinema")) {
- /* Slightly grotty two-part construction of Cinema here so that we can use
- shared_from_this.
- */
- auto cinema = make_shared<Cinema>(i);
- cinema->read_screens (i);
- _cinemas.push_back (cinema);
- }
-}
-
-void
Config::set_cinemas_file (boost::filesystem::path file)
{
if (file == _cinemas_file) {
@@ -1372,25 +1353,27 @@ Config::set_cinemas_file (boost::filesystem::path file)
_cinemas_file = file;
- if (dcp::filesystem::exists(_cinemas_file)) {
- /* Existing file; read it in */
- cxml::Document f ("Cinemas");
- f.read_file(dcp::filesystem::fix_long_path(_cinemas_file));
- read_cinemas (f);
- }
-
- changed (CINEMAS);
changed (OTHER);
}
void
-Config::read_dkdm_recipients (cxml::Document const & f)
+Config::set_dkdm_recipients_file(boost::filesystem::path file)
{
- _dkdm_recipients.clear ();
- for (auto i: f.node_children("DKDMRecipient")) {
- _dkdm_recipients.push_back (make_shared<DKDMRecipient>(i));
+ if (file == _dkdm_recipients_file) {
+ return;
}
+
+ _dkdm_recipients_file = file;
+
+ changed(OTHER);
+}
+
+
+void
+Config::save_default_template(shared_ptr<const Film> film) const
+{
+ film->write_template(write_path("default.xml"));
}
@@ -1401,14 +1384,14 @@ Config::save_template (shared_ptr<const Film> film, string name) const
}
-list<string>
+vector<string>
Config::templates () const
{
if (!dcp::filesystem::exists(read_path("templates"))) {
return {};
}
- list<string> n;
+ vector<string> n;
for (auto const& i: dcp::filesystem::directory_iterator(read_path("templates"))) {
n.push_back (i.path().filename().string());
}
@@ -1430,6 +1413,18 @@ Config::template_read_path (string name) const
boost::filesystem::path
+Config::default_template_read_path() const
+{
+ if (!boost::filesystem::exists(read_path("default.xml"))) {
+ auto film = std::make_shared<const Film>(optional<boost::filesystem::path>());
+ save_default_template(film);
+ }
+
+ return read_path("default.xml");
+}
+
+
+boost::filesystem::path
Config::template_write_path (string name) const
{
return write_path("templates") / tidy_for_filename (name);
@@ -1500,7 +1495,7 @@ void
Config::link (boost::filesystem::path new_file) const
{
xmlpp::Document doc;
- doc.create_root_node("Config")->add_child("Link")->add_child_text(new_file.string());
+ cxml::add_text_child(doc.create_root_node("Config"), "Link", new_file.string());
try {
doc.write_to_file_formatted(write_path("config.xml").string());
} catch (xmlpp::exception& e) {
@@ -1623,10 +1618,10 @@ 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 (dcp::filesystem::exists(config->cinemas_file())) {
- zipper.add ("cinemas.xml", dcp::file_to_string(config->cinemas_file()));
+ zipper.add("cinemas.sqlite3", dcp::file_to_string(config->cinemas_file()));
}
if (dcp::filesystem::exists(config->dkdm_recipients_file())) {
- zipper.add ("dkdm_recipients.xml", dcp::file_to_string(config->dkdm_recipients_file()));
+ zipper.add("dkdm_recipients.sqlite3", dcp::file_to_string(config->dkdm_recipients_file()));
}
zipper.close ();
@@ -1634,22 +1629,58 @@ save_all_config_as_zip (boost::filesystem::path zip_file)
void
-Config::load_from_zip(boost::filesystem::path zip_file)
+Config::load_from_zip(boost::filesystem::path zip_file, CinemasAction action)
{
+ backup();
+
+ auto const current_cinemas = cinemas_file();
+ /* This is (unfortunately) a full path, and the user can't change it, so
+ * we always want to use that same path in the future no matter what is in the
+ * config.xml that we are about to load.
+ */
+ auto const current_dkdm_recipients = dkdm_recipients_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&) {}
+ if (action == CinemasAction::WRITE_TO_PATH_IN_ZIPPED_CONFIG) {
+ /* Read the zipped config, so that the cinemas file path is the new one and
+ * we write the cinemas to it.
+ */
+ read();
+ boost::filesystem::create_directories(cinemas_file().parent_path());
+ set_dkdm_recipients_file(current_dkdm_recipients);
+ }
- read();
+ if (unzipper.contains("cinemas.xml") && action != CinemasAction::IGNORE) {
+ CinemaList cinemas;
+ cinemas.clear();
+ cinemas.read_legacy_string(unzipper.get("cinemas.xml"));
+ }
+
+ if (unzipper.contains("dkdm_recipients.xml")) {
+ DKDMRecipientList recipients;
+ recipients.clear();
+ recipients.read_legacy_string(unzipper.get("dkdm_recipients.xml"));
+ }
+
+ if (unzipper.contains("cinemas.sqlite3") && action != CinemasAction::IGNORE) {
+ dcp::write_string_to_file(unzipper.get("cinemas.sqlite3"), cinemas_file());
+ }
+
+ if (unzipper.contains("dkdm_recipients.sqlite3")) {
+ dcp::write_string_to_file(unzipper.get("dkdm_recipients.sqlite3"), dkdm_recipients_file());
+ }
+
+ if (action != CinemasAction::WRITE_TO_PATH_IN_ZIPPED_CONFIG) {
+ /* Read the zipped config, then reset the cinemas file to be the old one */
+ read();
+ set_cinemas_file(current_cinemas);
+ set_dkdm_recipients_file(current_dkdm_recipients);
+ }
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);
@@ -1685,3 +1716,57 @@ Config::initial_path(string id) const
return iter->second;
}
+
+bool
+Config::zip_contains_cinemas(boost::filesystem::path zip)
+{
+ Unzipper unzipper(zip);
+ return unzipper.contains("cinemas.sqlite3") || unzipper.contains("cinemas.xml");
+}
+
+
+boost::filesystem::path
+Config::cinemas_file_from_zip(boost::filesystem::path zip)
+{
+ Unzipper unzipper(zip);
+ DCPOMATIC_ASSERT(unzipper.contains("config.xml"));
+ cxml::Document document("Config");
+ document.read_string(unzipper.get("config.xml"));
+ return document.string_child("CinemasFile");
+}
+
+
+#ifdef DCPOMATIC_GROK
+
+Config::Grok::Grok(cxml::ConstNodePtr node)
+ : enable(node->bool_child("Enable"))
+ , binary_location(node->string_child("BinaryLocation"))
+ , selected(node->number_child<int>("Selected"))
+ , licence_server(node->string_child("LicenceServer"))
+ , licence_port(node->number_child<int>("LicencePort"))
+ , licence(node->string_child("Licence"))
+{
+
+}
+
+
+void
+Config::Grok::as_xml(xmlpp::Element* node) const
+{
+ node->add_child("BinaryLocation")->add_child_text(binary_location.string());
+ node->add_child("Enable")->add_child_text((enable ? "1" : "0"));
+ node->add_child("Selected")->add_child_text(raw_convert<string>(selected));
+ node->add_child("LicenceServer")->add_child_text(licence_server);
+ node->add_child("LicencePort")->add_child_text(raw_convert<string>(licence_port));
+ node->add_child("Licence")->add_child_text(licence);
+}
+
+
+void
+Config::set_grok(Grok const& grok)
+{
+ _grok = grok;
+ changed(GROK);
+}
+
+#endif
diff --git a/src/lib/config.h b/src/lib/config.h
index f3d080b0b..1f13381b7 100644
--- a/src/lib/config.h
+++ b/src/lib/config.h
@@ -28,9 +28,11 @@
#include "audio_mapping.h"
+#include "enum_indexed_vector.h"
#include "export_config.h"
#include "rough_duration.h"
#include "state.h"
+#include "video_encoding.h"
#include <dcp/name_format.h>
#include <dcp/certificate_chain.h>
#include <dcp/encrypted_kdm.h>
@@ -48,6 +50,8 @@ class DKDMRecipient;
class Film;
class Ratio;
+#undef IGNORE
+
extern void save_all_config_as_zip (boost::filesystem::path zip_file);
@@ -79,13 +83,28 @@ 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 class CinemasAction
+ {
+ /** Copy the cinemas.{xml,sqlite3} in the ZIP file to the path
+ * specified in the current config, overwriting whatever is there,
+ * and use that path.
+ */
+ WRITE_TO_CURRENT_PATH,
+ /** Copy the cinemas.{xml,sqlite3} in the ZIP file over the path
+ * specified in the config.xml from the ZIP, overwriting whatever
+ * is there and creating any required directories, and use
+ * that path.
+ */
+ WRITE_TO_PATH_IN_ZIPPED_CONFIG,
+ /** Do nothing with the cinemas.{xml,sqlite3} in the ZIP file */
+ IGNORE
+ };
+
+ void load_from_zip(boost::filesystem::path zip_file, CinemasAction action);
enum Property {
USE_ANY_SERVERS,
SERVERS,
- CINEMAS,
- DKDM_RECIPIENTS,
SOUND,
SOUND_OUTPUT,
PLAYER_CONTENT_DIRECTORY,
@@ -97,6 +116,10 @@ public:
AUTO_CROP_THRESHOLD,
ALLOW_SMPTE_BV20,
ISDCF_NAME_PART_LENGTH,
+ ALLOW_ANY_CONTAINER,
+#ifdef DCPOMATIC_GROK
+ GROK,
+#endif
OTHER
};
@@ -157,14 +180,6 @@ public:
return _tms_password;
}
- std::list<std::shared_ptr<Cinema>> cinemas () const {
- return _cinemas;
- }
-
- std::list<std::shared_ptr<DKDMRecipient>> dkdm_recipients () const {
- return _dkdm_recipients;
- }
-
std::list<int> allowed_dcp_frame_rates () const {
return _allowed_dcp_frame_rates;
}
@@ -229,8 +244,8 @@ public:
return _dcp_j2k_comment;
}
- int default_j2k_bandwidth () const {
- return _default_j2k_bandwidth;
+ int64_t default_video_bit_rate(VideoEncoding encoding) const {
+ return _default_video_bit_rate[encoding];
}
int default_audio_delay () const {
@@ -345,8 +360,8 @@ public:
return _check_for_test_updates;
}
- int maximum_j2k_bandwidth () const {
- return _maximum_j2k_bandwidth;
+ int64_t maximum_video_bit_rate(VideoEncoding encoding) const {
+ return _maximum_video_bit_rate[encoding];
}
int log_types () const {
@@ -531,6 +546,14 @@ public:
return _player_mode;
}
+ bool player_restricted_menus() const {
+ return _player_restricted_menus;
+ }
+
+ bool playlist_editor_restricted_menus() const {
+ return _playlist_editor_restricted_menus;
+ }
+
int image_display () const {
return _image_display;
}
@@ -621,10 +644,40 @@ public:
return _allow_smpte_bv20;
}
+#ifdef DCPOMATIC_GROK
+ class Grok
+ {
+ public:
+ Grok() = default;
+ Grok(cxml::ConstNodePtr node);
+
+ void as_xml(xmlpp::Element* node) const;
+
+ bool enable = false;
+ boost::filesystem::path binary_location;
+ int selected = 0;
+ std::string licence_server;
+ int licence_port = 5000;
+ std::string licence;
+ };
+
+ boost::optional<Grok> grok() const {
+ return _grok;
+ }
+#endif
+
int isdcf_name_part_length() const {
return _isdcf_name_part_length;
}
+ bool enable_player_http_server() const {
+ return _enable_player_http_server;
+ }
+
+ int player_http_server_port() const {
+ return _player_http_server_port;
+ }
+
/* SET (mostly) */
void set_master_encoding_threads (int n) {
@@ -680,26 +733,6 @@ public:
maybe_set (_tms_password, p);
}
- void add_cinema (std::shared_ptr<Cinema> c) {
- _cinemas.push_back (c);
- changed (CINEMAS);
- }
-
- void remove_cinema (std::shared_ptr<Cinema> c) {
- _cinemas.remove (c);
- changed (CINEMAS);
- }
-
- void add_dkdm_recipient (std::shared_ptr<DKDMRecipient> c) {
- _dkdm_recipients.push_back (c);
- changed (DKDM_RECIPIENTS);
- }
-
- void remove_dkdm_recipient (std::shared_ptr<DKDMRecipient> c) {
- _dkdm_recipients.remove (c);
- changed (DKDM_RECIPIENTS);
- }
-
void set_allowed_dcp_frame_rates (std::list<int> const & r) {
maybe_set (_allowed_dcp_frame_rates, r);
}
@@ -709,7 +742,7 @@ public:
}
void set_allow_any_container (bool a) {
- maybe_set (_allow_any_container, a);
+ maybe_set(_allow_any_container, a, ALLOW_ANY_CONTAINER);
}
void set_allow_96hhz_audio (bool a) {
@@ -745,14 +778,6 @@ public:
maybe_set (_default_still_length, s);
}
- void set_default_dcp_content_type (DCPContentType const * t) {
- maybe_set (_default_dcp_content_type, t);
- }
-
- void set_default_dcp_audio_channels (int c) {
- maybe_set (_default_dcp_audio_channels, c);
- }
-
void set_dcp_issuer (std::string i) {
maybe_set (_dcp_issuer, i);
}
@@ -777,18 +802,10 @@ public:
maybe_set (_dcp_j2k_comment, c);
}
- void set_default_j2k_bandwidth (int b) {
- maybe_set (_default_j2k_bandwidth, b);
- }
-
void set_default_audio_delay (int d) {
maybe_set (_default_audio_delay, d);
}
- void set_default_interop (bool i) {
- maybe_set (_default_interop, i);
- }
-
void set_default_audio_language(dcp::LanguageTag tag) {
maybe_set(_default_audio_language, tag);
}
@@ -797,18 +814,6 @@ public:
maybe_set(_default_audio_language, boost::optional<dcp::LanguageTag>());
}
- void set_default_territory(dcp::LanguageTag::RegionSubtag tag) {
- maybe_set(_default_territory, tag);
- }
-
- void unset_default_territory() {
- maybe_set(_default_territory, boost::optional<dcp::LanguageTag::RegionSubtag>());
- }
-
- void set_default_metadata (std::map<std::string, std::string> const& metadata) {
- maybe_set (_default_metadata, metadata);
- }
-
void set_upload_after_make_dcp (bool u) {
maybe_set (_upload_after_make_dcp, u);
}
@@ -900,8 +905,8 @@ public:
maybe_set (_check_for_test_updates, c);
}
- void set_maximum_j2k_bandwidth (int b) {
- maybe_set (_maximum_j2k_bandwidth, b);
+ void set_maximum_video_bit_rate(VideoEncoding encoding, int64_t b) {
+ maybe_set(_maximum_video_bit_rate[encoding], b);
}
void set_log_types (int t) {
@@ -929,6 +934,8 @@ public:
void set_cinemas_file (boost::filesystem::path file);
+ void set_dkdm_recipients_file(boost::filesystem::path file);
+
void set_show_hints_before_make_dcp (bool s) {
maybe_set (_show_hints_before_make_dcp, s);
}
@@ -1202,10 +1209,23 @@ public:
maybe_set(_allow_smpte_bv20, allow, ALLOW_SMPTE_BV20);
}
+#ifdef DCPOMATIC_GROK
+ void set_grok(Grok const& grok);
+#endif
+
void set_isdcf_name_part_length(int length) {
maybe_set(_isdcf_name_part_length, length, ISDCF_NAME_PART_LENGTH);
}
+ void set_enable_player_http_server(bool enable) {
+ maybe_set(_enable_player_http_server, enable);
+ }
+
+ void set_player_http_server_port(int port) {
+ maybe_set(_player_http_server_port, port);
+ }
+
+
void changed (Property p = OTHER);
boost::signals2::signal<void (Property)> Changed;
/** Emitted if read() failed on an existing Config file. There is nothing
@@ -1213,8 +1233,6 @@ public:
*/
enum class LoadFailure {
CONFIG,
- CINEMAS,
- DKDM_RECIPIENTS
};
static boost::signals2::signal<void (LoadFailure)> FailedToLoad;
/** Emitted if read() issued a warning which the user might want to know about */
@@ -1234,17 +1252,17 @@ public:
void write () const override;
void write_config () const;
- void write_cinemas () const;
- void write_dkdm_recipients () const;
void link (boost::filesystem::path new_file) const;
void copy_and_link (boost::filesystem::path new_file) const;
bool have_write_permission () const;
+ void save_default_template(std::shared_ptr<const Film> film) const;
void save_template (std::shared_ptr<const Film> film, std::string name) const;
bool existing_template (std::string name) const;
- std::list<std::string> templates () const;
+ /** @return Template names (not including the default) */
+ std::vector<std::string> templates() const;
boost::filesystem::path template_read_path (std::string name) const;
- boost::filesystem::path template_write_path (std::string name) const;
+ boost::filesystem::path default_template_read_path() const;
void rename_template (std::string old_name, std::string new_name) const;
void delete_template (std::string name) const;
@@ -1256,6 +1274,9 @@ public:
static bool have_existing (std::string);
static boost::filesystem::path config_read_file ();
static boost::filesystem::path config_write_file ();
+ static bool zip_contains_cinemas(boost::filesystem::path zip);
+ static boost::filesystem::path cinemas_file_from_zip(boost::filesystem::path zip);
+
template <class T>
void maybe_set (T& member, T new_value, Property prop = OTHER) {
@@ -1278,20 +1299,16 @@ public:
private:
Config ();
void read () override;
- void read_config();
- void read_cinemas();
- void read_dkdm_recipients();
void set_defaults ();
void set_kdm_email_to_default ();
void set_notification_email_to_default ();
void set_cover_sheet_to_default ();
- void read_cinemas (cxml::Document const & f);
- void read_dkdm_recipients (cxml::Document const & f);
std::shared_ptr<dcp::CertificateChain> create_certificate_chain ();
boost::filesystem::path directory_or (boost::optional<boost::filesystem::path> dir, boost::filesystem::path a) const;
void add_to_history_internal (std::vector<boost::filesystem::path>& h, boost::filesystem::path p);
void clean_history_internal (std::vector<boost::filesystem::path>& h);
void backup ();
+ boost::filesystem::path template_write_path(std::string name) const;
/** number of threads which a master DoM should use for J2K encoding on the local machine */
int _master_encoding_threads;
@@ -1342,7 +1359,7 @@ private:
std::string _dcp_product_name;
std::string _dcp_product_version;
std::string _dcp_j2k_comment;
- int _default_j2k_bandwidth;
+ EnumIndexedVector<int64_t, VideoEncoding> _default_video_bit_rate;
int _default_audio_delay;
bool _default_interop;
boost::optional<dcp::LanguageTag> _default_audio_language;
@@ -1353,8 +1370,6 @@ private:
*/
boost::optional<boost::filesystem::path> _default_kdm_directory;
bool _upload_after_make_dcp;
- std::list<std::shared_ptr<Cinema>> _cinemas;
- std::list<std::shared_ptr<DKDMRecipient>> _dkdm_recipients;
std::string _mail_server;
int _mail_port;
EmailProtocol _mail_protocol;
@@ -1379,8 +1394,8 @@ private:
/** true to check for updates on startup */
bool _check_for_updates;
bool _check_for_test_updates;
- /** maximum allowed J2K bandwidth in bits per second */
- int _maximum_j2k_bandwidth;
+ /** maximum allowed video bit rate in bits per second */
+ EnumIndexedVector<int64_t, VideoEncoding> _maximum_video_bit_rate;
int _log_types;
bool _analyse_ebur128;
bool _automatic_audio_analysis;
@@ -1419,6 +1434,8 @@ private:
boost::optional<std::string> _gdc_username;
boost::optional<std::string> _gdc_password;
PlayerMode _player_mode;
+ bool _player_restricted_menus = false;
+ bool _playlist_editor_restricted_menus = false;
int _image_display;
VideoViewType _video_view_type;
bool _respect_kdm_validity_periods;
@@ -1446,6 +1463,12 @@ private:
DefaultAddFileLocation _default_add_file_location;
bool _allow_smpte_bv20;
int _isdcf_name_part_length;
+ bool _enable_player_http_server;
+ int _player_http_server_port;
+
+#ifdef DCPOMATIC_GROK
+ boost::optional<Grok> _grok;
+#endif
ExportConfig _export;
diff --git a/src/lib/constants.h b/src/lib/constants.h
index 3b1871554..cfa156778 100644
--- a/src/lib/constants.h
+++ b/src/lib/constants.h
@@ -29,7 +29,6 @@
#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)
@@ -47,6 +46,7 @@
#define MAX_CLOSED_CAPTION_XML_SIZE (256 * 1024)
#define MAX_CLOSED_CAPTION_XML_SIZE_TEXT "256KB"
#define CERTIFICATE_VALIDITY_PERIOD (10 * 365)
+#define SNAP_SUBDIVISION 64
#endif
diff --git a/src/lib/content.cc b/src/lib/content.cc
index f13201b93..496a68a63 100644
--- a/src/lib/content.cc
+++ b/src/lib/content.cc
@@ -140,23 +140,23 @@ Content::Content (vector<shared_ptr<Content>> c)
void
-Content::as_xml (xmlpp::Node* node, bool with_paths) const
+Content::as_xml(xmlpp::Element* element, bool with_paths) const
{
boost::mutex::scoped_lock lm (_mutex);
if (with_paths) {
for (size_t i = 0; i < _paths.size(); ++i) {
- auto p = node->add_child("Path");
+ auto p = cxml::add_child(element, "Path");
p->add_child_text (_paths[i].string());
p->set_attribute ("mtime", raw_convert<string>(_last_write_times[i]));
}
}
- node->add_child("Digest")->add_child_text(_digest);
- node->add_child("Position")->add_child_text(raw_convert<string>(_position.get()));
- node->add_child("TrimStart")->add_child_text(raw_convert<string>(_trim_start.get()));
- node->add_child("TrimEnd")->add_child_text(raw_convert<string>(_trim_end.get()));
+ cxml::add_text_child(element, "Digest", _digest);
+ cxml::add_text_child(element, "Position", raw_convert<string>(_position.get()));
+ cxml::add_text_child(element, "TrimStart", raw_convert<string>(_trim_start.get()));
+ cxml::add_text_child(element, "TrimEnd", raw_convert<string>(_trim_end.get()));
if (_video_frame_rate) {
- node->add_child("VideoFrameRate")->add_child_text(raw_convert<string>(_video_frame_rate.get()));
+ cxml::add_text_child(element, "VideoFrameRate", raw_convert<string>(_video_frame_rate.get()));
}
}
diff --git a/src/lib/content.h b/src/lib/content.h
index 540abdd8a..d4b99d39e 100644
--- a/src/lib/content.h
+++ b/src/lib/content.h
@@ -100,7 +100,7 @@ public:
*/
virtual std::string technical_summary () const;
- virtual void as_xml (xmlpp::Node *, bool with_paths) const;
+ virtual void as_xml(xmlpp::Element* element, bool with_paths) const;
virtual dcpomatic::DCPTime full_length (std::shared_ptr<const Film>) const = 0;
virtual dcpomatic::DCPTime approximate_length () const = 0;
virtual std::string identifier () const;
diff --git a/src/lib/content_video.h b/src/lib/content_video.h
index 4fdab717a..1c145f602 100644
--- a/src/lib/content_video.h
+++ b/src/lib/content_video.h
@@ -23,6 +23,7 @@
#define DCPOMATIC_CONTENT_VIDEO_H
+#include "dcpomatic_time.h"
#include "types.h"
@@ -36,22 +37,22 @@ class ContentVideo
{
public:
ContentVideo ()
- : frame (0)
- , eyes (Eyes::LEFT)
+ : eyes (Eyes::LEFT)
, part (Part::WHOLE)
{}
- ContentVideo (std::shared_ptr<const ImageProxy> i, Frame f, Eyes e, Part p)
+ ContentVideo (std::shared_ptr<const ImageProxy> i, dcpomatic::ContentTime t, Eyes e, Part p)
: image (i)
- , frame (f)
+ , time (t)
, eyes (e)
, part (p)
{}
std::shared_ptr<const ImageProxy> image;
- Frame frame;
+ dcpomatic::ContentTime time;
Eyes eyes;
Part part;
};
+
#endif
diff --git a/src/lib/cpu_j2k_encoder_thread.cc b/src/lib/cpu_j2k_encoder_thread.cc
new file mode 100644
index 000000000..580facae9
--- /dev/null
+++ b/src/lib/cpu_j2k_encoder_thread.cc
@@ -0,0 +1,62 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "cpu_j2k_encoder_thread.h"
+#include "cross.h"
+#include "dcpomatic_log.h"
+#include "dcp_video.h"
+#include "j2k_encoder.h"
+#include "util.h"
+
+#include "i18n.h"
+
+
+using std::make_shared;
+using std::shared_ptr;
+
+
+CPUJ2KEncoderThread::CPUJ2KEncoderThread(J2KEncoder& encoder)
+ : J2KSyncEncoderThread(encoder)
+{
+
+}
+
+
+void
+CPUJ2KEncoderThread::log_thread_start() const
+{
+ start_of_thread("CPUJ2KEncoder");
+ LOG_TIMING("start-encoder-thread thread=%1 server=localhost", thread_id());
+}
+
+
+shared_ptr<dcp::ArrayData>
+CPUJ2KEncoderThread::encode(DCPVideo const& frame)
+{
+ try {
+ return make_shared<dcp::ArrayData>(frame.encode_locally());
+ } catch (std::exception& e) {
+ LOG_ERROR(N_("Local encode failed (%1)"), e.what());
+ }
+
+ return {};
+}
+
diff --git a/src/lib/cpu_j2k_encoder_thread.h b/src/lib/cpu_j2k_encoder_thread.h
new file mode 100644
index 000000000..fb138f484
--- /dev/null
+++ b/src/lib/cpu_j2k_encoder_thread.h
@@ -0,0 +1,16 @@
+#include "j2k_sync_encoder_thread.h"
+#include <dcp/data.h>
+
+
+class DCPVideo;
+
+
+class CPUJ2KEncoderThread : public J2KSyncEncoderThread
+{
+public:
+ CPUJ2KEncoderThread(J2KEncoder& encoder);
+
+ void log_thread_start() const override;
+ std::shared_ptr<dcp::ArrayData> encode(DCPVideo const& frame) override;
+};
+
diff --git a/src/lib/create_cli.cc b/src/lib/create_cli.cc
index 1c2f2c635..8fe5a2a14 100644
--- a/src/lib/create_cli.cc
+++ b/src/lib/create_cli.cc
@@ -26,6 +26,7 @@
#include "dcpomatic_log.h"
#include "film.h"
#include "ratio.h"
+#include "variant.h"
#include <dcp/raw_convert.h>
#include <iostream>
#include <string>
@@ -40,8 +41,8 @@ using boost::optional;
string CreateCLI::_help =
- "\nSyntax: %1 [OPTION] <CONTENT> [OPTION] [<CONTENT> ...]\n"
- " -v, --version show DCP-o-matic version\n"
+ string("\nSyntax: %1 [OPTION] <CONTENT> [OPTION] [<CONTENT> ...]\n") +
+ variant::insert_dcpomatic(" -v, --version show %1 version\n") +
" -h, --help show this help\n"
" -n, --name <name> film name\n"
" -t, --template <name> template name\n"
@@ -132,7 +133,7 @@ CreateCLI::CreateCLI (int argc, char* argv[])
optional<string> standard_string;
int dcp_frame_rate_int = 0;
string template_name_string;
- int j2k_bandwidth_int = 0;
+ int64_t video_bit_rate_int = 0;
auto next_frame_type = VideoFrameType::TWO_D;
optional<dcp::Channel> channel;
optional<float> gain;
@@ -205,7 +206,7 @@ CreateCLI::CreateCLI (int argc, char* argv[])
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);
+ argument_option(i, argc, argv, "", "--video-bit-rate", &claimed, &error, &video_bit_rate_int);
std::function<optional<dcp::Channel> (string)> convert_channel = [](string channel) -> optional<dcp::Channel>{
if (channel == "L") {
@@ -271,8 +272,8 @@ CreateCLI::CreateCLI (int argc, char* argv[])
dcp_frame_rate = dcp_frame_rate_int;
}
- if (j2k_bandwidth_int) {
- _j2k_bandwidth = j2k_bandwidth_int * 1000000;
+ if (video_bit_rate_int) {
+ _video_bit_rate = video_bit_rate_int * 1000000;
}
if (dcp_content_type_string) {
@@ -319,8 +320,8 @@ CreateCLI::CreateCLI (int argc, char* argv[])
_name = content[0].path.filename().string();
}
- 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));
+ if (_video_bit_rate && (*_video_bit_rate < 10000000 || *_video_bit_rate > Config::instance()->maximum_video_bit_rate(VideoEncoding::JPEG2000))) {
+ error = String::compose("%1: video-bit-rate must be between 10 and %2 Mbit/s", argv[0], (Config::instance()->maximum_video_bit_rate(VideoEncoding::JPEG2000) / 1000000));
return;
}
}
@@ -371,8 +372,8 @@ CreateCLI::make_film() const
if (_fourk) {
film->set_resolution(Resolution::FOUR_K);
}
- if (_j2k_bandwidth) {
- film->set_j2k_bandwidth(*_j2k_bandwidth);
+ if (_video_bit_rate) {
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, *_video_bit_rate);
}
int channels = 6;
diff --git a/src/lib/create_cli.h b/src/lib/create_cli.h
index 782aaf974..850cddea9 100644
--- a/src/lib/create_cli.h
+++ b/src/lib/create_cli.h
@@ -72,7 +72,7 @@ private:
bool _no_use_isdcf_name = false;
bool _twok = false;
bool _fourk = false;
- boost::optional<int> _j2k_bandwidth;
+ boost::optional<int64_t> _video_bit_rate;
static std::string _help;
};
diff --git a/src/lib/crop.cc b/src/lib/crop.cc
index d2d020a9f..a6f7d4bac 100644
--- a/src/lib/crop.cc
+++ b/src/lib/crop.cc
@@ -42,12 +42,12 @@ Crop::Crop(shared_ptr<cxml::Node> node)
void
-Crop::as_xml(xmlpp::Node* node) const
+Crop::as_xml(xmlpp::Element* element) const
{
- node->add_child("LeftCrop")->add_child_text(raw_convert<string>(left));
- node->add_child("RightCrop")->add_child_text(raw_convert<string>(right));
- node->add_child("TopCrop")->add_child_text(raw_convert<string>(top));
- node->add_child("BottomCrop")->add_child_text(raw_convert<string>(bottom));
+ cxml::add_text_child(element, "LeftCrop", raw_convert<string>(left));
+ cxml::add_text_child(element, "RightCrop", raw_convert<string>(right));
+ cxml::add_text_child(element, "TopCrop", raw_convert<string>(top));
+ cxml::add_text_child(element, "BottomCrop", raw_convert<string>(bottom));
}
diff --git a/src/lib/crop.h b/src/lib/crop.h
index 00734d5e9..24330731d 100644
--- a/src/lib/crop.h
+++ b/src/lib/crop.h
@@ -65,7 +65,7 @@ struct Crop
return s;
}
- void as_xml (xmlpp::Node *) const;
+ void as_xml(xmlpp::Element*) const;
};
diff --git a/src/lib/cross.h b/src/lib/cross.h
index 6904811b7..150199561 100644
--- a/src/lib/cross.h
+++ b/src/lib/cross.h
@@ -138,29 +138,19 @@ private:
void disk_write_finished ();
-struct OSXMediaPath
-{
- bool real; ///< true for a "real" disk, false for a synthesized APFS one
- std::vector<std::string> parts; ///< parts of the media path after the :
-};
-
-
struct OSXDisk
{
std::string device;
boost::optional<std::string> vendor;
boost::optional<std::string> model;
- OSXMediaPath media_path;
- bool whole;
std::vector<boost::filesystem::path> mount_points;
unsigned long size;
+ bool system;
+ bool writeable;
+ bool partition;
};
-boost::optional<OSXMediaPath> analyse_osx_media_path (std::string path);
-std::vector<Drive> osx_disks_to_drives (std::vector<OSXDisk> disks);
-
-
class ArgFixer
{
public:
diff --git a/src/lib/cross_common.cc b/src/lib/cross_common.cc
index b4d322096..1e145c8fc 100644
--- a/src/lib/cross_common.cc
+++ b/src/lib/cross_common.cc
@@ -40,9 +40,6 @@ using std::vector;
using boost::optional;
-auto constexpr MEDIA_PATH_REQUIRED_MATCHES = 3;
-
-
Drive::Drive (string xml)
{
cxml::Document doc;
@@ -62,16 +59,16 @@ Drive::as_xml () const
{
xmlpp::Document doc;
auto root = doc.create_root_node ("Drive");
- root->add_child("Device")->add_child_text(_device);
+ cxml::add_text_child(root, "Device", _device);
for (auto i: _mount_points) {
- root->add_child("MountPoint")->add_child_text(i.string());
+ cxml::add_text_child(root, "MountPoint", i.string());
}
- root->add_child("Size")->add_child_text(dcp::raw_convert<string>(_size));
+ cxml::add_text_child(root, "Size", dcp::raw_convert<string>(_size));
if (_vendor) {
- root->add_child("Vendor")->add_child_text(*_vendor);
+ cxml::add_text_child(root, "Vendor", *_vendor);
}
if (_model) {
- root->add_child("Model")->add_child_text(*_model);
+ cxml::add_text_child(root, "Model", *_model);
}
return doc.write_to_string("UTF-8");
@@ -122,97 +119,3 @@ Drive::log_summary () const
);
}
-
-
-/* This is in _common so we can use it in unit tests */
-optional<OSXMediaPath>
-analyse_osx_media_path (string path)
-{
- if (path.find("/IOHDIXController") != string::npos) {
- /* This is a disk image, so we completely ignore it */
- LOG_DISK_NC("Ignoring this as it seems to be a disk image");
- return {};
- }
-
- OSXMediaPath mp;
- vector<string> parts;
- split(parts, path, boost::is_any_of("/"));
- std::copy(parts.begin() + 1, parts.end(), back_inserter(mp.parts));
-
- if (!parts.empty() && parts[0] == "IODeviceTree:") {
- mp.real = true;
- if (mp.parts.size() < MEDIA_PATH_REQUIRED_MATCHES) {
- /* Later we expect at least MEDIA_PATH_REQUIRED_MATCHES parts in a IODeviceTree */
- LOG_DISK_NC("Ignoring this as it has a strange media path");
- return {};
- }
- } else if (!parts.empty() && parts[0] == "IOService:") {
- mp.real = false;
- } else {
- return {};
- }
-
- return mp;
-}
-
-
-/* Take some OSXDisk objects, representing disks that `DARegisterDiskAppearedCallback` told us about,
- * and find those drives that we could write a DCP to. The drives returned are "real" (not synthesized)
- * and are whole disks (not partitions). They may be mounted, or contain mounted partitions, in which
- * their mounted() method will return true.
- */
-vector<Drive>
-osx_disks_to_drives (vector<OSXDisk> disks)
-{
- using namespace boost::algorithm;
-
- /* Mark disks containing mounted partitions as themselves mounted */
- for (auto& i: disks) {
- if (!i.whole) {
- continue;
- }
- for (auto& j: disks) {
- 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));
- }
- }
- }
-
- /* Mark containers of mounted synths as themselves mounted */
- for (auto& i: disks) {
- if (i.media_path.real) {
- for (auto& j: disks) {
- if (!j.media_path.real && !j.mount_points.empty()) {
- /* i is real, j is a mounted synth; if we see the first MEDIA_PATH_REQUIRED_MATCHES parts
- * of i anywhere in j we assume they are related and so i shares j's mount points.
- */
- bool one_missing = false;
- string all_parts;
- DCPOMATIC_ASSERT (i.media_path.parts.size() >= MEDIA_PATH_REQUIRED_MATCHES);
- for (auto k = 0; k < MEDIA_PATH_REQUIRED_MATCHES; ++k) {
- if (find(j.media_path.parts.begin(), j.media_path.parts.end(), i.media_path.parts[k]) == j.media_path.parts.end()) {
- one_missing = true;
- }
- all_parts += i.media_path.parts[k] + " ";
- }
-
- if (!one_missing) {
- LOG_DISK("Marking %1 as mounted because %2 is (found %3)", i.device, j.device, all_parts);
- std::copy(j.mount_points.begin(), j.mount_points.end(), back_inserter(i.mount_points));
- }
- }
- }
- }
- }
-
- vector<Drive> drives;
- for (auto const& i: disks) {
- if (i.whole && i.media_path.real) {
- drives.push_back(Drive(i.device, i.mount_points, i.size, i.vendor, i.model));
- LOG_DISK_NC(drives.back().log_summary());
- }
- }
-
- return drives;
-}
diff --git a/src/lib/cross_linux.cc b/src/lib/cross_linux.cc
index 015158aa8..be3233b32 100644
--- a/src/lib/cross_linux.cc
+++ b/src/lib/cross_linux.cc
@@ -85,7 +85,12 @@ cpu_info ()
boost::filesystem::path
resources_path ()
{
- return directory_containing_executable().parent_path() / "share" / "dcpomatic2";
+ auto installed = directory_containing_executable().parent_path() / "share" / "dcpomatic2";
+ if (boost::filesystem::exists(installed)) {
+ return installed;
+ }
+
+ return directory_containing_executable().parent_path().parent_path().parent_path();
}
diff --git a/src/lib/cross_osx.cc b/src/lib/cross_osx.cc
index 913b19103..6df2f959e 100644
--- a/src/lib/cross_osx.cc
+++ b/src/lib/cross_osx.cc
@@ -19,12 +19,13 @@
*/
-#include "cross.h"
#include "compose.hpp"
-#include "log.h"
-#include "dcpomatic_log.h"
#include "config.h"
+#include "cross.h"
+#include "dcpomatic_log.h"
#include "exceptions.h"
+#include "log.h"
+#include "variant.h"
#include <dcp/filesystem.h>
#include <dcp/raw_convert.h>
#include <glib.h>
@@ -168,6 +169,8 @@ Waker::~Waker ()
void
start_tool (string executable, string app)
{
+ boost::algorithm::replace_all(app, " ", "\\ ");
+
auto exe_path = directory_containing_executable();
exe_path = exe_path.parent_path(); // Contents
exe_path = exe_path.parent_path(); // DCP-o-matic 2.app
@@ -191,14 +194,14 @@ start_tool (string executable, string app)
void
start_batch_converter ()
{
- start_tool ("dcpomatic2_batch", "DCP-o-matic\\ 2\\ Batch\\ Converter.app");
+ start_tool("dcpomatic2_batch", variant::dcpomatic_batch_converter_app());
}
void
start_player ()
{
- start_tool ("dcpomatic2_player", "DCP-o-matic\\ 2\\ Player.app");
+ start_tool("dcpomatic2_player", variant::dcpomatic_player_app());
}
@@ -240,44 +243,6 @@ get_model (CFDictionaryRef& description)
}
-static optional<OSXMediaPath>
-analyse_media_path (CFDictionaryRef& description)
-{
- using namespace boost::algorithm;
-
- void const* str = CFDictionaryGetValue (description, kDADiskDescriptionMediaPathKey);
- if (!str) {
- LOG_DISK_NC("There is no MediaPathKey (no dictionary value)");
- return {};
- }
-
- auto path_key_cstr = CFStringGetCStringPtr((CFStringRef) str, kCFStringEncodingUTF8);
- if (!path_key_cstr) {
- LOG_DISK_NC("There is no MediaPathKey (no cstring)");
- return {};
- }
-
- string path(path_key_cstr);
- LOG_DISK("MediaPathKey is %1", path);
- return analyse_osx_media_path (path);
-}
-
-
-static bool
-is_whole_drive (DADiskRef& disk)
-{
- io_service_t service = DADiskCopyIOMedia (disk);
- CFTypeRef whole_media_ref = IORegistryEntryCreateCFProperty (service, CFSTR(kIOMediaWholeKey), kCFAllocatorDefault, 0);
- bool whole_media = false;
- if (whole_media_ref) {
- whole_media = CFBooleanGetValue((CFBooleanRef) whole_media_ref);
- CFRelease (whole_media_ref);
- }
- IOObjectRelease (service);
- return whole_media;
-}
-
-
static optional<boost::filesystem::path>
mount_point (CFDictionaryRef& description)
{
@@ -294,29 +259,17 @@ mount_point (CFDictionaryRef& description)
}
-/* Here follows some rather intricate and (probably) fragile code to find the list of available
- * "real" drives on macOS that we might want to write a DCP to.
- *
- * We use the Disk Arbitration framework to give us a series of mount_points (/dev/disk0, /dev/disk1,
- * /dev/disk1s1 and so on) and we use the API to gather useful information about these mount_points into
- * a vector of Disk structs.
- *
- * Then we read the Disks that we found and try to derive a list of drives that we should offer to the
- * user, with details of whether those drives are currently mounted or not.
- *
- * At the basic level we find the "disk"-level mount_points, looking at whether any of their partitions are mounted.
- *
- * This is complicated enormously by recent-ish macOS versions' habit of making `synthesized' volumes which
- * reflect data in `real' partitions. So, for example, we might have a real (physical) drive /dev/disk2 with
- * a partition /dev/disk2s2 whose content is made into a synthesized /dev/disk3, itself containing some partitions
- * which are mounted. /dev/disk2s2 is not considered to be mounted, in this case. So we need to know that
- * disk2s2 is related to disk3 so we can consider disk2s2 as mounted if any parts of disk3 are. In order to do
- * this I am taking the first two parts of the IODeviceTree and seeing if they exist anywhere in a
- * IOService identifier. If they do, I am assuming the IOService device is on the matching IODeviceTree device.
- *
- * Lots of this is guesswork and may be broken. In my defence the documentation that I have been able to
- * unearth is, to put it impolitely, crap.
- */
+static bool
+get_bool(CFDictionaryRef& description, void const* key)
+{
+ auto value = CFDictionaryGetValue(description, key);
+ if (!value) {
+ return false;
+ }
+
+ return CFBooleanGetValue(reinterpret_cast<CFBooleanRef>(value));
+}
+
static void
disk_appeared (DADiskRef disk, void* context)
@@ -339,32 +292,30 @@ disk_appeared (DADiskRef disk, void* context)
this_disk.model = get_model (description);
LOG_DISK("Vendor/model: %1 %2", this_disk.vendor.get_value_or("[none]"), this_disk.model.get_value_or("[none]"));
- auto media_path = analyse_media_path (description);
- if (!media_path) {
- LOG_DISK("Finding media path for %1 failed", bsd_name);
- return;
- }
-
- this_disk.media_path = *media_path;
- this_disk.whole = is_whole_drive (disk);
auto mp = mount_point (description);
if (mp) {
this_disk.mount_points.push_back (*mp);
}
- LOG_DISK(
- "%1 %2 mounted at %3",
- this_disk.media_path.real ? "Real" : "Synth",
- this_disk.whole ? "whole" : "part",
- mp ? mp->string() : "[nowhere]"
- );
-
auto media_size_cstr = CFDictionaryGetValue (description, kDADiskDescriptionMediaSizeKey);
if (!media_size_cstr) {
LOG_DISK_NC("Could not read media size");
return;
}
+ this_disk.system = get_bool(description, kDADiskDescriptionDeviceInternalKey) && !get_bool(description, kDADiskDescriptionMediaRemovableKey);
+ this_disk.writeable = get_bool(description, kDADiskDescriptionMediaWritableKey);
+ this_disk.partition = string(bsd_name).find("s", 5) != std::string::npos;
+
+ LOG_DISK(
+ "%1 %2 %3 %4 mounted at %5",
+ bsd_name,
+ this_disk.system ? "system" : "non-system",
+ this_disk.writeable ? "writeable" : "read-only",
+ this_disk.partition ? "partition" : "drive",
+ mp ? mp->string() : "[nowhere]"
+ );
+
CFNumberGetValue ((CFNumberRef) media_size_cstr, kCFNumberLongType, &this_disk.size);
CFRelease (description);
@@ -395,7 +346,12 @@ Drive::get ()
DAUnregisterCallback(session, (void *) disk_appeared, &disks);
CFRelease(session);
- auto drives = osx_disks_to_drives(disks);
+ vector<Drive> drives;
+ for (auto const& disk: disks) {
+ if (!disk.system && !disk.partition && disk.writeable) {
+ drives.push_back({disk.device, disk.mount_points, disk.size, disk.vendor, disk.model});
+ }
+ }
LOG_DISK("Drive::get() found %1 drives:", drives.size());
for (auto const& drive: drives) {
diff --git a/src/lib/dcp_content.cc b/src/lib/dcp_content.cc
index 6e573c639..b4e979481 100644
--- a/src/lib/dcp_content.cc
+++ b/src/lib/dcp_content.cc
@@ -139,6 +139,11 @@ DCPContent::DCPContent (cxml::ConstNodePtr node, int version)
DCPOMATIC_ASSERT (false);
}
}
+
+ if (auto encoding = node->optional_string_child("VideoEncoding")) {
+ _video_encoding = video_encoding_from_string(*encoding);
+ }
+
_three_d = node->optional_bool_child("ThreeD").get_value_or (false);
auto ck = node->optional_string_child("ContentKind");
@@ -163,6 +168,13 @@ DCPContent::DCPContent (cxml::ConstNodePtr node, int version)
}
_active_audio_channels = node->optional_number_child<int>("ActiveAudioChannels");
+
+ for (auto non_zero: node->node_children("HasNonZeroEntryPoint")) {
+ try {
+ auto type = string_to_text_type(non_zero->string_attribute("type"));
+ _has_non_zero_entry_point[type] = non_zero->content() == "1";
+ } catch (MetadataError&) {}
+ }
}
void
@@ -299,6 +311,7 @@ DCPContent::examine (shared_ptr<const Film> film, shared_ptr<Job> job)
_needs_assets = examiner->needs_assets ();
_kdm_valid = examiner->kdm_valid ();
_standard = examiner->standard ();
+ _video_encoding = examiner->video_encoding();
_three_d = examiner->three_d ();
_content_kind = examiner->content_kind ();
_cpl = examiner->cpl ();
@@ -308,6 +321,7 @@ DCPContent::examine (shared_ptr<const Film> film, shared_ptr<Job> job)
}
_ratings = examiner->ratings ();
_content_versions = examiner->content_versions ();
+ _has_non_zero_entry_point = examiner->has_non_zero_entry_point();
}
if (needed_assets == needs_assets()) {
@@ -347,88 +361,99 @@ DCPContent::technical_summary () const
return s;
}
+
void
-DCPContent::as_xml (xmlpp::Node* node, bool with_paths) const
+DCPContent::as_xml(xmlpp::Element* element, bool with_paths) const
{
- node->add_child("Type")->add_child_text ("DCP");
+ cxml::add_text_child(element, "Type", "DCP");
- Content::as_xml (node, with_paths);
+ Content::as_xml(element, with_paths);
if (video) {
- video->as_xml (node);
+ video->as_xml(element);
}
if (audio) {
- audio->as_xml (node);
- node->add_child("AudioFrameRate")->add_child_text (raw_convert<string> (audio->stream()->frame_rate()));
- node->add_child("AudioLength")->add_child_text (raw_convert<string> (audio->stream()->length()));
- audio->stream()->mapping().as_xml (node->add_child("AudioMapping"));
+ audio->as_xml(element);
+ cxml::add_text_child(element, "AudioFrameRate", raw_convert<string>(audio->stream()->frame_rate()));
+ cxml::add_text_child(element, "AudioLength", raw_convert<string>(audio->stream()->length()));
+ audio->stream()->mapping().as_xml(cxml::add_child(element, "AudioMapping"));
}
for (auto i: text) {
- i->as_xml (node);
+ i->as_xml(element);
}
if (atmos) {
- atmos->as_xml (node);
+ atmos->as_xml(element);
}
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");
+ cxml::add_text_child(element, "Name", _name);
+ cxml::add_text_child(element, "Encrypted", _encrypted ? "1" : "0");
+ cxml::add_text_child(element, "NeedsAssets", _needs_assets ? "1" : "0");
if (_kdm) {
- node->add_child("KDM")->add_child_text (_kdm->as_xml ());
+ cxml::add_text_child(element, "KDM", _kdm->as_xml());
}
- 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[TextType::OPEN_SUBTITLE] ? "1" : "0");
- node->add_child("ReferenceClosedCaption")->add_child_text(_reference_text[TextType::CLOSED_CAPTION] ? "1" : "0");
+ cxml::add_text_child(element, "KDMValid", _kdm_valid ? "1" : "0");
+ cxml::add_text_child(element, "ReferenceVideo", _reference_video ? "1" : "0");
+ cxml::add_text_child(element, "ReferenceAudio", _reference_audio ? "1" : "0");
+ cxml::add_text_child(element, "ReferenceOpenSubtitle", _reference_text[TextType::OPEN_SUBTITLE] ? "1" : "0");
+ cxml::add_text_child(element, "ReferenceClosedCaption", _reference_text[TextType::CLOSED_CAPTION] ? "1" : "0");
if (_standard) {
switch (_standard.get ()) {
case dcp::Standard::INTEROP:
- node->add_child("Standard")->add_child_text ("Interop");
+ cxml::add_text_child(element, "Standard", "Interop");
break;
case dcp::Standard::SMPTE:
- node->add_child("Standard")->add_child_text ("SMPTE");
+ cxml::add_text_child(element, "Standard", "SMPTE");
break;
default:
DCPOMATIC_ASSERT (false);
}
}
- node->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0");
+ cxml::add_text_child(element, "VideoEncoding", video_encoding_to_string(_video_encoding));
+ cxml::add_text_child(element, "ThreeD", _three_d ? "1" : "0");
if (_content_kind) {
- node->add_child("ContentKind")->add_child_text(_content_kind->name());
+ cxml::add_text_child(element, "ContentKind", _content_kind->name());
}
if (_cpl) {
- node->add_child("CPL")->add_child_text (_cpl.get ());
+ cxml::add_text_child(element, "CPL", _cpl.get());
}
for (auto i: _reel_lengths) {
- node->add_child("ReelLength")->add_child_text (raw_convert<string> (i));
+ cxml::add_text_child(element, "ReelLength", raw_convert<string>(i));
}
for (auto const& i: _markers) {
- auto marker = node->add_child("Marker");
+ auto marker = cxml::add_child(element, "Marker");
marker->set_attribute("type", dcp::marker_to_string(i.first));
marker->add_child_text(raw_convert<string>(i.second.get()));
}
for (auto i: _ratings) {
- auto rating = node->add_child("Rating");
+ auto rating = cxml::add_child(element, "Rating");
i.as_xml (rating);
}
for (auto i: _content_versions) {
- node->add_child("ContentVersion")->add_child_text(i);
+ cxml::add_text_child(element, "ContentVersion", i);
}
if (_active_audio_channels) {
- node->add_child("ActiveAudioChannels")->add_child_text(raw_convert<string>(*_active_audio_channels));
+ cxml::add_text_child(element, "ActiveAudioChannels", raw_convert<string>(*_active_audio_channels));
+ }
+
+ for (auto i = 0; i < static_cast<int>(TextType::COUNT); ++i) {
+ if (_has_non_zero_entry_point[i]) {
+ auto has = cxml::add_child(element, "HasNonZeroEntryPoint");
+ has->add_child_text("1");
+ has->set_attribute("type", text_type_to_string(static_cast<TextType>(i)));
+ }
}
}
+
DCPTime
DCPContent::full_length (shared_ptr<const Film> film) const
{
@@ -612,7 +637,7 @@ DCPContent::reel_split_points (shared_ptr<const Film> film) const
}
bool
-DCPContent::can_reference (shared_ptr<const Film> film, function<bool (shared_ptr<const Content>)> part, string overlapping, string& why_not) const
+DCPContent::can_reference_anything(shared_ptr<const Film> film, string& why_not) const
{
/* We must be using the same standard as the film */
if (_standard) {
@@ -658,15 +683,16 @@ DCPContent::can_reference (shared_ptr<const Film> film, function<bool (shared_pt
}
}
- auto a = overlaps (film, film->content(), part, position(), end(film));
- if (a.size() != 1 || a.front().get() != this) {
- why_not = overlapping;
- return false;
- }
-
return true;
}
+bool
+DCPContent::overlaps(shared_ptr<const Film> film, function<bool (shared_ptr<const Content>)> part) const
+{
+ auto const a = dcpomatic::overlaps(film, film->content(), part, position(), end(film));
+ return a.size() != 1 || a.front().get() != this;
+}
+
bool
DCPContent::can_reference_video (shared_ptr<const Film> film, string& why_not) const
@@ -691,35 +717,23 @@ DCPContent::can_reference_video (shared_ptr<const Film> film, string& why_not) c
return false;
}
- /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
- return can_reference(
- film,
- [](shared_ptr<const Content> c) {
- return static_cast<bool>(c->video) && c->video->use();
- },
- _("it overlaps other video content; remove the other content."),
- why_not
- );
+ auto part = [](shared_ptr<const Content> c) {
+ return static_cast<bool>(c->video) && c->video->use();
+ };
+
+ if (overlaps(film, part)) {
+ /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
+ why_not = _("it overlaps other video content.");
+ return false;
+ }
+
+ return can_reference_anything(film, why_not);
}
bool
DCPContent::can_reference_audio (shared_ptr<const Film> film, string& why_not) const
{
- shared_ptr<DCPDecoder> decoder;
- try {
- decoder = make_shared<DCPDecoder>(film, shared_from_this(), false, film->tolerant(), shared_ptr<DCPDecoder>());
- } 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;
- }
-
if (audio && audio->stream()) {
auto const channels = audio->stream()->channels();
if (channels != film->audio_channels()) {
@@ -728,51 +742,33 @@ DCPContent::can_reference_audio (shared_ptr<const Film> film, string& why_not) c
}
}
- /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
- return can_reference(
- film, [](shared_ptr<const Content> c) {
- return c->has_mapped_audio();
- },
- _("it overlaps other audio content; remove the other content."),
- why_not
- );
+ auto part = [](shared_ptr<const Content> c) {
+ return c->has_mapped_audio();
+ };
+
+ if (overlaps(film, part)) {
+ /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
+ why_not = _("it overlaps other audio content.");
+ return false;
+ }
+
+ return can_reference_anything(film, why_not);
}
bool
DCPContent::can_reference_text (shared_ptr<const Film> film, TextType type, string& why_not) const
{
- shared_ptr<DCPDecoder> decoder;
- try {
- decoder = make_shared<DCPDecoder>(film, shared_from_this(), false, film->tolerant(), shared_ptr<DCPDecoder>());
- } 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 */
+ if (_has_non_zero_entry_point[TextType::OPEN_SUBTITLE]) {
+ /// 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;
}
- for (auto i: decoder->reels()) {
- if (type == TextType::OPEN_SUBTITLE) {
- 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) {
- for (auto j: i->closed_captions()) {
- if (j->entry_point().get_value_or(0) != 0) {
- /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
- why_not = _("one of its closed caption has a non-zero entry point so it must be re-written.");
- return false;
- }
- }
- }
+ if (_has_non_zero_entry_point[TextType::CLOSED_CAPTION]) {
+ /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
+ why_not = _("one of its closed caption has a non-zero entry point so it must be re-written.");
+ return false;
}
if (trim_start() != dcpomatic::ContentTime()) {
@@ -781,15 +777,17 @@ DCPContent::can_reference_text (shared_ptr<const Film> film, TextType type, stri
return false;
}
- /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
- return can_reference(
- film,
- [type](shared_ptr<const Content> c) {
- return std::find_if(c->text.begin(), c->text.end(), [type](shared_ptr<const TextContent> t) { return t->type() == type; }) != c->text.end();
- },
- _("they overlap other text content; remove the other content."),
- why_not
- );
+ auto part = [type](shared_ptr<const Content> c) {
+ return std::find_if(c->text.begin(), c->text.end(), [type](shared_ptr<const TextContent> t) { return t->type() == type; }) != c->text.end();
+ };
+
+ if (overlaps(film, part)) {
+ /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
+ why_not = _("it overlaps other text content.");
+ return false;
+ }
+
+ return can_reference_anything(film, why_not);
}
void
@@ -853,8 +851,14 @@ DCPContent::check_font_ids()
return;
}
+ /* This might be called on a TextContent that already has some fonts
+ * (e.g. if run from a build with a LastWrittenBy containing only a git
+ * hash, or from a version between 2.16.15 and 2.17.17) so we'll get an
+ * error if we don't clear them out first.
+ */
+ text[0]->clear_fonts();
DCPExaminer examiner(shared_from_this(), true);
- examiner.add_fonts(text.front());
+ examiner.add_fonts(text[0]);
}
diff --git a/src/lib/dcp_content.h b/src/lib/dcp_content.h
index 3753740a2..be2c72002 100644
--- a/src/lib/dcp_content.h
+++ b/src/lib/dcp_content.h
@@ -32,6 +32,7 @@
#include "enum_indexed_vector.h"
#include "font.h"
#include "resolution.h"
+#include "video_encoding.h"
#include <libcxml/cxml.h>
#include <dcp/content_kind.h>
#include <dcp/encrypted_kdm.h>
@@ -75,7 +76,7 @@ public:
void examine (std::shared_ptr<const Film> film, std::shared_ptr<Job>) override;
std::string summary () const override;
std::string technical_summary () const override;
- void as_xml (xmlpp::Node *, bool with_paths) const override;
+ void as_xml(xmlpp::Element*, bool with_paths) const override;
std::string identifier () const override;
void take_settings_from (std::shared_ptr<const Content> c) override;
@@ -100,6 +101,8 @@ public:
bool needs_kdm () const;
bool needs_assets () const;
+ bool can_reference_anything(std::shared_ptr<const Film> film, std::string& why_not) const;
+
void set_reference_video (bool r);
bool reference_video () const {
@@ -158,6 +161,11 @@ public:
return _standard.get ();
}
+ VideoEncoding video_encoding() const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _video_encoding;
+ }
+
std::map<dcp::Marker, dcpomatic::ContentTime> markers () const {
return _markers;
}
@@ -186,12 +194,7 @@ private:
void read_directory (boost::filesystem::path);
void read_sub_directory (boost::filesystem::path);
std::list<dcpomatic::DCPTimePeriod> reels (std::shared_ptr<const Film> film) const;
- bool can_reference (
- std::shared_ptr<const Film> film,
- std::function <bool (std::shared_ptr<const Content>)>,
- std::string overlapping,
- std::string& why_not
- ) const;
+ bool overlaps(std::shared_ptr<const Film> film, std::function<bool (std::shared_ptr<const Content>)> part) const;
std::string _name;
/** true if our DCP is encrypted */
@@ -216,6 +219,7 @@ private:
EnumIndexedVector<bool, TextType> _reference_text;
boost::optional<dcp::Standard> _standard;
+ VideoEncoding _video_encoding;
boost::optional<dcp::ContentKind> _content_kind;
bool _three_d;
/** ID of the CPL to use; older metadata might not specify this: in that case
@@ -227,6 +231,7 @@ private:
std::map<dcp::Marker, dcpomatic::ContentTime> _markers;
std::vector<dcp::Rating> _ratings;
std::vector<std::string> _content_versions;
+ EnumIndexedVector<bool, TextType> _has_non_zero_entry_point;
boost::optional<int> _active_audio_channels;
};
diff --git a/src/lib/dcp_content_type.cc b/src/lib/dcp_content_type.cc
index bc9bf0d67..d689f138c 100644
--- a/src/lib/dcp_content_type.cc
+++ b/src/lib/dcp_content_type.cc
@@ -28,8 +28,9 @@
#include "i18n.h"
+using std::string;
+using std::vector;
using boost::optional;
-using namespace std;
vector<DCPContentType> DCPContentType::_dcp_content_types;
diff --git a/src/lib/dcp_decoder.cc b/src/lib/dcp_decoder.cc
index 727dcbf26..abbaaf15d 100644
--- a/src/lib/dcp_decoder.cc
+++ b/src/lib/dcp_decoder.cc
@@ -26,19 +26,23 @@
#include "constants.h"
#include "dcp_content.h"
#include "dcp_decoder.h"
+#include "dcpomatic_log.h"
#include "digester.h"
#include "ffmpeg_image_proxy.h"
#include "frame_interval_checker.h"
#include "image.h"
#include "j2k_image_proxy.h"
+#include "raw_image_proxy.h"
#include "text_decoder.h"
+#include "util.h"
#include "video_decoder.h"
#include <dcp/cpl.h>
#include <dcp/dcp.h>
#include <dcp/decrypted_kdm.h>
-#include <dcp/mono_picture_asset.h>
-#include <dcp/mono_picture_asset_reader.h>
-#include <dcp/mono_picture_frame.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/mono_j2k_picture_asset_reader.h>
+#include <dcp/mono_j2k_picture_frame.h>
+#include <dcp/mono_mpeg2_picture_asset.h>
#include <dcp/reel.h>
#include <dcp/reel_atmos_asset.h>
#include <dcp/reel_closed_caption_asset.h>
@@ -48,9 +52,9 @@
#include <dcp/search.h>
#include <dcp/sound_asset_reader.h>
#include <dcp/sound_frame.h>
-#include <dcp/stereo_picture_asset.h>
-#include <dcp/stereo_picture_asset_reader.h>
-#include <dcp/stereo_picture_frame.h>
+#include <dcp/stereo_j2k_picture_asset.h>
+#include <dcp/stereo_j2k_picture_asset_reader.h>
+#include <dcp/stereo_j2k_picture_frame.h>
#include <dcp/subtitle_image.h>
#include <iostream>
@@ -169,43 +173,60 @@ DCPDecoder::pass ()
*/
pass_texts (_next, picture_asset->size());
- if ((_mono_reader || _stereo_reader) && (_decode_referenced || !_dcp_content->reference_video())) {
+ if ((_j2k_mono_reader || _j2k_stereo_reader || _mpeg2_mono_reader) && (_decode_referenced || !_dcp_content->reference_video())) {
auto const entry_point = (*_reel)->main_picture()->entry_point().get_value_or(0);
- if (_mono_reader) {
+ if (_j2k_mono_reader) {
video->emit (
film(),
std::make_shared<J2KImageProxy>(
- _mono_reader->get_frame (entry_point + frame),
+ _j2k_mono_reader->get_frame(entry_point + frame),
picture_asset->size(),
AV_PIX_FMT_XYZ12LE,
_forced_reduction
),
- _offset + frame
+ ContentTime::from_frames(_offset + frame, vfr)
);
- } else {
+ } else if (_j2k_stereo_reader) {
video->emit (
film(),
std::make_shared<J2KImageProxy>(
- _stereo_reader->get_frame (entry_point + frame),
+ _j2k_stereo_reader->get_frame (entry_point + frame),
picture_asset->size(),
dcp::Eye::LEFT,
AV_PIX_FMT_XYZ12LE,
_forced_reduction
),
- _offset + frame
+ ContentTime::from_frames(_offset + frame, vfr)
);
video->emit (
film(),
std::make_shared<J2KImageProxy>(
- _stereo_reader->get_frame (entry_point + frame),
+ _j2k_stereo_reader->get_frame (entry_point + frame),
picture_asset->size(),
dcp::Eye::RIGHT,
AV_PIX_FMT_XYZ12LE,
_forced_reduction
),
- _offset + frame
+ ContentTime::from_frames(_offset + frame, vfr)
);
+ } else if (_mpeg2_mono_reader) {
+ /* XXX: got to flush this at some point */
+ try {
+ for (auto const& image: _mpeg2_decompressor->decompress_frame(_mpeg2_mono_reader->get_frame(entry_point + frame))) {
+ video->emit(
+ film(),
+ /* XXX: should this be PADDED? */
+ std::make_shared<RawImageProxy>(std::make_shared<Image>(image.frame(), Image::Alignment::COMPACT)),
+ /* XXX: this will be wrong */
+ ContentTime::from_frames(_offset + frame, vfr)
+ );
+ }
+ } catch (dcp::MPEG2DecompressionError& e) {
+ LOG_ERROR("Failed to decompress MPEG video frame %1 (%2)", entry_point + frame, e.what());
+ } catch (dcp::ReadError& e) {
+ LOG_ERROR("Failed to read MPEG2 video frame %1 (%2)", entry_point + frame, e.what());
+ }
}
}
@@ -214,15 +235,32 @@ DCPDecoder::pass ()
auto sf = _sound_reader->get_frame (entry_point + frame);
auto from = sf->data ();
- int const channels = _dcp_content->audio->stream()->channels ();
- int const frames = sf->size() / (3 * channels);
+ int const channels = _dcp_content->audio->stream()->channels();
+ int const frames = sf->size() / (sf->bits() * channels / 8);
auto data = make_shared<AudioBuffers>(channels, frames);
auto data_data = data->data();
- for (int i = 0; i < frames; ++i) {
- for (int j = 0; j < channels; ++j) {
- data_data[j][i] = static_cast<int> ((from[0] << 8) | (from[1] << 16) | (from[2] << 24)) / static_cast<float> (INT_MAX - 256);
- from += 3;
+
+ switch (sf->bits()) {
+ case 24:
+ {
+ for (int i = 0; i < frames; ++i) {
+ for (int j = 0; j < channels; ++j) {
+ data_data[j][i] = static_cast<int>((from[0] << 8) | (from[1] << 16) | (from[2] << 24)) / static_cast<float> (INT_MAX - 256);
+ from += 3;
+ }
+ }
+ break;
+ }
+ case 16:
+ {
+ for (int i = 0; i < frames; ++i) {
+ for (int j = 0; j < channels; ++j) {
+ data_data[j][i] = static_cast<int>(from[0] << 16 | (from[1] << 24)) / static_cast<float> (INT_MAX - 256);
+ from += 2;
+ }
}
+ break;
+ }
}
audio->emit (film(), _dcp_content->audio->stream(), data, ContentTime::from_frames (_offset, vfr) + _next);
@@ -367,38 +405,40 @@ DCPDecoder::next_reel ()
void
DCPDecoder::get_readers ()
{
+ _j2k_mono_reader.reset();
+ _j2k_stereo_reader.reset();
+ _mpeg2_mono_reader.reset();
+ _sound_reader.reset();
+ _atmos_reader.reset();
+ _mpeg2_decompressor.reset();
+ _atmos_metadata = boost::none;
+
if (_reel == _reels.end() || !_dcp_content->can_be_played ()) {
- _mono_reader.reset ();
- _stereo_reader.reset ();
- _sound_reader.reset ();
- _atmos_reader.reset ();
return;
}
if (video && !video->ignore() && (*_reel)->main_picture()) {
auto asset = (*_reel)->main_picture()->asset ();
- auto mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (asset);
- auto stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (asset);
- DCPOMATIC_ASSERT (mono || stereo);
- if (mono) {
- _mono_reader = mono->start_read ();
- _mono_reader->set_check_hmac (false);
- _stereo_reader.reset ();
+ auto j2k_mono = dynamic_pointer_cast<dcp::MonoJ2KPictureAsset>(asset);
+ auto j2k_stereo = dynamic_pointer_cast<dcp::StereoJ2KPictureAsset>(asset);
+ auto mpeg2_mono = dynamic_pointer_cast<dcp::MonoMPEG2PictureAsset>(asset);
+ DCPOMATIC_ASSERT(j2k_mono || j2k_stereo || mpeg2_mono)
+ if (j2k_mono) {
+ _j2k_mono_reader = j2k_mono->start_read();
+ _j2k_mono_reader->set_check_hmac(false);
+ } else if (j2k_stereo) {
+ _j2k_stereo_reader = j2k_stereo->start_read();
+ _j2k_stereo_reader->set_check_hmac(false);
} else {
- _stereo_reader = stereo->start_read ();
- _stereo_reader->set_check_hmac (false);
- _mono_reader.reset ();
+ _mpeg2_mono_reader = mpeg2_mono->start_read();
+ _mpeg2_mono_reader->set_check_hmac(false);
+ _mpeg2_decompressor = std::make_shared<dcp::MPEG2Decompressor>();
}
- } else {
- _mono_reader.reset ();
- _stereo_reader.reset ();
}
if (audio && !audio->ignore() && (*_reel)->main_sound()) {
_sound_reader = (*_reel)->main_sound()->asset()->start_read ();
_sound_reader->set_check_hmac (false);
- } else {
- _sound_reader.reset ();
}
if ((*_reel)->atmos()) {
@@ -406,9 +446,6 @@ DCPDecoder::get_readers ()
_atmos_reader = asset->start_read();
_atmos_reader->set_check_hmac (false);
_atmos_metadata = AtmosMetadata (asset);
- } else {
- _atmos_reader.reset ();
- _atmos_metadata = boost::none;
}
}
diff --git a/src/lib/dcp_decoder.h b/src/lib/dcp_decoder.h
index 2c0cd8f41..ee0f30694 100644
--- a/src/lib/dcp_decoder.h
+++ b/src/lib/dcp_decoder.h
@@ -27,8 +27,10 @@
#include "atmos_metadata.h"
#include "decoder.h"
#include "font_id_allocator.h"
-#include <dcp/mono_picture_asset_reader.h>
-#include <dcp/stereo_picture_asset_reader.h>
+#include <dcp/mono_j2k_picture_asset_reader.h>
+#include <dcp/stereo_j2k_picture_asset_reader.h>
+#include <dcp/mono_mpeg2_picture_asset_reader.h>
+#include <dcp/mpeg2_transcode.h>
#include <dcp/sound_asset_reader.h>
#include <dcp/subtitle_asset.h>
@@ -94,15 +96,19 @@ private:
std::vector<std::shared_ptr<dcp::Reel>>::iterator _reel;
/** Offset of _reel from the start of the content in frames */
int64_t _offset = 0;
- /** Reader for current mono picture asset, if applicable */
- std::shared_ptr<dcp::MonoPictureAssetReader> _mono_reader;
- /** Reader for current stereo picture asset, if applicable */
- std::shared_ptr<dcp::StereoPictureAssetReader> _stereo_reader;
+ /** Reader for current J2K mono picture asset, if applicable */
+ std::shared_ptr<dcp::MonoJ2KPictureAssetReader> _j2k_mono_reader;
+ /** Reader for current J2K stereo picture asset, if applicable */
+ std::shared_ptr<dcp::StereoJ2KPictureAssetReader> _j2k_stereo_reader;
+ /** Reader for current MPEG2 mono picture asset, if applicable */
+ std::shared_ptr<dcp::MonoMPEG2PictureAssetReader> _mpeg2_mono_reader;
/** Reader for current sound asset, if applicable */
std::shared_ptr<dcp::SoundAssetReader> _sound_reader;
std::shared_ptr<dcp::AtmosAssetReader> _atmos_reader;
boost::optional<AtmosMetadata> _atmos_metadata;
+ std::shared_ptr<dcp::MPEG2Decompressor> _mpeg2_decompressor;
+
bool _decode_referenced = false;
boost::optional<int> _forced_reduction;
diff --git a/src/lib/dcp_digest_file.cc b/src/lib/dcp_digest_file.cc
index eb243b60d..4278caa98 100644
--- a/src/lib/dcp_digest_file.cc
+++ b/src/lib/dcp_digest_file.cc
@@ -42,14 +42,14 @@ template <class R, class A>
void add_asset(string film_key, shared_ptr<R> reel_asset, shared_ptr<A> asset, xmlpp::Element* reel, string name)
{
if (asset) {
- auto out = reel->add_child(name);
- out->add_child("Id")->add_child_text("urn:uuid:" + asset->id());
+ auto out = cxml::add_child(reel, name);
+ cxml::add_text_child(out, "Id", "urn:uuid:" + asset->id());
if (reel_asset->annotation_text()) {
- out->add_child("AnnotationText")->add_child_text(reel_asset->annotation_text().get());
+ cxml::add_text_child(out, "AnnotationText", reel_asset->annotation_text().get());
}
if (asset->key_id()) {
- out->add_child("KeyId")->add_child_text("urn:uuid:" + asset->key_id().get());
- out->add_child("Key")->add_child_text(asset->key() ? asset->key()->hex() : film_key);
+ cxml::add_text_child(out, "KeyId", "urn:uuid:" + asset->key_id().get());
+ cxml::add_text_child(out, "Key", asset->key() ? asset->key()->hex() : film_key);
}
}
};
@@ -64,16 +64,16 @@ write_dcp_digest_file (
{
xmlpp::Document doc;
auto root = doc.create_root_node("FHG_DCP_DIGEST", "http://www.fhg.de/2009/04/02/dcpdig");
- root->add_child("InteropMode")->add_child_text(cpl->standard() == dcp::Standard::INTEROP ? "true" : "false");
- auto composition = root->add_child("CompositionList")->add_child("Composition");
- composition->add_child("Id")->add_child_text("urn:uuid:" + cpl->id());
- composition->add_child("AnnotationText")->add_child_text(cpl->annotation_text().get_value_or(""));
- composition->add_child("ContentTitleText")->add_child_text(cpl->content_title_text());
- auto reel_list = composition->add_child("ReelList");
+ cxml::add_text_child(root, "InteropMode", cpl->standard() == dcp::Standard::INTEROP ? "true" : "false");
+ auto composition = cxml::add_child(cxml::add_child(root, "CompositionList"), "Composition");
+ cxml::add_text_child(composition, "Id", "urn:uuid:" + cpl->id());
+ cxml::add_text_child(composition, "AnnotationText", cpl->annotation_text().get_value_or(""));
+ cxml::add_text_child(composition, "ContentTitleText", cpl->content_title_text());
+ auto reel_list = cxml::add_child(composition, "ReelList");
for (auto in_reel: cpl->reels()) {
- auto out_reel = reel_list->add_child("Reel");
- out_reel->add_child("Id")->add_child_text("urn:uuid:" + in_reel->id());
- out_reel->add_child("AnnotationText");
+ auto out_reel = cxml::add_child(reel_list, "Reel");
+ cxml::add_text_child(out_reel, "Id", "urn:uuid:" + in_reel->id());
+ cxml::add_child(out_reel, "AnnotationText");
if (in_reel->main_picture()) {
add_asset(film_key, in_reel->main_picture(), in_reel->main_picture()->asset(), out_reel, "MainPicture");
}
diff --git a/src/lib/dcp_examiner.cc b/src/lib/dcp_examiner.cc
index ae5f9e9c0..a7af9feca 100644
--- a/src/lib/dcp_examiner.cc
+++ b/src/lib/dcp_examiner.cc
@@ -32,9 +32,11 @@
#include <dcp/cpl.h>
#include <dcp/dcp.h>
#include <dcp/decrypted_kdm.h>
-#include <dcp/mono_picture_asset.h>
-#include <dcp/mono_picture_asset_reader.h>
-#include <dcp/mono_picture_frame.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/mono_j2k_picture_asset_reader.h>
+#include <dcp/mono_j2k_picture_frame.h>
+#include <dcp/mono_mpeg2_picture_asset.h>
+#include <dcp/mpeg2_transcode.h>
#include <dcp/reel.h>
#include <dcp/reel_atmos_asset.h>
#include <dcp/reel_closed_caption_asset.h>
@@ -46,9 +48,9 @@
#include <dcp/sound_asset.h>
#include <dcp/sound_asset.h>
#include <dcp/sound_asset_reader.h>
-#include <dcp/stereo_picture_asset.h>
-#include <dcp/stereo_picture_asset_reader.h>
-#include <dcp/stereo_picture_frame.h>
+#include <dcp/stereo_j2k_picture_asset.h>
+#include <dcp/stereo_j2k_picture_asset_reader.h>
+#include <dcp/stereo_j2k_picture_frame.h>
#include <dcp/subtitle_asset.h>
#include <iostream>
@@ -162,6 +164,10 @@ DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content, bool tolerant)
} else if (_video_size.get() != asset->size ()) {
throw DCPError (_("Mismatched video sizes in DCP"));
}
+
+ if (dynamic_pointer_cast<dcp::MPEG2PictureAsset>(asset)) {
+ _video_range = VideoRange::VIDEO;
+ }
}
}
@@ -196,17 +202,20 @@ DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content, bool tolerant)
}
}
- 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());
+ if (auto sub = reel->main_subtitle()) {
+ if (sub->entry_point().get_value_or(0) != 0) {
+ _has_non_zero_entry_point[TextType::OPEN_SUBTITLE] = true;
+ }
+ if (!sub->asset_ref().resolved()) {
+ LOG_GENERAL("Main subtitle %1 of reel %2 is missing", sub->id(), reel->id());
_needs_assets = true;
} else {
- LOG_GENERAL("Main subtitle %1 of reel %2 found", reel->main_subtitle()->id(), reel->id());
+ LOG_GENERAL("Main subtitle %1 of reel %2 found", sub->id(), reel->id());
_text_count[TextType::OPEN_SUBTITLE] = 1;
- _open_subtitle_language = try_to_parse_language(reel->main_subtitle()->language());
+ _open_subtitle_language = try_to_parse_language(sub->language());
- auto asset = reel->main_subtitle()->asset();
+ auto asset = sub->asset();
for (auto const& font: asset->font_data()) {
_fonts.push_back({reel_index, asset->id(), make_shared<dcpomatic::Font>(font.first, font.second)});
}
@@ -226,6 +235,9 @@ DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content, bool tolerant)
}
for (auto ccap: reel->closed_captions()) {
+ if (ccap->entry_point().get_value_or(0) != 0) {
+ _has_non_zero_entry_point[TextType::CLOSED_CAPTION] = true;
+ }
if (!ccap->asset_ref().resolved()) {
LOG_GENERAL("Closed caption %1 of reel %2 is missing", ccap->id(), reel->id());
_needs_assets = true;
@@ -288,17 +300,26 @@ DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content, bool tolerant)
LOG_GENERAL_NC ("Picture has no key");
break;
}
- auto mono = dynamic_pointer_cast<dcp::MonoPictureAsset>(pic);
- auto stereo = dynamic_pointer_cast<dcp::StereoPictureAsset>(pic);
+ auto j2k_mono = dynamic_pointer_cast<dcp::MonoJ2KPictureAsset>(pic);
+ auto j2k_stereo = dynamic_pointer_cast<dcp::StereoJ2KPictureAsset>(pic);
+ auto mpeg2_mono = dynamic_pointer_cast<dcp::MonoMPEG2PictureAsset>(pic);
- if (mono) {
- auto reader = mono->start_read();
+ if (j2k_mono) {
+ auto reader = j2k_mono->start_read();
reader->set_check_hmac (false);
reader->get_frame(0)->xyz_image();
- } else {
- auto reader = stereo->start_read();
+ _video_encoding = VideoEncoding::JPEG2000;
+ } else if (j2k_stereo) {
+ auto reader = j2k_stereo->start_read();
reader->set_check_hmac (false);
reader->get_frame(0)->xyz_image(dcp::Eye::LEFT);
+ _video_encoding = VideoEncoding::JPEG2000;
+ } else if (mpeg2_mono) {
+ auto reader = mpeg2_mono->start_read();
+ reader->set_check_hmac(false);
+ dcp::MPEG2Decompressor decompressor;
+ decompressor.decompress_frame(reader->get_frame(0));
+ _video_encoding = VideoEncoding::MPEG2;
}
}
@@ -349,7 +370,7 @@ DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content, bool tolerant)
_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<dcp::StereoPictureAsset>(first_reel->main_picture()->asset());
+ _three_d = first_reel->main_picture() && first_reel->main_picture()->asset_ref().resolved() && dynamic_pointer_cast<dcp::StereoJ2KPictureAsset>(first_reel->main_picture()->asset());
}
_ratings = selected_cpl->ratings();
for (auto version: selected_cpl->content_versions()) {
diff --git a/src/lib/dcp_examiner.h b/src/lib/dcp_examiner.h
index 04fa31ea4..28b59ee2f 100644
--- a/src/lib/dcp_examiner.h
+++ b/src/lib/dcp_examiner.h
@@ -61,7 +61,7 @@ public:
}
VideoRange range () const override {
- return VideoRange::FULL;
+ return _video_range;
}
PixelQuanta pixel_quanta () const override {
@@ -130,6 +130,10 @@ public:
return _standard;
}
+ VideoEncoding video_encoding() const {
+ return _video_encoding;
+ }
+
bool three_d () const {
return _three_d;
}
@@ -171,6 +175,10 @@ public:
return _atmos_edit_rate;
}
+ EnumIndexedVector<bool, TextType> has_non_zero_entry_point() const {
+ return _has_non_zero_entry_point;
+ }
+
void add_fonts(std::shared_ptr<TextContent> content);
private:
@@ -196,6 +204,7 @@ private:
bool _needs_assets = false;
bool _kdm_valid = false;
boost::optional<dcp::Standard> _standard;
+ VideoEncoding _video_encoding = VideoEncoding::JPEG2000;
bool _three_d = false;
boost::optional<dcp::ContentKind> _content_kind;
std::string _cpl;
@@ -206,6 +215,8 @@ private:
bool _has_atmos = false;
Frame _atmos_length = 0;
dcp::Fraction _atmos_edit_rate;
+ EnumIndexedVector<bool, TextType> _has_non_zero_entry_point;
+ VideoRange _video_range = VideoRange::FULL;
struct Font
{
diff --git a/src/lib/dcp_encoder.cc b/src/lib/dcp_film_encoder.cc
index a4bc133f8..83da57756 100644
--- a/src/lib/dcp_encoder.cc
+++ b/src/lib/dcp_film_encoder.cc
@@ -18,38 +18,42 @@
*/
-/** @file src/dcp_encoder.cc
+
+/** @file src/dcp_film_encoder.cc
* @brief A class which takes a Film and some Options, then uses those to encode the film into a DCP.
*
* A decoder is selected according to the content type, and the encoder can be specified
* as a parameter to the constructor.
*/
-#include "dcp_encoder.h"
-#include "j2k_encoder.h"
-#include "film.h"
-#include "video_decoder.h"
+
#include "audio_decoder.h"
-#include "player.h"
-#include "job.h"
-#include "writer.h"
#include "compose.hpp"
+#include "dcp_film_encoder.h"
+#include "film.h"
+#include "j2k_encoder.h"
+#include "job.h"
+#include "mpeg2_encoder.h"
+#include "player.h"
+#include "player_video.h"
#include "referenced_reel_asset.h"
#include "text_content.h"
-#include "player_video.h"
+#include "video_decoder.h"
+#include "writer.h"
#include <boost/signals2.hpp>
#include <iostream>
#include "i18n.h"
-using std::string;
+
using std::cout;
+using std::dynamic_pointer_cast;
using std::list;
-using std::vector;
+using std::make_shared;
using std::shared_ptr;
+using std::string;
+using std::vector;
using std::weak_ptr;
-using std::dynamic_pointer_cast;
-using std::make_shared;
using boost::optional;
#if BOOST_VERSION >= 106100
using namespace boost::placeholders;
@@ -61,17 +65,30 @@ using namespace dcpomatic;
* @param film Film that we are encoding.
* @param job Job that this encoder is being used in.
*/
-DCPEncoder::DCPEncoder (shared_ptr<const Film> film, weak_ptr<Job> job)
- : Encoder (film, job)
- , _writer(film, job)
- , _j2k_encoder(film, _writer)
+DCPFilmEncoder::DCPFilmEncoder(shared_ptr<const Film> film, weak_ptr<Job> job)
+ : FilmEncoder(film, job)
+ , _writer(film, job, film->dir(film->dcp_name()))
, _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));
+ switch (_film->video_encoding()) {
+ case VideoEncoding::JPEG2000:
+ _encoder.reset(new J2KEncoder(film, _writer));
+ break;
+ case VideoEncoding::MPEG2:
+ _encoder.reset(new MPEG2Encoder(film, _writer));
+ break;
+ case VideoEncoding::COUNT:
+ DCPOMATIC_ASSERT(false);
+ }
+
+ /* Now that we have a Writer we can clear out the assets directory */
+ clean_up_asset_directory(film->assets_path());
+
+ _player_video_connection = _player.Video.connect(bind(&DCPFilmEncoder::video, this, _1, _2));
+ _player_audio_connection = _player.Audio.connect(bind(&DCPFilmEncoder::audio, this, _1, _2));
+ _player_text_connection = _player.Text.connect(bind(&DCPFilmEncoder::text, this, _1, _2, _3, _4));
+ _player_atmos_connection = _player.Atmos.connect(bind(&DCPFilmEncoder::atmos, this, _1, _2, _3));
for (auto c: film->content ()) {
for (auto i: c->text) {
@@ -82,7 +99,7 @@ DCPEncoder::DCPEncoder (shared_ptr<const Film> film, weak_ptr<Job> job)
}
}
-DCPEncoder::~DCPEncoder ()
+DCPFilmEncoder::~DCPFilmEncoder()
{
/* We must stop receiving more video data before we die */
_player_video_connection.release ();
@@ -92,10 +109,10 @@ DCPEncoder::~DCPEncoder ()
}
void
-DCPEncoder::go ()
+DCPFilmEncoder::go()
{
_writer.start();
- _j2k_encoder.begin();
+ _encoder->begin();
{
auto job = _job.lock ();
@@ -121,24 +138,38 @@ DCPEncoder::go ()
}
_finishing = true;
- _j2k_encoder.end();
- _writer.finish(_film->dir(_film->dcp_name()));
+ _encoder->end();
+ _writer.finish();
+}
+
+
+void
+DCPFilmEncoder::pause()
+{
+ _encoder->pause();
+}
+
+
+void
+DCPFilmEncoder::resume()
+{
+ _encoder->resume();
}
void
-DCPEncoder::video (shared_ptr<PlayerVideo> data, DCPTime time)
+DCPFilmEncoder::video(shared_ptr<PlayerVideo> data, DCPTime time)
{
- _j2k_encoder.encode(data, time);
+ _encoder->encode(data, time);
}
void
-DCPEncoder::audio (shared_ptr<AudioBuffers> data, DCPTime time)
+DCPFilmEncoder::audio(shared_ptr<AudioBuffers> data, DCPTime time)
{
_writer.write(data, time);
}
void
-DCPEncoder::text (PlayerText data, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
+DCPFilmEncoder::text(PlayerText data, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
{
if (type == TextType::CLOSED_CAPTION || _non_burnt_subtitles) {
_writer.write(data, type, track, period);
@@ -147,20 +178,20 @@ DCPEncoder::text (PlayerText data, TextType type, optional<DCPTextTrack> track,
void
-DCPEncoder::atmos (shared_ptr<const dcp::AtmosFrame> data, DCPTime time, AtmosMetadata metadata)
+DCPFilmEncoder::atmos(shared_ptr<const dcp::AtmosFrame> data, DCPTime time, AtmosMetadata metadata)
{
_writer.write(data, time, metadata);
}
optional<float>
-DCPEncoder::current_rate () const
+DCPFilmEncoder::current_rate() const
{
- return _j2k_encoder.current_encoding_rate();
+ return _encoder->current_encoding_rate();
}
Frame
-DCPEncoder::frames_done () const
+DCPFilmEncoder::frames_done() const
{
return _player.frames_done();
}
diff --git a/src/lib/dcp_encoder.h b/src/lib/dcp_film_encoder.h
index ad77f6951..3c697a115 100644
--- a/src/lib/dcp_encoder.h
+++ b/src/lib/dcp_film_encoder.h
@@ -22,7 +22,7 @@
#include "atmos_metadata.h"
#include "dcp_text_track.h"
#include "dcpomatic_time.h"
-#include "encoder.h"
+#include "film_encoder.h"
#include "player_text.h"
#include "j2k_encoder.h"
#include "writer.h"
@@ -35,13 +35,15 @@ class Job;
class Player;
class PlayerVideo;
+struct frames_not_lost_when_threads_disappear;
-/** @class DCPEncoder */
-class DCPEncoder : public Encoder
+
+/** @class DCPFilmEncoder */
+class DCPFilmEncoder : public FilmEncoder
{
public:
- DCPEncoder (std::shared_ptr<const Film> film, std::weak_ptr<Job> job);
- ~DCPEncoder ();
+ DCPFilmEncoder(std::shared_ptr<const Film> film, std::weak_ptr<Job> job);
+ ~DCPFilmEncoder();
void go () override;
@@ -53,15 +55,20 @@ public:
return _finishing;
}
+ void pause() override;
+ void resume() override;
+
private:
+ friend struct ::frames_not_lost_when_threads_disappear;
+
void video (std::shared_ptr<PlayerVideo>, dcpomatic::DCPTime);
void audio (std::shared_ptr<AudioBuffers>, dcpomatic::DCPTime);
void text (PlayerText, TextType, boost::optional<DCPTextTrack>, dcpomatic::DCPTimePeriod);
void atmos (std::shared_ptr<const dcp::AtmosFrame>, dcpomatic::DCPTime, AtmosMetadata metadata);
Writer _writer;
- J2KEncoder _j2k_encoder;
+ std::unique_ptr<VideoEncoder> _encoder;
bool _finishing;
bool _non_burnt_subtitles;
diff --git a/src/lib/dcp_subtitle_content.cc b/src/lib/dcp_subtitle_content.cc
index 8de5967ef..9ca3750d1 100644
--- a/src/lib/dcp_subtitle_content.cc
+++ b/src/lib/dcp_subtitle_content.cc
@@ -137,14 +137,14 @@ DCPSubtitleContent::technical_summary () const
}
void
-DCPSubtitleContent::as_xml (xmlpp::Node* node, bool with_paths) const
+DCPSubtitleContent::as_xml(xmlpp::Element* element, bool with_paths) const
{
- node->add_child("Type")->add_child_text ("DCPSubtitle");
- Content::as_xml (node, with_paths);
+ cxml::add_text_child(element, "Type", "DCPSubtitle");
+ Content::as_xml(element, with_paths);
if (only_text()) {
- only_text()->as_xml (node);
+ only_text()->as_xml(element);
}
- node->add_child("Length")->add_child_text (raw_convert<string> (_length.get ()));
+ cxml::add_text_child(element, "Length", raw_convert<string>(_length.get()));
}
diff --git a/src/lib/dcp_subtitle_content.h b/src/lib/dcp_subtitle_content.h
index 89a6f26a2..068b6dbac 100644
--- a/src/lib/dcp_subtitle_content.h
+++ b/src/lib/dcp_subtitle_content.h
@@ -30,7 +30,7 @@ public:
void examine (std::shared_ptr<const Film> film, std::shared_ptr<Job>) override;
std::string summary () const override;
std::string technical_summary () const override;
- void as_xml (xmlpp::Node *, bool with_paths) const override;
+ void as_xml(xmlpp::Element*, bool with_paths) const override;
dcpomatic::DCPTime full_length (std::shared_ptr<const Film> film) const override;
dcpomatic::DCPTime approximate_length () const override;
diff --git a/src/lib/dcp_text_track.cc b/src/lib/dcp_text_track.cc
index 0bd751275..292932396 100644
--- a/src/lib/dcp_text_track.cc
+++ b/src/lib/dcp_text_track.cc
@@ -56,9 +56,9 @@ DCPTextTrack::summary () const
void
DCPTextTrack::as_xml (xmlpp::Element* parent) const
{
- parent->add_child("Name")->add_child_text(name);
+ cxml::add_text_child(parent, "Name", name);
if (language) {
- parent->add_child("Language")->add_child_text(language->to_string());
+ cxml::add_text_child(parent, "Language", language->to_string());
}
}
diff --git a/src/lib/dcp_video.cc b/src/lib/dcp_video.cc
index 217b72183..ec88888dd 100644
--- a/src/lib/dcp_video.cc
+++ b/src/lib/dcp_video.cc
@@ -74,15 +74,15 @@ using namespace boost::placeholders;
/** Construct a DCP video frame.
* @param frame Input frame.
* @param index Index of the frame within the DCP.
- * @param bw J2K bandwidth to use (see Config::j2k_bandwidth ())
+ * @param bit_rate Video bit rate to use.
*/
DCPVideo::DCPVideo (
- shared_ptr<const PlayerVideo> frame, int index, int dcp_fps, int bw, Resolution r
+ shared_ptr<const PlayerVideo> frame, int index, int dcp_fps, int64_t bit_rate, Resolution r
)
: _frame (frame)
, _index (index)
, _frames_per_second (dcp_fps)
- , _j2k_bandwidth (bw)
+ , _video_bit_rate(bit_rate)
, _resolution (r)
{
@@ -93,7 +93,7 @@ DCPVideo::DCPVideo (shared_ptr<const PlayerVideo> frame, shared_ptr<const cxml::
{
_index = node->number_child<int> ("Index");
_frames_per_second = node->number_child<int> ("FramesPerSecond");
- _j2k_bandwidth = node->number_child<int> ("J2KBandwidth");
+ _video_bit_rate = node->number_child<int64_t>("VideoBitRate");
_resolution = Resolution (node->optional_number_child<int>("Resolution").get_value_or(static_cast<int>(Resolution::TWO_K)));
}
@@ -117,6 +117,30 @@ DCPVideo::convert_to_xyz (shared_ptr<const PlayerVideo> frame)
return xyz;
}
+dcp::Size
+DCPVideo::get_size() const
+{
+ auto image = _frame->image(bind(&PlayerVideo::keep_xyz_or_rgb, _1), VideoRange::FULL, false);
+ return image->size();
+}
+
+
+void
+DCPVideo::convert_to_xyz(uint16_t* dst) const
+{
+ auto image = _frame->image(bind(&PlayerVideo::keep_xyz_or_rgb, _1), VideoRange::FULL, false);
+ if (_frame->colour_conversion()) {
+ dcp::rgb_to_xyz (
+ image->data()[0],
+ dst,
+ image->size(),
+ image->stride()[0],
+ _frame->colour_conversion().get()
+ );
+ }
+}
+
+
/** J2K-encode this frame on the local host.
* @return Encoded data.
*/
@@ -136,7 +160,7 @@ DCPVideo::encode_locally () const
while (true) {
enc = dcp::compress_j2k (
xyz,
- _j2k_bandwidth,
+ _video_bit_rate,
_frames_per_second,
_frame->eyes() == Eyes::LEFT || _frame->eyes() == Eyes::RIGHT,
_resolution == Resolution::FOUR_K,
@@ -215,7 +239,7 @@ DCPVideo::encode_remotely (EncodeServerDescription serv, int timeout) const
/* Collect all XML metadata */
xmlpp::Document doc;
auto root = doc.create_root_node ("EncodingRequest");
- root->add_child("Version")->add_child_text (raw_convert<string> (SERVER_LINK_VERSION));
+ cxml::add_text_child(root, "Version", raw_convert<string>(SERVER_LINK_VERSION));
add_metadata (root);
LOG_DEBUG_ENCODE (N_("Sending frame %1 to remote"), _index);
@@ -254,10 +278,10 @@ DCPVideo::encode_remotely (EncodeServerDescription serv, int timeout) const
void
DCPVideo::add_metadata (xmlpp::Element* el) const
{
- el->add_child("Index")->add_child_text (raw_convert<string> (_index));
- el->add_child("FramesPerSecond")->add_child_text (raw_convert<string> (_frames_per_second));
- el->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth));
- el->add_child("Resolution")->add_child_text (raw_convert<string> (int (_resolution)));
+ cxml::add_text_child(el, "Index", raw_convert<string>(_index));
+ cxml::add_text_child(el, "FramesPerSecond", raw_convert<string>(_frames_per_second));
+ cxml::add_text_child(el, "VideoBitRate", raw_convert<string>(_video_bit_rate));
+ cxml::add_text_child(el, "Resolution", raw_convert<string>(int(_resolution)));
_frame->add_metadata (el);
}
@@ -274,7 +298,7 @@ bool
DCPVideo::same (shared_ptr<const DCPVideo> other) const
{
if (_frames_per_second != other->_frames_per_second ||
- _j2k_bandwidth != other->_j2k_bandwidth ||
+ _video_bit_rate != other->_video_bit_rate ||
_resolution != other->_resolution) {
return false;
}
diff --git a/src/lib/dcp_video.h b/src/lib/dcp_video.h
index bf95ccfe6..92c155f0c 100644
--- a/src/lib/dcp_video.h
+++ b/src/lib/dcp_video.h
@@ -17,6 +17,8 @@
along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
*/
+#ifndef DCPOMATIC_DCP_VIDEO_H
+#define DCPOMATIC_DCP_VIDEO_H
#include "encode_server_description.h"
@@ -47,7 +49,7 @@ class PlayerVideo;
class DCPVideo
{
public:
- DCPVideo (std::shared_ptr<const PlayerVideo>, int index, int dcp_fps, int bandwidth, Resolution r);
+ DCPVideo(std::shared_ptr<const PlayerVideo>, int index, int dcp_fps, int64_t bit_rate, Resolution r);
DCPVideo (std::shared_ptr<const PlayerVideo>, cxml::ConstNodePtr);
DCPVideo (DCPVideo const&) = default;
@@ -66,6 +68,9 @@ public:
static std::shared_ptr<dcp::OpenJPEGImage> convert_to_xyz(std::shared_ptr<const PlayerVideo> frame);
+ void convert_to_xyz(uint16_t* dst) const;
+ dcp::Size get_size() const;
+
private:
void add_metadata (xmlpp::Element *) const;
@@ -73,6 +78,8 @@ private:
std::shared_ptr<const PlayerVideo> _frame;
int _index; ///< frame index within the DCP's intrinsic duration
int _frames_per_second; ///< Frames per second that we will use for the DCP
- int _j2k_bandwidth; ///< J2K bandwidth to use
+ int64_t _video_bit_rate; ///< Video bit rate to use
Resolution _resolution; ///< Resolution (2K or 4K)
};
+
+#endif
diff --git a/src/lib/dcpomatic_log.h b/src/lib/dcpomatic_log.h
index 6a1c3a6ec..2372c8d01 100644
--- a/src/lib/dcpomatic_log.h
+++ b/src/lib/dcpomatic_log.h
@@ -44,4 +44,6 @@ extern std::shared_ptr<Log> dcpomatic_log;
#define LOG_DEBUG_PLAYER_NC(...) dcpomatic_log->log(__VA_ARGS__, LogEntry::TYPE_DEBUG_PLAYER);
#define LOG_DEBUG_AUDIO_ANALYSIS(...) dcpomatic_log->log(String::compose(__VA_ARGS__), LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS);
#define LOG_DEBUG_AUDIO_ANALYSIS_NC(...) dcpomatic_log->log(__VA_ARGS__, LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS);
+#define LOG_HTTP(...) dcpomatic_log->log(String::compose(__VA_ARGS__), LogEntry::TYPE_HTTP);
+#define LOG_HTTP_NC(...) dcpomatic_log->log(__VA_ARGS__, LogEntry::TYPE_HTTP);
diff --git a/src/lib/dcpomatic_socket.cc b/src/lib/dcpomatic_socket.cc
index 8f8f639cc..83ab072d2 100644
--- a/src/lib/dcpomatic_socket.cc
+++ b/src/lib/dcpomatic_socket.cc
@@ -63,7 +63,7 @@ Socket::check ()
void
Socket::connect (boost::asio::ip::tcp::endpoint endpoint)
{
- _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+ set_deadline_from_now(_timeout);
boost::system::error_code ec = boost::asio::error::would_block;
_socket.async_connect (endpoint, boost::lambda::var(ec) = boost::lambda::_1);
do {
@@ -95,7 +95,7 @@ Socket::connect (boost::asio::ip::tcp::endpoint endpoint)
void
Socket::write (uint8_t const * data, int size)
{
- _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+ set_deadline_from_now(_timeout);
boost::system::error_code ec = boost::asio::error::would_block;
boost::asio::async_write (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
@@ -115,6 +115,13 @@ Socket::write (uint8_t const * data, int size)
void
+Socket::write(std::string const& str)
+{
+ write(reinterpret_cast<uint8_t const*>(str.c_str()), str.size());
+}
+
+
+void
Socket::write (uint32_t v)
{
v = htonl (v);
@@ -129,7 +136,7 @@ Socket::write (uint32_t v)
void
Socket::read (uint8_t* data, int size)
{
- _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+ set_deadline_from_now(_timeout);
boost::system::error_code ec = boost::asio::error::would_block;
boost::asio::async_read (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
@@ -259,3 +266,22 @@ Socket::set_send_buffer_size (int size)
_send_buffer_size = size;
}
+
+void
+Socket::set_deadline_from_now(int seconds)
+{
+ _deadline.expires_from_now(boost::posix_time::seconds(seconds));
+}
+
+void
+Socket::run()
+{
+ _io_service.run_one();
+}
+
+void
+Socket::close()
+{
+ _socket.close();
+}
+
diff --git a/src/lib/dcpomatic_socket.h b/src/lib/dcpomatic_socket.h
index 7dff979e0..86e5f1266 100644
--- a/src/lib/dcpomatic_socket.h
+++ b/src/lib/dcpomatic_socket.h
@@ -47,10 +47,19 @@ public:
void write (uint32_t n);
void write (uint8_t const * data, int size);
+ void write(std::string const& str);
void read (uint8_t* data, int size);
uint32_t read_uint32 ();
+ void set_deadline_from_now(int seconds);
+ void run();
+ void close();
+
+ bool is_open() const {
+ return _socket.is_open();
+ }
+
class ReadDigestScope
{
public:
diff --git a/src/lib/dcpomatic_time.cc b/src/lib/dcpomatic_time.cc
index ac797f8f4..60fc5342a 100644
--- a/src/lib/dcpomatic_time.cc
+++ b/src/lib/dcpomatic_time.cc
@@ -27,6 +27,25 @@ using std::string;
using namespace dcpomatic;
+bool
+dcpomatic::operator<=(HMSF const& a, HMSF const& b)
+{
+ if (a.h != b.h) {
+ return a.h <= b.h;
+ }
+
+ if (a.m != b.m) {
+ return a.m <= b.m;
+ }
+
+ if (a.s != b.s) {
+ return a.s <= b.s;
+ }
+
+ return a.f <= b.f;
+}
+
+
template <>
Time<ContentTimeDifferentiator, DCPTimeDifferentiator>::Time (DCPTime d, FrameRateChange f)
: _t (llrint(d.get() * f.speed_up))
diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h
index 1b12ea901..63bb86549 100644
--- a/src/lib/dcpomatic_time.h
+++ b/src/lib/dcpomatic_time.h
@@ -64,6 +64,9 @@ public:
};
+bool operator<=(HMSF const& a, HMSF const& b);
+
+
/** A time in seconds, expressed as a number scaled up by Time::HZ. We want two different
* versions of this class, dcpomatic::ContentTime and dcpomatic::DCPTime, and we want it to be impossible to
* convert implicitly between the two. Hence there's this template hack. I'm not
@@ -152,6 +155,10 @@ public:
return *this;
}
+ Time<S, O> operator* (int o) const {
+ return Time<S, O> (_t * o);
+ }
+
Time<S, O> operator/ (int o) const {
return Time<S, O> (_t / o);
}
diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc
index 1bda93c94..ea0eda83d 100644
--- a/src/lib/decoder_factory.cc
+++ b/src/lib/decoder_factory.cc
@@ -59,13 +59,11 @@ maybe_cast (shared_ptr<Decoder> d)
shared_ptr<Decoder>
decoder_factory (shared_ptr<const Film> film, shared_ptr<const Content> content, bool fast, bool tolerant, shared_ptr<Decoder> old_decoder)
{
- auto fc = dynamic_pointer_cast<const FFmpegContent> (content);
- if (fc) {
+ if (auto fc = dynamic_pointer_cast<const FFmpegContent>(content)) {
return make_shared<FFmpegDecoder>(film, fc, fast);
}
- auto dc = dynamic_pointer_cast<const DCPContent> (content);
- if (dc) {
+ if (auto dc = dynamic_pointer_cast<const DCPContent>(content)) {
try {
return make_shared<DCPDecoder>(film, dc, fast, tolerant, maybe_cast<DCPDecoder>(old_decoder));
} catch (KDMError& e) {
@@ -74,28 +72,23 @@ decoder_factory (shared_ptr<const Film> film, shared_ptr<const Content> content,
}
}
- auto ic = dynamic_pointer_cast<const ImageContent> (content);
- if (ic) {
+ if (auto ic = dynamic_pointer_cast<const ImageContent>(content)) {
return make_shared<ImageDecoder>(film, ic);
}
- auto rc = dynamic_pointer_cast<const StringTextFileContent> (content);
- if (rc) {
+ if (auto rc = dynamic_pointer_cast<const StringTextFileContent>(content)) {
return make_shared<StringTextFileDecoder>(film, rc);
}
- auto dsc = dynamic_pointer_cast<const DCPSubtitleContent> (content);
- if (dsc) {
+ if (auto dsc = dynamic_pointer_cast<const DCPSubtitleContent>(content)) {
return make_shared<DCPSubtitleDecoder>(film, dsc);
}
- auto vmc = dynamic_pointer_cast<const VideoMXFContent> (content);
- if (vmc) {
+ if (auto vmc = dynamic_pointer_cast<const VideoMXFContent>(content)) {
return make_shared<VideoMXFDecoder>(film, vmc);
}
- auto amc = dynamic_pointer_cast<const AtmosMXFContent> (content);
- if (amc) {
+ if (auto amc = dynamic_pointer_cast<const AtmosMXFContent>(content)) {
return make_shared<AtmosMXFDecoder>(film, amc);
}
diff --git a/src/lib/dkdm_recipient.cc b/src/lib/dkdm_recipient.cc
index c73379bed..83ba96de6 100644
--- a/src/lib/dkdm_recipient.cc
+++ b/src/lib/dkdm_recipient.cc
@@ -19,12 +19,12 @@
*/
+#include "cinema_list.h"
#include "config.h"
#include "dkdm_recipient.h"
#include "film.h"
#include "kdm_with_metadata.h"
#include <dcp/raw_convert.h>
-#include <dcp/utc_offset.h>
using std::make_shared;
@@ -34,62 +34,33 @@ using std::vector;
using dcp::raw_convert;
-DKDMRecipient::DKDMRecipient (cxml::ConstNodePtr node)
- : KDMRecipient (node)
-{
- for (auto i: node->node_children("Email")) {
- emails.push_back (i->content());
- }
-
- utc_offset_hour = node->number_child<int>("UTCOffsetHour");
- utc_offset_minute = node->number_child<int>("UTCOffsetMinute");
-}
-
-
-void
-DKDMRecipient::as_xml (xmlpp::Element* node) const
-{
- KDMRecipient::as_xml (node);
-
- for (auto i: emails) {
- node->add_child("Email")->add_child_text(i);
- }
-
- node->add_child("UTCOffsetHour")->add_child_text(raw_convert<string>(utc_offset_hour));
- node->add_child("UTCOffsetMinute")->add_child_text(raw_convert<string>(utc_offset_minute));
-}
-
-
KDMWithMetadataPtr
kdm_for_dkdm_recipient (
shared_ptr<const Film> film,
boost::filesystem::path cpl,
- shared_ptr<DKDMRecipient> recipient,
- boost::posix_time::ptime valid_from,
- boost::posix_time::ptime valid_to
+ DKDMRecipient const& recipient,
+ dcp::LocalTime valid_from,
+ dcp::LocalTime valid_to
)
{
- if (!recipient->recipient) {
+ if (!recipient.recipient) {
return {};
}
- 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);
+ auto const decrypted_kdm = film->make_kdm(cpl, valid_from, valid_to);
+ 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();
- name_values['b'] = begin.date() + " " + begin.time_of_day(true, false);
- name_values['e'] = end.date() + " " + end.time_of_day(true, false);
+ name_values['b'] = valid_from.date() + " " + valid_from.time_of_day(true, false);
+ name_values['e'] = valid_to.date() + " " + valid_to.time_of_day(true, false);
name_values['i'] = kdm.cpl_id();
- return make_shared<KDMWithMetadata>(name_values, nullptr, recipient->emails, kdm);
+ return make_shared<KDMWithMetadata>(name_values, CinemaID(0), recipient.emails, kdm);
}
diff --git a/src/lib/dkdm_recipient.h b/src/lib/dkdm_recipient.h
index 7a0fa0185..64da41cff 100644
--- a/src/lib/dkdm_recipient.h
+++ b/src/lib/dkdm_recipient.h
@@ -33,25 +33,15 @@ public:
std::string const& name_,
std::string const& notes_,
boost::optional<dcp::Certificate> recipient_,
- std::vector<std::string> emails_,
- int utc_offset_hour_,
- int utc_offset_minute_
+ std::vector<std::string> emails_
)
: KDMRecipient (name_, notes_, recipient_, boost::none)
, emails (emails_)
- , utc_offset_hour (utc_offset_hour_)
- , utc_offset_minute (utc_offset_minute_)
{
}
- explicit DKDMRecipient (cxml::ConstNodePtr);
-
- void as_xml (xmlpp::Element *) const override;
-
std::vector<std::string> emails;
- int utc_offset_hour;
- int utc_offset_minute;
};
@@ -59,8 +49,8 @@ KDMWithMetadataPtr
kdm_for_dkdm_recipient (
std::shared_ptr<const Film> film,
boost::filesystem::path cpl,
- std::shared_ptr<DKDMRecipient> recipient,
- boost::posix_time::ptime valid_from,
- boost::posix_time::ptime valid_to
+ DKDMRecipient const& recipient,
+ dcp::LocalTime valid_from,
+ dcp::LocalTime valid_to
);
diff --git a/src/lib/dkdm_recipient_list.cc b/src/lib/dkdm_recipient_list.cc
new file mode 100644
index 000000000..34179337e
--- /dev/null
+++ b/src/lib/dkdm_recipient_list.cc
@@ -0,0 +1,243 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "config.h"
+#include "dkdm_recipient.h"
+#include "dkdm_recipient_list.h"
+#include "sqlite_statement.h"
+#include "sqlite_transaction.h"
+#include "util.h"
+#include <boost/algorithm/string.hpp>
+
+
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::vector;
+using boost::optional;
+
+
+DKDMRecipientList::DKDMRecipientList()
+ : _dkdm_recipients("dkdm_recipients")
+{
+ setup(Config::instance()->dkdm_recipients_file());
+}
+
+
+DKDMRecipientList::DKDMRecipientList(boost::filesystem::path db_file)
+ : _dkdm_recipients("dkdm_recipients")
+{
+ setup(db_file);
+}
+
+
+
+DKDMRecipientList::~DKDMRecipientList()
+{
+ if (_db) {
+ sqlite3_close(_db);
+ }
+}
+
+
+void
+DKDMRecipientList::read_legacy_file(boost::filesystem::path xml_file)
+{
+ cxml::Document doc("DKDMRecipients");
+ doc.read_file(xml_file);
+
+ read_legacy_document(doc);
+}
+
+
+void
+DKDMRecipientList::read_legacy_string(string const& xml)
+{
+ cxml::Document doc("DKDMRecipients");
+ doc.read_file(xml);
+
+ read_legacy_document(doc);
+}
+
+
+void
+DKDMRecipientList::read_legacy_document(cxml::Document const& doc)
+{
+ for (auto recipient_node: doc.node_children("DKDMRecipient")) {
+ vector<string> emails;
+ for (auto email_node: recipient_node->node_children("Email")) {
+ emails.push_back(email_node->content());
+ }
+
+ optional<dcp::Certificate> certificate;
+ if (auto certificate_string = recipient_node->optional_string_child("Recipient")) {
+ certificate = dcp::Certificate(*certificate_string);
+ }
+
+ DKDMRecipient recipient(
+ recipient_node->string_child("Name"),
+ recipient_node->string_child("Notes"),
+ certificate,
+ emails
+ );
+
+ add_dkdm_recipient(recipient);
+ }
+}
+
+
+void
+DKDMRecipientList::setup(boost::filesystem::path db_file)
+{
+ _dkdm_recipients.add_column("name", "TEXT");
+ _dkdm_recipients.add_column("notes", "TEXT");
+ _dkdm_recipients.add_column("recipient", "TEXT");
+ _dkdm_recipients.add_column("emails", "TEXT");
+
+#ifdef DCPOMATIC_WINDOWS
+ auto rc = sqlite3_open16(db_file.c_str(), &_db);
+#else
+ auto rc = sqlite3_open(db_file.c_str(), &_db);
+#endif
+ if (rc != SQLITE_OK) {
+ throw FileError("Could not open SQLite database", db_file);
+ }
+
+ sqlite3_busy_timeout(_db, 500);
+
+ SQLiteStatement screens(_db, _dkdm_recipients.create());
+ screens.execute();
+}
+
+
+DKDMRecipientList::DKDMRecipientList(DKDMRecipientList&& other)
+ : _dkdm_recipients(std::move(other._dkdm_recipients))
+{
+ _db = other._db;
+ other._db = nullptr;
+}
+
+
+DKDMRecipientList&
+DKDMRecipientList::operator=(DKDMRecipientList&& other)
+{
+ if (this != &other) {
+ _db = other._db;
+ other._db = nullptr;
+ }
+ return *this;
+}
+
+
+DKDMRecipientID
+DKDMRecipientList::add_dkdm_recipient(DKDMRecipient const& dkdm_recipient)
+{
+ SQLiteStatement add_dkdm_recipient(_db, _dkdm_recipients.insert());
+
+ add_dkdm_recipient.bind_text(1, dkdm_recipient.name);
+ add_dkdm_recipient.bind_text(2, dkdm_recipient.notes);
+ add_dkdm_recipient.bind_text(3, dkdm_recipient.recipient ? dkdm_recipient.recipient->certificate(true) : "");
+ add_dkdm_recipient.bind_text(4, join_strings(dkdm_recipient.emails));
+
+ add_dkdm_recipient.execute();
+
+ return sqlite3_last_insert_rowid(_db);
+}
+
+
+void
+DKDMRecipientList::update_dkdm_recipient(DKDMRecipientID id, DKDMRecipient const& dkdm_recipient)
+{
+ SQLiteStatement add_dkdm_recipient(_db, _dkdm_recipients.update("WHERE id=?"));
+
+ add_dkdm_recipient.bind_text(1, dkdm_recipient.name);
+ add_dkdm_recipient.bind_text(2, dkdm_recipient.notes);
+ add_dkdm_recipient.bind_text(3, dkdm_recipient.recipient ? dkdm_recipient.recipient->certificate(true) : "");
+ add_dkdm_recipient.bind_text(4, join_strings(dkdm_recipient.emails));
+ add_dkdm_recipient.bind_int64(5, id.get());
+
+ add_dkdm_recipient.execute();
+}
+
+
+void
+DKDMRecipientList::remove_dkdm_recipient(DKDMRecipientID id)
+{
+ SQLiteStatement statement(_db, "DELETE FROM dkdm_recipients WHERE ID=?");
+ statement.bind_int64(1, id.get());
+ statement.execute();
+}
+
+
+static
+vector<pair<DKDMRecipientID, DKDMRecipient>>
+dkdm_recipients_from_result(SQLiteStatement& statement)
+{
+ vector<pair<DKDMRecipientID, DKDMRecipient>> output;
+
+ statement.execute([&output](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 5);
+ DKDMRecipientID const id = statement.column_int64(0);
+ auto const name = statement.column_text(1);
+ auto const notes = statement.column_text(2);
+ auto certificate_string = statement.column_text(3);
+ optional<dcp::Certificate> certificate = certificate_string.empty() ? optional<dcp::Certificate>() : dcp::Certificate(certificate_string);
+ auto const join_with_spaces = statement.column_text(4);
+ vector<string> emails;
+ boost::algorithm::split(emails, join_with_spaces, boost::is_any_of(" "));
+ output.push_back(make_pair(id, DKDMRecipient(name, notes, certificate, { emails })));
+ });
+
+ return output;
+}
+
+
+
+
+vector<std::pair<DKDMRecipientID, DKDMRecipient>>
+DKDMRecipientList::dkdm_recipients() const
+{
+ SQLiteStatement statement(_db, _dkdm_recipients.select("ORDER BY name ASC"));
+ return dkdm_recipients_from_result(statement);
+}
+
+
+boost::optional<DKDMRecipient>
+DKDMRecipientList::dkdm_recipient(DKDMRecipientID id) const
+{
+ SQLiteStatement statement(_db, _dkdm_recipients.select("WHERE id=?"));
+ statement.bind_int64(1, id.get());
+ auto result = dkdm_recipients_from_result(statement);
+ if (result.empty()) {
+ return {};
+ }
+ return result[0].second;
+}
+
+
+void
+DKDMRecipientList::clear()
+{
+ SQLiteStatement sql(_db, "DELETE FROM dkdm_recipients");
+ sql.execute();
+}
+
+
diff --git a/src/lib/dkdm_recipient_list.h b/src/lib/dkdm_recipient_list.h
new file mode 100644
index 000000000..fc4d84b60
--- /dev/null
+++ b/src/lib/dkdm_recipient_list.h
@@ -0,0 +1,90 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_DKDM_RECIPIENT_LIST_H
+#define DCPOMATIC_DKDM_RECIPIENT_LIST_H
+
+
+#include "id.h"
+#include "sqlite_table.h"
+#include <libcxml/cxml.h>
+#include <boost/filesystem.hpp>
+#include <boost/optional.hpp>
+
+
+class DKDMRecipient;
+
+
+class DKDMRecipientID : public ID
+{
+public:
+ DKDMRecipientID(sqlite3_int64 id)
+ : ID(id) {}
+
+ bool operator==(DKDMRecipientID const& other) const {
+ return get() == other.get();
+ }
+
+ bool operator!=(DKDMRecipientID const& other) const {
+ return get() != other.get();
+ }
+
+ bool operator<(DKDMRecipientID const& other) const {
+ return get() < other.get();
+ }
+};
+
+
+class DKDMRecipientList
+{
+public:
+ DKDMRecipientList();
+ DKDMRecipientList(boost::filesystem::path db_file);
+ ~DKDMRecipientList();
+
+ DKDMRecipientList(DKDMRecipientList const&) = delete;
+ DKDMRecipientList& operator=(DKDMRecipientList const&) = delete;
+
+ DKDMRecipientList(DKDMRecipientList&& other);
+ DKDMRecipientList& operator=(DKDMRecipientList&& other);
+
+ void read_legacy_file(boost::filesystem::path xml_file);
+ void read_legacy_string(std::string const& xml);
+
+ void clear();
+
+ DKDMRecipientID add_dkdm_recipient(DKDMRecipient const& dkdm_recipient);
+ void update_dkdm_recipient(DKDMRecipientID id, DKDMRecipient const& dkdm_recipient);
+ void remove_dkdm_recipient(DKDMRecipientID id);
+ std::vector<std::pair<DKDMRecipientID, DKDMRecipient>> dkdm_recipients() const;
+ boost::optional<DKDMRecipient> dkdm_recipient(DKDMRecipientID id) const;
+
+private:
+ void setup(boost::filesystem::path db_file);
+ void read_legacy_document(cxml::Document const& doc);
+
+ sqlite3* _db = nullptr;
+ SQLiteTable _dkdm_recipients;
+};
+
+
+#endif
+
diff --git a/src/lib/dkdm_wrapper.cc b/src/lib/dkdm_wrapper.cc
index 016c77c3f..4c7838a8d 100644
--- a/src/lib/dkdm_wrapper.cc
+++ b/src/lib/dkdm_wrapper.cc
@@ -41,7 +41,11 @@ DKDMBase::read (cxml::ConstNodePtr node)
if (node->name() == "DKDM") {
return make_shared<DKDM>(dcp::EncryptedKDM(node->content()));
} else if (node->name() == "DKDMGroup") {
- auto group = make_shared<DKDMGroup>(node->string_attribute("Name"));
+ auto name = node->optional_string_attribute("Name");
+ if (!name) {
+ name = node->string_attribute("name");
+ }
+ auto group = make_shared<DKDMGroup>(*name);
for (auto i: node->node_children()) {
if (auto c = read(i)) {
group->add (c);
@@ -62,17 +66,17 @@ DKDM::name () const
void
-DKDM::as_xml (xmlpp::Element* node) const
+DKDM::as_xml(xmlpp::Element* element) const
{
- node->add_child("DKDM")->add_child_text (_dkdm.as_xml ());
+ cxml::add_text_child(element, "DKDM", _dkdm.as_xml());
}
void
-DKDMGroup::as_xml (xmlpp::Element* node) const
+DKDMGroup::as_xml(xmlpp::Element* element) const
{
- auto f = node->add_child("DKDMGroup");
- f->set_attribute ("Name", _name);
+ auto f = cxml::add_child(element, "DKDMGroup");
+ f->set_attribute("name", _name);
for (auto i: _children) {
i->as_xml (f);
}
diff --git a/src/lib/email.cc b/src/lib/email.cc
index 8557b40e0..6b4012c22 100644
--- a/src/lib/email.cc
+++ b/src/lib/email.cc
@@ -23,6 +23,7 @@
#include "config.h"
#include "email.h"
#include "exceptions.h"
+#include "variant.h"
#include <curl/curl.h>
#include <boost/algorithm/string.hpp>
#include <boost/date_time/c_local_time_adjustor.hpp>
@@ -118,7 +119,8 @@ Email::send(string server, int port, EmailProtocol protocol, string user, string
auto const utc_now = boost::posix_time::second_clock::universal_time ();
auto const local_now = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::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()));
+ auto end = date_buffer + strlen(date_buffer);
+ snprintf(end, sizeof(date_buffer) - (end - 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"
@@ -143,9 +145,8 @@ Email::send(string server, int port, EmailProtocol protocol, string user, string
"Content-Type: multipart/mixed; boundary=" + boundary + "\r\n";
}
- _email += "Subject: " + encode_rfc1342(_subject) + "\r\n"
- "User-Agent: DCP-o-matic\r\n"
- "\r\n";
+ _email += "Subject: " + encode_rfc1342(_subject) + "\r\n" +
+ variant::insert_dcpomatic("User-Agent: %1\r\n\r\n");
if (!_attachments.empty ()) {
_email += "--" + boundary + "\r\n"
diff --git a/src/lib/email.h b/src/lib/email.h
index 36398bfd8..ac4703453 100644
--- a/src/lib/email.h
+++ b/src/lib/email.h
@@ -19,6 +19,7 @@
*/
+#include "types.h"
#include <curl/curl.h>
#include <boost/scoped_array.hpp>
diff --git a/src/lib/encode_server.cc b/src/lib/encode_server.cc
index 036ea58a5..8d1759986 100644
--- a/src/lib/encode_server.cc
+++ b/src/lib/encode_server.cc
@@ -37,6 +37,8 @@
#include "image.h"
#include "log.h"
#include "player_video.h"
+#include "util.h"
+#include "variant.h"
#include "version.h"
#include <dcp/raw_convert.h>
#include <dcp/warnings.h>
@@ -81,6 +83,7 @@ EncodeServer::EncodeServer (bool verbose, int num_threads)
#endif
, _verbose (verbose)
, _num_threads (num_threads)
+ , _frames_encoded(0)
{
}
@@ -169,6 +172,8 @@ EncodeServer::process (shared_ptr<Socket> socket, struct timeval& after_read, st
throw;
}
+ ++_frames_encoded;
+
return dcp_video_frame.index ();
}
@@ -243,7 +248,7 @@ EncodeServer::run ()
{
LOG_GENERAL ("Server %1 (%2) starting with %3 threads", dcpomatic_version, dcpomatic_git_commit, _num_threads);
if (_verbose) {
- cout << "DCP-o-matic server starting with " << _num_threads << " threads.\n";
+ cout << variant::dcpomatic_encode_server() << " starting with " << _num_threads << " threads.\n";
}
for (int i = 0; i < _num_threads; ++i) {
@@ -268,12 +273,9 @@ void
EncodeServer::broadcast_thread ()
try
{
- auto address = boost::asio::ip::address_v4::any ();
- boost::asio::ip::udp::endpoint listen_endpoint (address, HELLO_PORT);
+ boost::asio::ip::udp::endpoint listen_endpoint(boost::asio::ip::udp::v4(), HELLO_PORT);
- _broadcast.socket = new boost::asio::ip::udp::socket (_broadcast.io_service);
- _broadcast.socket->open (listen_endpoint.protocol ());
- _broadcast.socket->bind (listen_endpoint);
+ _broadcast.socket = new boost::asio::ip::udp::socket(_broadcast.io_service, listen_endpoint);
_broadcast.socket->async_receive_from (
boost::asio::buffer (_broadcast.buffer, sizeof (_broadcast.buffer)),
@@ -298,8 +300,8 @@ EncodeServer::broadcast_received ()
/* Reply to the client saying what we can do */
xmlpp::Document doc;
auto root = doc.create_root_node ("ServerAvailable");
- root->add_child("Threads")->add_child_text (raw_convert<string> (_worker_threads.size ()));
- root->add_child("Version")->add_child_text (raw_convert<string> (SERVER_LINK_VERSION));
+ cxml::add_text_child(root, "Threads", raw_convert<string>(_worker_threads.size()));
+ cxml::add_text_child(root, "Version", raw_convert<string>(SERVER_LINK_VERSION));
auto xml = doc.write_to_string ("UTF-8");
if (_verbose) {
diff --git a/src/lib/encode_server.h b/src/lib/encode_server.h
index f93d66746..8059abd0f 100644
--- a/src/lib/encode_server.h
+++ b/src/lib/encode_server.h
@@ -32,6 +32,7 @@
#include "exception_store.h"
#include "server.h"
#include <boost/asio.hpp>
+#include <boost/atomic.hpp>
#include <boost/thread.hpp>
#include <boost/thread/condition.hpp>
#include <string>
@@ -53,6 +54,10 @@ public:
void run () override;
+ int frames_encoded() const {
+ return _frames_encoded;
+ }
+
private:
void handle (std::shared_ptr<Socket>) override;
void worker_thread ();
@@ -67,6 +72,7 @@ private:
bool _verbose;
int _num_threads;
Waker _waker;
+ boost::atomic<int> _frames_encoded;
struct Broadcast {
diff --git a/src/lib/encode_server_finder.cc b/src/lib/encode_server_finder.cc
index 1d4ced595..143569e16 100644
--- a/src/lib/encode_server_finder.cc
+++ b/src/lib/encode_server_finder.cc
@@ -26,6 +26,8 @@
#include "encode_server_description.h"
#include "encode_server_finder.h"
#include "exceptions.h"
+#include "util.h"
+#include "variant.h"
#include <dcp/raw_convert.h>
#include <libcxml/cxml.h>
#include <boost/bind/placeholders.hpp>
@@ -188,7 +190,7 @@ try {
new tcp::acceptor (_listen_io_service, tcp::endpoint(tcp::v4(), is_batch_converter ? BATCH_SERVER_PRESENCE_PORT : MAIN_SERVER_PRESENCE_PORT))
);
} catch (...) {
- boost::throw_exception (NetworkError (_("Could not listen for remote encode servers. Perhaps another instance of DCP-o-matic is running.")));
+ boost::throw_exception(NetworkError(variant::insert_dcpomatic(_("Could not listen for remote encode servers. Perhaps another instance of %1 is running."))));
}
start_accept ();
diff --git a/src/lib/encode_server_finder.h b/src/lib/encode_server_finder.h
index f8a30af54..c478387f9 100644
--- a/src/lib/encode_server_finder.h
+++ b/src/lib/encode_server_finder.h
@@ -50,8 +50,6 @@ public:
static EncodeServerFinder* instance ();
static void drop ();
- void stop ();
-
std::list<EncodeServerDescription> servers () const;
/** Emitted whenever the list of servers changes */
@@ -62,6 +60,7 @@ private:
~EncodeServerFinder ();
void start ();
+ void stop ();
void search_thread ();
void listen_thread ();
diff --git a/src/lib/environment_info.cc b/src/lib/environment_info.cc
index 2e0347c79..d6592d126 100644
--- a/src/lib/environment_info.cc
+++ b/src/lib/environment_info.cc
@@ -22,6 +22,7 @@
#include "compose.hpp"
#include "cross.h"
#include "log.h"
+#include "variant.h"
#include "version.h"
#include <dcp/version.h>
#include <dcp/warnings.h>
@@ -85,7 +86,7 @@ environment_info ()
{
list<string> info;
- info.push_back (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
+ info.push_back(String::compose("%1 %2 git %3 using %4", variant::dcpomatic(), dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
{
char buffer[128];
@@ -94,9 +95,9 @@ environment_info ()
}
#ifdef DCPOMATIC_DEBUG
- info.push_back ("DCP-o-matic built in debug mode.");
+ info.push_back(variant::insert_dcpomatic("%1 built in debug mode."));
#else
- info.push_back ("DCP-o-matic built in optimised mode.");
+ info.push_back(variant::insert_dcpomatic("%1 built in optimised mode."));
#endif
#ifdef LIBDCP_DEBUG
info.push_back ("libdcp built in debug mode.");
diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h
index 10231f59a..e08392689 100644
--- a/src/lib/exceptions.h
+++ b/src/lib/exceptions.h
@@ -34,6 +34,7 @@ extern "C" {
}
#include <boost/filesystem.hpp>
#include <boost/optional.hpp>
+#include <sqlite3.h>
#include <cstring>
#include <stdexcept>
@@ -480,4 +481,58 @@ public:
};
+class SQLError : public std::runtime_error
+{
+public:
+ SQLError(sqlite3* db, char const* s)
+ : std::runtime_error(get_message(db, s))
+ {
+ _filename = get_filename(db);
+ }
+
+ SQLError(sqlite3* db, int rc)
+ : std::runtime_error(get_message(db, rc))
+ {
+ _filename = get_filename(db);
+ }
+
+ SQLError(sqlite3* db, int rc, std::string doing)
+ : std::runtime_error(get_message(db, rc, doing))
+ {
+ _filename = get_filename(db);
+ }
+
+ boost::filesystem::path filename() const {
+ return _filename;
+ }
+
+private:
+ boost::filesystem::path get_filename(sqlite3* db)
+ {
+ if (auto filename = sqlite3_db_filename(db, "main")) {
+ return filename;
+ }
+
+ return {};
+ }
+
+ std::string get_message(sqlite3* db, char const* s)
+ {
+ return String::compose("%1 (in %2)", s, get_filename(db));
+ }
+
+ std::string get_message(sqlite3* db, int rc)
+ {
+ return String::compose("%1 (in %2)", sqlite3_errstr(rc), get_filename(db));
+ }
+
+ std::string get_message(sqlite3* db, int rc, std::string doing)
+ {
+ return String::compose("%1 (while doing %2) (in %3)", sqlite3_errstr(rc), doing, get_filename(db));
+ }
+
+ boost::filesystem::path _filename;
+};
+
+
#endif
diff --git a/src/lib/export_config.cc b/src/lib/export_config.cc
index e030b98e2..79042e59c 100644
--- a/src/lib/export_config.cc
+++ b/src/lib/export_config.cc
@@ -65,6 +65,8 @@ ExportConfig::read(cxml::ConstNodePtr node)
_format = ExportFormat::H264_AAC;
} else if (format == "prores-4444") {
_format = ExportFormat::PRORES_4444;
+ } else if (format == "prores-lt") {
+ _format = ExportFormat::PRORES_LT;
} else {
_format = ExportFormat::PRORES_HQ;
}
@@ -77,7 +79,7 @@ ExportConfig::read(cxml::ConstNodePtr node)
void
-ExportConfig::write(xmlpp::Element* node) const
+ExportConfig::write(xmlpp::Element* element) const
{
string name;
@@ -89,6 +91,9 @@ ExportConfig::write(xmlpp::Element* node) const
/* Write this but we also accept 'prores' for backwards compatibility */
name = "prores-hq";
break;
+ case ExportFormat::PRORES_LT:
+ name = "prores-lt";
+ break;
case ExportFormat::H264_AAC:
name = "h264-aac";
break;
@@ -97,11 +102,11 @@ ExportConfig::write(xmlpp::Element* node) const
break;
}
- node->add_child("Format")->add_child_text(name);
- node->add_child("MixdownToStereo")->add_child_text(_mixdown_to_stereo ? "1" : "0");
- node->add_child("SplitReels")->add_child_text(_split_reels ? "1" : "0");
- node->add_child("SplitStreams")->add_child_text(_split_streams ? "1" : "0");
- node->add_child("X264CRF")->add_child_text(dcp::raw_convert<string>(_x264_crf));
+ cxml::add_text_child(element, "Format", name);
+ cxml::add_text_child(element, "MixdownToStereo", _mixdown_to_stereo ? "1" : "0");
+ cxml::add_text_child(element, "SplitReels", _split_reels ? "1" : "0");
+ cxml::add_text_child(element, "SplitStreams", _split_streams ? "1" : "0");
+ cxml::add_text_child(element, "X264CRF", dcp::raw_convert<string>(_x264_crf));
}
@@ -138,4 +143,3 @@ ExportConfig::set_x264_crf(int crf)
{
_config->maybe_set(_x264_crf, crf);
}
-
diff --git a/src/lib/export_config.h b/src/lib/export_config.h
index 47dddb6c3..52d7b601d 100644
--- a/src/lib/export_config.h
+++ b/src/lib/export_config.h
@@ -37,7 +37,7 @@ public:
void set_defaults();
void read(cxml::ConstNodePtr node);
- void write(xmlpp::Element* node) const;
+ void write(xmlpp::Element* element) const;
ExportFormat format() const {
return _format;
diff --git a/src/lib/ffmpeg_audio_stream.cc b/src/lib/ffmpeg_audio_stream.cc
index 9400eb60d..24f96b889 100644
--- a/src/lib/ffmpeg_audio_stream.cc
+++ b/src/lib/ffmpeg_audio_stream.cc
@@ -52,19 +52,19 @@ FFmpegAudioStream::FFmpegAudioStream (cxml::ConstNodePtr node, int version)
void
-FFmpegAudioStream::as_xml (xmlpp::Node* root) const
+FFmpegAudioStream::as_xml(xmlpp::Element* root) const
{
FFmpegStream::as_xml (root);
- root->add_child("FrameRate")->add_child_text(raw_convert<string>(frame_rate()));
- root->add_child("Length")->add_child_text(raw_convert<string>(length()));
- mapping().as_xml (root->add_child("Mapping"));
+ cxml::add_text_child(root, "FrameRate", raw_convert<string>(frame_rate()));
+ cxml::add_text_child(root, "Length", raw_convert<string>(length()));
+ mapping().as_xml(cxml::add_child(root, "Mapping"));
if (first_audio) {
- root->add_child("FirstAudio")->add_child_text(raw_convert<string>(first_audio.get().get()));
+ cxml::add_text_child(root, "FirstAudio", raw_convert<string>(first_audio.get().get()));
}
if (codec_name) {
- root->add_child("CodecName")->add_child_text(codec_name.get());
+ cxml::add_text_child(root, "CodecName", codec_name.get());
}
if (bit_depth()) {
- root->add_child("BitDepth")->add_child_text(raw_convert<string>(bit_depth().get()));
+ cxml::add_text_child(root, "BitDepth", raw_convert<string>(bit_depth().get()));
}
}
diff --git a/src/lib/ffmpeg_audio_stream.h b/src/lib/ffmpeg_audio_stream.h
index aae982f9e..afd30e905 100644
--- a/src/lib/ffmpeg_audio_stream.h
+++ b/src/lib/ffmpeg_audio_stream.h
@@ -48,7 +48,7 @@ public:
FFmpegAudioStream (cxml::ConstNodePtr, int);
- void as_xml (xmlpp::Node *) const;
+ void as_xml(xmlpp::Element*) const;
/* XXX: should probably be locked */
diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc
index 4a7c87b34..c2bb5ffe4 100644
--- a/src/lib/ffmpeg_content.cc
+++ b/src/lib/ffmpeg_content.cc
@@ -34,6 +34,7 @@
#include "job.h"
#include "log.h"
#include "text_content.h"
+#include "variant.h"
#include "video_content.h"
#include <dcp/raw_convert.h>
#include <libcxml/cxml.h>
@@ -118,7 +119,7 @@ FFmpegContent::FFmpegContent (cxml::ConstNodePtr node, int version, list<string>
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()));
+ notes.push_back(String::compose(_("%1 no longer supports the `%2' filter, so it has been turned off."), variant::dcpomatic(), i->content()));
}
}
@@ -197,61 +198,61 @@ FFmpegContent::FFmpegContent (vector<shared_ptr<Content>> c)
void
-FFmpegContent::as_xml (xmlpp::Node* node, bool with_paths) const
+FFmpegContent::as_xml(xmlpp::Element* element, bool with_paths) const
{
- node->add_child("Type")->add_child_text("FFmpeg");
- Content::as_xml (node, with_paths);
+ cxml::add_text_child(element, "Type", "FFmpeg");
+ Content::as_xml(element, with_paths);
if (video) {
- video->as_xml (node);
+ video->as_xml(element);
}
if (audio) {
- audio->as_xml (node);
+ audio->as_xml(element);
for (auto i: audio->streams()) {
auto f = dynamic_pointer_cast<FFmpegAudioStream> (i);
DCPOMATIC_ASSERT (f);
- f->as_xml (node->add_child("AudioStream"));
+ f->as_xml(cxml::add_child(element, "AudioStream"));
}
}
if (only_text()) {
- only_text()->as_xml (node);
+ only_text()->as_xml(element);
}
boost::mutex::scoped_lock lm (_mutex);
for (auto i: _subtitle_streams) {
- auto t = node->add_child("SubtitleStream");
+ auto t = cxml::add_child(element, "SubtitleStream");
if (_subtitle_stream && i == _subtitle_stream) {
- t->add_child("Selected")->add_child_text("1");
+ cxml::add_text_child(t, "Selected", "1");
}
i->as_xml (t);
}
for (auto i: _filters) {
- node->add_child("Filter")->add_child_text(i.id());
+ cxml::add_text_child(element, "Filter", i.id());
}
if (_first_video) {
- node->add_child("FirstVideo")->add_child_text(raw_convert<string>(_first_video.get().get()));
+ cxml::add_text_child(element, "FirstVideo", raw_convert<string>(_first_video.get().get()));
}
if (_color_range) {
- node->add_child("ColorRange")->add_child_text(raw_convert<string>(static_cast<int>(*_color_range)));
+ cxml::add_text_child(element, "ColorRange", raw_convert<string>(static_cast<int>(*_color_range)));
}
if (_color_primaries) {
- node->add_child("ColorPrimaries")->add_child_text(raw_convert<string>(static_cast<int>(*_color_primaries)));
+ cxml::add_text_child(element, "ColorPrimaries", raw_convert<string>(static_cast<int>(*_color_primaries)));
}
if (_color_trc) {
- node->add_child("ColorTransferCharacteristic")->add_child_text(raw_convert<string>(static_cast<int>(*_color_trc)));
+ cxml::add_text_child(element, "ColorTransferCharacteristic", raw_convert<string>(static_cast<int>(*_color_trc)));
}
if (_colorspace) {
- node->add_child("Colorspace")->add_child_text(raw_convert<string>(static_cast<int>(*_colorspace)));
+ cxml::add_text_child(element, "Colorspace", raw_convert<string>(static_cast<int>(*_colorspace)));
}
if (_bits_per_pixel) {
- node->add_child("BitsPerPixel")->add_child_text(raw_convert<string>(*_bits_per_pixel));
+ cxml::add_text_child(element, "BitsPerPixel", raw_convert<string>(*_bits_per_pixel));
}
}
diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h
index a86358b76..ce067f2d3 100644
--- a/src/lib/ffmpeg_content.h
+++ b/src/lib/ffmpeg_content.h
@@ -70,7 +70,7 @@ public:
void take_settings_from (std::shared_ptr<const Content> c) override;
std::string summary () const override;
std::string technical_summary () const override;
- void as_xml (xmlpp::Node *, bool with_paths) const override;
+ void as_xml(xmlpp::Element* element, bool with_paths) const override;
dcpomatic::DCPTime full_length (std::shared_ptr<const Film> film) const override;
dcpomatic::DCPTime approximate_length () const override;
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index 45983795b..29ab5aaf8 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -178,9 +178,8 @@ FFmpegDecoder::flush_fill()
full_length = full_length.ceil (frc.source);
if (video && !video->ignore()) {
double const vfr = _ffmpeg_content->video_frame_rate().get();
- auto const f = full_length.frames_round (vfr);
- auto const v = video->position(film()).get_value_or(ContentTime()).frames_round(vfr) + 1;
- if (v < f) {
+ auto const v = video->position(film()).get_value_or(ContentTime()) + ContentTime::from_frames(1, vfr);
+ if (v < full_length) {
video->emit(film(), make_shared<const RawImageProxy>(_black_image), v);
did_something = true;
}
@@ -260,7 +259,7 @@ deinterleave_audio(AVFrame* frame)
/* XXX: can't we use swr_convert() to do the format conversion? */
- int const channels = frame->channels;
+ int const channels = frame->ch_layout.nb_channels;
int const frames = frame->nb_samples;
int const total_samples = frames * channels;
auto audio = make_shared<AudioBuffers>(channels, frames);
@@ -622,7 +621,7 @@ FFmpegDecoder::process_video_frame ()
video->emit (
film(),
make_shared<RawImageProxy>(image),
- llrint(pts * _ffmpeg_content->active_video_frame_rate(film()))
+ ContentTime::from_seconds(pts)
);
} else {
LOG_WARNING_NC ("Dropping frame without PTS");
diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc
index 51ade8e89..d173c6bb6 100644
--- a/src/lib/ffmpeg_examiner.cc
+++ b/src/lib/ffmpeg_examiner.cc
@@ -73,14 +73,6 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
auto codec = _codec_context[i] ? _codec_context[i]->codec : nullptr;
if (s->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && codec) {
- /* This is a hack; sometimes it seems that _audio_codec_context->channel_layout isn't set up,
- so bodge it here. No idea why we should have to do this.
- */
-
- if (s->codecpar->channel_layout == 0) {
- s->codecpar->channel_layout = av_get_default_channel_layout (s->codecpar->channels);
- }
-
DCPOMATIC_ASSERT (_format_context->duration != AV_NOPTS_VALUE);
DCPOMATIC_ASSERT (codec->name);
@@ -91,7 +83,7 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
s->id,
s->codecpar->sample_rate,
llrint ((double(_format_context->duration) / AV_TIME_BASE) * s->codecpar->sample_rate),
- s->codecpar->channels,
+ s->codecpar->ch_layout.nb_channels,
s->codecpar->bits_per_raw_sample ? s->codecpar->bits_per_raw_sample : s->codecpar->bits_per_coded_sample
)
);
@@ -161,7 +153,7 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
av_packet_free (&packet);
- if (_first_video && got_all_audio && temporal_reference.size() >= (PULLDOWN_CHECK_FRAMES * 2)) {
+ if (got_all_audio && (!_video_stream || (_first_video && temporal_reference.size() >= (PULLDOWN_CHECK_FRAMES * 2)))) {
/* All done */
break;
}
@@ -181,8 +173,6 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
/* This code taken from get_rotation() in ffmpeg:cmdutils.c */
auto stream = _format_context->streams[*_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);
-
if (rotate_tag && *rotate_tag->value && strcmp(rotate_tag->value, "0")) {
char *tail;
_rotation = av_strtod (rotate_tag->value, &tail);
@@ -191,8 +181,9 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
}
}
- if (displaymatrix && !_rotation) {
- _rotation = - av_display_rotation_get ((int32_t*) displaymatrix);
+ auto side_data = av_packet_side_data_get(stream->codecpar->coded_side_data, stream->codecpar->nb_coded_side_data, AV_PKT_DATA_DISPLAYMATRIX);
+ if (side_data && !_rotation) {
+ _rotation = - av_display_rotation_get(reinterpret_cast<int32_t*>(side_data->data));
}
if (_rotation) {
@@ -253,7 +244,7 @@ FFmpegExaminer::video_packet (AVCodecContext* context, string& temporal_referenc
).get_value_or (ContentTime ()).frames_round (video_frame_rate().get ());
}
if (temporal_reference.size() < (PULLDOWN_CHECK_FRAMES * 2)) {
- temporal_reference += (_video_frame->top_field_first ? "T" : "B");
+ temporal_reference += ((_video_frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST) ? "T" : "B");
temporal_reference += (_video_frame->repeat_pict ? "3" : "2");
}
diff --git a/src/lib/ffmpeg_file_encoder.cc b/src/lib/ffmpeg_file_encoder.cc
index 6d1ad68f7..147e83a65 100644
--- a/src/lib/ffmpeg_file_encoder.cc
+++ b/src/lib/ffmpeg_file_encoder.cc
@@ -21,7 +21,7 @@
#include "compose.hpp"
#include "cross.h"
-#include "ffmpeg_encoder.h"
+#include "ffmpeg_file_encoder.h"
#include "ffmpeg_wrapper.h"
#include "film.h"
#include "image.h"
@@ -73,8 +73,7 @@ public:
_codec_context->bit_rate = channels * 128 * 1024;
_codec_context->sample_fmt = sample_format;
_codec_context->sample_rate = frame_rate;
- _codec_context->channel_layout = av_get_default_channel_layout (channels);
- _codec_context->channels = channels;
+ av_channel_layout_default(&_codec_context->ch_layout, channels);
int r = avcodec_open2 (_codec_context, _codec, 0);
if (r < 0) {
@@ -96,7 +95,7 @@ public:
~ExportAudioStream ()
{
- avcodec_close (_codec_context);
+ avcodec_free_context(&_codec_context);
}
ExportAudioStream (ExportAudioStream const&) = delete;
@@ -143,7 +142,7 @@ public:
frame->nb_samples = size;
frame->format = _codec_context->sample_fmt;
- frame->channels = channels;
+ frame->ch_layout.nb_channels = channels;
int r = avcodec_fill_audio_frame (frame, channels, _codec_context->sample_fmt, (const uint8_t *) samples, buffer_size, 0);
DCPOMATIC_ASSERT (r >= 0);
@@ -242,6 +241,13 @@ FFmpegFileEncoder::FFmpegFileEncoder (
av_dict_set (&_video_options, "profile", "3", 0);
av_dict_set (&_video_options, "threads", "auto", 0);
break;
+ case ExportFormat::PRORES_LT:
+ _sample_format = AV_SAMPLE_FMT_S32;
+ _video_codec_name = "prores_ks";
+ _audio_codec_name = "pcm_s24le";
+ av_dict_set(&_video_options, "profile", "1", 0);
+ av_dict_set(&_video_options, "threads", "auto", 0);
+ break;
case ExportFormat::H264_AAC:
_sample_format = AV_SAMPLE_FMT_FLTP;
_video_codec_name = "libx264";
@@ -279,7 +285,7 @@ FFmpegFileEncoder::FFmpegFileEncoder (
FFmpegFileEncoder::~FFmpegFileEncoder ()
{
_audio_streams.clear ();
- avcodec_close (_video_codec_context);
+ avcodec_free_context(&_video_codec_context);
avio_close (_format_context->pb);
_format_context->pb = nullptr;
avformat_free_context (_format_context);
@@ -293,6 +299,7 @@ FFmpegFileEncoder::pixel_format (ExportFormat format)
case ExportFormat::PRORES_4444:
return AV_PIX_FMT_YUV444P10;
case ExportFormat::PRORES_HQ:
+ case ExportFormat::PRORES_LT:
return AV_PIX_FMT_YUV422P10;
case ExportFormat::H264_AAC:
return AV_PIX_FMT_YUV420P;
diff --git a/src/lib/ffmpeg_file_encoder.h b/src/lib/ffmpeg_file_encoder.h
index 78840d6a8..a365f463a 100644
--- a/src/lib/ffmpeg_file_encoder.h
+++ b/src/lib/ffmpeg_file_encoder.h
@@ -23,12 +23,14 @@
#define DCPOMATIC_FFMPEG_FILE_ENCODER_H
+#include "audio_buffers.h"
#include "audio_mapping.h"
#include "dcpomatic_time.h"
-#include "encoder.h"
#include "event_history.h"
#include "image_store.h"
#include "log.h"
+#include "player_text.h"
+#include "player_video.h"
#include <dcp/key.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
@@ -46,6 +48,7 @@ enum class ExportFormat
{
PRORES_4444,
PRORES_HQ,
+ PRORES_LT,
H264_AAC,
SUBTITLES_DCP
};
diff --git a/src/lib/ffmpeg_encoder.cc b/src/lib/ffmpeg_film_encoder.cc
index c1170f098..a2d26fd66 100644
--- a/src/lib/ffmpeg_encoder.cc
+++ b/src/lib/ffmpeg_film_encoder.cc
@@ -21,7 +21,7 @@
#include "butler.h"
#include "cross.h"
-#include "ffmpeg_encoder.h"
+#include "ffmpeg_film_encoder.h"
#include "film.h"
#include "image.h"
#include "job.h"
@@ -48,7 +48,7 @@ using namespace boost::placeholders;
#endif
-FFmpegEncoder::FFmpegEncoder (
+FFmpegFilmEncoder::FFmpegFilmEncoder(
shared_ptr<const Film> film,
weak_ptr<Job> job,
boost::filesystem::path output,
@@ -58,7 +58,7 @@ FFmpegEncoder::FFmpegEncoder (
bool audio_stream_per_channel,
int x264_crf
)
- : Encoder (film, job)
+ : FilmEncoder(film, job)
, _output_audio_channels(mixdown_to_stereo ? 2 : (_film->audio_channels() > 8 ? 16 : _film->audio_channels()))
, _history (200)
, _output (output)
@@ -85,7 +85,7 @@ FFmpegEncoder::FFmpegEncoder (
AudioMapping
-FFmpegEncoder::stereo_map() const
+FFmpegFilmEncoder::stereo_map() const
{
auto map = AudioMapping(_film->audio_channels(), 2);
float const overall_gain = 2 / (4 + sqrt(2));
@@ -116,7 +116,7 @@ FFmpegEncoder::stereo_map() const
AudioMapping
-FFmpegEncoder::many_channel_map() const
+FFmpegFilmEncoder::many_channel_map() const
{
auto map = AudioMapping(_film->audio_channels(), _output_audio_channels);
for (int i = 0; i < _film->audio_channels(); ++i) {
@@ -127,7 +127,7 @@ FFmpegEncoder::many_channel_map() const
void
-FFmpegEncoder::go ()
+FFmpegFilmEncoder::go()
{
{
auto job = _job.lock ();
@@ -234,19 +234,19 @@ FFmpegEncoder::go ()
}
optional<float>
-FFmpegEncoder::current_rate () const
+FFmpegFilmEncoder::current_rate() const
{
return _history.rate ();
}
Frame
-FFmpegEncoder::frames_done () const
+FFmpegFilmEncoder::frames_done() const
{
boost::mutex::scoped_lock lm (_mutex);
return _last_time.frames_round (_film->video_frame_rate ());
}
-FFmpegEncoder::FileEncoderSet::FileEncoderSet (
+FFmpegFilmEncoder::FileEncoderSet::FileEncoderSet(
dcp::Size video_frame_size,
int video_frame_rate,
int audio_frame_rate,
@@ -279,7 +279,7 @@ FFmpegEncoder::FileEncoderSet::FileEncoderSet (
}
shared_ptr<FFmpegFileEncoder>
-FFmpegEncoder::FileEncoderSet::get (Eyes eyes) const
+FFmpegFilmEncoder::FileEncoderSet::get(Eyes eyes) const
{
if (_encoders.size() == 1) {
/* We are doing a 2D export... */
@@ -298,7 +298,7 @@ FFmpegEncoder::FileEncoderSet::get (Eyes eyes) const
}
void
-FFmpegEncoder::FileEncoderSet::flush ()
+FFmpegFilmEncoder::FileEncoderSet::flush()
{
for (auto& i: _encoders) {
i.second->flush ();
@@ -306,7 +306,7 @@ FFmpegEncoder::FileEncoderSet::flush ()
}
void
-FFmpegEncoder::FileEncoderSet::audio (shared_ptr<AudioBuffers> a)
+FFmpegFilmEncoder::FileEncoderSet::audio(shared_ptr<AudioBuffers> a)
{
for (auto& i: _encoders) {
i.second->audio (a);
diff --git a/src/lib/ffmpeg_encoder.h b/src/lib/ffmpeg_film_encoder.h
index 2d5c6af87..ec6bb4594 100644
--- a/src/lib/ffmpeg_encoder.h
+++ b/src/lib/ffmpeg_film_encoder.h
@@ -23,15 +23,15 @@
#include "audio_mapping.h"
#include "butler.h"
-#include "encoder.h"
#include "event_history.h"
#include "ffmpeg_file_encoder.h"
+#include "film_encoder.h"
-class FFmpegEncoder : public Encoder
+class FFmpegFilmEncoder : public FilmEncoder
{
public:
- FFmpegEncoder (
+ FFmpegFilmEncoder(
std::shared_ptr<const Film> film,
std::weak_ptr<Job> job,
boost::filesystem::path output,
diff --git a/src/lib/ffmpeg_image_proxy.cc b/src/lib/ffmpeg_image_proxy.cc
index 2fcd486df..0ee41deab 100644
--- a/src/lib/ffmpeg_image_proxy.cc
+++ b/src/lib/ffmpeg_image_proxy.cc
@@ -234,9 +234,9 @@ FFmpegImageProxy::image (Image::Alignment alignment, optional<dcp::Size>) const
void
-FFmpegImageProxy::add_metadata (xmlpp::Node* node) const
+FFmpegImageProxy::add_metadata(xmlpp::Element* element) const
{
- node->add_child("Type")->add_child_text (N_("FFmpeg"));
+ cxml::add_text_child(element, "Type", N_("FFmpeg"));
}
void
diff --git a/src/lib/ffmpeg_image_proxy.h b/src/lib/ffmpeg_image_proxy.h
index b567fadee..3817d709f 100644
--- a/src/lib/ffmpeg_image_proxy.h
+++ b/src/lib/ffmpeg_image_proxy.h
@@ -28,14 +28,14 @@ class FFmpegImageProxy : public ImageProxy
public:
explicit FFmpegImageProxy (boost::filesystem::path);
explicit FFmpegImageProxy (dcp::ArrayData);
- FFmpegImageProxy (std::shared_ptr<Socket> socket);
+ explicit FFmpegImageProxy (std::shared_ptr<Socket> socket);
Result image (
Image::Alignment alignment,
boost::optional<dcp::Size> size = boost::optional<dcp::Size> ()
) const override;
- void add_metadata (xmlpp::Node *) const override;
+ void add_metadata(xmlpp::Element*) const override;
void write_to_socket (std::shared_ptr<Socket>) const override;
bool same (std::shared_ptr<const ImageProxy> other) const override;
size_t memory_used () const override;
diff --git a/src/lib/ffmpeg_stream.cc b/src/lib/ffmpeg_stream.cc
index 47f71d119..800c45eb6 100644
--- a/src/lib/ffmpeg_stream.cc
+++ b/src/lib/ffmpeg_stream.cc
@@ -40,10 +40,10 @@ FFmpegStream::FFmpegStream (cxml::ConstNodePtr node)
}
void
-FFmpegStream::as_xml (xmlpp::Node* root) const
+FFmpegStream::as_xml(xmlpp::Element* root) const
{
- root->add_child("Name")->add_child_text (name);
- root->add_child("Id")->add_child_text (raw_convert<string> (_id));
+ cxml::add_text_child(root, "Name", name);
+ cxml::add_text_child(root, "Id", raw_convert<string>(_id));
}
bool
diff --git a/src/lib/ffmpeg_stream.h b/src/lib/ffmpeg_stream.h
index 84b2a8853..221075eeb 100644
--- a/src/lib/ffmpeg_stream.h
+++ b/src/lib/ffmpeg_stream.h
@@ -37,7 +37,7 @@ public:
explicit FFmpegStream (cxml::ConstNodePtr);
- void as_xml (xmlpp::Node *) const;
+ void as_xml(xmlpp::Element*) const;
/** @param c An AVFormatContext.
* @param index A stream index within the AVFormatContext.
diff --git a/src/lib/ffmpeg_subtitle_stream.cc b/src/lib/ffmpeg_subtitle_stream.cc
index 1101901b7..3f43b6ec5 100644
--- a/src/lib/ffmpeg_subtitle_stream.cc
+++ b/src/lib/ffmpeg_subtitle_stream.cc
@@ -49,15 +49,15 @@ FFmpegSubtitleStream::FFmpegSubtitleStream (cxml::ConstNodePtr node, int version
}
void
-FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
+FFmpegSubtitleStream::as_xml(xmlpp::Element* root) const
{
- FFmpegStream::as_xml (root);
+ FFmpegStream::as_xml(root);
boost::mutex::scoped_lock lm (_mutex);
for (map<RGBA, RGBA>::const_iterator i = _colours.begin(); i != _colours.end(); ++i) {
- xmlpp::Node* node = root->add_child("Colour");
- i->first.as_xml (node->add_child("From"));
- i->second.as_xml (node->add_child("To"));
+ auto node = cxml::add_child(root, "Colour");
+ i->first.as_xml(cxml::add_child(node, "From"));
+ i->second.as_xml(cxml::add_child(node, "To"));
}
}
diff --git a/src/lib/ffmpeg_subtitle_stream.h b/src/lib/ffmpeg_subtitle_stream.h
index 8f56d1975..6251350ce 100644
--- a/src/lib/ffmpeg_subtitle_stream.h
+++ b/src/lib/ffmpeg_subtitle_stream.h
@@ -33,7 +33,7 @@ public:
FFmpegSubtitleStream (cxml::ConstNodePtr node, int version);
- void as_xml (xmlpp::Node *) const;
+ void as_xml(xmlpp::Element*) const;
void set_colour (RGBA from, RGBA to);
std::map<RGBA, RGBA> colours () const;
diff --git a/src/lib/film.cc b/src/lib/film.cc
index d9ab6e2a3..174b3ce83 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -36,7 +36,7 @@
#include "cross.h"
#include "dcp_content.h"
#include "dcp_content_type.h"
-#include "dcp_encoder.h"
+#include "dcp_film_encoder.h"
#include "dcpomatic_log.h"
#include "digester.h"
#include "environment_info.h"
@@ -57,6 +57,7 @@
#include "text_content.h"
#include "transcode_job.h"
#include "upload_job.h"
+#include "variant.h"
#include "video_content.h"
#include "version.h"
#include <libcxml/cxml.h>
@@ -88,18 +89,13 @@ using std::back_inserter;
using std::copy;
using std::cout;
using std::dynamic_pointer_cast;
-using std::exception;
using std::find;
using std::list;
-using std::make_pair;
using std::make_shared;
using std::map;
using std::max;
-using std::min;
using std::pair;
using std::runtime_error;
-using std::set;
-using std::setfill;
using std::shared_ptr;
using std::string;
using std::vector;
@@ -114,6 +110,8 @@ using namespace dcpomatic;
static constexpr char metadata_file[] = "metadata.xml";
+static constexpr char ui_state_file[] = "ui.xml";
+static constexpr char assets_file[] = "assets.xml";
/* 5 -> 6
@@ -150,6 +148,14 @@ int const Film::current_state_version = 38;
/** Construct a Film object in a given directory.
*
+ * Some initial values are taken from the Config object, and usually then overwritten
+ * by reading in the default template. Setting up things like _dcp_content_type in
+ * this constructor is useful so that if people configured such things in old versions
+ * they are used when the first default template is written by Config
+ *
+ * At some point these config entries can be removed, and this constructor could
+ * perhaps read the default template itself.
+ *
* @param dir Film directory.
*/
@@ -161,12 +167,12 @@ Film::Film (optional<boost::filesystem::path> dir)
, _resolution (Resolution::TWO_K)
, _encrypted (false)
, _context_id (dcp::make_uuid ())
- , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ())
, _video_frame_rate (24)
, _audio_channels (Config::instance()->default_dcp_audio_channels ())
, _three_d (false)
, _sequence (true)
, _interop (Config::instance()->default_interop ())
+ , _video_encoding(VideoEncoding::JPEG2000)
, _limit_to_smpte_bv20(false)
, _audio_processor (0)
, _reel_type (ReelType::SINGLE)
@@ -200,6 +206,10 @@ Film::Film (optional<boost::filesystem::path> dir)
_studio = metadata["studio"];
}
+ for (auto encoding: {VideoEncoding::JPEG2000, VideoEncoding::MPEG2}) {
+ _video_bit_rate[encoding] = Config::instance()->default_video_bit_rate(encoding);
+ }
+
_playlist_change_connection = _playlist->Change.connect (bind (&Film::playlist_change, this, _1));
_playlist_order_changed_connection = _playlist->OrderChange.connect (bind (&Film::playlist_order_changed, this));
_playlist_content_change_connection = _playlist->ContentChange.connect (bind (&Film::playlist_content_change, this, _1, _2, _3, _4));
@@ -238,7 +248,7 @@ Film::video_identifier () const
+ "_" + resolution_to_string (_resolution)
+ "_" + _playlist->video_identifier()
+ "_" + raw_convert<string>(_video_frame_rate)
- + "_" + raw_convert<string>(j2k_bandwidth());
+ + "_" + raw_convert<string>(video_bit_rate(video_encoding()));
if (encrypted ()) {
/* This is insecure but hey, the key is in plaintext in metadata.xml */
@@ -249,6 +259,9 @@ Film::video_identifier () const
if (_interop) {
s += "_I";
+ if (_video_encoding == VideoEncoding::MPEG2) {
+ s += "_M";
+ }
} else {
s += "_S";
if (_limit_to_smpte_bv20) {
@@ -279,17 +292,6 @@ Film::info_file (DCPTimePeriod period) const
return file (p);
}
-boost::filesystem::path
-Film::internal_video_asset_dir () const
-{
- return dir ("video");
-}
-
-boost::filesystem::path
-Film::internal_video_asset_filename (DCPTimePeriod p) const
-{
- return video_identifier() + "_" + raw_convert<string> (p.from.get()) + "_" + raw_convert<string> (p.to.get()) + ".mxf";
-}
boost::filesystem::path
Film::audio_analysis_path (shared_ptr<const Playlist> playlist) const
@@ -334,6 +336,13 @@ Film::audio_analysis_path (shared_ptr<const Playlist> playlist) const
boost::filesystem::path
+Film::assets_path() const
+{
+ return dir("assets");
+}
+
+
+boost::filesystem::path
Film::subtitle_analysis_path (shared_ptr<const Content> content) const
{
auto p = dir ("analysis");
@@ -379,88 +388,93 @@ Film::metadata (bool with_content_paths) const
auto doc = make_shared<xmlpp::Document>();
auto root = doc->create_root_node ("Metadata");
- root->add_child("Version")->add_child_text (raw_convert<string> (current_state_version));
- auto last_write = root->add_child("LastWrittenBy");
+ cxml::add_text_child(root, "Version", raw_convert<string>(current_state_version));
+ auto last_write = cxml::add_child(root, "LastWrittenBy");
last_write->add_child_text (dcpomatic_version);
last_write->set_attribute("git", dcpomatic_git_commit);
- root->add_child("Name")->add_child_text (_name);
- root->add_child("UseISDCFName")->add_child_text (_use_isdcf_name ? "1" : "0");
+ cxml::add_text_child(root, "Name", _name);
+ cxml::add_text_child(root, "UseISDCFName", _use_isdcf_name ? "1" : "0");
if (_dcp_content_type) {
- root->add_child("DCPContentType")->add_child_text (_dcp_content_type->isdcf_name ());
+ cxml::add_text_child(root, "DCPContentType", _dcp_content_type->isdcf_name());
}
if (_container) {
- root->add_child("Container")->add_child_text (_container->id ());
- }
-
- root->add_child("Resolution")->add_child_text (resolution_to_string (_resolution));
- root->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth));
- root->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate));
- root->add_child("AudioFrameRate")->add_child_text(raw_convert<string>(_audio_frame_rate));
- root->add_child("ISDCFDate")->add_child_text (boost::gregorian::to_iso_string (_isdcf_date));
- root->add_child("AudioChannels")->add_child_text (raw_convert<string> (_audio_channels));
- 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);
+ cxml::add_text_child(root, "Container", _container->id());
+ }
+
+ cxml::add_text_child(root, "Resolution", resolution_to_string(_resolution));
+ cxml::add_text_child(root, "J2KVideoBitRate", raw_convert<string>(_video_bit_rate[VideoEncoding::JPEG2000]));
+ cxml::add_text_child(root, "MPEG2VideoBitRate", raw_convert<string>(_video_bit_rate[VideoEncoding::MPEG2]));
+ cxml::add_text_child(root, "VideoFrameRate", raw_convert<string>(_video_frame_rate));
+ cxml::add_text_child(root, "AudioFrameRate", raw_convert<string>(_audio_frame_rate));
+ cxml::add_text_child(root, "ISDCFDate", boost::gregorian::to_iso_string(_isdcf_date));
+ cxml::add_text_child(root, "AudioChannels", raw_convert<string>(_audio_channels));
+ cxml::add_text_child(root, "ThreeD", _three_d ? "1" : "0");
+ cxml::add_text_child(root, "Sequence", _sequence ? "1" : "0");
+ cxml::add_text_child(root, "Interop", _interop ? "1" : "0");
+ cxml::add_text_child(root, "VideoEncoding", video_encoding_to_string(_video_encoding));
+ cxml::add_text_child(root, "LimitToSMPTEBv20", _limit_to_smpte_bv20 ? "1" : "0");
+ cxml::add_text_child(root, "Encrypted", _encrypted ? "1" : "0");
+ cxml::add_text_child(root, "Key", _key.hex ());
+ cxml::add_text_child(root, "ContextID", _context_id);
if (_audio_processor) {
- root->add_child("AudioProcessor")->add_child_text (_audio_processor->id ());
+ cxml::add_text_child(root, "AudioProcessor", _audio_processor->id());
+ }
+ cxml::add_text_child(root, "ReelType", raw_convert<string>(static_cast<int> (_reel_type)));
+ cxml::add_text_child(root, "ReelLength", raw_convert<string>(_reel_length));
+ for (auto boundary: _custom_reel_boundaries) {
+ cxml::add_text_child(root, "CustomReelBoundary", raw_convert<string>(boundary.get()));
}
- root->add_child("ReelType")->add_child_text (raw_convert<string> (static_cast<int> (_reel_type)));
- root->add_child("ReelLength")->add_child_text (raw_convert<string> (_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");
+ cxml::add_text_child(root, "ReencodeJ2K", _reencode_j2k ? "1" : "0");
+ cxml::add_text_child(root, "UserExplicitVideoFrameRate", _user_explicit_video_frame_rate ? "1" : "0");
for (auto const& marker: _markers) {
- auto m = root->add_child("Marker");
- m->set_attribute("Type", dcp::marker_to_string(marker.first));
+ auto m = cxml::add_child(root, "Marker");
+ m->set_attribute("type", dcp::marker_to_string(marker.first));
m->add_child_text(raw_convert<string>(marker.second.get()));
}
for (auto i: _ratings) {
- i.as_xml (root->add_child("Rating"));
+ i.as_xml(cxml::add_child(root, "Rating"));
}
for (auto i: _content_versions) {
- root->add_child("ContentVersion")->add_child_text(i);
+ cxml::add_text_child(root, "ContentVersion", 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));
+ cxml::add_text_child(root, "NameLanguage", _name_language.to_string());
+ cxml::add_text_child(root, "TerritoryType", territory_type_to_string(_territory_type));
if (_release_territory) {
- root->add_child("ReleaseTerritory")->add_child_text(_release_territory->subtag());
+ cxml::add_text_child(root, "ReleaseTerritory", _release_territory->subtag());
}
if (_sign_language_video_language) {
- root->add_child("SignLanguageVideoLanguage")->add_child_text(_sign_language_video_language->to_string());
+ cxml::add_text_child(root, "SignLanguageVideoLanguage", _sign_language_video_language->to_string());
}
- root->add_child("VersionNumber")->add_child_text(raw_convert<string>(_version_number));
- root->add_child("Status")->add_child_text(dcp::status_to_string(_status));
+ cxml::add_text_child(root, "VersionNumber", raw_convert<string>(_version_number));
+ cxml::add_text_child(root, "Status", dcp::status_to_string(_status));
if (_chain) {
- root->add_child("Chain")->add_child_text(*_chain);
+ cxml::add_text_child(root, "Chain", *_chain);
}
if (_distributor) {
- root->add_child("Distributor")->add_child_text(*_distributor);
+ cxml::add_text_child(root, "Distributor", *_distributor);
}
if (_facility) {
- root->add_child("Facility")->add_child_text(*_facility);
+ cxml::add_text_child(root, "Facility", *_facility);
}
if (_studio) {
- root->add_child("Studio")->add_child_text(*_studio);
+ cxml::add_text_child(root, "Studio", *_studio);
}
- root->add_child("TempVersion")->add_child_text(_temp_version ? "1" : "0");
- root->add_child("PreRelease")->add_child_text(_pre_release ? "1" : "0");
- root->add_child("RedBand")->add_child_text(_red_band ? "1" : "0");
- root->add_child("TwoDVersionOfThreeD")->add_child_text(_two_d_version_of_three_d ? "1" : "0");
+ cxml::add_text_child(root, "TempVersion", _temp_version ? "1" : "0");
+ cxml::add_text_child(root, "PreRelease", _pre_release ? "1" : "0");
+ cxml::add_text_child(root, "RedBand", _red_band ? "1" : "0");
+ cxml::add_text_child(root, "TwoDVersionOfThreeD", _two_d_version_of_three_d ? "1" : "0");
if (_luminance) {
- root->add_child("LuminanceValue")->add_child_text(raw_convert<string>(_luminance->value()));
- root->add_child("LuminanceUnit")->add_child_text(dcp::Luminance::unit_to_string(_luminance->unit()));
+ cxml::add_text_child(root, "LuminanceValue", raw_convert<string>(_luminance->value()));
+ cxml::add_text_child(root, "LuminanceUnit", dcp::Luminance::unit_to_string(_luminance->unit()));
}
- root->add_child("UserExplicitContainer")->add_child_text(_user_explicit_container ? "1" : "0");
- root->add_child("UserExplicitResolution")->add_child_text(_user_explicit_resolution ? "1" : "0");
+ cxml::add_text_child(root, "UserExplicitContainer", _user_explicit_container ? "1" : "0");
+ cxml::add_text_child(root, "UserExplicitResolution", _user_explicit_resolution ? "1" : "0");
if (_audio_language) {
- root->add_child("AudioLanguage")->add_child_text(_audio_language->to_string());
+ cxml::add_text_child(root, "AudioLanguage", _audio_language->to_string());
}
- _playlist->as_xml (root->add_child ("Playlist"), with_content_paths);
+ _playlist->as_xml(cxml::add_child(root, "Playlist"), with_content_paths);
return doc;
}
@@ -503,7 +517,13 @@ Film::read_metadata (optional<boost::filesystem::path> path)
{
if (!path) {
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!"));
+ throw runtime_error(
+ variant::insert_dcpomatic(
+ _("This film was created with an older version of %1, 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);
@@ -518,7 +538,7 @@ Film::read_metadata (optional<boost::filesystem::path> path)
_state_version = f.number_child<int> ("Version");
if (_state_version > current_state_version) {
- throw runtime_error (_("This film was created with a newer version of DCP-o-matic, and it cannot be loaded into this version. Sorry!"));
+ throw runtime_error(variant::insert_dcpomatic(_("This film was created with a newer version of %1, and it cannot be loaded into this version. Sorry!")));
} 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);
@@ -558,7 +578,12 @@ Film::read_metadata (optional<boost::filesystem::path> path)
}
_resolution = string_to_resolution (f.string_child ("Resolution"));
- _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
+ if (auto j2k = f.optional_number_child<int>("J2KBandwidth")) {
+ _video_bit_rate[VideoEncoding::JPEG2000] = *j2k;
+ } else {
+ _video_bit_rate[VideoEncoding::JPEG2000] = f.number_child<int64_t>("J2KVideoBitRate");
+ }
+ _video_bit_rate[VideoEncoding::MPEG2] = f.optional_number_child<int64_t>("MPEG2VideoBitRate").get_value_or(Config::instance()->default_video_bit_rate(VideoEncoding::MPEG2));
_video_frame_rate = f.number_child<int> ("VideoFrameRate");
_audio_frame_rate = f.optional_number_child<int>("AudioFrameRate").get_value_or(48000);
_encrypted = f.bool_child ("Encrypted");
@@ -580,6 +605,9 @@ Film::read_metadata (optional<boost::filesystem::path> path)
_three_d = f.bool_child ("ThreeD");
_interop = f.bool_child ("Interop");
+ if (auto encoding = f.optional_string_child("VideoEncoding")) {
+ _video_encoding = video_encoding_from_string(*encoding);
+ }
_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 ());
@@ -599,11 +627,18 @@ Film::read_metadata (optional<boost::filesystem::path> path)
_reel_type = static_cast<ReelType> (f.optional_number_child<int>("ReelType").get_value_or (static_cast<int>(ReelType::SINGLE)));
_reel_length = f.optional_number_child<int64_t>("ReelLength").get_value_or (2000000000);
+ for (auto boundary: f.node_children("CustomReelBoundary")) {
+ _custom_reel_boundaries.push_back(DCPTime(raw_convert<int64_t>(boundary->content())));
+ }
_reencode_j2k = f.optional_bool_child("ReencodeJ2K").get_value_or(false);
_user_explicit_video_frame_rate = f.optional_bool_child("UserExplicitVideoFrameRate").get_value_or(false);
for (auto i: f.node_children("Marker")) {
- _markers[dcp::marker_from_string(i->string_attribute("Type"))] = DCPTime(dcp::raw_convert<DCPTime::Type>(i->content()));
+ auto type = i->optional_string_attribute("Type");
+ if (!type) {
+ type = i->string_attribute("type");
+ }
+ _markers[dcp::marker_from_string(*type)] = DCPTime(dcp::raw_convert<DCPTime::Type>(i->content()));
}
for (auto i: f.node_children("Rating")) {
@@ -1151,10 +1186,10 @@ Film::set_resolution (Resolution r, bool explicit_user)
void
-Film::set_j2k_bandwidth (int b)
+Film::set_video_bit_rate(VideoEncoding encoding, int64_t bit_rate)
{
- FilmChangeSignaller ch(this, FilmProperty::J2K_BANDWIDTH);
- _j2k_bandwidth = b;
+ FilmChangeSignaller ch(this, FilmProperty::VIDEO_BIT_RATE);
+ _video_bit_rate[encoding] = bit_rate;
}
/** @param f New frame rate.
@@ -1198,6 +1233,15 @@ Film::set_interop (bool i)
void
+Film::set_video_encoding(VideoEncoding encoding)
+{
+ FilmChangeSignaller ch(this, FilmProperty::VIDEO_ENCODING);
+ _video_encoding = encoding;
+ check_settings_consistency();
+}
+
+
+void
Film::set_limit_to_smpte_bv20(bool limit)
{
FilmChangeSignaller ch(this, FilmProperty::LIMIT_TO_SMPTE_BV20);
@@ -1228,6 +1272,16 @@ Film::set_reel_length (int64_t r)
_reel_length = r;
}
+
+void
+Film::set_custom_reel_boundaries(vector<DCPTime> boundaries)
+{
+ FilmChangeSignaller ch(this, FilmProperty::CUSTOM_REEL_BOUNDARIES);
+ std::sort(boundaries.begin(), boundaries.end());
+ _custom_reel_boundaries = std::move(boundaries);
+}
+
+
void
Film::set_reencode_j2k (bool r)
{
@@ -1561,7 +1615,7 @@ Film::check_settings_consistency ()
} else if (!atmos_rate && rate != video_frame_rate()) {
atmos_rate = rate;
set_video_frame_rate (rate, false);
- Message (_("DCP-o-matic had to change your settings so that the film's frame rate is the same as that of your Atmos content."));
+ Message(variant::insert_dcpomatic(_("%1 had to change your settings so that the film's frame rate is the same as that of your Atmos content.")));
}
}
}
@@ -1593,7 +1647,41 @@ Film::check_settings_consistency ()
}
if (change_made) {
- Message (_("DCP-o-matic had to change your settings for referring to DCPs as OV. Please review those settings to make sure they are what you want."));
+ Message(variant::insert_dcpomatic(_("%1 had to change your settings for referring to DCPs as OV. Please review those settings to make sure they are what you want.")));
+ }
+
+ if (reel_type() == ReelType::CUSTOM) {
+ auto boundaries = custom_reel_boundaries();
+ auto too_late = std::find_if(boundaries.begin(), boundaries.end(), [this](dcpomatic::DCPTime const& time) {
+ return time >= length();
+ });
+
+ if (too_late != boundaries.end()) {
+ if (std::distance(too_late, boundaries.end()) > 1) {
+ Message(variant::insert_dcpomatic(_("%1 had to remove some of your custom reel boundaries as they no longer lie within the film.")));
+ } else {
+ Message(variant::insert_dcpomatic(_("%1 had to remove one of your custom reel boundaries as it no longer lies within the film.")));
+ }
+ boundaries.erase(too_late, boundaries.end());
+ set_custom_reel_boundaries(boundaries);
+ }
+ }
+
+ auto const hd = Ratio::from_id("178");
+
+ if (video_encoding() == VideoEncoding::MPEG2) {
+ if (container() != hd || resolution() == Resolution::FOUR_K) {
+ set_container(hd);
+ set_resolution(Resolution::TWO_K);
+ Message(_("DCP-o-matic had to set your container to 1920x1080 as it's the only one that can be used with MPEG2 encoding."));
+ }
+ if (three_d()) {
+ set_three_d(false);
+ Message(_("DCP-o-matic had to set your film to 2D as 3D is not yet supported with MPEG2 encoding."));
+ }
+ } else if (container() == hd && !Config::instance()->allow_any_container()) {
+ set_container(Ratio::from_id("185"));
+ Message(_("DCP-o-matic set your container to DCI Flat as it was previously 1920x1080 and that is not a standard ratio with JPEG2000 encoding."));
}
}
@@ -1720,7 +1808,7 @@ Film::make_kdm(boost::filesystem::path cpl_file, dcp::LocalTime from, dcp::Local
uint64_t
Film::required_disk_space () const
{
- return _playlist->required_disk_space (shared_from_this(), j2k_bandwidth(), audio_channels(), audio_frame_rate());
+ return _playlist->required_disk_space (shared_from_this(), video_bit_rate(video_encoding()), audio_channels(), audio_frame_rate());
}
/** This method checks the disk that the Film is on and tries to decide whether or not
@@ -1731,30 +1819,11 @@ Film::required_disk_space () const
* Note: the decision made by this method isn't, of course, 100% reliable.
*/
bool
-Film::should_be_enough_disk_space (double& required, double& available, bool& can_hard_link) const
-{
- /* Create a test file and see if we can hard-link it */
- boost::filesystem::path test = internal_video_asset_dir() / "test";
- boost::filesystem::path test2 = internal_video_asset_dir() / "test2";
- can_hard_link = true;
- dcp::File f(test, "w");
- if (f) {
- f.close();
- boost::system::error_code ec;
- dcp::filesystem::create_hard_link(test, test2, ec);
- if (ec) {
- can_hard_link = false;
- }
- dcp::filesystem::remove(test);
- dcp::filesystem::remove(test2);
- }
-
- auto s = dcp::filesystem::space(internal_video_asset_dir());
- required = double (required_disk_space ()) / 1073741824.0f;
- if (!can_hard_link) {
- required *= 2;
- }
- available = double (s.available) / 1073741824.0f;
+Film::should_be_enough_disk_space(double& required, double& available) const
+{
+ DCPOMATIC_ASSERT(directory());
+ required = required_disk_space() / 1073741824.0f;
+ available = dcp::filesystem::space(*directory()).available / 1073741824.0f;
return (available - required) > 1;
}
@@ -1799,15 +1868,16 @@ Film::audio_analysis_finished ()
/* XXX */
}
-list<DCPTimePeriod>
+
+vector<DCPTimePeriod>
Film::reels () const
{
- list<DCPTimePeriod> p;
+ vector<DCPTimePeriod> periods;
auto const len = length();
switch (reel_type ()) {
case ReelType::SINGLE:
- p.push_back (DCPTimePeriod (DCPTime (), len));
+ periods.emplace_back(DCPTime(), len);
break;
case ReelType::BY_VIDEO_CONTENT:
{
@@ -1832,7 +1902,7 @@ Film::reels () const
for (auto t: split_points) {
if (last && (t - *last) >= DCPTime::from_seconds(1)) {
/* Period from *last to t is long enough; use it and start a new one */
- p.push_back (DCPTimePeriod(*last, t));
+ periods.emplace_back(*last, t);
last = t;
} else if (!last) {
/* That was the first time, so start a new period */
@@ -1840,8 +1910,8 @@ Film::reels () const
}
}
- if (!p.empty()) {
- p.back().to = split_points.back();
+ if (!periods.empty()) {
+ periods.back().to = split_points.back();
}
break;
}
@@ -1851,19 +1921,32 @@ Film::reels () const
/* Integer-divide reel length by the size of one frame to give the number of frames per reel,
* making sure we don't go less than 1s long.
*/
- Frame const reel_in_frames = max(_reel_length / ((j2k_bandwidth() / video_frame_rate()) / 8), static_cast<Frame>(video_frame_rate()));
+ Frame const reel_in_frames = max(_reel_length / ((video_bit_rate(video_encoding()) / video_frame_rate()) / 8), static_cast<Frame>(video_frame_rate()));
while (current < len) {
DCPTime end = min (len, current + DCPTime::from_frames (reel_in_frames, video_frame_rate ()));
- p.push_back (DCPTimePeriod (current, end));
+ periods.emplace_back(current, end);
current = end;
}
break;
}
+ case ReelType::CUSTOM:
+ {
+ DCPTimePeriod current;
+ for (auto boundary: _custom_reel_boundaries) {
+ current.to = boundary;
+ periods.push_back(current);
+ current.from = boundary;
+ }
+ current.to = len;
+ periods.push_back(current);
+ break;
+ }
}
- return p;
+ return periods;
}
+
/** @param period A period within the DCP
* @return Name of the content which most contributes to the given period.
*/
@@ -1874,15 +1957,21 @@ Film::content_summary (DCPTimePeriod period) const
}
void
-Film::use_template (string name)
+Film::use_template(optional<string> name)
{
- _template_film.reset (new Film (optional<boost::filesystem::path>()));
- _template_film->read_metadata (Config::instance()->template_read_path(name));
+ _template_film = std::make_shared<Film>(optional<boost::filesystem::path>());
+ if (name) {
+ _template_film->read_metadata(Config::instance()->template_read_path(*name));
+ } else {
+ _template_film->read_metadata(Config::instance()->default_template_read_path());
+ }
_use_isdcf_name = _template_film->_use_isdcf_name;
_dcp_content_type = _template_film->_dcp_content_type;
_container = _template_film->_container;
_resolution = _template_film->_resolution;
- _j2k_bandwidth = _template_film->_j2k_bandwidth;
+ for (auto encoding: { VideoEncoding::JPEG2000, VideoEncoding::MPEG2 }) {
+ _video_bit_rate[encoding] = _template_film->_video_bit_rate[encoding];
+ }
_video_frame_rate = _template_film->_video_frame_rate;
_encrypted = _template_film->_encrypted;
_audio_channels = _template_film->_audio_channels;
@@ -1892,6 +1981,12 @@ Film::use_template (string name)
_audio_processor = _template_film->_audio_processor;
_reel_type = _template_film->_reel_type;
_reel_length = _template_film->_reel_length;
+ _chain = _template_film->_chain;
+ _distributor = _template_film->_distributor;
+ _facility = _template_film->_facility;
+ _studio = _template_film->_studio;
+ _territory_type = _template_film->_territory_type;
+ _release_territory = _template_film->_release_territory;
}
pair<double, double>
@@ -2230,3 +2325,94 @@ Film::set_territory_type(TerritoryType type)
_territory_type = type;
}
+
+void
+Film::set_ui_state(string key, string value)
+{
+ _ui_state[key] = value;
+ write_ui_state();
+}
+
+
+boost::optional<std::string>
+Film::ui_state(string key) const
+{
+ auto iter = _ui_state.find(key);
+ if (iter == _ui_state.end()) {
+ return {};
+ }
+
+ return iter->second;
+}
+
+
+void
+Film::write_ui_state() const
+{
+ auto doc = make_shared<xmlpp::Document>();
+ auto root = doc->create_root_node("UI");
+
+ for (auto state: _ui_state) {
+ cxml::add_text_child(root, state.first, state.second);
+ }
+
+ try {
+ doc->write_to_file_formatted(dcp::filesystem::fix_long_path(file(ui_state_file)).string());
+ } catch (...) {}
+}
+
+
+void
+Film::read_ui_state()
+{
+ try {
+ cxml::Document xml("UI");
+ xml.read_file(dcp::filesystem::fix_long_path(file(ui_state_file)));
+ for (auto node: xml.node_children()) {
+ if (!node->is_text()) {
+ _ui_state[node->name()] = node->content();
+ }
+ }
+ } catch (...) {}
+}
+
+
+vector<RememberedAsset>
+Film::read_remembered_assets() const
+{
+ vector<RememberedAsset> assets;
+
+ try {
+ cxml::Document xml("Assets");
+ xml.read_file(dcp::filesystem::fix_long_path(file(assets_file)));
+ for (auto node: xml.node_children("Asset")) {
+ assets.push_back(RememberedAsset(node));
+ }
+ } catch (std::exception& e) {
+ LOG_ERROR("Could not read assets file %1 (%2)", file(assets_file), e.what());
+ } catch (...) {
+ LOG_ERROR("Could not read assets file %1", file(assets_file));
+ }
+
+ return assets;
+}
+
+
+void
+Film::write_remembered_assets(vector<RememberedAsset> const& assets) const
+{
+ auto doc = make_shared<xmlpp::Document>();
+ auto root = doc->create_root_node("Assets");
+
+ for (auto asset: assets) {
+ asset.as_xml(cxml::add_child(root, "Asset"));
+ }
+
+ try {
+ doc->write_to_file_formatted(dcp::filesystem::fix_long_path(file(assets_file)).string());
+ } catch (std::exception& e) {
+ LOG_ERROR("Could not write assets file %1 (%2)", file(assets_file), e.what());
+ } catch (...) {
+ LOG_ERROR("Could not write assets file %1", file(assets_file));
+ }
+}
diff --git a/src/lib/film.h b/src/lib/film.h
index 43a41ad45..d71435566 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -32,15 +32,18 @@
#include "change_signaller.h"
#include "dcp_text_track.h"
#include "dcpomatic_time.h"
+#include "enum_indexed_vector.h"
#include "film_property.h"
#include "frame_rate_change.h"
#include "named_channel.h"
+#include "remembered_asset.h"
#include "resolution.h"
#include "signaller.h"
#include "territory_type.h"
#include "transcode_job.h"
#include "types.h"
#include "util.h"
+#include "video_encoding.h"
#include <dcp/encrypted_kdm.h>
#include <dcp/file.h>
#include <dcp/key.h>
@@ -116,11 +119,10 @@ public:
std::shared_ptr<InfoFileHandle> info_file_handle (dcpomatic::DCPTimePeriod period, bool read) const;
boost::filesystem::path j2c_path (int, Frame, Eyes, bool) const;
- boost::filesystem::path internal_video_asset_dir () const;
- boost::filesystem::path internal_video_asset_filename (dcpomatic::DCPTimePeriod p) const;
boost::filesystem::path audio_analysis_path (std::shared_ptr<const Playlist>) const;
boost::filesystem::path subtitle_analysis_path (std::shared_ptr<const Content>) const;
+ boost::filesystem::path assets_path() const;
void send_dcp_to_tms ();
@@ -134,7 +136,7 @@ public:
boost::filesystem::path file (boost::filesystem::path f) const;
boost::filesystem::path dir (boost::filesystem::path d, bool create = true) const;
- void use_template (std::string name);
+ void use_template(boost::optional<std::string> name);
std::list<std::string> read_metadata (boost::optional<boost::filesystem::path> path = boost::optional<boost::filesystem::path> ());
void write_metadata ();
void write_metadata (boost::filesystem::path path) const;
@@ -160,7 +162,7 @@ public:
std::list<DCPTextTrack> closed_caption_tracks () const;
uint64_t required_disk_space () const;
- bool should_be_enough_disk_space (double& required, double& available, bool& can_hard_link) const;
+ bool should_be_enough_disk_space(double& required, double& available) const;
bool has_sign_language_video_channel () const;
@@ -186,7 +188,7 @@ public:
return _playlist;
}
- std::list<dcpomatic::DCPTimePeriod> reels () const;
+ std::vector<dcpomatic::DCPTimePeriod> reels() const;
std::list<int> mapped_audio_channels () const;
boost::optional<dcp::LanguageTag> audio_language () const {
@@ -250,8 +252,8 @@ public:
return _key;
}
- int j2k_bandwidth () const {
- return _j2k_bandwidth;
+ int video_bit_rate(VideoEncoding encoding) const {
+ return _video_bit_rate[encoding];
}
/** @return The frame rate of the DCP */
@@ -275,6 +277,10 @@ public:
return _interop;
}
+ VideoEncoding video_encoding() const {
+ return _video_encoding;
+ }
+
bool limit_to_smpte_bv20() const {
return _limit_to_smpte_bv20;
}
@@ -291,6 +297,10 @@ public:
return _reel_length;
}
+ std::vector<dcpomatic::DCPTime> custom_reel_boundaries() const {
+ return _custom_reel_boundaries;
+ }
+
std::string context_id () const {
return _context_id;
}
@@ -397,17 +407,19 @@ public:
void set_container (Ratio const *, bool user_explicit = true);
void set_resolution (Resolution, bool user_explicit = true);
void set_encrypted (bool);
- void set_j2k_bandwidth (int);
+ void set_video_bit_rate(VideoEncoding encoding, int64_t);
void set_video_frame_rate (int rate, bool user_explicit = false);
void set_audio_channels (int);
void set_three_d (bool);
void set_isdcf_date_today ();
void set_sequence (bool);
void set_interop (bool);
+ void set_video_encoding(VideoEncoding encoding);
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);
+ void set_custom_reel_boundaries(std::vector<dcpomatic::DCPTime> boundaries);
void set_reencode_j2k (bool);
void set_marker (dcp::Marker type, dcpomatic::DCPTime time);
void unset_marker (dcp::Marker type);
@@ -434,6 +446,14 @@ public:
void add_ffoc_lfoc (Markers& markers) const;
+ void set_ui_state(std::string key, std::string value);
+ boost::optional<std::string> ui_state(std::string key) const;
+ void read_ui_state();
+
+ std::vector<RememberedAsset> read_remembered_assets() const;
+ void write_remembered_assets(std::vector<RememberedAsset> const& assets) const;
+ std::string video_identifier() const;
+
/** Emitted when some property has of the Film is about to change or has changed */
mutable boost::signals2::signal<void (ChangeType, FilmProperty)> Change;
@@ -467,7 +487,6 @@ private:
void signal_change (ChangeType, FilmProperty);
void signal_change (ChangeType, int);
- std::string video_identifier () const;
void playlist_change (ChangeType);
void playlist_order_changed ();
void playlist_content_change (ChangeType type, std::weak_ptr<Content>, int, bool frequent);
@@ -477,6 +496,7 @@ private:
void check_settings_consistency ();
void maybe_set_container_and_resolution ();
void set_dirty (bool dirty);
+ void write_ui_state() const;
/** Log to write to */
std::shared_ptr<Log> _log;
@@ -505,8 +525,8 @@ private:
* re-start picture MXF encodes.
*/
std::string _context_id;
- /** bandwidth for J2K files in bits per second */
- int _j2k_bandwidth;
+ /** bit rate for encoding video using in bits per second */
+ EnumIndexedVector<int64_t, VideoEncoding> _video_bit_rate;
/** Frames per second to run our DCP at */
int _video_frame_rate;
/** The date that we should use in a ISDCF name */
@@ -519,11 +539,14 @@ private:
bool _three_d;
bool _sequence;
bool _interop;
+ VideoEncoding _video_encoding;
bool _limit_to_smpte_bv20;
AudioProcessor const * _audio_processor;
ReelType _reel_type;
- /** Desired reel length in bytes, if _reel_type == REELTYPE_BY_LENGTH */
+ /** Desired reel length in bytes, if _reel_type == BY_LENGTH */
int64_t _reel_length;
+ /** Reel boundaries (excluding those at the start and end, sorted in ascending order) if _reel_type == CUSTOM */
+ std::vector<dcpomatic::DCPTime> _custom_reel_boundaries;
bool _reencode_j2k;
/** true if the user has ever explicitly set the video frame rate of this film */
bool _user_explicit_video_frame_rate;
@@ -562,6 +585,8 @@ private:
*/
bool _tolerant;
+ std::map<std::string, std::string> _ui_state;
+
mutable boost::mutex _info_file_mutex;
boost::signals2::scoped_connection _playlist_change_connection;
diff --git a/src/lib/encoder.cc b/src/lib/film_encoder.cc
index 5268d8620..05b911daf 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/film_encoder.cc
@@ -19,7 +19,7 @@
*/
-/** @file src/encoder.cc
+/** @file src/film_encoder.cc
* @brief A class which takes a Film and some Options, then uses those to encode the film
* into some output format.
*
@@ -28,17 +28,17 @@
*/
-#include "encoder.h"
+#include "film_encoder.h"
#include "player.h"
#include "i18n.h"
-/** Construct an encoder.
+/** Construct a FilmEncoder.
* @param film Film that we are encoding.
* @param job Job that this encoder is being used in.
*/
-Encoder::Encoder (std::shared_ptr<const Film> film, std::weak_ptr<Job> job)
+FilmEncoder::FilmEncoder(std::shared_ptr<const Film> film, std::weak_ptr<Job> job)
: _film (film)
, _job (job)
, _player(film, Image::Alignment::PADDED)
diff --git a/src/lib/encoder.h b/src/lib/film_encoder.h
index 9b67720d3..ed7626c68 100644
--- a/src/lib/encoder.h
+++ b/src/lib/film_encoder.h
@@ -19,8 +19,8 @@
*/
-#ifndef DCPOMATIC_ENCODER_H
-#define DCPOMATIC_ENCODER_H
+#ifndef DCPOMATIC_FILM_ENCODER_H
+#define DCPOMATIC_FILM_ENCODER_H
#include "player.h"
@@ -29,24 +29,23 @@
class Film;
-class Encoder;
class Player;
class Job;
class PlayerVideo;
class AudioBuffers;
-/** @class Encoder
+/** @class FilmEncoder
* @brief Parent class for something that can encode a film into some format
*/
-class Encoder
+class FilmEncoder
{
public:
- Encoder (std::shared_ptr<const Film> film, std::weak_ptr<Job> job);
- virtual ~Encoder () {}
+ FilmEncoder(std::shared_ptr<const Film> film, std::weak_ptr<Job> job);
+ virtual ~FilmEncoder() {}
- Encoder (Encoder const&) = delete;
- Encoder& operator= (Encoder const&) = delete;
+ FilmEncoder(FilmEncoder const&) = delete;
+ FilmEncoder& operator=(FilmEncoder const&) = delete;
virtual void go () = 0;
@@ -58,6 +57,8 @@ public:
/** @return the number of frames that are done */
virtual Frame frames_done () const = 0;
virtual bool finishing () const = 0;
+ virtual void pause() {}
+ virtual void resume() {}
protected:
std::shared_ptr<const Film> _film;
diff --git a/src/lib/film_property.h b/src/lib/film_property.h
index c23297965..ebda0e807 100644
--- a/src/lib/film_property.h
+++ b/src/lib/film_property.h
@@ -39,7 +39,7 @@ enum class FilmProperty {
CONTAINER,
RESOLUTION,
ENCRYPTED,
- J2K_BANDWIDTH,
+ VIDEO_BIT_RATE,
VIDEO_FRAME_RATE,
AUDIO_FRAME_RATE,
AUDIO_CHANNELS,
@@ -47,10 +47,12 @@ enum class FilmProperty {
THREE_D,
SEQUENCE,
INTEROP,
+ VIDEO_ENCODING,
LIMIT_TO_SMPTE_BV20,
AUDIO_PROCESSOR,
REEL_TYPE,
REEL_LENGTH,
+ CUSTOM_REEL_BOUNDARIES,
REENCODE_J2K,
MARKERS,
RATINGS,
diff --git a/src/lib/filter.cc b/src/lib/filter.cc
index 9a14795ec..465b5991a 100644
--- a/src/lib/filter.cc
+++ b/src/lib/filter.cc
@@ -37,7 +37,8 @@ LIBDCP_ENABLE_WARNINGS
#include "i18n.h"
-using namespace std;
+using std::string;
+using std::vector;
using boost::optional;
diff --git a/src/lib/font.cc b/src/lib/font.cc
index 955a2ad1c..246d3eeb8 100644
--- a/src/lib/font.cc
+++ b/src/lib/font.cc
@@ -63,11 +63,11 @@ Font& Font::operator=(Font const& other)
void
-Font::as_xml (xmlpp::Node* node)
+Font::as_xml(xmlpp::Element* element)
{
- node->add_child("Id")->add_child_text(_id);
+ cxml::add_text_child(element, "Id", _id);
if (_content.file) {
- node->add_child("File")->add_child_text(_content.file->string());
+ cxml::add_text_child(element, "File", _content.file->string());
}
}
diff --git a/src/lib/font.h b/src/lib/font.h
index 12a14aba4..092b682ba 100644
--- a/src/lib/font.h
+++ b/src/lib/font.h
@@ -57,7 +57,7 @@ public:
Font(Font const& other);
Font& operator=(Font const& other);
- void as_xml (xmlpp::Node* node);
+ void as_xml(xmlpp::Element* element);
std::string id () const {
return _id;
diff --git a/src/lib/font_id_allocator.cc b/src/lib/font_id_allocator.cc
index 5e4ae9c0f..d5430e858 100644
--- a/src/lib/font_id_allocator.cc
+++ b/src/lib/font_id_allocator.cc
@@ -76,37 +76,23 @@ FontIDAllocator::add_font(int reel_index, string asset_id, string font_id)
if (!_default_font) {
_default_font = font;
}
- _map[font] = 0;
+ _map[font] = {};
}
void
FontIDAllocator::allocate()
{
- /* We'll first try adding <reel>_ to the start of the font ID, but if a reel has multiple
- * identical font IDs we will need to use some number that is not a reel ID. Find the
- * first such number (1 higher than the highest reel index)
- */
- auto next_unused = std::max_element(
- _map.begin(),
- _map.end(),
- [] (std::pair<Font, int> const& a, std::pair<Font, int> const& b) {
- return a.first.reel_index < b.first.reel_index;
- })->first.reel_index + 1;
-
std::set<string> used_ids;
for (auto& font: _map) {
- auto const proposed = String::compose("%1_%2", font.first.reel_index, font.first.font_id);
- if (used_ids.find(proposed) != used_ids.end()) {
- /* This ID was already used; we need to disambiguate it. Do so by using
- * one of our unused prefixes.
- */
- font.second = next_unused++;
- } else {
- /* This ID was not yet used */
- font.second = font.first.reel_index;
+ auto proposed = font.first.font_id;
+ int prefix = 0;
+ while (used_ids.find(proposed) != used_ids.end()) {
+ proposed = String::compose("%1_%2", prefix++, font.first.font_id);
+ DCPOMATIC_ASSERT(prefix < 128);
}
+ font.second = proposed;
used_ids.insert(proposed);
}
}
@@ -119,7 +105,7 @@ FontIDAllocator::font_id(int reel_index, string asset_id, string font_id) const
if (iter == _map.end()) {
return default_font_id();
}
- return String::compose("%1_%2", iter->second, font_id);
+ return iter->second;
}
diff --git a/src/lib/font_id_allocator.h b/src/lib/font_id_allocator.h
index fe4b9ef07..6737907c1 100644
--- a/src/lib/font_id_allocator.h
+++ b/src/lib/font_id_allocator.h
@@ -101,7 +101,7 @@ private:
std::string font_id;
};
- std::map<Font, int> _map;
+ std::map<Font, std::string> _map;
boost::optional<Font> _default_font;
};
diff --git a/src/lib/frame_info.cc b/src/lib/frame_info.cc
new file mode 100644
index 000000000..f348bca6a
--- /dev/null
+++ b/src/lib/frame_info.cc
@@ -0,0 +1,84 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "frame_info.h"
+#include <string>
+
+
+using std::shared_ptr;
+using std::string;
+
+
+J2KFrameInfo::J2KFrameInfo(uint64_t offset_, uint64_t size_, string hash_)
+ : dcp::J2KFrameInfo(offset_, size_, hash_)
+{
+
+}
+
+
+J2KFrameInfo::J2KFrameInfo(dcp::J2KFrameInfo const& info)
+ : dcp::J2KFrameInfo(info)
+{
+
+}
+
+
+J2KFrameInfo::J2KFrameInfo(shared_ptr<InfoFileHandle> info_file, Frame frame, Eyes eyes)
+{
+ info_file->get().seek(position(frame, eyes), SEEK_SET);
+ info_file->get().checked_read(&offset, sizeof(offset));
+ info_file->get().checked_read(&size, sizeof(size));
+
+ char hash_buffer[33];
+ info_file->get().checked_read(hash_buffer, 32);
+ hash_buffer[32] = '\0';
+ hash = hash_buffer;
+}
+
+
+long
+J2KFrameInfo::position(Frame frame, Eyes eyes) const
+{
+ switch (eyes) {
+ case Eyes::BOTH:
+ return frame * _size_on_disk;
+ case Eyes::LEFT:
+ return frame * _size_on_disk * 2;
+ case Eyes::RIGHT:
+ return frame * _size_on_disk * 2 + _size_on_disk;
+ default:
+ DCPOMATIC_ASSERT(false);
+ }
+
+ DCPOMATIC_ASSERT(false);
+}
+
+
+/** @param frame reel-relative frame */
+void
+J2KFrameInfo::write(shared_ptr<InfoFileHandle> info_file, Frame frame, Eyes eyes) const
+{
+ info_file->get().seek(position(frame, eyes), SEEK_SET);
+ info_file->get().checked_write(&offset, sizeof(offset));
+ info_file->get().checked_write(&size, sizeof(size));
+ info_file->get().checked_write(hash.c_str(), hash.size());
+}
+
diff --git a/src/lib/frame_info.h b/src/lib/frame_info.h
new file mode 100644
index 000000000..a5a22bd9e
--- /dev/null
+++ b/src/lib/frame_info.h
@@ -0,0 +1,45 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "film.h"
+#include <dcp/frame_info.h>
+#include <memory>
+
+
+class J2KFrameInfo : public dcp::J2KFrameInfo
+{
+public:
+ J2KFrameInfo(dcp::J2KFrameInfo const& info);
+ J2KFrameInfo(uint64_t offset_, uint64_t size_, std::string hash_);
+ J2KFrameInfo(std::shared_ptr<InfoFileHandle> info_file, Frame frame, Eyes eyes);
+
+ void write(std::shared_ptr<InfoFileHandle> info_file, Frame frame, Eyes eyes) const;
+
+ static int size_on_disk() {
+ return _size_on_disk;
+ }
+
+private:
+ long position(Frame frame, Eyes eyes) const;
+
+ static constexpr auto _size_on_disk = 32 + sizeof(dcp::J2KFrameInfo::offset) + sizeof(dcp::J2KFrameInfo::size);
+};
+
diff --git a/src/lib/grok/context.h b/src/lib/grok/context.h
new file mode 100644
index 000000000..81622ad9d
--- /dev/null
+++ b/src/lib/grok/context.h
@@ -0,0 +1,291 @@
+/*
+ Copyright (C) 2023 Grok Image Compression Inc.
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+#pragma once
+
+
+#include "../config.h"
+#include "../dcp_video.h"
+#include "../film.h"
+#include "../log.h"
+#include "../dcpomatic_log.h"
+#include "../writer.h"
+#include "messenger.h"
+#include <dcp/array_data.h>
+#include <boost/filesystem.hpp>
+
+
+static std::mutex launchMutex;
+
+namespace grk_plugin
+{
+
+struct GrokLogger : public MessengerLogger {
+ explicit GrokLogger(const std::string &preamble) : MessengerLogger(preamble)
+ {}
+ virtual ~GrokLogger() = default;
+ void info(const char* fmt, ...) override{
+ va_list arg;
+ va_start(arg, fmt);
+ dcpomatic_log->log(preamble_ + log_message(fmt, arg),LogEntry::TYPE_GENERAL);
+ va_end(arg);
+ }
+ void warn(const char* fmt, ...) override{
+ va_list arg;
+ va_start(arg, fmt);
+ dcpomatic_log->log(preamble_ + log_message(fmt, arg),LogEntry::TYPE_WARNING);
+ va_end(arg);
+ }
+ void error(const char* fmt, ...) override{
+ va_list arg;
+ va_start(arg, fmt);
+ dcpomatic_log->log(preamble_ + log_message(fmt, arg),LogEntry::TYPE_ERROR);
+ va_end(arg);
+ }
+};
+
+struct FrameProxy {
+ FrameProxy(int index, Eyes eyes, DCPVideo dcpv) : index_(index), eyes_(eyes), vf(dcpv)
+ {}
+ int index() const {
+ return index_;
+ }
+ Eyes eyes(void) const {
+ return eyes_;
+ }
+ int index_;
+ Eyes eyes_;
+ DCPVideo vf;
+};
+
+struct DcpomaticContext
+{
+ DcpomaticContext(
+ std::shared_ptr<const Film> film_,
+ Writer& writer_,
+ EventHistory& history_,
+ boost::filesystem::path const& location_
+ )
+ : film(film_)
+ , writer(writer_)
+ , history(history_)
+ , location(location_)
+ {
+
+ }
+
+ void set_dimensions(uint32_t w, uint32_t h)
+ {
+ width = w;
+ height = h;
+ }
+
+ std::shared_ptr<const Film> film;
+ Writer& writer;
+ EventHistory& history;
+ boost::filesystem::path location;
+ uint32_t width = 0;
+ uint32_t height = 0;
+};
+
+
+class GrokContext
+{
+public:
+ explicit GrokContext(DcpomaticContext* dcpomatic_context)
+ : _dcpomatic_context(dcpomatic_context)
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ if (!grok.enable) {
+ return;
+ }
+
+ boost::filesystem::path folder(_dcpomatic_context->location);
+ boost::filesystem::path binary_path = folder / "grk_compress";
+ if (!boost::filesystem::exists(binary_path)) {
+ getMessengerLogger()->error(
+ "Invalid binary location %s", _dcpomatic_context->location.c_str()
+ );
+ return;
+ }
+
+ auto proc = [this](const std::string& str) {
+ try {
+ Msg msg(str);
+ auto tag = msg.next();
+ if (tag == GRK_MSGR_BATCH_SUBMIT_COMPRESSED) {
+ auto clientFrameId = msg.nextUint();
+ msg.nextUint(); // compressed frame ID
+ auto compressedFrameLength = msg.nextUint();
+ auto processor = [this](FrameProxy srcFrame, uint8_t* compressed, uint32_t compressedFrameLength) {
+ auto compressed_data = std::make_shared<dcp::ArrayData>(compressed, compressedFrameLength);
+ _dcpomatic_context->writer.write(compressed_data, srcFrame.index(), srcFrame.eyes());
+ frame_done ();
+ };
+
+ int const minimum_size = 16384;
+
+ bool needsRecompression = compressedFrameLength < minimum_size;
+ _messenger->processCompressed(str, processor, needsRecompression);
+
+ if (needsRecompression) {
+ auto fp = _messenger->retrieve(clientFrameId);
+ if (!fp) {
+ return;
+ }
+
+ auto encoded = std::make_shared<dcp::ArrayData>(fp->vf.encode_locally());
+ _dcpomatic_context->writer.write(encoded, fp->vf.index(), fp->vf.eyes());
+ frame_done ();
+ }
+ }
+ } catch (std::exception& ex) {
+ getMessengerLogger()->error("%s",ex.what());
+ }
+ };
+
+ auto clientInit = MessengerInit(
+ clientToGrokMessageBuf,
+ clientSentSynch,
+ grokReceiveReadySynch,
+ grokToClientMessageBuf,
+ grokSentSynch,
+ clientReceiveReadySynch,
+ proc,
+ std::thread::hardware_concurrency()
+ );
+
+ _messenger = new ScheduledMessenger<FrameProxy>(clientInit);
+ }
+
+ ~GrokContext()
+ {
+ shutdown();
+ }
+
+ bool launch(DCPVideo dcpv, int device)
+ {
+ namespace fs = boost::filesystem;
+
+ if (!_messenger) {
+ return false;
+ }
+ if (_launched) {
+ return true;
+ }
+ if (_launch_failed) {
+ return false;
+ }
+
+ std::unique_lock<std::mutex> lk_global(launchMutex);
+
+ if (!_messenger) {
+ return false;
+ }
+ if (_launched) {
+ return true;
+ }
+ if (_launch_failed) {
+ return false;
+ }
+
+ if (MessengerInit::firstLaunch(true)) {
+
+ if (!fs::exists(_dcpomatic_context->location) || !fs::is_directory(_dcpomatic_context->location)) {
+ getMessengerLogger()->error("Invalid directory %s", _dcpomatic_context->location.c_str());
+ return false;
+ }
+
+ auto s = dcpv.get_size();
+ _dcpomatic_context->set_dimensions(s.width, s.height);
+ auto grok = Config::instance()->grok().get_value_or({});
+ if (!_messenger->launchGrok(
+ _dcpomatic_context->location,
+ _dcpomatic_context->width,
+ _dcpomatic_context->width,
+ _dcpomatic_context->height,
+ 3,
+ 12,
+ device,
+ _dcpomatic_context->film->resolution() == Resolution::FOUR_K,
+ _dcpomatic_context->film->video_frame_rate(),
+ _dcpomatic_context->film->video_bit_rate(VideoEncoding::JPEG2000),
+ grok.licence_server,
+ grok.licence_port,
+ grok.licence)) {
+ _launch_failed = true;
+ return false;
+ }
+ }
+
+ _launched = _messenger->waitForClientInit();
+ _launch_failed = _launched;
+
+ return _launched;
+ }
+
+ bool scheduleCompress(DCPVideo const& vf)
+ {
+ if (!_messenger) {
+ return false;
+ }
+
+ auto fp = FrameProxy(vf.index(), vf.eyes(), vf);
+ auto cvt = [this, &fp](BufferSrc src) {
+ fp.vf.convert_to_xyz((uint16_t*)src.framePtr_);
+ };
+
+ return _messenger->scheduleCompress(fp, cvt);
+ }
+
+ void shutdown()
+ {
+ if (!_messenger) {
+ return;
+ }
+
+ std::unique_lock<std::mutex> lk_global(launchMutex);
+
+ if (!_messenger) {
+ return;
+ }
+
+ if (_launched) {
+ _messenger->shutdown();
+ }
+
+ delete _messenger;
+ _messenger = nullptr;
+ }
+
+ void frame_done()
+ {
+ _dcpomatic_context->history.event();
+ }
+
+private:
+ DcpomaticContext* _dcpomatic_context;
+ ScheduledMessenger<FrameProxy>* _messenger = nullptr;
+ bool _launched = false;
+ bool _launch_failed = false;
+};
+
+}
+
diff --git a/src/lib/grok/messenger.h b/src/lib/grok/messenger.h
new file mode 100644
index 000000000..eb2fe9560
--- /dev/null
+++ b/src/lib/grok/messenger.h
@@ -0,0 +1,906 @@
+/*
+ Copyright (C) 2023 Grok Image Compression Inc.
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+#pragma once
+
+#include <iostream>
+#include <string>
+#include <cstring>
+#include <atomic>
+#include <functional>
+#include <sstream>
+#include <future>
+#include <map>
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+#include <queue>
+#include <cassert>
+#include <cstdarg>
+
+#ifdef _WIN32
+#include <windows.h>
+#include <direct.h>
+#include <tlhelp32.h>
+#pragma warning(disable : 4100)
+#else
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <semaphore.h>
+#include <signal.h>
+#endif
+
+namespace grk_plugin
+{
+static std::string grokToClientMessageBuf = "Global\\grok_to_client_message";
+static std::string grokSentSynch = "Global\\grok_sent";
+static std::string clientReceiveReadySynch = "Global\\client_receive_ready";
+static std::string clientToGrokMessageBuf = "Global\\client_to_grok_message";
+static std::string clientSentSynch = "Global\\client_sent";
+static std::string grokReceiveReadySynch = "Global\\grok_receive_ready";
+static std::string grokUncompressedBuf = "Global\\grok_uncompressed_buf";
+static std::string grokCompressedBuf = "Global\\grok_compressed_buf";
+static const std::string GRK_MSGR_BATCH_IMAGE = "GRK_MSGR_BATCH_IMAGE";
+static const std::string GRK_MSGR_BATCH_COMPRESS_INIT = "GRK_MSGR_BATCH_COMPRESS_INIT";
+static const std::string GRK_MSGR_BATCH_SUBMIT_UNCOMPRESSED = "GRK_MSGR_BATCH_SUBMIT_UNCOMPRESSED";
+static const std::string GRK_MSGR_BATCH_PROCESSED_UNCOMPRESSED =
+ "GRK_MSGR_BATCH_PROCESSED_UNCOMPRESSED";
+static const std::string GRK_MSGR_BATCH_SUBMIT_COMPRESSED = "GRK_MSGR_BATCH_SUBMIT_COMPRESSED";
+static const std::string GRK_MSGR_BATCH_PROCESSSED_COMPRESSED =
+ "GRK_MSGR_BATCH_PROCESSSED_COMPRESSED";
+static const std::string GRK_MSGR_BATCH_SHUTDOWN = "GRK_MSGR_BATCH_SHUTDOWN";
+static const std::string GRK_MSGR_BATCH_FLUSH = "GRK_MSGR_BATCH_FLUSH";
+static const size_t messageBufferLen = 256;
+struct IMessengerLogger
+{
+ virtual ~IMessengerLogger(void) = default;
+ virtual void info(const char* fmt, ...) = 0;
+ virtual void warn(const char* fmt, ...) = 0;
+ virtual void error(const char* fmt, ...) = 0;
+
+ protected:
+ template<typename... Args>
+ std::string log_message(char const* const format, Args&... args) noexcept
+ {
+ constexpr size_t message_size = 512;
+ char message[message_size];
+
+ std::snprintf(message, message_size, format, args...);
+ return std::string(message);
+ }
+};
+struct MessengerLogger : public IMessengerLogger
+{
+ explicit MessengerLogger(const std::string &preamble) : preamble_(preamble) {}
+ virtual ~MessengerLogger() = default;
+ virtual void info(const char* fmt, ...) override
+ {
+ va_list args;
+ std::string new_fmt = preamble_ + fmt + "\n";
+ va_start(args, fmt);
+ vfprintf(stdout, new_fmt.c_str(), args);
+ va_end(args);
+ }
+ virtual void warn(const char* fmt, ...) override
+ {
+ va_list args;
+ std::string new_fmt = preamble_ + fmt + "\n";
+ va_start(args, fmt);
+ vfprintf(stdout, new_fmt.c_str(), args);
+ va_end(args);
+ }
+ virtual void error(const char* fmt, ...) override
+ {
+ va_list args;
+ std::string new_fmt = preamble_ + fmt + "\n";
+ va_start(args, fmt);
+ vfprintf(stderr, new_fmt.c_str(), args);
+ va_end(args);
+ }
+
+ protected:
+ std::string preamble_;
+};
+
+extern IMessengerLogger* sLogger;
+void setMessengerLogger(IMessengerLogger* logger);
+IMessengerLogger* getMessengerLogger(void);
+
+struct MessengerInit
+{
+ MessengerInit(const std::string &outBuf, const std::string &outSent,
+ const std::string &outReceiveReady, const std::string &inBuf,
+ const std::string &inSent,
+ const std::string &inReceiveReady,
+ std::function<void(std::string)> processor,
+ size_t numProcessingThreads)
+ : outboundMessageBuf(outBuf), outboundSentSynch(outSent),
+ outboundReceiveReadySynch(outReceiveReady), inboundMessageBuf(inBuf),
+ inboundSentSynch(inSent), inboundReceiveReadySynch(inReceiveReady), processor_(processor),
+ numProcessingThreads_(numProcessingThreads),
+ uncompressedFrameSize_(0), compressedFrameSize_(0),
+ numFrames_(0)
+ {
+ if(firstLaunch(true))
+ unlink();
+ }
+ void unlink(void)
+ {
+#ifndef _WIN32
+ shm_unlink(grokToClientMessageBuf.c_str());
+ shm_unlink(clientToGrokMessageBuf.c_str());
+#endif
+ }
+ static bool firstLaunch(bool isClient)
+ {
+ bool debugGrok = false;
+ return debugGrok != isClient;
+ }
+ std::string outboundMessageBuf;
+ std::string outboundSentSynch;
+ std::string outboundReceiveReadySynch;
+
+ std::string inboundMessageBuf;
+ std::string inboundSentSynch;
+ std::string inboundReceiveReadySynch;
+
+ std::function<void(std::string)> processor_;
+ size_t numProcessingThreads_;
+
+ size_t uncompressedFrameSize_;
+ size_t compressedFrameSize_;
+ size_t numFrames_;
+};
+
+/*************************** Synchronization *******************************/
+enum SynchDirection
+{
+ SYNCH_SENT,
+ SYNCH_RECEIVE_READY
+};
+
+typedef int grk_handle;
+struct Synch
+{
+ Synch(const std::string &sentSemName, const std::string &receiveReadySemName)
+ : sentSemName_(sentSemName), receiveReadySemName_(receiveReadySemName)
+ {
+ // unlink semaphores in case of previous crash
+ if(MessengerInit::firstLaunch(true))
+ unlink();
+ open();
+ }
+ ~Synch()
+ {
+ close();
+ if(MessengerInit::firstLaunch(true))
+ unlink();
+ }
+ void post(SynchDirection dir)
+ {
+ auto sem = (dir == SYNCH_SENT ? sentSem_ : receiveReadySem_);
+ int rc = sem_post(sem);
+ if(rc)
+ getMessengerLogger()->error("Error posting to semaphore: %s", strerror(errno));
+ }
+ void wait(SynchDirection dir)
+ {
+ auto sem = dir == SYNCH_SENT ? sentSem_ : receiveReadySem_;
+ int rc = sem_wait(sem);
+ if(rc)
+ getMessengerLogger()->error("Error waiting for semaphore: %s", strerror(errno));
+ }
+ void open(void)
+ {
+ sentSem_ = sem_open(sentSemName_.c_str(), O_CREAT, 0666, 0);
+ if(!sentSem_)
+ getMessengerLogger()->error("Error opening shared memory: %s", strerror(errno));
+ receiveReadySem_ = sem_open(receiveReadySemName_.c_str(), O_CREAT, 0666, 1);
+ if(!receiveReadySem_)
+ getMessengerLogger()->error("Error opening shared memory: %s", strerror(errno));
+ }
+ void close(void)
+ {
+ int rc = sem_close(sentSem_);
+ if(rc)
+ getMessengerLogger()->error("Error closing semaphore %s: %s", sentSemName_.c_str(),
+ strerror(errno));
+ rc = sem_close(receiveReadySem_);
+ if(rc)
+ getMessengerLogger()->error("Error closing semaphore %s: %s",
+ receiveReadySemName_.c_str(), strerror(errno));
+ }
+ void unlink(void)
+ {
+ int rc = sem_unlink(sentSemName_.c_str());
+ if(rc == -1 && errno != ENOENT)
+ getMessengerLogger()->error("Error unlinking semaphore %s: %s", sentSemName_.c_str(),
+ strerror(errno));
+ rc = sem_unlink(receiveReadySemName_.c_str());
+ if(rc == -1 && errno != ENOENT)
+ getMessengerLogger()->error("Error unlinking semaphore %s: %s",
+ receiveReadySemName_.c_str(), strerror(errno));
+ }
+ sem_t* sentSem_;
+ sem_t* receiveReadySem_;
+
+ private:
+ std::string sentSemName_;
+ std::string receiveReadySemName_;
+};
+struct SharedMemoryManager
+{
+ static bool initShm(const std::string &name, size_t len, grk_handle* shm_fd, char** buffer)
+ {
+ *shm_fd = shm_open(name.c_str(), O_CREAT | O_RDWR, 0666);
+ if(*shm_fd < 0)
+ {
+ getMessengerLogger()->error("Error opening shared memory: %s", strerror(errno));
+ return false;
+ }
+ int rc = ftruncate(*shm_fd, sizeof(char) * len);
+ if(rc)
+ {
+ getMessengerLogger()->error("Error truncating shared memory: %s", strerror(errno));
+ rc = close(*shm_fd);
+ if(rc)
+ getMessengerLogger()->error("Error closing shared memory: %s", strerror(errno));
+ rc = shm_unlink(name.c_str());
+ // 2 == No such file or directory
+ if(rc && errno != 2)
+ getMessengerLogger()->error("Error unlinking shared memory: %s", strerror(errno));
+ return false;
+ }
+ *buffer = static_cast<char*>(mmap(0, len, PROT_WRITE, MAP_SHARED, *shm_fd, 0));
+ if(!*buffer)
+ {
+ getMessengerLogger()->error("Error mapping shared memory: %s", strerror(errno));
+ rc = close(*shm_fd);
+ if(rc)
+ getMessengerLogger()->error("Error closing shared memory: %s", strerror(errno));
+ rc = shm_unlink(name.c_str());
+ // 2 == No such file or directory
+ if(rc && errno != 2)
+ getMessengerLogger()->error("Error unlinking shared memory: %s", strerror(errno));
+ }
+
+ return *buffer != nullptr;
+ }
+ static bool deinitShm(const std::string &name, size_t len, grk_handle &shm_fd, char** buffer)
+ {
+ if (!*buffer || !shm_fd)
+ return true;
+
+ int rc = munmap(*buffer, len);
+ *buffer = nullptr;
+ if(rc)
+ getMessengerLogger()->error("Error unmapping shared memory %s: %s", name.c_str(), strerror(errno));
+ rc = close(shm_fd);
+ shm_fd = 0;
+ if(rc)
+ getMessengerLogger()->error("Error closing shared memory %s: %s", name.c_str(), strerror(errno));
+ rc = shm_unlink(name.c_str());
+ // 2 == No such file or directory
+ if(rc && errno != 2)
+ fprintf(stderr,"Error unlinking shared memory %s : %s\n", name.c_str(), strerror(errno));
+
+ return true;
+ }
+};
+
+template<typename Data>
+class MessengerBlockingQueue
+{
+ public:
+ explicit MessengerBlockingQueue(size_t max) : active_(true), max_size_(max) {}
+ MessengerBlockingQueue() : MessengerBlockingQueue(UINT_MAX) {}
+ size_t size() const
+ {
+ return queue_.size();
+ }
+ // deactivate and clear queue
+ void deactivate()
+ {
+ {
+ std::lock_guard<std::mutex> lk(mutex_);
+ active_ = false;
+ while(!queue_.empty())
+ queue_.pop();
+ }
+
+ // release all waiting threads
+ can_pop_.notify_all();
+ can_push_.notify_all();
+ }
+ void activate()
+ {
+ std::lock_guard<std::mutex> lk(mutex_);
+ active_ = true;
+ }
+ bool push(Data const& value)
+ {
+ bool rc;
+ {
+ std::unique_lock<std::mutex> lk(mutex_);
+ rc = push_(value);
+ }
+ if(rc)
+ can_pop_.notify_one();
+
+ return rc;
+ }
+ bool waitAndPush(Data& value)
+ {
+ bool rc;
+ {
+ std::unique_lock<std::mutex> lk(mutex_);
+ if(!active_)
+ return false;
+ // in case of spurious wakeup, loop until predicate in lambda
+ // is satisfied.
+ can_push_.wait(lk, [this] { return queue_.size() < max_size_ || !active_; });
+ rc = push_(value);
+ }
+ if(rc)
+ can_pop_.notify_one();
+
+ return rc;
+ }
+ bool pop(Data& value)
+ {
+ bool rc;
+ {
+ std::unique_lock<std::mutex> lk(mutex_);
+ rc = pop_(value);
+ }
+ if(rc)
+ can_push_.notify_one();
+
+ return rc;
+ }
+ bool waitAndPop(Data& value)
+ {
+ bool rc;
+ {
+ std::unique_lock<std::mutex> lk(mutex_);
+ if(!active_)
+ return false;
+ // in case of spurious wakeup, loop until predicate in lambda
+ // is satisfied.
+ can_pop_.wait(lk, [this] { return !queue_.empty() || !active_; });
+ rc = pop_(value);
+ }
+ if(rc)
+ can_push_.notify_one();
+
+ return rc;
+ }
+
+ private:
+ bool push_(Data const& value)
+ {
+ if(queue_.size() == max_size_ || !active_)
+ return false;
+ queue_.push(value);
+
+ return true;
+ }
+ bool pop_(Data& value)
+ {
+ if(queue_.empty() || !active_)
+ return false;
+ value = queue_.front();
+ queue_.pop();
+
+ return true;
+ }
+ std::queue<Data> queue_;
+ mutable std::mutex mutex_;
+ std::condition_variable can_pop_;
+ std::condition_variable can_push_;
+ bool active_;
+ size_t max_size_;
+};
+struct BufferSrc
+{
+ BufferSrc(void) : BufferSrc("") {}
+ explicit BufferSrc(const std::string &file) : file_(file), clientFrameId_(0), frameId_(0), framePtr_(nullptr)
+ {}
+ BufferSrc(size_t clientFrameId, size_t frameId, uint8_t* framePtr)
+ : file_(""), clientFrameId_(clientFrameId), frameId_(frameId), framePtr_(framePtr)
+ {}
+ bool fromDisk(void)
+ {
+ return !file_.empty() && framePtr_ == nullptr;
+ }
+ size_t index() const
+ {
+ return clientFrameId_;
+ }
+ std::string file_;
+ size_t clientFrameId_;
+ size_t frameId_;
+ uint8_t* framePtr_;
+};
+
+struct Messenger;
+static void outboundThread(Messenger* messenger, const std::string &sendBuf, Synch* synch);
+static void inboundThread(Messenger* messenger, const std::string &receiveBuf, Synch* synch);
+static void processorThread(Messenger* messenger, std::function<void(std::string)> processor);
+
+struct Messenger
+{
+ explicit Messenger(MessengerInit init)
+ : running(true), _initialized(false), _shutdown(false), init_(init),
+ outboundSynch_(nullptr),
+ inboundSynch_(nullptr), uncompressed_buffer_(nullptr), compressed_buffer_(nullptr),
+ uncompressed_fd_(0), compressed_fd_(0)
+ {}
+ virtual ~Messenger(void)
+ {
+ running = false;
+ sendQueue.deactivate();
+ receiveQueue.deactivate();
+
+ if (outboundSynch_) {
+ outboundSynch_->post(SYNCH_RECEIVE_READY);
+ outbound.join();
+ }
+
+ if (inboundSynch_) {
+ inboundSynch_->post(SYNCH_SENT);
+ inbound.join();
+ }
+
+ for(auto& p : processors_)
+ p.join();
+
+ delete outboundSynch_;
+ delete inboundSynch_;
+
+ deinitShm();
+ }
+ void startThreads(void) {
+ outboundSynch_ =
+ new Synch(init_.outboundSentSynch, init_.outboundReceiveReadySynch);
+ outbound = std::thread(outboundThread, this, init_.outboundMessageBuf, outboundSynch_);
+
+ inboundSynch_ =
+ new Synch(init_.inboundSentSynch, init_.inboundReceiveReadySynch);
+ inbound = std::thread(inboundThread, this, init_.inboundMessageBuf, inboundSynch_);
+
+ for(size_t i = 0; i < init_.numProcessingThreads_; ++i)
+ processors_.push_back(std::thread(processorThread, this, init_.processor_));
+ }
+ bool initBuffers(void)
+ {
+ bool rc = true;
+ if(init_.uncompressedFrameSize_)
+ {
+ rc = rc && SharedMemoryManager::initShm(grokUncompressedBuf,
+ init_.uncompressedFrameSize_ * init_.numFrames_,
+ &uncompressed_fd_, &uncompressed_buffer_);
+ }
+ if(init_.compressedFrameSize_)
+ {
+ rc = rc && SharedMemoryManager::initShm(grokCompressedBuf,
+ init_.compressedFrameSize_ * init_.numFrames_,
+ &compressed_fd_, &compressed_buffer_);
+ }
+
+ return rc;
+ }
+
+ bool deinitShm(void)
+ {
+ bool rc = SharedMemoryManager::deinitShm(grokUncompressedBuf,
+ init_.uncompressedFrameSize_ * init_.numFrames_,
+ uncompressed_fd_, &uncompressed_buffer_);
+ rc = rc && SharedMemoryManager::deinitShm(grokCompressedBuf,
+ init_.compressedFrameSize_ * init_.numFrames_,
+ compressed_fd_, &compressed_buffer_);
+
+ return rc;
+ }
+ template<typename... Args>
+ void send(const std::string& str, Args... args)
+ {
+ std::ostringstream oss;
+ oss << str;
+ int dummy[] = {0, ((void)(oss << ',' << args), 0)...};
+ static_cast<void>(dummy);
+
+ sendQueue.push(oss.str());
+ }
+
+ bool launchGrok(
+ boost::filesystem::path const& dir,
+ uint32_t width,
+ uint32_t stride,
+ uint32_t height,
+ uint32_t samplesPerPixel,
+ uint32_t depth,
+ int device,
+ bool is4K,
+ uint32_t fps,
+ uint32_t bandwidth,
+ const std::string server,
+ uint32_t port,
+ const std::string license
+ )
+ {
+
+ std::unique_lock<std::mutex> lk(shutdownMutex_);
+ if (async_result_.valid())
+ return true;
+ if(MessengerInit::firstLaunch(true))
+ init_.unlink();
+ startThreads();
+ char _cmd[4096];
+ auto fullServer = server + ":" + std::to_string(port);
+ sprintf(_cmd,
+ "./grk_compress -batch_src %s,%d,%d,%d,%d,%d -out_fmt j2k -k 1 "
+ "-G %d -%s %d,%d -j %s -J %s -v",
+ GRK_MSGR_BATCH_IMAGE.c_str(), width, stride, height, samplesPerPixel, depth,
+ device, is4K ? "cinema4K" : "cinema2K", fps, bandwidth,
+ license.c_str(), fullServer.c_str());
+
+ return launch(_cmd, dir);
+ }
+ void initClient(size_t uncompressedFrameSize, size_t compressedFrameSize, size_t numFrames)
+ {
+ // client fills queue with pending uncompressed buffers
+ init_.uncompressedFrameSize_ = uncompressedFrameSize;
+ init_.compressedFrameSize_ = compressedFrameSize;
+ init_.numFrames_ = numFrames;
+ initBuffers();
+ auto ptr = uncompressed_buffer_;
+ for(size_t i = 0; i < init_.numFrames_; ++i)
+ {
+ availableBuffers_.push(BufferSrc(0, i, (uint8_t*)ptr));
+ ptr += init_.uncompressedFrameSize_;
+ }
+
+ std::unique_lock<std::mutex> lk(shutdownMutex_);
+ _initialized = true;
+ clientInitializedCondition_.notify_all();
+ }
+
+ bool waitForClientInit()
+ {
+ if (_initialized) {
+ return true;
+ } else if (_shutdown) {
+ return false;
+ }
+
+ std::unique_lock<std::mutex> lk(shutdownMutex_);
+
+ if (_initialized) {
+ return true;
+ } else if (_shutdown) {
+ return false;
+ }
+
+ while (true) {
+ if (clientInitializedCondition_.wait_for(lk, std::chrono::seconds(1), [this]{ return _initialized || _shutdown; })) {
+ break;
+ }
+ auto status = async_result_.wait_for(std::chrono::milliseconds(100));
+ if (status == std::future_status::ready) {
+ getMessengerLogger()->error("Grok exited unexpectedly during initialization");
+ return false;
+ }
+ }
+
+ return _initialized && !_shutdown;
+ }
+
+ static size_t uncompressedFrameSize(uint32_t w, uint32_t h, uint32_t samplesPerPixel)
+ {
+ return sizeof(uint16_t) * w * h * samplesPerPixel;
+ }
+ void reclaimCompressed(size_t frameId)
+ {
+ availableBuffers_.push(BufferSrc(0, frameId, getCompressedFrame(frameId)));
+ }
+ void reclaimUncompressed(size_t frameId)
+ {
+ availableBuffers_.push(BufferSrc(0, frameId, getUncompressedFrame(frameId)));
+ }
+ uint8_t* getUncompressedFrame(size_t frameId)
+ {
+ assert(frameId < init_.numFrames_);
+ if(frameId >= init_.numFrames_)
+ return nullptr;
+
+ return (uint8_t*)(uncompressed_buffer_ + frameId * init_.uncompressedFrameSize_);
+ }
+ uint8_t* getCompressedFrame(size_t frameId)
+ {
+ assert(frameId < init_.numFrames_);
+ if(frameId >= init_.numFrames_)
+ return nullptr;
+
+ return (uint8_t*)(compressed_buffer_ + frameId * init_.compressedFrameSize_);
+ }
+ std::atomic_bool running;
+ bool _initialized;
+ bool _shutdown;
+ MessengerBlockingQueue<std::string> sendQueue;
+ MessengerBlockingQueue<std::string> receiveQueue;
+ MessengerBlockingQueue<BufferSrc> availableBuffers_;
+ MessengerInit init_;
+ std::string cmd_;
+ std::future<int> async_result_;
+ std::mutex shutdownMutex_;
+ std::condition_variable shutdownCondition_;
+
+ protected:
+ std::condition_variable clientInitializedCondition_;
+ private:
+ bool launch(std::string const& cmd, boost::filesystem::path const& dir)
+ {
+ // Change the working directory
+ if(!dir.empty())
+ {
+ boost::system::error_code ec;
+ boost::filesystem::current_path(dir, ec);
+ if (ec) {
+ getMessengerLogger()->error("Error: failed to change the working directory");
+ return false;
+ }
+ }
+ // Execute the command using std::async and std::system
+ cmd_ = cmd;
+ getMessengerLogger()->info(cmd.c_str());
+ async_result_ = std::async(std::launch::async, [this]() { return std::system(cmd_.c_str()); });
+ bool success = async_result_.valid();
+ if (!success)
+ getMessengerLogger()->error("Grok launch failed");
+
+ return success;
+
+ }
+ std::thread outbound;
+ Synch* outboundSynch_;
+
+ std::thread inbound;
+ Synch* inboundSynch_;
+
+ std::vector<std::thread> processors_;
+ char* uncompressed_buffer_;
+ char* compressed_buffer_;
+
+ grk_handle uncompressed_fd_;
+ grk_handle compressed_fd_;
+};
+
+static void outboundThread(Messenger* messenger, const std::string &sendBuf, Synch* synch)
+{
+ grk_handle shm_fd = 0;
+ char* send_buffer = nullptr;
+
+ if(!SharedMemoryManager::initShm(sendBuf, messageBufferLen, &shm_fd, &send_buffer))
+ return;
+ while(messenger->running)
+ {
+ synch->wait(SYNCH_RECEIVE_READY);
+ if(!messenger->running)
+ break;
+ std::string message;
+ if(!messenger->sendQueue.waitAndPop(message))
+ break;
+ if(!messenger->running)
+ break;
+ memcpy(send_buffer, message.c_str(), message.size() + 1);
+ synch->post(SYNCH_SENT);
+ }
+ SharedMemoryManager::deinitShm(sendBuf, messageBufferLen, shm_fd, &send_buffer);
+}
+
+static void inboundThread(Messenger* messenger, const std::string &receiveBuf, Synch* synch)
+{
+ grk_handle shm_fd = 0;
+ char* receive_buffer = nullptr;
+
+ if(!SharedMemoryManager::initShm(receiveBuf, messageBufferLen, &shm_fd, &receive_buffer))
+ return;
+ while(messenger->running)
+ {
+ synch->wait(SYNCH_SENT);
+ if(!messenger->running)
+ break;
+ auto message = std::string(receive_buffer);
+ synch->post(SYNCH_RECEIVE_READY);
+ messenger->receiveQueue.push(message);
+ }
+ SharedMemoryManager::deinitShm(receiveBuf, messageBufferLen, shm_fd, &receive_buffer);
+}
+struct Msg
+{
+ explicit Msg(const std::string &msg) : ct_(0)
+ {
+ std::stringstream ss(msg);
+ while(ss.good())
+ {
+ std::string substr;
+ std::getline(ss, substr, ',');
+ cs_.push_back(substr);
+ }
+ }
+ std::string next()
+ {
+ if(ct_ == cs_.size())
+ {
+ getMessengerLogger()->error("Msg: comma separated list exhausted. returning empty.");
+ return "";
+ }
+ return cs_[ct_++];
+ }
+
+ uint32_t nextUint(void)
+ {
+ return (uint32_t)std::stoi(next());
+ }
+
+ std::vector<std::string> cs_;
+ size_t ct_;
+};
+
+static void processorThread(Messenger* messenger, std::function<void(std::string)> processor)
+{
+ while (messenger->running) {
+ std::string message;
+ if (!messenger->receiveQueue.waitAndPop(message)) {
+ break;
+ }
+
+ if (!messenger->running) {
+ break;
+ }
+
+ Msg msg(message);
+ auto tag = msg.next();
+ if (tag == GRK_MSGR_BATCH_COMPRESS_INIT) {
+ auto width = msg.nextUint();
+ msg.nextUint(); // stride
+ auto height = msg.nextUint();
+ auto samples_per_pixel = msg.nextUint();
+ msg.nextUint(); // depth
+ messenger->init_.uncompressedFrameSize_ = Messenger::uncompressedFrameSize(width, height, samples_per_pixel);
+ auto compressed_frame_size = msg.nextUint();
+ auto num_frames = msg.nextUint();
+ messenger->initClient(compressed_frame_size, compressed_frame_size, num_frames);
+ } else if (tag == GRK_MSGR_BATCH_PROCESSED_UNCOMPRESSED) {
+ messenger->reclaimUncompressed(msg.nextUint());
+ } else if (tag == GRK_MSGR_BATCH_PROCESSSED_COMPRESSED) {
+ messenger->reclaimCompressed(msg.nextUint());
+ }
+ processor(message);
+ }
+}
+
+template<typename F>
+struct ScheduledFrames
+{
+ void store(F const& val)
+ {
+ std::unique_lock<std::mutex> lk(mapMutex_);
+ auto it = map_.find(val.index());
+ if (it == map_.end())
+ map_.emplace(std::make_pair(val.index(), val));
+ }
+ boost::optional<F> retrieve(size_t index)
+ {
+ std::unique_lock<std::mutex> lk(mapMutex_);
+ auto it = map_.find(index);
+ if(it == map_.end())
+ return {};
+
+ F val = it->second;
+ map_.erase(index);
+
+ return val;
+ }
+
+ private:
+ std::mutex mapMutex_;
+ std::map<size_t, F> map_;
+};
+
+template<typename F>
+struct ScheduledMessenger : public Messenger
+{
+ explicit ScheduledMessenger(MessengerInit init) : Messenger(init),
+ framesScheduled_(0),
+ framesCompressed_(0)
+ {}
+ ~ScheduledMessenger(void) {
+ shutdown();
+ }
+ bool scheduleCompress(F const& proxy, std::function<void(BufferSrc const&)> converter){
+ size_t frameSize = init_.uncompressedFrameSize_;
+ assert(frameSize >= init_.uncompressedFrameSize_);
+ BufferSrc src;
+ if(!availableBuffers_.waitAndPop(src))
+ return false;
+ converter(src);
+ scheduledFrames_.store(proxy);
+ framesScheduled_++;
+ send(GRK_MSGR_BATCH_SUBMIT_UNCOMPRESSED, proxy.index(), src.frameId_);
+
+ return true;
+ }
+ void processCompressed(const std::string &message, std::function<void(F,uint8_t*,uint32_t)> processor, bool needsRecompression) {
+ Msg msg(message);
+ msg.next();
+ auto clientFrameId = msg.nextUint();
+ auto compressedFrameId = msg.nextUint();
+ auto compressedFrameLength = msg.nextUint();
+ if (!needsRecompression) {
+ auto src_frame = scheduledFrames_.retrieve(clientFrameId);
+ if (!src_frame) {
+ return;
+ }
+ processor(*src_frame, getCompressedFrame(compressedFrameId),compressedFrameLength);
+ }
+ ++framesCompressed_;
+ send(GRK_MSGR_BATCH_PROCESSSED_COMPRESSED, compressedFrameId);
+ if (_shutdown && framesCompressed_ == framesScheduled_)
+ shutdownCondition_.notify_all();
+ }
+ void shutdown(void){
+ try {
+ std::unique_lock<std::mutex> lk(shutdownMutex_);
+ if (!async_result_.valid())
+ return;
+ _shutdown = true;
+ if (framesScheduled_) {
+ uint32_t scheduled = framesScheduled_;
+ send(GRK_MSGR_BATCH_FLUSH, scheduled);
+ shutdownCondition_.wait(lk, [this] { return framesScheduled_ == framesCompressed_; });
+ }
+ availableBuffers_.deactivate();
+ send(GRK_MSGR_BATCH_SHUTDOWN);
+ int result = async_result_.get();
+ if(result != 0)
+ getMessengerLogger()->error("Accelerator failed with return code: %d\n",result);
+ } catch (std::exception &ex) {
+ getMessengerLogger()->error("%s",ex.what());
+ }
+
+ }
+
+ boost::optional<F> retrieve(size_t index) {
+ return scheduledFrames_.retrieve(index);
+ }
+
+ void store(F& val) {
+ scheduledFrames_.store(val);
+ }
+
+private:
+ ScheduledFrames<F> scheduledFrames_;
+ std::atomic<uint32_t> framesScheduled_;
+ std::atomic<uint32_t> framesCompressed_;
+};
+
+} // namespace grk_plugin
diff --git a/src/lib/grok_j2k_encoder_thread.cc b/src/lib/grok_j2k_encoder_thread.cc
new file mode 100644
index 000000000..c3cd6f8dd
--- /dev/null
+++ b/src/lib/grok_j2k_encoder_thread.cc
@@ -0,0 +1,72 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "config.h"
+#include "cross.h"
+#include "dcpomatic_log.h"
+#include "dcp_video.h"
+#include "grok/context.h"
+#include "grok_j2k_encoder_thread.h"
+#include "j2k_encoder.h"
+#include "util.h"
+#include <dcp/scope_guard.h>
+
+#include "i18n.h"
+
+
+using std::make_shared;
+using std::shared_ptr;
+
+
+GrokJ2KEncoderThread::GrokJ2KEncoderThread(J2KEncoder& encoder, grk_plugin::GrokContext* context)
+ : J2KEncoderThread(encoder)
+ , _context(context)
+{
+
+}
+
+
+void
+GrokJ2KEncoderThread::run()
+try
+{
+ while (true)
+ {
+ LOG_TIMING("encoder-sleep thread=%1", thread_id());
+ auto frame = _encoder.pop();
+
+ dcp::ScopeGuard frame_guard([this, &frame]() {
+ LOG_ERROR("Failed to schedule encode of %1 using grok", frame.index());
+ _encoder.retry(frame);
+ });
+
+ LOG_TIMING("encoder-pop thread=%1 frame=%2 eyes=%3", thread_id(), frame.index(), static_cast<int>(frame.eyes()));
+
+ auto grok = Config::instance()->grok().get_value_or({});
+
+ if (_context->launch(frame, grok.selected) && _context->scheduleCompress(frame)) {
+ frame_guard.cancel();
+ }
+ }
+} catch (boost::thread_interrupted& e) {
+} catch (...) {
+ store_current();
+}
diff --git a/src/lib/grok_j2k_encoder_thread.h b/src/lib/grok_j2k_encoder_thread.h
new file mode 100644
index 000000000..5301e1670
--- /dev/null
+++ b/src/lib/grok_j2k_encoder_thread.h
@@ -0,0 +1,41 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "exception_store.h"
+#include "j2k_encoder_thread.h"
+
+
+namespace grk_plugin {
+ class GrokContext;
+}
+
+
+class GrokJ2KEncoderThread : public J2KEncoderThread, public ExceptionStore
+{
+public:
+ GrokJ2KEncoderThread(J2KEncoder& encoder, grk_plugin::GrokContext* context);
+
+ void run() override;
+
+private:
+ grk_plugin::GrokContext* _context;
+};
+
diff --git a/src/lib/hints.cc b/src/lib/hints.cc
index 2e2a8fd7b..1114d0acb 100644
--- a/src/lib/hints.cc
+++ b/src/lib/hints.cc
@@ -35,6 +35,7 @@
#include "player.h"
#include "ratio.h"
#include "text_content.h"
+#include "variant.h"
#include "video_content.h"
#include "writer.h"
#include <dcp/cpl.h>
@@ -76,7 +77,7 @@ using namespace boost::placeholders;
Hints::Hints (weak_ptr<const Film> weak_film)
: WeakConstFilm (weak_film)
- , _writer (new Writer(weak_film, weak_ptr<Job>(), true))
+ , _writer(new Writer(weak_film, weak_ptr<Job>(), film()->dir("hints") / dcpomatic::get_process_id(), true))
, _analyser (film(), film()->playlist(), true, [](float) {})
, _stop (false)
{
@@ -107,7 +108,13 @@ void
Hints::check_few_audio_channels ()
{
if (film()->audio_channels() < 6) {
- hint (_("Your DCP has fewer than 6 audio channels. This may cause problems on some projectors. You may want to set the DCP to have 6 channels. It does not matter if your content has fewer channels, as DCP-o-matic will fill the extras with silence."));
+ hint(
+ variant::insert_dcpomatic(
+ _("Your DCP has fewer than 6 audio channels. This may cause problems on some projectors. "
+ "You may want to set the DCP to have 6 channels. It does not matter if your content has "
+ "fewer channels, as %1 will fill the extras with silence.")
+ )
+ );
}
}
@@ -117,7 +124,12 @@ Hints::check_upmixers ()
{
auto ap = film()->audio_processor();
if (ap && (ap->id() == "stereo-5.1-upmix-a" || ap->id() == "stereo-5.1-upmix-b")) {
- hint (_("You are using DCP-o-matic's stereo-to-5.1 upmixer. This is experimental and may result in poor-quality audio. If you continue, you should listen to the resulting DCP in a cinema to make sure that it sounds good."));
+ hint(variant::insert_dcpomatic(
+ _("You are using %1's stereo-to-5.1 upmixer. This is experimental and "
+ "may result in poor-quality audio. If you continue, you should listen to the "
+ "resulting DCP in a cinema to make sure that it sounds good.")
+ )
+ );
}
}
@@ -154,17 +166,17 @@ void
Hints::check_unusual_container ()
{
auto const film_container = film()->container()->id();
- if (film_container != "185" && film_container != "239") {
+ if (film()->video_encoding() != VideoEncoding::MPEG2 && film_container != "185" && film_container != "239") {
hint (_("Your DCP uses an unusual container ratio. This may cause problems on some projectors. If possible, use Flat or Scope for the DCP container ratio."));
}
}
void
-Hints::check_high_j2k_bandwidth ()
+Hints::check_high_video_bit_rate()
{
- if (film()->j2k_bandwidth() >= 245000000) {
- hint (_("A few projectors have problems playing back very high bit-rate DCPs. It is a good idea to drop the JPEG2000 bandwidth down to about 200Mbit/s; this is unlikely to have any visible effect on the image."));
+ if (film()->video_encoding() == VideoEncoding::JPEG2000 && film()->video_bit_rate(VideoEncoding::JPEG2000) >= 245000000) {
+ hint (_("A few projectors have problems playing back very high bit-rate DCPs. It is a good idea to drop the video bit rate down to about 200Mbit/s; this is unlikely to have any visible effect on the image."));
}
}
@@ -252,6 +264,15 @@ Hints::check_interop ()
void
+Hints::check_video_encoding()
+{
+ if (film()->video_encoding() == VideoEncoding::MPEG2) {
+ hint(_("The vast majority of cinemas in Europe, Australasia and North America expect DCPs encoded with JPEG2000 rather than MPEG2. Make sure that your cinema really wants an old-style MPEG2 DCP."));
+ }
+}
+
+
+void
Hints::check_big_font_files ()
{
bool big_font_files = false;
@@ -451,12 +472,13 @@ try
check_certificates ();
check_interop ();
+ check_video_encoding();
check_big_font_files ();
check_few_audio_channels ();
check_upmixers ();
check_incorrect_container ();
check_unusual_container ();
- check_high_j2k_bandwidth ();
+ check_high_video_bit_rate();
check_frame_rate ();
check_4k_3d ();
check_speed_up ();
@@ -483,7 +505,7 @@ try
auto dcp_dir = film->dir("hints") / dcpomatic::get_process_id();
dcp::filesystem::remove_all(dcp_dir);
- _writer->finish (film->dir("hints") / dcpomatic::get_process_id());
+ _writer->finish();
dcp::DCP dcp (dcp_dir);
dcp.read ();
@@ -718,16 +740,20 @@ 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. 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."));
+ hint(variant::insert_dcpomatic(
+ _("The certificate chain that %1 uses for signing DCPs and KDMs contains a small error "
+ "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. "
- "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."));
+ hint(variant::insert_dcpomatic(
+ _("The certificate chain that %1 uses for signing DCPs and KDMs has a validity period "
+ "that is too long. This will cause problems playing back DCPs 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;
default:
/* Some bad situations can't happen here as DCP-o-matic would have refused to start until they are fixed */
@@ -740,7 +766,7 @@ void
Hints::check_8_or_16_audio_channels()
{
auto const channels = film()->audio_channels();
- if (channels != 8 && channels != 16) {
+ if (film()->video_encoding() != VideoEncoding::MPEG2 && 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 0d65edc21..9e94f210f 100644
--- a/src/lib/hints.h
+++ b/src/lib/hints.h
@@ -20,15 +20,16 @@
#include "audio_analyser.h"
-#include "signaller.h"
-#include "player_text.h"
#include "dcp_text_track.h"
#include "dcpomatic_time.h"
+#include "player_text.h"
+#include "signaller.h"
+#include "text_type.h"
#include "weak_film.h"
-#include <boost/signals2.hpp>
#include <boost/atomic.hpp>
-#include <vector>
+#include <boost/signals2.hpp>
#include <string>
+#include <vector>
class Film;
@@ -68,12 +69,13 @@ private:
void check_certificates ();
void check_interop ();
+ void check_video_encoding();
void check_big_font_files ();
void check_few_audio_channels ();
void check_upmixers ();
void check_incorrect_container ();
void check_unusual_container ();
- void check_high_j2k_bandwidth ();
+ void check_high_video_bit_rate();
void check_frame_rate ();
void check_4k_3d ();
void check_speed_up ();
diff --git a/src/lib/http_server.cc b/src/lib/http_server.cc
new file mode 100644
index 000000000..0ee62756a
--- /dev/null
+++ b/src/lib/http_server.cc
@@ -0,0 +1,255 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "cross.h"
+#include "dcpomatic_log.h"
+#include "dcpomatic_socket.h"
+#include "http_server.h"
+#include "util.h"
+#include "variant.h"
+#include <boost/algorithm/string.hpp>
+#include <stdexcept>
+
+
+using std::make_pair;
+using std::runtime_error;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+
+
+Response Response::ERROR_404 = { 404, "<html><head><title>Error 404</title></head><body><h1>Error 404</h1></body></html>"};
+
+
+HTTPServer::HTTPServer(int port, int timeout)
+ : Server(port, timeout)
+{
+
+}
+
+
+
+Response::Response(int code)
+ : _code(code)
+{
+
+}
+
+
+Response::Response(int code, string payload)
+ : _code(code)
+ , _payload(payload)
+{
+
+}
+
+
+void
+Response::add_header(string key, string value)
+{
+ _headers.push_back(make_pair(key, value));
+}
+
+
+void
+Response::send(shared_ptr<Socket> socket)
+{
+ socket->write(String::compose("HTTP/1.1 %1 OK\r\n", _code));
+ switch (_type) {
+ case Type::HTML:
+ socket->write("Content-Type: text/html; charset=utf-8\r\n");
+ break;
+ case Type::JSON:
+ socket->write("Content-Type: text/json; charset=utf-8\r\n");
+ break;
+ }
+ socket->write(String::compose("Content-Length: %1\r\n", _payload.length()));
+ for (auto const& header: _headers) {
+ socket->write(String::compose("%1: %2\r\n", header.first, header.second));
+ }
+ socket->write("\r\n");
+ socket->write(_payload);
+}
+
+
+Response
+HTTPServer::get(string const& url)
+{
+ if (url == "/") {
+ return Response(200, String::compose(dcp::file_to_string(resources_path() / "web" / "index.html"), variant::dcpomatic_player()));
+ } else if (url == "/api/v1/status") {
+ auto json = string{"{ "};
+ {
+ boost::mutex::scoped_lock lm(_mutex);
+ json += String::compose("\"playing\": %1, ", _playing ? "true" : "false");
+ json += String::compose("\"position\": \"%1\", ", seconds_to_hms(_position.seconds()));
+ json += String::compose("\"dcp_name\": \"%1\"", _dcp_name);
+ }
+ json += " }";
+ auto response = Response(200, json);
+ response.set_type(Response::Type::JSON);
+ return response;
+ } else {
+ LOG_HTTP("404 %1", url);
+ return Response::ERROR_404;
+ }
+}
+
+
+Response
+HTTPServer::post(string const& url)
+{
+ if (url == "/api/v1/play") {
+ emit(boost::bind(boost::ref(Play)));
+ auto response = Response(303);
+ response.add_header("Location", "/");
+ return response;
+ } else if (url == "/api/v1/stop") {
+ emit(boost::bind(boost::ref(Stop)));
+ auto response = Response(303);
+ response.add_header("Location", "/");
+ return response;
+ } else {
+ return Response::ERROR_404;
+ }
+}
+
+
+Response
+HTTPServer::request(vector<string> const& request)
+{
+ vector<string> parts;
+ boost::split(parts, request[0], boost::is_any_of(" "));
+ if (parts.size() != 3) {
+ return Response::ERROR_404;
+ }
+
+ try {
+ if (parts[0] == "GET") {
+ LOG_HTTP("GET %1", parts[1]);
+ return get(parts[1]);
+ } else if (parts[0] == "POST") {
+ LOG_HTTP("POST %1", parts[1]);
+ return post(parts[1]);
+ }
+ } catch (std::exception& e) {
+ LOG_ERROR("Error while handling HTTP request: %1", e.what());
+ } catch (...) {
+ LOG_ERROR_NC("Unknown exception while handling HTTP request");
+ }
+
+ LOG_HTTP("404 %1", parts[0]);
+ return Response::ERROR_404;
+}
+
+
+void
+HTTPServer::handle(shared_ptr<Socket> socket)
+{
+ class Reader
+ {
+ public:
+ void read_block(boost::system::error_code const& ec, uint8_t* data, std::size_t size)
+ {
+ if (ec.value() != boost::system::errc::success) {
+ _close = true;
+ _error_code = ec;
+ return;
+ }
+
+ for (std::size_t i = 0; i < size; ++i) {
+ if (_line.length() >= 1024) {
+ _close = true;
+ return;
+ }
+ _line += data[i];
+ if (_line.length() >= 2 && _line.substr(_line.length() - 2) == "\r\n") {
+ if (_line.length() == 2) {
+ _got_request = true;
+ return;
+ } else if (_request.size() > 64) {
+ _close = true;
+ return;
+ } else if (_line.size() >= 2) {
+ _line = _line.substr(0, _line.length() - 2);
+ }
+ LOG_HTTP("Receive: %1", _line);
+ _request.push_back(_line);
+ _line = "";
+ }
+ }
+ }
+
+
+ bool got_request() const {
+ return _got_request;
+ }
+
+ bool close() const {
+ return _close;
+ }
+
+ boost::system::error_code error_code() const {
+ return _error_code;
+ }
+
+ vector<std::string> const& get() const {
+ return _request;
+ }
+
+ private:
+ std::string _line;
+ vector<std::string> _request;
+ bool _got_request = false;
+ bool _close = false;
+ boost::system::error_code _error_code;
+ };
+
+ while (true) {
+
+ Reader reader;
+
+ vector<uint8_t> buffer(2048);
+ socket->socket().async_read_some(
+ boost::asio::buffer(buffer.data(), buffer.size()),
+ [&reader, &buffer, socket](boost::system::error_code const& ec, std::size_t bytes_transferred) {
+ socket->set_deadline_from_now(1);
+ reader.read_block(ec, buffer.data(), bytes_transferred);
+ });
+
+ while (!reader.got_request() && !reader.close() && socket->is_open()) {
+ socket->run();
+ }
+
+ if (reader.got_request() && !reader.close()) {
+ try {
+ auto response = request(reader.get());
+ response.send(socket);
+ } catch (runtime_error& e) {
+ LOG_ERROR_NC(e.what());
+ }
+ }
+
+ if (reader.close()) {
+ break;
+ }
+ }
+}
diff --git a/src/lib/http_server.h b/src/lib/http_server.h
new file mode 100644
index 000000000..f0ee50ef5
--- /dev/null
+++ b/src/lib/http_server.h
@@ -0,0 +1,91 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcpomatic_time.h"
+#include "server.h"
+#include "signaller.h"
+#include <boost/signals2.hpp>
+
+
+class Response
+{
+public:
+ Response(int code);
+ Response(int code, std::string payload);
+
+ enum class Type {
+ HTML,
+ JSON
+ };
+
+ void add_header(std::string key, std::string value);
+ void set_type(Type type) {
+ _type = type;
+ }
+
+ void send(std::shared_ptr<Socket> socket);
+
+ static Response ERROR_404;
+
+private:
+ int _code;
+
+ Type _type = Type::HTML;
+ std::string _payload;
+ std::vector<std::pair<std::string, std::string>> _headers;
+};
+
+
+class HTTPServer : public Server, public Signaller
+{
+public:
+ explicit HTTPServer(int port, int timeout = 30);
+
+ boost::signals2::signal<void ()> Play;
+ boost::signals2::signal<void ()> Stop;
+
+ void set_playing(bool playing) {
+ boost::mutex::scoped_lock lm(_mutex);
+ _playing = playing;
+ }
+
+ void set_position(dcpomatic::DCPTime position) {
+ boost::mutex::scoped_lock lm(_mutex);
+ _position = position;
+ }
+
+ void set_dcp_name(std::string name) {
+ boost::mutex::scoped_lock lm(_mutex);
+ _dcp_name = name;
+ }
+
+private:
+ void handle(std::shared_ptr<Socket> socket) override;
+ Response request(std::vector<std::string> const& request);
+ Response get(std::string const& url);
+ Response post(std::string const& url);
+
+ boost::mutex _mutex;
+ bool _playing = false;
+ dcpomatic::DCPTime _position;
+ std::string _dcp_name;
+};
+
diff --git a/src/lib/id.cc b/src/lib/id.cc
new file mode 100644
index 000000000..2891fb4ab
--- /dev/null
+++ b/src/lib/id.cc
@@ -0,0 +1,30 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "id.h"
+
+
+bool
+operator==(ID const& a, ID const& b)
+{
+ return a.get() == b.get();
+}
+
diff --git a/src/lib/id.h b/src/lib/id.h
new file mode 100644
index 000000000..ca4721039
--- /dev/null
+++ b/src/lib/id.h
@@ -0,0 +1,48 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_ID_H
+#define DCPOMATIC_ID_H
+
+
+#include <sqlite3.h>
+
+
+class ID
+{
+public:
+ sqlite3_int64 get() const {
+ return _id;
+ }
+
+protected:
+ ID(sqlite3_int64 id)
+ : _id(id) {}
+
+private:
+ sqlite3_int64 _id;
+};
+
+
+bool operator==(ID const& a, ID const& b);
+
+
+#endif
diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc
index 1a92c944e..1ac6e085b 100644
--- a/src/lib/image_content.cc
+++ b/src/lib/image_content.cc
@@ -99,13 +99,13 @@ ImageContent::technical_summary () const
void
-ImageContent::as_xml (xmlpp::Node* node, bool with_paths) const
+ImageContent::as_xml(xmlpp::Element* element, bool with_paths) const
{
- node->add_child("Type")->add_child_text ("Image");
- Content::as_xml (node, with_paths);
+ cxml::add_text_child(element, "Type", "Image");
+ Content::as_xml(element, with_paths);
if (video) {
- video->as_xml (node);
+ video->as_xml(element);
}
}
diff --git a/src/lib/image_content.h b/src/lib/image_content.h
index d817eeee8..140159bfa 100644
--- a/src/lib/image_content.h
+++ b/src/lib/image_content.h
@@ -40,7 +40,7 @@ public:
void examine (std::shared_ptr<const Film> film, std::shared_ptr<Job>) override;
std::string summary () const override;
std::string technical_summary () const override;
- void as_xml (xmlpp::Node *, bool with_paths) const override;
+ void as_xml(xmlpp::Element*, bool with_paths) const override;
dcpomatic::DCPTime full_length (std::shared_ptr<const Film> film) const override;
dcpomatic::DCPTime approximate_length () const override;
diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc
index ce5c8757f..527a98c7d 100644
--- a/src/lib/image_decoder.cc
+++ b/src/lib/image_decoder.cc
@@ -81,7 +81,7 @@ ImageDecoder::pass ()
}
}
- video->emit (film(), _image, _frame_video_position);
+ video->emit(film(), _image, dcpomatic::ContentTime::from_frames(_frame_video_position, _image_content->video_frame_rate().get_value_or(24)));
++_frame_video_position;
return false;
}
diff --git a/src/lib/image_proxy.h b/src/lib/image_proxy.h
index a37be580f..cec13b773 100644
--- a/src/lib/image_proxy.h
+++ b/src/lib/image_proxy.h
@@ -41,7 +41,7 @@ class Image;
class Socket;
namespace xmlpp {
- class Node;
+ class Element;
}
namespace cxml {
@@ -101,7 +101,7 @@ public:
boost::optional<dcp::Size> size = boost::optional<dcp::Size> ()
) const = 0;
- virtual void add_metadata (xmlpp::Node *) const = 0;
+ virtual void add_metadata(xmlpp::Element *) const = 0;
virtual void write_to_socket (std::shared_ptr<Socket>) const = 0;
/** @return true if our image is definitely the same as another, false if it is probably not */
virtual bool same (std::shared_ptr<const ImageProxy>) const = 0;
diff --git a/src/lib/internal_player_server.cc b/src/lib/internal_player_server.cc
new file mode 100644
index 000000000..676cfa412
--- /dev/null
+++ b/src/lib/internal_player_server.cc
@@ -0,0 +1,48 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcpomatic_socket.h"
+#include "internal_player_server.h"
+
+
+using std::shared_ptr;
+using std::string;
+using std::vector;
+
+
+void
+InternalPlayerServer::handle(shared_ptr<Socket> socket)
+{
+ try {
+ uint32_t const length = socket->read_uint32();
+ if (length > 65536) {
+ return;
+ }
+ vector<uint8_t> buffer(length);
+ socket->read(buffer.data(), buffer.size());
+ string s(reinterpret_cast<char*>(buffer.data()));
+ emit(boost::bind(boost::ref(LoadDCP), s));
+ socket->write(reinterpret_cast<uint8_t const *>("OK"), 3);
+ } catch (...) {
+
+ }
+}
+
diff --git a/src/lib/internal_player_server.h b/src/lib/internal_player_server.h
new file mode 100644
index 000000000..0648c9657
--- /dev/null
+++ b/src/lib/internal_player_server.h
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "config.h"
+#include "server.h"
+#include "signaller.h"
+#include "types.h"
+#include <boost/filesystem.hpp>
+#include <boost/signals2.hpp>
+
+
+/** A server which is always started, that listens for requests from the main
+ * DCP-o-matic to play a DCP.
+ */
+
+class InternalPlayerServer : public Server, public Signaller
+{
+public:
+ InternalPlayerServer()
+ : Server(PLAYER_PLAY_PORT)
+ {}
+
+ boost::signals2::signal<void (boost::filesystem::path)> LoadDCP;
+
+private:
+ void handle(std::shared_ptr<Socket> socket) override;
+};
+
diff --git a/src/lib/j2k_encoder.cc b/src/lib/j2k_encoder.cc
index 32d2fefc2..0b50bcd5a 100644
--- a/src/lib/j2k_encoder.cc
+++ b/src/lib/j2k_encoder.cc
@@ -32,6 +32,12 @@
#include "encode_server_description.h"
#include "encode_server_finder.h"
#include "film.h"
+#include "cpu_j2k_encoder_thread.h"
+#ifdef DCPOMATIC_GROK
+#include "grok/context.h"
+#include "grok_j2k_encoder_thread.h"
+#endif
+#include "remote_j2k_encoder_thread.h"
#include "j2k_encoder.h"
#include "log.h"
#include "player_video.h"
@@ -44,6 +50,7 @@
using std::cout;
+using std::dynamic_pointer_cast;
using std::exception;
using std::list;
using std::make_shared;
@@ -53,15 +60,47 @@ using boost::optional;
using dcp::Data;
using namespace dcpomatic;
+#ifdef DCPOMATIC_GROK
+
+namespace grk_plugin {
+
+IMessengerLogger* sLogger = nullptr;
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#endif
+void setMessengerLogger(grk_plugin::IMessengerLogger* logger)
+{
+ delete sLogger;
+ sLogger = logger;
+}
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+grk_plugin::IMessengerLogger* getMessengerLogger(void)
+{
+ return sLogger;
+}
+
+}
+
+#endif
+
/** @param film Film that we are encoding.
* @param writer Writer that we are using.
*/
J2KEncoder::J2KEncoder(shared_ptr<const Film> film, Writer& writer)
- : _film (film)
- , _history (200)
- , _writer (writer)
+ : VideoEncoder(film, writer)
{
+#ifdef DCPOMATIC_GROK
+ auto grok = Config::instance()->grok().get_value_or({});
+ _dcpomatic_context = new grk_plugin::DcpomaticContext(film, writer, _history, grok.binary_location);
+ if (grok.enable) {
+ _context = new grk_plugin::GrokContext(_dcpomatic_context);
+ }
+#endif
}
@@ -79,8 +118,29 @@ J2KEncoder::~J2KEncoder ()
*/
_writer.zombify();
- boost::mutex::scoped_lock lm (_threads_mutex);
- terminate_threads ();
+ terminate_threads();
+
+#ifdef DCPOMATIC_GROK
+ delete _context;
+ delete _dcpomatic_context;
+#endif
+}
+
+
+void
+J2KEncoder::servers_list_changed()
+{
+ auto config = Config::instance();
+#ifdef DCPOMATIC_GROK
+ auto const grok_enable = config->grok().get_value_or({}).enable;
+#else
+ auto const grok_enable = false;
+#endif
+
+ auto const cpu = (grok_enable || config->only_servers_encode()) ? 0 : config->master_encoding_threads();
+ auto const gpu = grok_enable ? config->master_encoding_threads() : 0;
+
+ remake_threads(cpu, gpu, EncodeServerFinder::instance()->servers());
}
@@ -95,7 +155,42 @@ J2KEncoder::begin ()
void
-J2KEncoder::end ()
+J2KEncoder::pause()
+{
+#ifdef DCPOMATIC_GROK
+ if (!Config::instance()->grok().get_value_or({}).enable) {
+ return;
+ }
+ return;
+
+ /* XXX; the same problem may occur here as in the destructor, perhaps? */
+
+ terminate_threads ();
+
+ /* Something might have been thrown during terminate_threads */
+ rethrow ();
+
+ delete _context;
+ _context = nullptr;
+#endif
+}
+
+
+void J2KEncoder::resume()
+{
+#ifdef DCPOMATIC_GROK
+ if (!Config::instance()->grok().get_value_or({}).enable) {
+ return;
+ }
+
+ _context = new grk_plugin::GrokContext(_dcpomatic_context);
+ servers_list_changed();
+#endif
+}
+
+
+void
+J2KEncoder::end()
{
boost::mutex::scoped_lock lock (_queue_mutex);
@@ -104,18 +199,13 @@ J2KEncoder::end ()
/* Keep waking workers until the queue is empty */
while (!_queue.empty ()) {
rethrow ();
- _empty_condition.notify_all ();
_full_condition.wait (lock);
}
-
lock.unlock ();
LOG_GENERAL_NC (N_("Terminating encoder threads"));
- {
- boost::mutex::scoped_lock lm (_threads_mutex);
- terminate_threads ();
- }
+ terminate_threads ();
/* Something might have been thrown during terminate_threads */
rethrow ();
@@ -130,42 +220,35 @@ J2KEncoder::end ()
So just mop up anything left in the queue here.
*/
-
- for (auto const& i: _queue) {
- LOG_GENERAL(N_("Encode left-over frame %1"), i.index());
- try {
- _writer.write(
- make_shared<dcp::ArrayData>(i.encode_locally()),
- i.index(),
- i.eyes()
- );
- frame_done ();
- } catch (std::exception& e) {
- LOG_ERROR (N_("Local encode failed (%1)"), e.what ());
+ for (auto & i: _queue) {
+#ifdef DCPOMATIC_GROK
+ if (Config::instance()->grok().get_value_or({}).enable) {
+ if (!_context->scheduleCompress(i)){
+ LOG_GENERAL (N_("[%1] J2KEncoder thread pushes frame %2 back onto queue after failure"), thread_id(), i.index());
+ // handle error
+ }
+ } else {
+#else
+ {
+#endif
+ LOG_GENERAL(N_("Encode left-over frame %1"), i.index());
+ try {
+ _writer.write(
+ make_shared<dcp::ArrayData>(i.encode_locally()),
+ i.index(),
+ i.eyes()
+ );
+ frame_done ();
+ } catch (std::exception& e) {
+ LOG_ERROR (N_("Local encode failed (%1)"), e.what ());
+ }
}
}
-}
-
-/** @return an estimate of the current number of frames we are encoding per second,
- * if known.
- */
-optional<float>
-J2KEncoder::current_encoding_rate () const
-{
- return _history.rate ();
-}
-
-
-/** @return Number of video frames that have been queued for encoding */
-int
-J2KEncoder::video_frames_enqueued () const
-{
- if (!_last_player_video_time) {
- return 0;
- }
-
- return _last_player_video_time->frames_floor (_film->video_frame_rate ());
+#ifdef DCPOMATIC_GROK
+ delete _context;
+ _context = nullptr;
+#endif
}
@@ -188,12 +271,14 @@ J2KEncoder::frame_done ()
void
J2KEncoder::encode (shared_ptr<PlayerVideo> pv, DCPTime time)
{
+ VideoEncoder::encode(pv, time);
+
_waker.nudge ();
size_t threads = 0;
{
boost::mutex::scoped_lock lm (_threads_mutex);
- threads = _threads->size();
+ threads = _threads.size();
}
boost::mutex::scoped_lock queue_lock (_queue_mutex);
@@ -233,13 +318,14 @@ J2KEncoder::encode (shared_ptr<PlayerVideo> pv, DCPTime time)
LOG_DEBUG_ENCODE("Frame @ %1 ENCODE", to_string(time));
/* Queue this new frame for encoding */
LOG_TIMING ("add-frame-to-queue queue=%1", _queue.size ());
- _queue.push_back (DCPVideo(
+ auto dcpv = DCPVideo(
pv,
position,
_film->video_frame_rate(),
- _film->j2k_bandwidth(),
+ _film->video_bit_rate(VideoEncoding::JPEG2000),
_film->resolution()
- ));
+ );
+ _queue.push_back (dcpv);
/* The queue might not be empty any more, so notify anything which is
waiting on that.
@@ -248,174 +334,144 @@ J2KEncoder::encode (shared_ptr<PlayerVideo> pv, DCPTime time)
}
_last_player_video[pv->eyes()] = pv;
- _last_player_video_time = time;
}
-/** Caller must hold a lock on _threads_mutex */
void
J2KEncoder::terminate_threads ()
{
+ boost::mutex::scoped_lock lm(_threads_mutex);
boost::this_thread::disable_interruption dis;
- if (!_threads) {
- return;
- }
-
- _threads->interrupt_all ();
- try {
- _threads->join_all ();
- } catch (exception& e) {
- LOG_ERROR ("join() threw an exception: %1", e.what());
- } catch (...) {
- LOG_ERROR_NC ("join() threw an exception");
+ for (auto& thread: _threads) {
+ thread->stop();
}
- _threads.reset ();
+ _threads.clear();
+ _ending = true;
}
void
-J2KEncoder::encoder_thread (optional<EncodeServerDescription> server)
-try
+J2KEncoder::remake_threads(int cpu, int gpu, list<EncodeServerDescription> servers)
{
- start_of_thread ("J2KEncoder");
+ LOG_GENERAL("Making threads: CPU=%1, GPU=%2, Remote=%3", cpu, gpu, servers.size());
- if (server) {
- LOG_TIMING ("start-encoder-thread thread=%1 server=%2", thread_id (), server->host_name ());
- } else {
- LOG_TIMING ("start-encoder-thread thread=%1 server=localhost", thread_id ());
+ boost::mutex::scoped_lock lm (_threads_mutex);
+ if (_ending) {
+ return;
}
- /* Number of seconds that we currently wait between attempts
- to connect to the server; not relevant for localhost
- encodings.
- */
- int remote_backoff = 0;
+ auto remove_threads = [this](int wanted, int current, std::function<bool (shared_ptr<J2KEncoderThread>)> predicate) {
+ for (auto i = wanted; i < current; ++i) {
+ auto iter = std::find_if(_threads.begin(), _threads.end(), predicate);
+ if (iter != _threads.end()) {
+ (*iter)->stop();
+ _threads.erase(iter);
+ }
+ }
+ };
+
+
+ /* CPU */
+
+ auto const is_cpu_thread = [](shared_ptr<J2KEncoderThread> thread) {
+ return static_cast<bool>(dynamic_pointer_cast<CPUJ2KEncoderThread>(thread));
+ };
+
+ auto const current_cpu_threads = std::count_if(_threads.begin(), _threads.end(), is_cpu_thread);
+
+ for (auto i = current_cpu_threads; i < cpu; ++i) {
+ auto thread = make_shared<CPUJ2KEncoderThread>(*this);
+ thread->start();
+ _threads.push_back(thread);
+ }
+
+ remove_threads(cpu, current_cpu_threads, is_cpu_thread);
+
+#ifdef DCPOMATIC_GROK
+ /* GPU */
+
+ auto const is_grok_thread = [](shared_ptr<J2KEncoderThread> thread) {
+ return static_cast<bool>(dynamic_pointer_cast<GrokJ2KEncoderThread>(thread));
+ };
+
+ auto const current_gpu_threads = std::count_if(_threads.begin(), _threads.end(), is_grok_thread);
+
+ for (auto i = current_gpu_threads; i < gpu; ++i) {
+ auto thread = make_shared<GrokJ2KEncoderThread>(*this, _context);
+ thread->start();
+ _threads.push_back(thread);
+ }
+
+ remove_threads(gpu, current_gpu_threads, is_grok_thread);
+#endif
- while (true) {
+ /* Remote */
- LOG_TIMING ("encoder-sleep thread=%1", thread_id ());
- boost::mutex::scoped_lock lock (_queue_mutex);
- while (_queue.empty ()) {
- _empty_condition.wait (lock);
+ for (auto const& server: servers) {
+ if (!server.current_link_version()) {
+ continue;
}
- LOG_TIMING ("encoder-wake thread=%1 queue=%2", thread_id(), _queue.size());
- auto vf = _queue.front ();
+ auto is_remote_thread = [server](shared_ptr<J2KEncoderThread> thread) {
+ auto remote = dynamic_pointer_cast<RemoteJ2KEncoderThread>(thread);
+ return remote && remote->server().host_name() == server.host_name();
+ };
- /* We're about to commit to either encoding this frame or putting it back onto the queue,
- so we must not be interrupted until one or other of these things have happened. This
- block has thread interruption disabled.
- */
- {
- boost::this_thread::disable_interruption dis;
-
- LOG_TIMING ("encoder-pop thread=%1 frame=%2 eyes=%3", thread_id(), vf.index(), static_cast<int>(vf.eyes()));
- _queue.pop_front ();
-
- lock.unlock ();
-
- shared_ptr<Data> encoded;
-
- /* We need to encode this input */
- if (server) {
- try {
- encoded = make_shared<dcp::ArrayData>(vf.encode_remotely(server.get()));
-
- if (remote_backoff > 0) {
- LOG_GENERAL ("%1 was lost, but now she is found; removing backoff", server->host_name ());
- }
-
- /* This job succeeded, so remove any backoff */
- remote_backoff = 0;
-
- } catch (std::exception& e) {
- if (remote_backoff < 60) {
- /* back off more */
- remote_backoff += 10;
- }
- LOG_ERROR (
- N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"),
- vf.index(), server->host_name(), e.what(), remote_backoff
- );
- }
-
- } else {
- try {
- LOG_TIMING ("start-local-encode thread=%1 frame=%2", thread_id(), vf.index());
- encoded = make_shared<dcp::ArrayData>(vf.encode_locally());
- LOG_TIMING ("finish-local-encode thread=%1 frame=%2", thread_id(), vf.index());
- } catch (std::exception& e) {
- /* This is very bad, so don't cope with it, just pass it on */
- LOG_ERROR (N_("Local encode failed (%1)"), e.what ());
- throw;
- }
- }
+ auto const current_threads = std::count_if(_threads.begin(), _threads.end(), is_remote_thread);
- if (encoded) {
- _writer.write(encoded, vf.index(), vf.eyes());
- frame_done ();
- } else {
- lock.lock ();
- LOG_GENERAL (N_("[%1] J2KEncoder thread pushes frame %2 back onto queue after failure"), thread_id(), vf.index());
- _queue.push_front (vf);
- lock.unlock ();
- }
+ auto const wanted_threads = server.threads();
+
+ if (wanted_threads > current_threads) {
+ LOG_GENERAL(N_("Adding %1 worker threads for remote %2"), wanted_threads - current_threads, server.host_name());
+ } else if (wanted_threads < current_threads) {
+ LOG_GENERAL(N_("Removing %1 worker threads for remote %2"), current_threads - wanted_threads, server.host_name());
}
- if (remote_backoff > 0) {
- boost::this_thread::sleep (boost::posix_time::seconds (remote_backoff));
+ for (auto i = current_threads; i < wanted_threads; ++i) {
+ auto thread = make_shared<RemoteJ2KEncoderThread>(*this, server);
+ thread->start();
+ _threads.push_back(thread);
}
- /* The queue might not be full any more, so notify anything that is waiting on that */
- lock.lock ();
- _full_condition.notify_all ();
+ remove_threads(wanted_threads, current_threads, is_remote_thread);
}
-}
-catch (boost::thread_interrupted& e) {
- /* Ignore these and just stop the thread */
- _full_condition.notify_all ();
-}
-catch (...)
-{
- store_current ();
- /* Wake anything waiting on _full_condition so it can see the exception */
- _full_condition.notify_all ();
+
+ _writer.set_encoder_threads(_threads.size());
}
-void
-J2KEncoder::servers_list_changed ()
+DCPVideo
+J2KEncoder::pop()
{
- boost::mutex::scoped_lock lm (_threads_mutex);
+ boost::mutex::scoped_lock lock(_queue_mutex);
+ while (_queue.empty()) {
+ _empty_condition.wait (lock);
+ }
- terminate_threads ();
- _threads = make_shared<boost::thread_group>();
+ LOG_TIMING("encoder-wake thread=%1 queue=%2", thread_id(), _queue.size());
- /* XXX: could re-use threads */
+ auto vf = _queue.front();
+ _queue.pop_front();
- if (!Config::instance()->only_servers_encode ()) {
- for (int i = 0; i < Config::instance()->master_encoding_threads (); ++i) {
-#ifdef DCPOMATIC_LINUX
- auto t = _threads->create_thread(boost::bind(&J2KEncoder::encoder_thread, this, optional<EncodeServerDescription>()));
- pthread_setname_np (t->native_handle(), "encode-worker");
-#else
- _threads->create_thread(boost::bind(&J2KEncoder::encoder_thread, this, optional<EncodeServerDescription>()));
-#endif
- }
- }
+ _full_condition.notify_all();
+ return vf;
+}
- for (auto i: EncodeServerFinder::instance()->servers()) {
- if (!i.current_link_version()) {
- continue;
- }
- LOG_GENERAL (N_("Adding %1 worker threads for remote %2"), i.threads(), i.host_name ());
- for (int j = 0; j < i.threads(); ++j) {
- _threads->create_thread(boost::bind(&J2KEncoder::encoder_thread, this, i));
- }
- }
+void
+J2KEncoder::retry(DCPVideo video)
+{
+ boost::mutex::scoped_lock lock(_queue_mutex);
+ _queue.push_front(video);
+ _empty_condition.notify_all();
+}
+
- _writer.set_encoder_threads(_threads->size());
+void
+J2KEncoder::write(shared_ptr<const dcp::Data> data, int index, Eyes eyes)
+{
+ _writer.write(data, index, eyes);
+ frame_done();
}
diff --git a/src/lib/j2k_encoder.h b/src/lib/j2k_encoder.h
index 63228a6b8..c72a1debe 100644
--- a/src/lib/j2k_encoder.h
+++ b/src/lib/j2k_encoder.h
@@ -32,7 +32,9 @@
#include "enum_indexed_vector.h"
#include "event_history.h"
#include "exception_store.h"
+#include "j2k_encoder_thread.h"
#include "writer.h"
+#include "video_encoder.h"
#include <boost/optional.hpp>
#include <boost/signals2.hpp>
#include <boost/thread.hpp>
@@ -48,6 +50,15 @@ class Film;
class Job;
class PlayerVideo;
+namespace grk_plugin {
+ struct DcpomaticContext;
+ struct GrokContext;
+}
+
+struct local_threads_created_and_destroyed;
+struct remote_threads_created_and_destroyed;
+struct frames_not_lost_when_threads_disappear;
+
/** @class J2KEncoder
* @brief Class to manage encoding to J2K.
@@ -55,7 +66,7 @@ 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
+class J2KEncoder : public VideoEncoder, public ExceptionStore
{
public:
J2KEncoder(std::shared_ptr<const Film> film, Writer& writer);
@@ -65,33 +76,33 @@ public:
J2KEncoder& operator= (J2KEncoder const&) = delete;
/** Called to indicate that a processing run is about to begin */
- void begin ();
+ void begin() override;
/** Called to pass a bit of video to be encoded as the next DCP frame */
- void encode (std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time);
+ void encode (std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time) override;
- /** Called when a processing run has finished */
- void end ();
+ void pause() override;
+ void resume() override;
- boost::optional<float> current_encoding_rate () const;
- int video_frames_enqueued () const;
+ /** Called when a processing run has finished */
+ void end() override;
- void servers_list_changed ();
+ DCPVideo pop();
+ void retry(DCPVideo frame);
+ void write(std::shared_ptr<const dcp::Data> data, int index, Eyes eyes);
private:
+ friend struct ::local_threads_created_and_destroyed;
+ friend struct ::remote_threads_created_and_destroyed;
+ friend struct ::frames_not_lost_when_threads_disappear;
void frame_done ();
-
- void encoder_thread (boost::optional<EncodeServerDescription>);
+ void servers_list_changed ();
+ void remake_threads(int cpu, int gpu, std::list<EncodeServerDescription> servers);
void terminate_threads ();
- /** Film that we are encoding */
- std::shared_ptr<const Film> _film;
-
- EventHistory _history;
-
boost::mutex _threads_mutex;
- std::shared_ptr<boost::thread_group> _threads;
+ std::vector<std::shared_ptr<J2KEncoderThread>> _threads;
mutable boost::mutex _queue_mutex;
std::list<DCPVideo> _queue;
@@ -100,13 +111,18 @@ private:
/** condition to manage thread wakeups when we have too much to do */
boost::condition _full_condition;
- Writer& _writer;
Waker _waker;
EnumIndexedVector<std::shared_ptr<PlayerVideo>, Eyes> _last_player_video;
- boost::optional<dcpomatic::DCPTime> _last_player_video_time;
boost::signals2::scoped_connection _server_found_connection;
+
+#ifdef DCPOMATIC_GROK
+ grk_plugin::DcpomaticContext* _dcpomatic_context = nullptr;
+ grk_plugin::GrokContext *_context = nullptr;
+#endif
+
+ bool _ending = false;
};
diff --git a/src/lib/j2k_encoder_thread.cc b/src/lib/j2k_encoder_thread.cc
new file mode 100644
index 000000000..d0e8a439c
--- /dev/null
+++ b/src/lib/j2k_encoder_thread.cc
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_video.h"
+#include "dcpomatic_log.h"
+#include "j2k_encoder.h"
+#include "j2k_encoder_thread.h"
+
+
+J2KEncoderThread::J2KEncoderThread(J2KEncoder& encoder)
+ : _encoder(encoder)
+{
+
+}
+
+
+void
+J2KEncoderThread::start()
+{
+ _thread = boost::thread(boost::bind(&J2KEncoderThread::run, this));
+#ifdef DCPOMATIC_LINUX
+ pthread_setname_np(_thread.native_handle(), "encode-worker");
+#endif
+}
+
+
+void
+J2KEncoderThread::stop()
+{
+ _thread.interrupt();
+ try {
+ _thread.join();
+ } catch (std::exception& e) {
+ LOG_ERROR("join() threw an exception: %1", e.what());
+ } catch (...) {
+ LOG_ERROR_NC("join() threw an exception");
+ }
+}
+
+
diff --git a/src/lib/j2k_encoder_thread.h b/src/lib/j2k_encoder_thread.h
new file mode 100644
index 000000000..b03b6f356
--- /dev/null
+++ b/src/lib/j2k_encoder_thread.h
@@ -0,0 +1,53 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_J2K_ENCODER_THREAD
+#define DCPOMATIC_J2K_ENCODER_THREAD
+
+
+#include <boost/thread.hpp>
+
+
+class J2KEncoder;
+
+
+class J2KEncoderThread
+{
+public:
+ J2KEncoderThread(J2KEncoder& encoder);
+
+ J2KEncoderThread(J2KEncoderThread const&) = delete;
+ J2KEncoderThread& operator=(J2KEncoderThread const&) = delete;
+
+ void start();
+ void stop();
+
+ virtual void run() = 0;
+
+protected:
+ J2KEncoder& _encoder;
+
+private:
+ boost::thread _thread;
+};
+
+
+#endif
diff --git a/src/lib/j2k_image_proxy.cc b/src/lib/j2k_image_proxy.cc
index 269b01bca..023723c8b 100644
--- a/src/lib/j2k_image_proxy.cc
+++ b/src/lib/j2k_image_proxy.cc
@@ -25,11 +25,11 @@
#include "j2k_image_proxy.h"
#include <dcp/colour_conversion.h>
#include <dcp/j2k_transcode.h>
-#include <dcp/mono_picture_frame.h>
+#include <dcp/mono_j2k_picture_frame.h>
#include <dcp/openjpeg_image.h>
#include <dcp/raw_convert.h>
#include <dcp/rgb_xyz.h>
-#include <dcp/stereo_picture_frame.h>
+#include <dcp/stereo_j2k_picture_frame.h>
#include <dcp/warnings.h>
#include <libcxml/cxml.h>
LIBDCP_DISABLE_WARNINGS
@@ -64,7 +64,7 @@ J2KImageProxy::J2KImageProxy (boost::filesystem::path path, dcp::Size size, AVPi
J2KImageProxy::J2KImageProxy (
- shared_ptr<const dcp::MonoPictureFrame> frame,
+ shared_ptr<const dcp::MonoJ2KPictureFrame> frame,
dcp::Size size,
AVPixelFormat pixel_format,
optional<int> forced_reduction
@@ -81,7 +81,7 @@ J2KImageProxy::J2KImageProxy (
J2KImageProxy::J2KImageProxy (
- shared_ptr<const dcp::StereoPictureFrame> frame,
+ shared_ptr<const dcp::StereoJ2KPictureFrame> frame,
dcp::Size size,
dcp::Eye eye,
AVPixelFormat pixel_format,
@@ -192,15 +192,15 @@ J2KImageProxy::image (Image::Alignment alignment, optional<dcp::Size> target_siz
void
-J2KImageProxy::add_metadata (xmlpp::Node* node) const
+J2KImageProxy::add_metadata(xmlpp::Element* element) const
{
- node->add_child("Type")->add_child_text(N_("J2K"));
- node->add_child("Width")->add_child_text(raw_convert<string>(_size.width));
- node->add_child("Height")->add_child_text(raw_convert<string>(_size.height));
+ cxml::add_text_child(element, "Type", N_("J2K"));
+ cxml::add_text_child(element, "Width", raw_convert<string>(_size.width));
+ cxml::add_text_child(element, "Height", raw_convert<string>(_size.height));
if (_eye) {
- node->add_child("Eye")->add_child_text(raw_convert<string>(static_cast<int>(_eye.get())));
+ cxml::add_text_child(element, "Eye", raw_convert<string>(static_cast<int>(_eye.get())));
}
- node->add_child("Size")->add_child_text(raw_convert<string>(_data->size()));
+ cxml::add_text_child(element, "Size", raw_convert<string>(_data->size()));
}
diff --git a/src/lib/j2k_image_proxy.h b/src/lib/j2k_image_proxy.h
index 9666ea406..1d2d5cc21 100644
--- a/src/lib/j2k_image_proxy.h
+++ b/src/lib/j2k_image_proxy.h
@@ -26,8 +26,8 @@
namespace dcp {
- class MonoPictureFrame;
- class StereoPictureFrame;
+ class MonoJ2KPictureFrame;
+ class StereoJ2KPictureFrame;
}
@@ -37,14 +37,14 @@ public:
J2KImageProxy (boost::filesystem::path path, dcp::Size, AVPixelFormat pixel_format);
J2KImageProxy (
- std::shared_ptr<const dcp::MonoPictureFrame> frame,
+ std::shared_ptr<const dcp::MonoJ2KPictureFrame> frame,
dcp::Size,
AVPixelFormat pixel_format,
boost::optional<int> forced_reduction
);
J2KImageProxy (
- std::shared_ptr<const dcp::StereoPictureFrame> frame,
+ std::shared_ptr<const dcp::StereoJ2KPictureFrame> frame,
dcp::Size,
dcp::Eye,
AVPixelFormat pixel_format,
@@ -61,7 +61,7 @@ public:
boost::optional<dcp::Size> size = boost::optional<dcp::Size> ()
) const override;
- void add_metadata (xmlpp::Node *) const override;
+ void add_metadata(xmlpp::Element*) const override;
void write_to_socket (std::shared_ptr<Socket> override) const override;
/** @return true if our image is definitely the same as another, false if it is probably not */
bool same (std::shared_ptr<const ImageProxy>) const override;
diff --git a/src/lib/j2k_sync_encoder_thread.cc b/src/lib/j2k_sync_encoder_thread.cc
new file mode 100644
index 000000000..ef6834f60
--- /dev/null
+++ b/src/lib/j2k_sync_encoder_thread.cc
@@ -0,0 +1,65 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_video.h"
+#include "dcpomatic_log.h"
+#include "j2k_encoder.h"
+#include "j2k_sync_encoder_thread.h"
+#include <dcp/scope_guard.h>
+
+
+J2KSyncEncoderThread::J2KSyncEncoderThread(J2KEncoder& encoder)
+ : J2KEncoderThread(encoder)
+{
+
+}
+
+
+void
+J2KSyncEncoderThread::run()
+try
+{
+ log_thread_start();
+
+ while (true) {
+ LOG_TIMING("encoder-sleep thread=%1", thread_id());
+ auto frame = _encoder.pop();
+
+ dcp::ScopeGuard frame_guard([this, &frame]() {
+ boost::this_thread::disable_interruption dis;
+ _encoder.retry(frame);
+ });
+
+ LOG_TIMING("encoder-pop thread=%1 frame=%2 eyes=%3", thread_id(), frame.index(), static_cast<int>(frame.eyes()));
+
+ auto encoded = encode(frame);
+
+ if (encoded) {
+ boost::this_thread::disable_interruption dis;
+ frame_guard.cancel();
+ _encoder.write(encoded, frame.index(), frame.eyes());
+ }
+ }
+} catch (boost::thread_interrupted& e) {
+} catch (...) {
+ store_current();
+}
+
diff --git a/src/lib/j2k_sync_encoder_thread.h b/src/lib/j2k_sync_encoder_thread.h
new file mode 100644
index 000000000..45222279e
--- /dev/null
+++ b/src/lib/j2k_sync_encoder_thread.h
@@ -0,0 +1,32 @@
+#ifndef DCPOMATIC_J2K_SYNC_ENCODER_THREAD_H
+#define DCPOMATIC_J2K_SYNC_ENCODER_THREAD_H
+
+
+#include "exception_store.h"
+#include "j2k_encoder_thread.h"
+#include <dcp/array_data.h>
+#include <boost/thread.hpp>
+
+
+class DCPVideo;
+class J2KEncoder;
+
+
+class J2KSyncEncoderThread : public J2KEncoderThread, public ExceptionStore
+{
+public:
+ J2KSyncEncoderThread(J2KEncoder& encoder);
+
+ J2KSyncEncoderThread(J2KSyncEncoderThread const&) = delete;
+ J2KSyncEncoderThread& operator=(J2KSyncEncoderThread const&) = delete;
+
+ virtual ~J2KSyncEncoderThread() {}
+
+ void run() override;
+
+ virtual void log_thread_start() const = 0;
+ virtual std::shared_ptr<dcp::ArrayData> encode(DCPVideo const& frame) = 0;
+};
+
+
+#endif
diff --git a/src/lib/job.cc b/src/lib/job.cc
index 9c9530b7a..ee6ad4e70 100644
--- a/src/lib/job.cc
+++ b/src/lib/job.cc
@@ -33,6 +33,7 @@
#include "job.h"
#include "log.h"
#include "util.h"
+#include "variant.h"
#include <dcp/exceptions.h>
#include <sub/exceptions.h>
#include <boost/date_time/posix_time/posix_time.hpp>
@@ -137,9 +138,13 @@ Job::run_wrapper ()
/* 32-bit */
set_error (
_("Failed to encode the DCP."),
- _("This error has probably occurred because you are running the 32-bit version of DCP-o-matic and "
- "trying to use too many encoding threads. Please reduce the 'number of threads DCP-o-matic should "
- "use' in the General tab of Preferences and try again.")
+ String::compose(
+ _("This error has probably occurred because you are running the 32-bit version of %1 and "
+ "trying to use too many encoding threads. Please reduce the 'number of threads %2 should "
+ "use' in the General tab of Preferences and try again."),
+ variant::dcpomatic(),
+ variant::dcpomatic()
+ )
);
done = true;
#else
@@ -147,7 +152,12 @@ Job::run_wrapper ()
if (running_32_on_64()) {
set_error (
_("Failed to encode the DCP."),
- _("This error has probably occurred because you are running the 32-bit version of DCP-o-matic. Please re-install DCP-o-matic with the 64-bit installer and try again.")
+ String::compose(
+ _("This error has probably occurred because you are running the 32-bit version of %1. "
+ "Please re-install %2 with the 64-bit installer and try again."),
+ variant::dcpomatic(),
+ variant::dcpomatic()
+ )
);
done = true;
}
@@ -157,7 +167,7 @@ Job::run_wrapper ()
if (!done) {
set_error (
e.what (),
- string (_("It is not known what caused this error.")) + " " + REPORT_PROBLEM
+ String::compose(_("It is not known what caused this error. %1"), report_problem())
);
}
@@ -168,8 +178,8 @@ Job::run_wrapper ()
set_error (
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."),
+ String::compose(_("%1 could not open the file %2 (%3). Perhaps it does not exist or is in an unexpected format."),
+ variant::dcpomatic(),
dcp::filesystem::absolute(e.file()).string(),
e.what()
)
@@ -183,8 +193,8 @@ Job::run_wrapper ()
if (e.code() == boost::system::errc::no_such_file_or_directory) {
set_error (
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."),
+ String::compose(_("%1 could not open the file %2 (%3). Perhaps it does not exist or is in an unexpected format."),
+ variant::dcpomatic(),
dcp::filesystem::absolute(e.path1()).string(),
e.what()
)
@@ -192,7 +202,7 @@ Job::run_wrapper ()
} else {
set_error (
e.what (),
- string (_("It is not known what caused this error.")) + " " + REPORT_PROBLEM
+ String::compose(_("It is not known what caused this error. %1"), report_problem())
);
}
@@ -252,7 +262,7 @@ Job::run_wrapper ()
set_error (
e.what (),
- string (_("It is not known what caused this error.")) + " " + REPORT_PROBLEM
+ String::compose(_("It is not known what caused this error. %1"), report_problem())
);
set_progress (1);
@@ -262,7 +272,7 @@ Job::run_wrapper ()
set_error (
_("Unknown error"),
- string (_("It is not known what caused this error.")) + " " + REPORT_PROBLEM
+ String::compose(_("It is not known what caused this error. %1"), report_problem())
);
set_progress (1);
@@ -662,7 +672,7 @@ void
Job::cancel ()
{
if (_thread.joinable()) {
- resume();
+ Job::resume();
_thread.interrupt ();
_thread.join ();
@@ -689,6 +699,7 @@ Job::pause_by_user ()
}
if (paused) {
+ pause();
_pause_changed.notify_all ();
}
@@ -701,6 +712,7 @@ Job::pause_by_priority ()
{
if (running ()) {
set_state (PAUSED_BY_PRIORITY);
+ pause();
_pause_changed.notify_all ();
}
}
diff --git a/src/lib/job.h b/src/lib/job.h
index d4d0f9510..9b5fdfa6e 100644
--- a/src/lib/job.h
+++ b/src/lib/job.h
@@ -62,9 +62,10 @@ public:
}
void start ();
+ virtual void pause() {}
bool pause_by_user ();
void pause_by_priority ();
- void resume ();
+ virtual void resume ();
void cancel ();
bool is_new () const;
diff --git a/src/lib/kdm_cli.cc b/src/lib/kdm_cli.cc
index ee9622255..551be65e5 100644
--- a/src/lib/kdm_cli.cc
+++ b/src/lib/kdm_cli.cc
@@ -25,6 +25,7 @@
#include "cinema.h"
+#include "cinema_list.h"
#include "cross.h"
#include "config.h"
#include "dkdm_wrapper.h"
@@ -33,6 +34,7 @@
#include "film.h"
#include "kdm_with_metadata.h"
#include "screen.h"
+#include "variant.h"
#include <dcp/certificate.h>
#include <dcp/decrypted_kdm.h>
#include <dcp/encrypted_kdm.h>
@@ -44,6 +46,7 @@
using std::dynamic_pointer_cast;
using std::list;
using std::make_shared;
+using std::pair;
using std::runtime_error;
using std::shared_ptr;
using std::string;
@@ -59,13 +62,19 @@ using namespace dcpomatic;
static void
help (std::function<void (string)> out)
{
- out (String::compose("Syntax: %1 [OPTION] <FILM|CPL-ID|DKDM>", program_name));
+ out (String::compose("Syntax: %1 [OPTION] [COMMAND] <FILM|CPL-ID|DKDM>", program_name));
+ out ("Commands:");
+ out ("create create KDMs; default if no other command is specified");
+ out (variant::insert_dcpomatic("list-cinemas list known cinemas from %1 settings"));
+ out (variant::insert_dcpomatic("list-dkdm-cpls list CPLs for which %1 has DKDMs"));
+ out (variant::insert_dcpomatic("add-dkdm add DKDM to %1's list"));
+ out (variant::insert_dcpomatic("dump-decryption-certificate write the %1 KDM decryption certificate to the console"));
out (" -h, --help show this help");
out (" -o, --output <path> output file or directory");
out (" -K, --filename-format <format> filename format for KDMs");
out (" -Z, --container-name-format <format> filename format for ZIP containers");
- out (" -f, --valid-from <time> valid from time (in local time zone of the cinema) (e.g. \"2013-09-28 01:41:51\") or \"now\"");
- out (" -t, --valid-to <time> valid to time (in local time zone of the cinema) (e.g. \"2014-09-28 01:41:51\")");
+ out (" -f, --valid-from <time> valid from time (e.g. \"2013-09-28T01:41:51+04:00\", \"2018-01-01T12:00:30\") or \"now\"");
+ out (" -t, --valid-to <time> valid to time (e.g. \"2014-09-28T01:41:51\")");
out (" -d, --valid-duration <duration> valid duration (e.g. \"1 day\", \"4 hours\", \"2 weeks\")");
out (" -F, --formulation <formulation> modified-transitional-1, multiple-modified-transitional-1, dci-any or dci-specific [default modified-transitional-1]");
out (" -p, --disable-forensic-marking-picture disable forensic marking of pictures essences");
@@ -75,19 +84,18 @@ help (std::function<void (string)> out)
out (" -v, --verbose be verbose");
out (" -c, --cinema <name|email> cinema name (when using -C) or name/email (to filter cinemas)");
out (" -S, --screen <name> screen name (when using -C) or screen name (to filter screens when using -c)");
- out (" -C, --certificate <file> file containing projector certificate");
- out (" -T, --trusted-device <file> file containing a trusted device's certificate");
+ out (" -C, --projector-certificate <file> file containing projector certificate");
+ out (" -T, --trusted-device-certificate <file> file containing a trusted device's certificate");
+ out (" --decryption-key <file> file containing the private key which can decrypt the given DKDM");
+ out (variant::insert_dcpomatic(" (%1's configured private key will be used otherwise)"));
out (" --cinemas-file <file> use the given file as a list of cinemas instead of the current configuration");
- out (" --dump-decryption-certificate write the DCP-o-matic KDM decryption certificate to the console");
- out (" --list-cinemas list known cinemas from the DCP-o-matic settings");
- out (" --list-dkdm-cpls list CPLs for which DCP-o-matic has DKDMs");
out ("");
- out ("CPL-ID must be the ID of a CPL that is mentioned in DCP-o-matic's DKDM list.");
+ out (variant::insert_dcpomatic("CPL-ID must be the ID of a CPL that is mentioned in %1's DKDM list."));
out ("");
out ("For example:");
out ("");
out ("Create KDMs for my_great_movie to play in all of Fred's Cinema's screens for the next two weeks and zip them up.");
- out ("(Fred's Cinema must have been set up in DCP-o-matic's KDM window)");
+ out (variant::insert_dcpomatic("(Fred's Cinema must have been set up in %1's KDM window)"));
out ("");
out (String::compose("\t%1 -c \"Fred's Cinema\" -f now -d \"2 weeks\" -z my_great_movie", program_name));
}
@@ -102,17 +110,6 @@ public:
};
-static boost::posix_time::ptime
-time_from_string (string t)
-{
- if (t == "now") {
- return boost::posix_time::second_clock::local_time ();
- }
-
- return boost::posix_time::time_from_string (t);
-}
-
-
static boost::posix_time::time_duration
duration_from_string (string d)
{
@@ -183,39 +180,32 @@ write_files (
}
-static
-shared_ptr<Cinema>
-find_cinema (string cinema_name)
+class ScreenDetails
{
- auto cinemas = Config::instance()->cinemas ();
- auto i = cinemas.begin();
- while (
- i != cinemas.end() &&
- (*i)->name != cinema_name &&
- find ((*i)->emails.begin(), (*i)->emails.end(), cinema_name) == (*i)->emails.end()) {
-
- ++i;
- }
-
- if (i == cinemas.end ()) {
- throw KDMCLIError (String::compose("could not find cinema \"%1\"", cinema_name));
- }
+public:
+ ScreenDetails(CinemaID const& cinema_id, Cinema const& cinema, Screen const& screen)
+ : cinema_id(cinema_id)
+ , cinema(cinema)
+ , screen(screen)
+ {}
- return *i;
-}
+ CinemaID cinema_id;
+ Cinema cinema;
+ Screen screen;
+};
static
void
from_film (
- vector<shared_ptr<Screen>> screens,
+ vector<ScreenDetails> const& screens,
boost::filesystem::path film_dir,
bool verbose,
boost::filesystem::path output,
dcp::NameFormat container_name_format,
dcp::NameFormat filename_format,
- boost::posix_time::ptime valid_from,
- boost::posix_time::ptime valid_to,
+ dcp::LocalTime valid_from,
+ dcp::LocalTime valid_to,
dcp::Formulation formulation,
bool disable_forensic_marking_picture,
optional<int> disable_forensic_marking_audio,
@@ -249,17 +239,27 @@ from_film (
try {
list<KDMWithMetadataPtr> kdms;
- for (auto i: screens) {
+ for (auto screen_details: screens) {
std::function<dcp::DecryptedKDM (dcp::LocalTime, dcp::LocalTime)> make_kdm = [film, cpl](dcp::LocalTime begin, dcp::LocalTime end) {
return film->make_kdm(cpl, begin, end);
};
- auto p = kdm_for_screen(make_kdm, i, valid_from, valid_to, formulation, disable_forensic_marking_picture, disable_forensic_marking_audio, period_checks);
+ auto p = kdm_for_screen(
+ make_kdm,
+ screen_details.cinema_id,
+ screen_details.cinema,
+ screen_details.screen,
+ valid_from,
+ valid_to,
+ formulation,
+ disable_forensic_marking_picture,
+ disable_forensic_marking_audio,
+ period_checks
+ );
if (p) {
kdms.push_back (p);
}
}
-
if (find_if(
period_checks.begin(),
period_checks.end(),
@@ -359,14 +359,14 @@ kdm_from_dkdm (
static
void
from_dkdm (
- vector<shared_ptr<Screen>> screens,
+ vector<ScreenDetails> const& screens,
dcp::DecryptedKDM dkdm,
bool verbose,
boost::filesystem::path output,
dcp::NameFormat container_name_format,
dcp::NameFormat filename_format,
- boost::posix_time::ptime valid_from,
- boost::posix_time::ptime valid_to,
+ dcp::LocalTime valid_from,
+ dcp::LocalTime valid_to,
dcp::Formulation formulation,
bool disable_forensic_marking_picture,
optional<int> disable_forensic_marking_audio,
@@ -379,37 +379,31 @@ from_dkdm (
try {
list<KDMWithMetadataPtr> kdms;
- for (auto i: screens) {
- if (!i->recipient) {
+ for (auto const& screen_details: screens) {
+ if (!screen_details.screen.recipient) {
continue;
}
- int const offset_hour = i->cinema ? i->cinema->utc_offset_hour() : 0;
- int const offset_minute = i->cinema ? i->cinema->utc_offset_minute() : 0;
-
- dcp::LocalTime begin(valid_from, dcp::UTCOffset(offset_hour, offset_minute));
- dcp::LocalTime end(valid_to, dcp::UTCOffset(offset_hour, offset_minute));
-
auto const kdm = kdm_from_dkdm(
dkdm,
- i->recipient.get(),
- i->trusted_device_thumbprints(),
- begin,
- end,
+ screen_details.screen.recipient.get(),
+ screen_details.screen.trusted_device_thumbprints(),
+ valid_from,
+ valid_to,
formulation,
disable_forensic_marking_picture,
disable_forensic_marking_audio
);
dcp::NameFormat::Map name_values;
- name_values['c'] = i->cinema ? i->cinema->name : "";
- name_values['s'] = i->name;
+ name_values['c'] = screen_details.cinema.name;
+ name_values['s'] = screen_details.screen.name;
name_values['f'] = kdm.content_title_text();
- name_values['b'] = begin.date() + " " + begin.time_of_day(true, false);
- name_values['e'] = end.date() + " " + end.time_of_day(true, false);
+ name_values['b'] = valid_from.date() + " " + valid_from.time_of_day(true, false);
+ name_values['e'] = valid_to.date() + " " + valid_to.time_of_day(true, false);
name_values['i'] = kdm.cpl_id();
- kdms.push_back(make_shared<KDMWithMetadata>(name_values, i->cinema.get(), i->cinema ? i->cinema->emails : vector<string>(), kdm));
+ kdms.push_back(make_shared<KDMWithMetadata>(name_values, screen_details.cinema_id, screen_details.cinema.emails, kdm));
}
write_files (kdms, zip, output, container_name_format, filename_format, verbose, out);
if (email) {
@@ -443,6 +437,22 @@ dump_dkdm_group (shared_ptr<DKDMGroup> group, int indent, std::function<void (st
}
+static
+dcp::LocalTime
+time_from_string(string time)
+{
+ if (time == "now") {
+ return {};
+ }
+
+ if (time.length() > 10 && time[10] == ' ') {
+ time[10] = 'T';
+ }
+
+ return dcp::LocalTime(time);
+}
+
+
void
dump_decryption_certificate(std::function<void (string)> out)
{
@@ -464,17 +474,20 @@ try
boost::filesystem::path output = dcp::filesystem::current_path();
auto container_name_format = Config::instance()->kdm_container_name_format();
auto filename_format = Config::instance()->kdm_filename_format();
+ /* either a cinema name to search for, or the name of a cinema to associate with certificate */
optional<string> cinema_name;
- shared_ptr<Cinema> cinema;
- optional<boost::filesystem::path> certificate;
- optional<string> screen;
- vector<shared_ptr<Screen>> screens;
+ /* either a screen name to search for, or the name of a screen to associate with certificate */
+ optional<string> screen_name;
+ /* a certificate that we will use to make up a temporary cinema and screen */
+ optional<boost::filesystem::path> projector_certificate;
+ optional<boost::filesystem::path> decryption_key;
+ /* trusted devices that we will use to make up a temporary cinema and screen */
+ vector<TrustedDevice> trusted_devices;
optional<dcp::EncryptedKDM> dkdm;
- optional<boost::posix_time::ptime> valid_from;
- optional<boost::posix_time::ptime> valid_to;
+ optional<dcp::LocalTime> valid_from;
+ optional<dcp::LocalTime> valid_to;
bool zip = false;
- bool list_cinemas = false;
- bool list_dkdm_cpls = false;
+ string command = "create";
optional<string> duration_string;
bool verbose = false;
dcp::Formulation formulation = dcp::Formulation::MODIFIED_TRANSITIONAL_1;
@@ -506,16 +519,14 @@ try
{ "verbose", no_argument, 0, 'v' },
{ "cinema", required_argument, 0, 'c' },
{ "screen", required_argument, 0, 'S' },
- { "certificate", required_argument, 0, 'C' },
- { "trusted-device", required_argument, 0, 'T' },
- { "list-cinemas", no_argument, 0, 'B' },
- { "list-dkdm-cpls", no_argument, 0, 'D' },
+ { "projector-certificate", required_argument, 0, 'C' },
+ { "trusted-device-certificate", required_argument, 0, 'T' },
+ { "decryption-key", required_argument, 0, 'G' },
{ "cinemas-file", required_argument, 0, 'E' },
- { "dump-decryption-certificate", no_argument, 0, 'G' },
{ 0, 0, 0, 0 }
};
- int c = getopt_long(fixer.argc(), fixer.argv(), "ho:K:Z:f:t:d:F:pae::zvc:S:C:T:BDE:G", long_options, &option_index);
+ int c = getopt_long(fixer.argc(), fixer.argv(), "ho:K:Z:f:t:d:F:pae::zvc:S:C:T:E:G", long_options, &option_index);
if (c == -1) {
break;
@@ -535,10 +546,10 @@ try
container_name_format = dcp::NameFormat (optarg);
break;
case 'f':
- valid_from = time_from_string (optarg);
+ valid_from = time_from_string(optarg);
break;
case 't':
- valid_to = time_from_string (optarg);
+ valid_to = dcp::LocalTime(optarg);
break;
case 'd':
duration_string = optarg;
@@ -577,70 +588,92 @@ try
verbose = true;
break;
case 'c':
- /* This could be a cinema to search for in the configured list or the name of a cinema being
- built up on-the-fly in the option. Cater for both possilibities here by storing the name
- (for lookup) and by creating a Cinema which the next Screen will be added to.
- */
cinema_name = optarg;
- cinema = make_shared<Cinema>(optarg, vector<string>(), "", 0, 0);
break;
case 'S':
- /* Similarly, this could be the name of a new (temporary) screen or the name of a screen
- * to search for.
- */
- screen = optarg;
+ screen_name = optarg;
break;
case 'C':
- certificate = optarg;
+ projector_certificate = optarg;
break;
case 'T':
- /* A trusted device ends up in the last screen we made */
- if (!screens.empty ()) {
- screens.back()->trusted_devices.push_back(TrustedDevice(dcp::Certificate(dcp::file_to_string(optarg))));
- }
- break;
- case 'B':
- list_cinemas = true;
+ trusted_devices.push_back(TrustedDevice(dcp::Certificate(dcp::file_to_string(optarg))));
break;
- case 'D':
- list_dkdm_cpls = true;
+ case 'G':
+ decryption_key = optarg;
break;
case 'E':
cinemas_file = optarg;
break;
- case 'G':
- dump_decryption_certificate(out);
- return {};
}
}
+ vector<string> commands = {
+ "create",
+ "list-cinemas",
+ "list-dkdm-cpls",
+ "add-dkdm",
+ "dump-decryption-certificate"
+ };
+
+ if (optind < argc - 1) {
+ /* Command with some KDM / CPL / whever specified afterwards */
+ command = argv[optind++];
+ } else if (optind < argc) {
+ /* Look for a valid command, hoping that it's not the name of the KDM / CPL / whatever */
+ if (std::find(commands.begin(), commands.end(), argv[optind]) != commands.end()) {
+ command = argv[optind++];
+ }
+ }
+
+ if (std::find(commands.begin(), commands.end(), command) == commands.end()) {
+ throw KDMCLIError(String::compose("Unrecognised command %1", command));
+ }
+
if (cinemas_file) {
Config::instance()->set_cinemas_file(*cinemas_file);
}
- if (certificate) {
- /* Make a new screen and add it to the current cinema */
- dcp::CertificateChain chain(dcp::file_to_string(*certificate));
- auto screen_to_add = std::make_shared<Screen>(screen.get_value_or(""), "", chain.leaf(), boost::none, vector<TrustedDevice>());
- if (cinema) {
- cinema->add_screen(screen_to_add);
- }
- screens.push_back(screen_to_add);
+ /* If we've been given a certificate we can make up a temporary cinema and screen (not written to the
+ * database) to then use for making KDMs.
+ */
+ optional<Cinema> temp_cinema;
+ optional<Screen> temp_screen;
+ if (projector_certificate) {
+ temp_cinema = Cinema(cinema_name.get_value_or(""), {}, "", dcp::UTCOffset());
+ dcp::CertificateChain chain(dcp::file_to_string(*projector_certificate));
+ temp_screen = Screen(screen_name.get_value_or(""), "", chain.leaf(), boost::none, trusted_devices);
}
- if (list_cinemas) {
- auto cinemas = Config::instance()->cinemas ();
- for (auto i: cinemas) {
- out (String::compose("%1 (%2)", i->name, Email::address_list(i->emails)));
+ if (command == "list-cinemas") {
+ CinemaList cinemas;
+ for (auto const& cinema: cinemas.cinemas()) {
+ out(String::compose("%1 (%2)", cinema.second.name, Email::address_list(cinema.second.emails)));
}
return {};
}
- if (list_dkdm_cpls) {
+ if (command == "list-dkdm-cpls") {
dump_dkdm_group (Config::instance()->dkdms(), 0, out);
return {};
}
+ if (command == "dump-deccryption-certificate") {
+ dump_decryption_certificate(out);
+ return {};
+ }
+
+ if (optind >= argc) {
+ throw KDMCLIError("no film, CPL ID or DKDM specified");
+ }
+
+ if (command == "add-dkdm") {
+ auto dkdms = Config::instance()->dkdms();
+ dkdms->add(make_shared<DKDM>(dcp::EncryptedKDM(dcp::file_to_string(argv[optind]))));
+ Config::instance()->write_config();
+ return {};
+ }
+
if (!duration_string && !valid_to) {
throw KDMCLIError ("you must specify a --valid-duration or --valid-to");
}
@@ -653,23 +686,37 @@ try
throw KDMCLIError ("no film, CPL ID or DKDM specified");
}
- if (screens.empty()) {
+ vector<ScreenDetails> screens;
+
+ if (!temp_cinema) {
if (!cinema_name) {
- throw KDMCLIError ("you must specify either a cinema or one or more screens using certificate files");
+ throw KDMCLIError("you must specify either a cinema or one or more screens using certificate files");
}
- screens = find_cinema (*cinema_name)->screens ();
- if (screen) {
- screens.erase(std::remove_if(screens.begin(), screens.end(), [&screen](shared_ptr<Screen> s) { return s->name != *screen; }), screens.end());
+ CinemaList cinema_list;
+ if (auto cinema = cinema_list.cinema_by_name_or_email(*cinema_name)) {
+ if (screen_name) {
+ for (auto screen: cinema_list.screens_by_cinema_and_name(cinema->first, *screen_name)) {
+ screens.push_back({cinema->first, cinema->second, screen.second});
+ }
+ } else {
+ for (auto screen: cinema_list.screens(cinema->first)) {
+ screens.push_back({cinema->first, cinema->second, screen.second});
+ }
+ }
}
+ } else {
+ DCPOMATIC_ASSERT(temp_screen);
+ screens.push_back({CinemaID(0), *temp_cinema, *temp_screen});
}
if (duration_string) {
- valid_to = valid_from.get() + duration_from_string (*duration_string);
+ valid_to = valid_from.get();
+ valid_to->add(duration_from_string(*duration_string));
}
if (verbose) {
- out (String::compose("Making KDMs valid from %1 to %2", boost::posix_time::to_simple_string(valid_from.get()), boost::posix_time::to_simple_string(valid_to.get())));
+ out(String::compose("Making KDMs valid from %1 to %2", valid_from->as_string(), valid_to->as_string()));
}
string const thing = argv[optind];
@@ -701,9 +748,11 @@ try
throw KDMCLIError ("could not find film or CPL ID corresponding to " + thing);
}
+ string const key = decryption_key ? dcp::file_to_string(*decryption_key) : Config::instance()->decryption_chain()->key().get();
+
from_dkdm (
screens,
- dcp::DecryptedKDM (*dkdm, Config::instance()->decryption_chain()->key().get()),
+ dcp::DecryptedKDM(*dkdm, key),
verbose,
output,
container_name_format,
diff --git a/src/lib/kdm_recipient.cc b/src/lib/kdm_recipient.cc
index 671e9797c..c33eb1b9f 100644
--- a/src/lib/kdm_recipient.cc
+++ b/src/lib/kdm_recipient.cc
@@ -39,14 +39,14 @@ KDMRecipient::KDMRecipient (cxml::ConstNodePtr node)
void
KDMRecipient::as_xml (xmlpp::Element* parent) const
{
- parent->add_child("Name")->add_child_text(name);
+ cxml::add_text_child(parent, "Name", name);
if (recipient) {
- parent->add_child("Recipient")->add_child_text(recipient->certificate(true));
+ cxml::add_text_child(parent, "Recipient", recipient->certificate(true));
}
if (recipient_file) {
- parent->add_child("RecipientFile")->add_child_text(*recipient_file);
+ cxml::add_text_child(parent, "RecipientFile", *recipient_file);
}
- parent->add_child("Notes")->add_child_text(notes);
+ cxml::add_text_child(parent, "Notes", notes);
}
diff --git a/src/lib/kdm_with_metadata.h b/src/lib/kdm_with_metadata.h
index fbeeffbc1..6198564b1 100644
--- a/src/lib/kdm_with_metadata.h
+++ b/src/lib/kdm_with_metadata.h
@@ -23,6 +23,7 @@
#define DCPOMATIC_KDM_WITH_METADATA_H
+#include "id.h"
#include <dcp/encrypted_kdm.h>
#include <dcp/name_format.h>
@@ -33,7 +34,7 @@ class Cinema;
class KDMWithMetadata
{
public:
- KDMWithMetadata(dcp::NameFormat::Map const& name_values, void const* group, std::vector<std::string> emails, dcp::EncryptedKDM kdm)
+ KDMWithMetadata(dcp::NameFormat::Map const& name_values, ID group, std::vector<std::string> emails, dcp::EncryptedKDM kdm)
: _name_values (name_values)
, _group (group)
, _emails (emails)
@@ -54,7 +55,7 @@ public:
boost::optional<std::string> get (char k) const;
- void const* group () const {
+ ID group() const {
return _group;
}
@@ -64,7 +65,7 @@ public:
private:
dcp::NameFormat::Map _name_values;
- void const* _group;
+ ID _group;
std::vector<std::string> _emails;
dcp::EncryptedKDM _kdm;
};
diff --git a/src/lib/log_entry.cc b/src/lib/log_entry.cc
index d5065e03a..af9148e68 100644
--- a/src/lib/log_entry.cc
+++ b/src/lib/log_entry.cc
@@ -38,6 +38,7 @@ int const LogEntry::TYPE_DEBUG_VIDEO_VIEW = 0x080;
int const LogEntry::TYPE_DISK = 0x100;
int const LogEntry::TYPE_DEBUG_PLAYER = 0x200;
int const LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS = 0x400;
+int const LogEntry::TYPE_HTTP = 0x800;
using std::string;
diff --git a/src/lib/log_entry.h b/src/lib/log_entry.h
index 95c4e4f1f..89357d201 100644
--- a/src/lib/log_entry.h
+++ b/src/lib/log_entry.h
@@ -42,6 +42,7 @@ public:
static const int TYPE_DISK;
static const int TYPE_DEBUG_PLAYER; ///< the Player class
static const int TYPE_DEBUG_AUDIO_ANALYSIS; ///< audio analysis job
+ static const int TYPE_HTTP;
explicit LogEntry (int type);
virtual ~LogEntry () {}
diff --git a/src/lib/make_dcp.cc b/src/lib/make_dcp.cc
index 17d45be46..b72756194 100644
--- a/src/lib/make_dcp.cc
+++ b/src/lib/make_dcp.cc
@@ -21,7 +21,7 @@
#include "config.h"
#include "dcp_content.h"
-#include "dcp_encoder.h"
+#include "dcp_film_encoder.h"
#include "dcp_transcode_job.h"
#include "dcpomatic_log.h"
#include "environment_info.h"
@@ -40,8 +40,8 @@ using std::shared_ptr;
using std::string;
-/** Add suitable Jobs to the JobManager to create a DCP for a Film */
-void
+/** Add suitable Job to the JobManager to create a DCP for a Film */
+shared_ptr<TranscodeJob>
make_dcp (shared_ptr<Film> film, TranscodeJob::ChangedBehaviour behaviour)
{
if (film->dcp_name().find("/") != string::npos) {
@@ -91,15 +91,12 @@ make_dcp (shared_ptr<Film> film, TranscodeJob::ChangedBehaviour behaviour)
LOG_GENERAL ("Content: %1", content->technical_summary());
}
LOG_GENERAL ("DCP video rate %1 fps", film->video_frame_rate());
- if (Config::instance()->only_servers_encode()) {
- LOG_GENERAL_NC ("0 threads: ONLY SERVERS SET TO ENCODE");
- } else {
- LOG_GENERAL ("%1 threads", Config::instance()->master_encoding_threads());
- }
- LOG_GENERAL ("J2K bandwidth %1", film->j2k_bandwidth());
+ LOG_GENERAL("Video bit rate %1", film->video_bit_rate(film->video_encoding()));
auto tj = make_shared<DCPTranscodeJob>(film, behaviour);
- tj->set_encoder (make_shared<DCPEncoder>(film, tj));
+ tj->set_encoder(make_shared<DCPFilmEncoder>(film, tj));
JobManager::instance()->add (tj);
+
+ return tj;
}
diff --git a/src/lib/make_dcp.h b/src/lib/make_dcp.h
index 9f5072782..fe0bcd2f6 100644
--- a/src/lib/make_dcp.h
+++ b/src/lib/make_dcp.h
@@ -25,5 +25,5 @@
class Film;
-void make_dcp (std::shared_ptr<Film> film, TranscodeJob::ChangedBehaviour behaviour);
+std::shared_ptr<TranscodeJob> make_dcp(std::shared_ptr<Film> film, TranscodeJob::ChangedBehaviour behaviour);
diff --git a/src/lib/map_cli.cc b/src/lib/map_cli.cc
index b158499a8..be3841deb 100644
--- a/src/lib/map_cli.cc
+++ b/src/lib/map_cli.cc
@@ -27,7 +27,7 @@
#include <dcp/interop_subtitle_asset.h>
#include <dcp/filesystem.h>
#include <dcp/font_asset.h>
-#include <dcp/mono_picture_asset.h>
+#include <dcp/mono_j2k_picture_asset.h>
#include <dcp/reel.h>
#include <dcp/reel_atmos_asset.h>
#include <dcp/reel_closed_caption_asset.h>
@@ -37,7 +37,7 @@
#include <dcp/reel_subtitle_asset.h>
#include <dcp/smpte_subtitle_asset.h>
#include <dcp/sound_asset.h>
-#include <dcp/stereo_picture_asset.h>
+#include <dcp/stereo_j2k_picture_asset.h>
#include <boost/optional.hpp>
#include <getopt.h>
#include <algorithm>
diff --git a/src/lib/mpeg2_encoder.cc b/src/lib/mpeg2_encoder.cc
new file mode 100644
index 000000000..9b9cdfd09
--- /dev/null
+++ b/src/lib/mpeg2_encoder.cc
@@ -0,0 +1,80 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "mpeg2_encoder.h"
+#include "writer.h"
+#include <dcp/ffmpeg_image.h>
+extern "C" {
+#include <libavutil/pixfmt.h>
+}
+
+
+using std::shared_ptr;
+
+
+MPEG2Encoder::MPEG2Encoder(shared_ptr<const Film> film, Writer& writer)
+ : VideoEncoder(film, writer)
+ , _transcoder(film->frame_size(), film->video_frame_rate(), film->video_bit_rate(VideoEncoding::MPEG2))
+{
+
+}
+
+
+void
+MPEG2Encoder::encode(shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time)
+{
+ VideoEncoder::encode(pv, time);
+
+ auto image = pv->image(
+ [](AVPixelFormat) { return AV_PIX_FMT_YUV420P; },
+ VideoRange::VIDEO,
+ false
+ );
+
+ dcp::FFmpegImage ffmpeg_image(time.get() * _film->video_frame_rate() / dcpomatic::DCPTime::HZ);
+
+ DCPOMATIC_ASSERT(image->size() == ffmpeg_image.size());
+
+ auto height = image->size().height;
+
+ for (int y = 0; y < height; ++y) {
+ memcpy(ffmpeg_image.y() + ffmpeg_image.y_stride() * y, image->data()[0] + image->stride()[0] * y, ffmpeg_image.y_stride());
+ }
+
+ for (int y = 0; y < height / 2; ++y) {
+ memcpy(ffmpeg_image.u() + ffmpeg_image.u_stride() * y, image->data()[1] + image->stride()[1] * y, ffmpeg_image.u_stride());
+ memcpy(ffmpeg_image.v() + ffmpeg_image.v_stride() * y, image->data()[2] + image->stride()[2] * y, ffmpeg_image.v_stride());
+ }
+
+ if (auto compressed = _transcoder.compress_frame(std::move(ffmpeg_image))) {
+ _writer.write(compressed->first, compressed->second);
+ }
+}
+
+
+void
+MPEG2Encoder::end()
+{
+ if (auto compressed = _transcoder.flush()) {
+ _writer.write(compressed->first, compressed->second);
+ }
+}
+
diff --git a/src/lib/mpeg2_encoder.h b/src/lib/mpeg2_encoder.h
new file mode 100644
index 000000000..1b2259d26
--- /dev/null
+++ b/src/lib/mpeg2_encoder.h
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "video_encoder.h"
+#include <dcp/mpeg2_transcode.h>
+
+
+class MPEG2Encoder : public VideoEncoder
+{
+public:
+ MPEG2Encoder(std::shared_ptr<const Film> film, Writer& writer);
+
+ void encode(std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time) override;
+
+ void pause() override {}
+ void resume() override {}
+
+ /** Called when a processing run has finished */
+ void end() override;
+
+private:
+ dcp::MPEG2Compressor _transcoder;
+};
+
diff --git a/src/lib/overlaps.cc b/src/lib/overlaps.cc
index fbc6b5cb1..bed75a822 100644
--- a/src/lib/overlaps.cc
+++ b/src/lib/overlaps.cc
@@ -28,7 +28,8 @@ using std::shared_ptr;
using namespace dcpomatic;
-ContentList overlaps (shared_ptr<const Film> film, ContentList cl, function<bool (shared_ptr<const Content>)> part, DCPTime from, DCPTime to)
+ContentList
+dcpomatic::overlaps(shared_ptr<const Film> film, ContentList cl, function<bool (shared_ptr<const Content>)> part, DCPTime from, DCPTime to)
{
ContentList overlaps;
DCPTimePeriod period (from, to);
diff --git a/src/lib/overlaps.h b/src/lib/overlaps.h
index bb8277eaa..f405c0fab 100644
--- a/src/lib/overlaps.h
+++ b/src/lib/overlaps.h
@@ -27,6 +27,9 @@ class ContentPart;
class Film;
+namespace dcpomatic {
+
+
/** @return Pieces of content with a given part (video, audio,
* subtitle) that overlap a specified time range in the given
* ContentList
@@ -34,3 +37,7 @@ class Film;
ContentList overlaps (
std::shared_ptr<const Film> film, ContentList cl, std::function<bool (std::shared_ptr<const Content>)> part, dcpomatic::DCPTime from, dcpomatic::DCPTime to
);
+
+
+}
+
diff --git a/src/lib/pixel_quanta.cc b/src/lib/pixel_quanta.cc
index 7c1d285cf..07690f3ac 100644
--- a/src/lib/pixel_quanta.cc
+++ b/src/lib/pixel_quanta.cc
@@ -34,8 +34,8 @@ PixelQuanta::PixelQuanta (cxml::ConstNodePtr node)
void
PixelQuanta::as_xml (xmlpp::Element* node) const
{
- node->add_child("X")->add_child_text(dcp::raw_convert<std::string>(x));
- node->add_child("Y")->add_child_text(dcp::raw_convert<std::string>(y));
+ cxml::add_text_child(node, "X", dcp::raw_convert<std::string>(x));
+ cxml::add_text_child(node, "Y", dcp::raw_convert<std::string>(y));
}
diff --git a/src/lib/player.cc b/src/lib/player.cc
index 75f6b7919..14cd95906 100644
--- a/src/lib/player.cc
+++ b/src/lib/player.cc
@@ -71,10 +71,8 @@ using std::dynamic_pointer_cast;
using std::list;
using std::make_pair;
using std::make_shared;
-using std::make_shared;
using std::max;
using std::min;
-using std::min;
using std::pair;
using std::shared_ptr;
using std::vector;
@@ -412,7 +410,6 @@ Player::setup_pieces ()
_silent = Empty(film, playlist(), bind(&have_audio, _1), _playback_length);
_next_video_time = boost::none;
- _next_video_eyes = Eyes::BOTH;
_next_audio_time = boost::none;
}
@@ -525,7 +522,7 @@ Player::black_player_video_frame (Eyes eyes) const
boost::mutex::scoped_lock lm(_black_image_mutex);
return std::make_shared<PlayerVideo> (
- std::make_shared<const RawImageProxy>(_black_image),
+ make_shared<const RawImageProxy>(_black_image),
Crop(),
optional<double>(),
_video_container_size,
@@ -535,7 +532,7 @@ Player::black_player_video_frame (Eyes eyes) const
PresetColourConversion::all().front().conversion,
VideoRange::FULL,
std::weak_ptr<Content>(),
- boost::optional<Frame>(),
+ boost::optional<dcpomatic::ContentTime>(),
false
);
}
@@ -736,7 +733,7 @@ Player::pass ()
if (_playback_length.load() == DCPTime() || !film) {
/* Special; just give one black frame */
- emit_video (black_player_video_frame(Eyes::BOTH), DCPTime());
+ use_video(black_player_video_frame(Eyes::BOTH), DCPTime(), one_video_frame());
return true;
}
@@ -775,22 +772,33 @@ Player::pass ()
LOG_DEBUG_PLAYER ("Calling pass() on %1", earliest_content->content->path(0));
earliest_content->done = earliest_content->decoder->pass ();
auto dcp = dynamic_pointer_cast<DCPContent>(earliest_content->content);
- if (dcp && !_play_referenced && dcp->reference_audio()) {
- /* We are skipping some referenced DCP audio content, so we need to update _next_audio_time
- to `hide' the fact that no audio was emitted during the referenced DCP (though
- we need to behave as though it was).
- */
- _next_audio_time = dcp->end(film);
+ if (dcp && !_play_referenced) {
+ if (dcp->reference_video()) {
+ _next_video_time = dcp->end(film);
+ }
+ if (dcp->reference_audio()) {
+ /* We are skipping some referenced DCP audio content, so we need to update _next_audio_time
+ to `hide' the fact that no audio was emitted during the referenced DCP (though
+ we need to behave as though it was).
+ */
+ _next_audio_time = dcp->end(film);
+ }
}
break;
}
case BLACK:
LOG_DEBUG_PLAYER ("Emit black for gap at %1", to_string(_black.position()));
+ if (!_next_video_time) {
+ /* Deciding to emit black has the same effect as getting some video from the content
+ * when we are inaccurately seeking.
+ */
+ _next_video_time = _black.position();
+ }
if (film->three_d()) {
- emit_video(black_player_video_frame(Eyes::LEFT), _black.position());
- emit_video(black_player_video_frame(Eyes::RIGHT), _black.position());
+ use_video(black_player_video_frame(Eyes::LEFT), _black.position(), _black.period_at_position().to);
+ use_video(black_player_video_frame(Eyes::RIGHT), _black.position(), _black.period_at_position().to);
} else {
- emit_video(black_player_video_frame(Eyes::BOTH), _black.position());
+ use_video(black_player_video_frame(Eyes::BOTH), _black.position(), _black.period_at_position().to);
}
_black.set_position (_black.position() + one_video_frame());
break;
@@ -893,24 +901,16 @@ Player::pass ()
}
if (done) {
+ if (_next_video_time) {
+ LOG_DEBUG_PLAYER("Done: emit video until end of film at %1", to_string(film->length()));
+ emit_video_until(film->length());
+ }
+
if (_shuffler) {
_shuffler->flush ();
}
for (auto const& i: _delay) {
- do_emit_video(i.first, i.second);
- }
-
- /* Perhaps we should have Empty entries for both eyes in the 3D case (somehow).
- * However, if we have L and R video files, and one is shorter than the other,
- * the fill code in ::video mostly takes care of filling in the gaps.
- * However, since it fills at the point when it knows there is more video coming
- * at time t (so it should fill any gap up to t) it can't do anything right at the
- * end. This is particularly bad news if the last frame emitted is a LEFT
- * eye, as the MXF writer will complain about the 3D sequence being wrong.
- * Here's a hack to workaround that particular case.
- */
- if (_next_video_eyes && _next_video_time && *_next_video_eyes == Eyes::RIGHT) {
- do_emit_video (black_player_video_frame(Eyes::RIGHT), *_next_video_time);
+ emit_video(i.first, i.second);
}
}
@@ -973,15 +973,61 @@ Player::open_subtitles_for_frame (DCPTime time) const
}
-static
-Eyes
-increment_eyes (Eyes e)
+void
+Player::emit_video_until(DCPTime time)
{
- if (e == Eyes::LEFT) {
- return Eyes::RIGHT;
- }
+ LOG_DEBUG_PLAYER("emit_video_until %1; next video time is %2", to_string(time), to_string(_next_video_time.get_value_or({})));
+ auto frame = [this](shared_ptr<PlayerVideo> pv, DCPTime time) {
+ /* We need a delay to give a little wiggle room to ensure that relevant subtitles arrive at the
+ player before the video that requires them.
+ */
+ _delay.push_back(make_pair(pv, time));
+
+ if (pv->eyes() == Eyes::BOTH || pv->eyes() == Eyes::RIGHT) {
+ _next_video_time = time + one_video_frame();
+ }
+
+ if (_delay.size() < 3) {
+ return;
+ }
+
+ auto to_do = _delay.front();
+ _delay.pop_front();
+ emit_video(to_do.first, to_do.second);
+ };
- return Eyes::LEFT;
+ auto const age_threshold = one_video_frame() * 2;
+
+ while (_next_video_time.get_value_or({}) < time) {
+ auto left = _last_video[Eyes::LEFT];
+ auto right = _last_video[Eyes::RIGHT];
+ auto both = _last_video[Eyes::BOTH];
+
+ auto const next = _next_video_time.get_value_or({});
+
+ if (
+ left.first &&
+ right.first &&
+ (!both.first || (left.second >= both.second && right.second >= both.second)) &&
+ (left.second - next) < age_threshold &&
+ (right.second - next) < age_threshold
+ ) {
+ frame(left.first, next);
+ frame(right.first, next);
+ } else if (both.first && (both.second - next) < age_threshold) {
+ frame(both.first, next);
+ LOG_DEBUG_PLAYER("Content %1 selected for DCP %2 (age %3)", to_string(both.second), to_string(next), to_string(both.second - next));
+ } else {
+ auto film = _film.lock();
+ if (film && film->three_d()) {
+ frame(black_player_video_frame(Eyes::LEFT), next);
+ frame(black_player_video_frame(Eyes::RIGHT), next);
+ } else {
+ frame(black_player_video_frame(Eyes::BOTH), next);
+ }
+ LOG_DEBUG_PLAYER("Black selected for DCP %1", to_string(next));
+ }
+ }
}
@@ -1006,11 +1052,6 @@ Player::video (weak_ptr<Piece> weak_piece, ContentVideo video)
return;
}
- FrameRateChange frc(film, piece->content);
- if (frc.skip && (video.frame % 2) == 1) {
- return;
- }
-
vector<Eyes> eyes_to_emit;
if (!film->three_d()) {
@@ -1033,15 +1074,11 @@ Player::video (weak_ptr<Piece> weak_piece, ContentVideo video)
eyes_to_emit = { video.eyes };
}
- /* Time of the first frame we will emit */
- DCPTime const time = content_video_to_dcp (piece, video.frame);
- LOG_DEBUG_PLAYER("Received video frame %1 at %2", video.frame, to_string(time));
+ /* Time of the frame we just received within the DCP */
+ auto const time = content_time_to_dcp(piece, video.time);
+ LOG_DEBUG_PLAYER("Received video frame %1 %2 eyes %3", to_string(video.time), to_string(time), static_cast<int>(video.eyes));
- /* Discard if it's before the content's period or the last accurate seek. We can't discard
- if it's after the content's period here as in that case we still need to fill any gap between
- `now' and the end of the content's period.
- */
- if (time < piece->content->position() || (_next_video_time && time < *_next_video_time)) {
+ if (time < piece->content->position()) {
return;
}
@@ -1054,56 +1091,8 @@ Player::video (weak_ptr<Piece> weak_piece, ContentVideo video)
return;
}
- /* Fill gaps that we discover now that we have some video which needs to be emitted.
- This is where we need to fill to.
- */
- DCPTime fill_to = min(time, piece->content->end(film));
-
- if (_next_video_time) {
- DCPTime fill_from = max (*_next_video_time, piece->content->position());
-
- /* Fill if we have more than half a frame to do */
- if ((fill_to - fill_from) > one_video_frame() / 2) {
- auto last = _last_video.find (weak_piece);
- if (film->three_d()) {
- auto fill_to_eyes = eyes_to_emit[0];
- if (fill_to_eyes == Eyes::BOTH) {
- fill_to_eyes = Eyes::LEFT;
- }
- if (fill_to == piece->content->end(film)) {
- /* Don't fill after the end of the content */
- fill_to_eyes = Eyes::LEFT;
- }
- auto j = fill_from;
- auto eyes = _next_video_eyes.get_value_or(Eyes::LEFT);
- if (eyes == Eyes::BOTH) {
- eyes = Eyes::LEFT;
- }
- while (j < fill_to || eyes != fill_to_eyes) {
- if (last != _last_video.end()) {
- LOG_DEBUG_PLAYER("Fill using last video at %1 in 3D mode", to_string(j));
- auto copy = last->second->shallow_copy();
- copy->set_eyes (eyes);
- emit_video (copy, j);
- } else {
- LOG_DEBUG_PLAYER("Fill using black at %1 in 3D mode", to_string(j));
- emit_video (black_player_video_frame(eyes), j);
- }
- if (eyes == Eyes::RIGHT) {
- j += one_video_frame();
- }
- eyes = increment_eyes (eyes);
- }
- } else {
- for (DCPTime j = fill_from; j < fill_to; j += one_video_frame()) {
- if (last != _last_video.end()) {
- emit_video (last->second, j);
- } else {
- emit_video (black_player_video_frame(Eyes::BOTH), j);
- }
- }
- }
- }
+ if (!_next_video_time) {
+ _next_video_time = time.round(film->video_frame_rate());
}
auto const content_video = piece->content->video;
@@ -1112,33 +1101,38 @@ Player::video (weak_ptr<Piece> weak_piece, ContentVideo video)
DCPOMATIC_ASSERT(scaled_size);
for (auto eyes: eyes_to_emit) {
- _last_video[weak_piece] = std::make_shared<PlayerVideo>(
- video.image,
- content_video->actual_crop(),
- content_video->fade(film, video.frame),
- scale_for_display(
- *scaled_size,
+ use_video(
+ std::make_shared<PlayerVideo>(
+ video.image,
+ content_video->actual_crop(),
+ content_video->fade(film, video.time),
+ scale_for_display(
+ *scaled_size,
+ _video_container_size,
+ film->frame_size(),
+ content_video->pixel_quanta()
+ ),
_video_container_size,
- film->frame_size(),
- content_video->pixel_quanta()
+ eyes,
+ video.part,
+ content_video->colour_conversion(),
+ content_video->range(),
+ piece->content,
+ video.time,
+ false
),
- _video_container_size,
- eyes,
- video.part,
- content_video->colour_conversion(),
- content_video->range(),
- piece->content,
- video.frame,
- false
+ time,
+ piece->content->end(film)
);
+ }
+}
- DCPTime t = time;
- for (int i = 0; i < frc.repeat; ++i) {
- if (t < piece->content->end(film)) {
- emit_video (_last_video[weak_piece], t);
- }
- t += one_video_frame ();
- }
+void
+Player::use_video(shared_ptr<PlayerVideo> pv, DCPTime time, DCPTime end)
+{
+ _last_video[pv->eyes()] = { pv, time };
+ if (pv->eyes() != Eyes::LEFT) {
+ emit_video_until(std::min(time + one_video_frame() / 2, end));
}
}
@@ -1256,7 +1250,7 @@ Player::bitmap_text_start (weak_ptr<Piece> weak_piece, weak_ptr<const TextConten
{
/* Apply content's subtitle offsets */
sub.rectangle.x += content->x_offset ();
- sub.rectangle.y += content->y_offset ();
+ sub.rectangle.y -= content->y_offset ();
/* Apply a corrective translation to keep the subtitle centred after the scale that is coming up */
sub.rectangle.x -= sub.rectangle.width * ((content->x_scale() - 1) / 2);
@@ -1420,18 +1414,18 @@ Player::seek (DCPTime time, bool accurate)
if (accurate) {
_next_video_time = time;
- _next_video_eyes = Eyes::LEFT;
_next_audio_time = time;
} else {
_next_video_time = boost::none;
- _next_video_eyes = boost::none;
_next_audio_time = boost::none;
}
_black.set_position (time);
_silent.set_position (time);
- _last_video.clear ();
+ _last_video[Eyes::LEFT] = {};
+ _last_video[Eyes::RIGHT] = {};
+ _last_video[Eyes::BOTH] = {};
for (auto& state: _stream_states) {
state.second.last_push_end = boost::none;
@@ -1440,33 +1434,7 @@ Player::seek (DCPTime time, bool accurate)
void
-Player::emit_video (shared_ptr<PlayerVideo> pv, DCPTime time)
-{
- auto film = _film.lock();
- DCPOMATIC_ASSERT(film);
-
- /* We need a delay to give a little wiggle room to ensure that relevant subtitles arrive at the
- player before the video that requires them.
- */
- _delay.push_back (make_pair (pv, time));
-
- if (pv->eyes() == Eyes::BOTH || pv->eyes() == Eyes::RIGHT) {
- _next_video_time = time + one_video_frame();
- }
- _next_video_eyes = increment_eyes (pv->eyes());
-
- if (_delay.size() < 3) {
- return;
- }
-
- auto to_do = _delay.front();
- _delay.pop_front();
- do_emit_video (to_do.first, to_do.second);
-}
-
-
-void
-Player::do_emit_video (shared_ptr<PlayerVideo> pv, DCPTime time)
+Player::emit_video(shared_ptr<PlayerVideo> pv, DCPTime time)
{
if (pv->eyes() == Eyes::BOTH || pv->eyes() == Eyes::RIGHT) {
std::for_each(_active_texts.begin(), _active_texts.end(), [time](ActiveText& a) { a.clear_before(time); });
diff --git a/src/lib/player.h b/src/lib/player.h
index 48f6f97ca..314031698 100644
--- a/src/lib/player.h
+++ b/src/lib/player.h
@@ -157,6 +157,8 @@ private:
dcpomatic::ContentTime dcp_to_content_time (std::shared_ptr<const Piece> piece, dcpomatic::DCPTime t) const;
dcpomatic::DCPTime content_time_to_dcp (std::shared_ptr<const Piece> piece, dcpomatic::ContentTime t) const;
std::shared_ptr<PlayerVideo> black_player_video_frame (Eyes eyes) const;
+ void emit_video_until(dcpomatic::DCPTime time);
+ void insert_video(std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time, dcpomatic::DCPTime end);
std::pair<std::shared_ptr<Piece>, boost::optional<dcpomatic::DCPTime>> earliest_piece_and_time() const;
void video (std::weak_ptr<Piece>, ContentVideo);
@@ -172,8 +174,8 @@ private:
std::shared_ptr<const AudioBuffers> audio, dcpomatic::DCPTime time, dcpomatic::DCPTime discard_to
) const;
boost::optional<PositionImage> open_subtitles_for_frame (dcpomatic::DCPTime time) const;
- void emit_video (std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time);
- void do_emit_video (std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time);
+ void emit_video(std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time);
+ void use_video(std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time, dcpomatic::DCPTime end);
void emit_audio (std::shared_ptr<AudioBuffers> data, dcpomatic::DCPTime time);
std::shared_ptr<const Playlist> playlist () const;
@@ -214,15 +216,12 @@ private:
/** Time of the next video that we will emit, or the time of the last accurate seek */
boost::optional<dcpomatic::DCPTime> _next_video_time;
- /** Eyes of the next video that we will emit */
- boost::optional<Eyes> _next_video_eyes;
/** Time of the next audio that we will emit, or the time of the last accurate seek */
boost::optional<dcpomatic::DCPTime> _next_audio_time;
boost::atomic<boost::optional<int>> _dcp_decode_reduction;
- typedef std::map<std::weak_ptr<Piece>, std::shared_ptr<PlayerVideo>, std::owner_less<std::weak_ptr<Piece>>> LastVideoMap;
- LastVideoMap _last_video;
+ EnumIndexedVector<std::pair<std::shared_ptr<PlayerVideo>, dcpomatic::DCPTime>, Eyes> _last_video;
AudioMerger _audio_merger;
std::unique_ptr<Shuffler> _shuffler;
diff --git a/src/lib/player_video.cc b/src/lib/player_video.cc
index 35c5d3daa..247301d58 100644
--- a/src/lib/player_video.cc
+++ b/src/lib/player_video.cc
@@ -45,6 +45,7 @@ using std::weak_ptr;
using boost::optional;
using dcp::Data;
using dcp::raw_convert;
+using namespace dcpomatic;
PlayerVideo::PlayerVideo (
@@ -58,7 +59,7 @@ PlayerVideo::PlayerVideo (
optional<ColourConversion> colour_conversion,
VideoRange video_range,
weak_ptr<Content> content,
- optional<Frame> video_frame,
+ optional<ContentTime> video_time,
bool error
)
: _in (in)
@@ -71,7 +72,7 @@ PlayerVideo::PlayerVideo (
, _colour_conversion (colour_conversion)
, _video_range (video_range)
, _content (content)
- , _video_frame (video_frame)
+ , _video_time(video_time)
, _error (error)
{
@@ -199,29 +200,29 @@ PlayerVideo::make_image (function<AVPixelFormat (AVPixelFormat)> pixel_format, V
void
-PlayerVideo::add_metadata (xmlpp::Node* node) const
+PlayerVideo::add_metadata(xmlpp::Element* element) const
{
- _crop.as_xml (node);
+ _crop.as_xml(element);
if (_fade) {
- node->add_child("Fade")->add_child_text (raw_convert<string> (_fade.get ()));
+ cxml::add_text_child(element, "Fade", raw_convert<string>(_fade.get()));
}
- _in->add_metadata (node->add_child ("In"));
- node->add_child("InterWidth")->add_child_text (raw_convert<string> (_inter_size.width));
- node->add_child("InterHeight")->add_child_text (raw_convert<string> (_inter_size.height));
- node->add_child("OutWidth")->add_child_text (raw_convert<string> (_out_size.width));
- node->add_child("OutHeight")->add_child_text (raw_convert<string> (_out_size.height));
- node->add_child("Eyes")->add_child_text (raw_convert<string> (static_cast<int> (_eyes)));
- node->add_child("Part")->add_child_text (raw_convert<string> (static_cast<int> (_part)));
- node->add_child("VideoRange")->add_child_text(raw_convert<string>(static_cast<int>(_video_range)));
- node->add_child("Error")->add_child_text(_error ? "1" : "0");
+ _in->add_metadata(cxml::add_child(element, "In"));
+ cxml::add_text_child(element, "InterWidth", raw_convert<string>(_inter_size.width));
+ cxml::add_text_child(element, "InterHeight", raw_convert<string>(_inter_size.height));
+ cxml::add_text_child(element, "OutWidth", raw_convert<string>(_out_size.width));
+ cxml::add_text_child(element, "OutHeight", raw_convert<string>(_out_size.height));
+ cxml::add_text_child(element, "Eyes", raw_convert<string>(static_cast<int>(_eyes)));
+ cxml::add_text_child(element, "Part", raw_convert<string>(static_cast<int>(_part)));
+ cxml::add_text_child(element, "VideoRange", raw_convert<string>(static_cast<int>(_video_range)));
+ cxml::add_text_child(element, "Error", _error ? "1" : "0");
if (_colour_conversion) {
- _colour_conversion.get().as_xml (node);
+ _colour_conversion.get().as_xml(element);
}
if (_text) {
- node->add_child ("SubtitleWidth")->add_child_text (raw_convert<string> (_text->image->size().width));
- node->add_child ("SubtitleHeight")->add_child_text (raw_convert<string> (_text->image->size().height));
- node->add_child ("SubtitleX")->add_child_text (raw_convert<string> (_text->position.x));
- node->add_child ("SubtitleY")->add_child_text (raw_convert<string> (_text->position.y));
+ cxml::add_text_child(element, "SubtitleWidth", raw_convert<string>(_text->image->size().width));
+ cxml::add_text_child(element, "SubtitleHeight", raw_convert<string>(_text->image->size().height));
+ cxml::add_text_child(element, "SubtitleX", raw_convert<string>(_text->position.x));
+ cxml::add_text_child(element, "SubtitleY", raw_convert<string>(_text->position.y));
}
}
@@ -343,7 +344,7 @@ PlayerVideo::shallow_copy () const
_colour_conversion,
_video_range,
_content,
- _video_frame,
+ _video_time,
_error
);
}
@@ -356,12 +357,12 @@ bool
PlayerVideo::reset_metadata (shared_ptr<const Film> film, dcp::Size player_video_container_size)
{
auto content = _content.lock();
- if (!content || !_video_frame) {
+ if (!content || !_video_time) {
return false;
}
_crop = content->video->actual_crop();
- _fade = content->video->fade(film, _video_frame.get());
+ _fade = content->video->fade(film, _video_time.get());
auto const size = content->video->scaled_size(film->frame_size());
if (!size) {
return false;
diff --git a/src/lib/player_video.h b/src/lib/player_video.h
index f2781c1a0..e2968749c 100644
--- a/src/lib/player_video.h
+++ b/src/lib/player_video.h
@@ -59,7 +59,7 @@ public:
boost::optional<ColourConversion> colour_conversion,
VideoRange video_range,
std::weak_ptr<Content> content,
- boost::optional<Frame> video_frame,
+ boost::optional<dcpomatic::ContentTime> video_time,
bool error
);
@@ -82,7 +82,7 @@ public:
static AVPixelFormat force (AVPixelFormat);
static AVPixelFormat keep_xyz_or_rgb (AVPixelFormat);
- void add_metadata (xmlpp::Node* node) const;
+ void add_metadata(xmlpp::Element* element) const;
void write_to_socket (std::shared_ptr<Socket> socket) const;
bool reset_metadata (std::shared_ptr<const Film> film, dcp::Size player_video_container_size);
@@ -141,8 +141,8 @@ private:
boost::optional<PositionImage> _text;
/** Content that we came from. This is so that reset_metadata() can work. */
std::weak_ptr<Content> _content;
- /** Video frame that we came from. Again, this is for reset_metadata() */
- boost::optional<Frame> _video_frame;
+ /** Video time that we came from. Again, this is for reset_metadata() */
+ boost::optional<dcpomatic::ContentTime> _video_time;
mutable boost::mutex _mutex;
mutable std::shared_ptr<Image> _image;
diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc
index 85957e106..d2af000e5 100644
--- a/src/lib/playlist.cc
+++ b/src/lib/playlist.cc
@@ -282,10 +282,10 @@ Playlist::set_from_xml (shared_ptr<const Film> film, cxml::ConstNodePtr node, in
* @param with_content_paths true to include &lt;Path&gt; nodes in &lt;Content&gt; nodes, false to omit them.
*/
void
-Playlist::as_xml (xmlpp::Node* node, bool with_content_paths)
+Playlist::as_xml(xmlpp::Element* element, bool with_content_paths)
{
for (auto i: content()) {
- i->as_xml (node->add_child ("Content"), with_content_paths);
+ i->as_xml(cxml::add_child(element, "Content"), with_content_paths);
}
}
@@ -645,16 +645,16 @@ Playlist::move_later (shared_ptr<const Film> film, shared_ptr<Content> c)
int64_t
-Playlist::required_disk_space (shared_ptr<const Film> film, int j2k_bandwidth, int audio_channels, int audio_frame_rate) const
+Playlist::required_disk_space(shared_ptr<const Film> film, int64_t video_bit_rate, int audio_channels, int audio_frame_rate) const
{
- int64_t video = uint64_t(j2k_bandwidth / 8) * length(film).seconds();
+ int64_t video = uint64_t(video_bit_rate / 8) * length(film).seconds();
int64_t audio = uint64_t(audio_channels) * audio_frame_rate * 3 * length(film).seconds();
for (auto i: content()) {
auto d = dynamic_pointer_cast<DCPContent> (i);
if (d) {
if (d->reference_video()) {
- video -= uint64_t (j2k_bandwidth / 8) * d->length_after_trim(film).seconds();
+ video -= uint64_t(video_bit_rate / 8) * d->length_after_trim(film).seconds();
}
if (d->reference_audio()) {
audio -= uint64_t(audio_channels) * audio_frame_rate * 3 * d->length_after_trim(film).seconds();
diff --git a/src/lib/playlist.h b/src/lib/playlist.h
index e2662eb45..3868c0b51 100644
--- a/src/lib/playlist.h
+++ b/src/lib/playlist.h
@@ -50,7 +50,7 @@ public:
Playlist (Playlist const&) = delete;
Playlist& operator= (Playlist const&) = delete;
- void as_xml (xmlpp::Node *, bool with_content_paths);
+ void as_xml(xmlpp::Element*, bool with_content_paths);
void set_from_xml (std::shared_ptr<const Film> film, cxml::ConstNodePtr node, int version, std::list<std::string>& notes);
void add (std::shared_ptr<const Film> film, std::shared_ptr<Content>);
@@ -65,7 +65,7 @@ public:
dcpomatic::DCPTime length (std::shared_ptr<const Film> film) const;
boost::optional<dcpomatic::DCPTime> start () const;
- int64_t required_disk_space (std::shared_ptr<const Film> film, int j2k_bandwidth, int audio_channels, int audio_frame_rate) const;
+ int64_t required_disk_space(std::shared_ptr<const Film> film, int64_t video_bit_rate, int audio_channels, int audio_frame_rate) const;
int best_video_frame_rate () const;
dcpomatic::DCPTime video_end (std::shared_ptr<const Film> film) const;
diff --git a/src/lib/raw_image_proxy.cc b/src/lib/raw_image_proxy.cc
index c606ddd99..1ad78827c 100644
--- a/src/lib/raw_image_proxy.cc
+++ b/src/lib/raw_image_proxy.cc
@@ -73,12 +73,12 @@ RawImageProxy::image (Image::Alignment alignment, optional<dcp::Size>) const
void
-RawImageProxy::add_metadata (xmlpp::Node* node) const
+RawImageProxy::add_metadata(xmlpp::Element* element) const
{
- node->add_child("Type")->add_child_text(N_("Raw"));
- node->add_child("Width")->add_child_text(raw_convert<string>(_image->size().width));
- node->add_child("Height")->add_child_text(raw_convert<string>(_image->size().height));
- node->add_child("PixelFormat")->add_child_text(raw_convert<string>(static_cast<int>(_image->pixel_format())));
+ cxml::add_text_child(element, "Type", N_("Raw"));
+ cxml::add_text_child(element, "Width", raw_convert<string>(_image->size().width));
+ cxml::add_text_child(element, "Height", raw_convert<string>(_image->size().height));
+ cxml::add_text_child(element, "PixelFormat", raw_convert<string>(static_cast<int>(_image->pixel_format())));
}
diff --git a/src/lib/raw_image_proxy.h b/src/lib/raw_image_proxy.h
index d5ae2457d..852f7c076 100644
--- a/src/lib/raw_image_proxy.h
+++ b/src/lib/raw_image_proxy.h
@@ -37,7 +37,7 @@ public:
boost::optional<dcp::Size> size = boost::optional<dcp::Size> ()
) const override;
- void add_metadata (xmlpp::Node *) const override;
+ void add_metadata(xmlpp::Element*) const override;
void write_to_socket (std::shared_ptr<Socket>) const override;
bool same (std::shared_ptr<const ImageProxy>) const override;
size_t memory_used () const override;
diff --git a/src/lib/reel_writer.cc b/src/lib/reel_writer.cc
index 1b33cae85..d8aff1162 100644
--- a/src/lib/reel_writer.cc
+++ b/src/lib/reel_writer.cc
@@ -28,11 +28,13 @@
#include "digester.h"
#include "film.h"
#include "film_util.h"
+#include "frame_info.h"
#include "image.h"
#include "image_png.h"
#include "job.h"
#include "log.h"
#include "reel_writer.h"
+#include "remembered_asset.h"
#include <dcp/atmos_asset.h>
#include <dcp/atmos_asset_writer.h>
#include <dcp/certificate_chain.h>
@@ -40,7 +42,7 @@
#include <dcp/dcp.h>
#include <dcp/filesystem.h>
#include <dcp/interop_subtitle_asset.h>
-#include <dcp/mono_picture_asset.h>
+#include <dcp/mono_j2k_picture_asset.h>
#include <dcp/raw_convert.h>
#include <dcp/reel.h>
#include <dcp/reel_atmos_asset.h>
@@ -55,7 +57,7 @@
#include <dcp/smpte_subtitle_asset.h>
#include <dcp/sound_asset.h>
#include <dcp/sound_asset_writer.h>
-#include <dcp/stereo_picture_asset.h>
+#include <dcp/stereo_j2k_picture_asset.h>
#include <dcp/subtitle_image.h>
#include "i18n.h"
@@ -81,9 +83,6 @@ using dcp::raw_convert;
using namespace dcpomatic;
-int const ReelWriter::_info_size = 48;
-
-
static dcp::MXFMetadata
mxf_metadata ()
{
@@ -108,9 +107,10 @@ mxf_metadata ()
* subtitle / closed caption files.
*/
ReelWriter::ReelWriter (
- weak_ptr<const Film> weak_film, DCPTimePeriod period, shared_ptr<Job> job, int reel_index, int reel_count, bool text_only
+ weak_ptr<const Film> weak_film, DCPTimePeriod period, shared_ptr<Job> job, int reel_index, int reel_count, bool text_only, boost::filesystem::path output_dir
)
- : WeakConstFilm (weak_film)
+ : WeakConstFilm(weak_film)
+ , _output_dir(std::move(output_dir))
, _period (period)
, _reel_index (reel_index)
, _reel_count (reel_count)
@@ -119,59 +119,87 @@ ReelWriter::ReelWriter (
, _text_only (text_only)
, _font_metrics(film()->frame_size().height)
{
- /* Create or find our picture asset in a subdirectory, named
- according to those film's parameters which affect the video
- output. We will hard-link it into the DCP later.
- */
+ _default_font = dcp::ArrayData(default_font_file());
+
+ if (text_only) {
+ return;
+ }
auto const standard = film()->interop() ? dcp::Standard::INTEROP : dcp::Standard::SMPTE;
- boost::filesystem::path const asset =
- film()->internal_video_asset_dir() / film()->internal_video_asset_filename(_period);
+ auto remembered_assets = film()->read_remembered_assets();
+ DCPOMATIC_ASSERT(film()->directory());
- _first_nonexistent_frame = check_existing_picture_asset (asset);
+ auto existing_asset_filename = find_asset(remembered_assets, *film()->directory(), period, film()->video_identifier());
+ if (existing_asset_filename) {
+ _first_nonexistent_frame = check_existing_picture_asset(*existing_asset_filename);
+ }
if (_first_nonexistent_frame < period.duration().frames_round(film()->video_frame_rate())) {
- /* We do not have a complete picture asset. If there is an
- existing asset, break any hard links to it as we are about
- to change its contents (if only by changing the IDs); see
- #1126.
- */
- if (dcp::filesystem::exists(asset) && dcp::filesystem::hard_link_count(asset) > 1) {
- if (job) {
- job->sub (_("Copying old video file"));
- copy_in_bits (asset, asset.string() + ".tmp", bind(&Job::set_progress, job.get(), _1, false));
- } else {
- dcp::filesystem::copy_file(asset, asset.string() + ".tmp");
+ /* No existing asset, or an incomplete one */
+
+ auto const rate = dcp::Fraction(film()->video_frame_rate(), 1);
+
+ auto setup = [this](shared_ptr<dcp::PictureAsset> asset) {
+ asset->set_size(film()->frame_size());
+ asset->set_metadata(mxf_metadata());
+
+ if (film()->encrypted()) {
+ asset->set_key(film()->key());
+ asset->set_context_id(film()->context_id());
}
- dcp::filesystem::remove(asset);
- dcp::filesystem::rename(asset.string() + ".tmp", asset);
- }
+ };
+ shared_ptr<dcp::PictureAsset> picture_asset;
- if (film()->three_d()) {
- _picture_asset.reset (new dcp::StereoPictureAsset(dcp::Fraction(film()->video_frame_rate(), 1), standard));
+ if (film()->video_encoding() == VideoEncoding::JPEG2000) {
+ if (film()->three_d()) {
+ _j2k_picture_asset = std::make_shared<dcp::StereoJ2KPictureAsset>(rate, standard);
+ } else {
+ _j2k_picture_asset = std::make_shared<dcp::MonoJ2KPictureAsset>(rate, standard);
+ }
+ setup(_j2k_picture_asset);
+ picture_asset = _j2k_picture_asset;
} else {
- _picture_asset.reset (new dcp::MonoPictureAsset(dcp::Fraction(film()->video_frame_rate(), 1), standard));
+ _mpeg2_picture_asset = std::make_shared<dcp::MonoMPEG2PictureAsset>(rate);
+ setup(_mpeg2_picture_asset);
+ picture_asset = _mpeg2_picture_asset;
}
- _picture_asset->set_size (film()->frame_size());
- _picture_asset->set_metadata (mxf_metadata());
-
- if (film()->encrypted()) {
- _picture_asset->set_key (film()->key());
- _picture_asset->set_context_id (film()->context_id());
+ auto new_asset_filename = _output_dir / video_asset_filename(picture_asset, _reel_index, _reel_count, _content_summary);
+ if (_first_nonexistent_frame > 0) {
+ LOG_GENERAL("Re-using partial asset %1: has frames up to %2", *existing_asset_filename, _first_nonexistent_frame);
+ dcp::filesystem::rename(*existing_asset_filename, new_asset_filename);
}
+ remembered_assets.push_back(RememberedAsset(new_asset_filename.filename(), period, film()->video_identifier()));
+ film()->write_remembered_assets(remembered_assets);
+ picture_asset->set_file(new_asset_filename);
- _picture_asset->set_file (asset);
- _picture_asset_writer = _picture_asset->start_write(asset, _first_nonexistent_frame > 0 ? dcp::PictureAsset::Behaviour::OVERWRITE_EXISTING : dcp::PictureAsset::Behaviour::MAKE_NEW);
- } else if (!text_only) {
+ dcp::Behaviour const behaviour = _first_nonexistent_frame > 0 ? dcp::Behaviour::OVERWRITE_EXISTING : dcp::Behaviour::MAKE_NEW;
+ if (_j2k_picture_asset) {
+ _j2k_picture_asset_writer = _j2k_picture_asset->start_write(new_asset_filename, behaviour);
+ } else {
+ _mpeg2_picture_asset_writer = _mpeg2_picture_asset->start_write(new_asset_filename, behaviour);
+ }
+ } else {
+ LOG_GENERAL("Re-using complete asset %1", *existing_asset_filename);
/* We already have a complete picture asset that we can just re-use */
/* XXX: what about if the encryption key changes? */
- if (film()->three_d()) {
- _picture_asset = make_shared<dcp::StereoPictureAsset>(asset);
+ auto new_asset_filename = _output_dir / existing_asset_filename->filename();
+ if (new_asset_filename != *existing_asset_filename) {
+ dcp::filesystem::copy(*existing_asset_filename, new_asset_filename);
+ remembered_assets.push_back(RememberedAsset(new_asset_filename, period, film()->video_identifier()));
+ }
+ film()->write_remembered_assets(remembered_assets);
+
+ if (film()->video_encoding() == VideoEncoding::JPEG2000) {
+ if (film()->three_d()) {
+ _j2k_picture_asset = make_shared<dcp::StereoJ2KPictureAsset>(new_asset_filename);
+ } else {
+ _j2k_picture_asset = make_shared<dcp::MonoJ2KPictureAsset>(new_asset_filename);
+ }
} else {
- _picture_asset = make_shared<dcp::MonoPictureAsset>(asset);
+ _mpeg2_picture_asset = make_shared<dcp::MonoMPEG2PictureAsset>(new_asset_filename);
}
}
@@ -210,55 +238,6 @@ ReelWriter::ReelWriter (
film()->limit_to_smpte_bv20() ? dcp::SoundAsset::MCASubDescriptors::DISABLED : dcp::SoundAsset::MCASubDescriptors::ENABLED
);
}
-
- _default_font = dcp::ArrayData(default_font_file());
-}
-
-
-/** @param frame reel-relative frame */
-void
-ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
-{
- auto handle = film()->info_file_handle(_period, false);
- handle->get().seek(frame_info_position(frame, eyes), SEEK_SET);
- handle->get().checked_write(&info.offset, sizeof(info.offset));
- handle->get().checked_write(&info.size, sizeof(info.size));
- handle->get().checked_write(info.hash.c_str(), info.hash.size());
-}
-
-
-dcp::FrameInfo
-ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
-{
- dcp::FrameInfo frame_info;
- info->get().seek(frame_info_position(frame, eyes), SEEK_SET);
- info->get().checked_read(&frame_info.offset, sizeof(frame_info.offset));
- info->get().checked_read(&frame_info.size, sizeof(frame_info.size));
-
- char hash_buffer[33];
- info->get().checked_read(hash_buffer, 32);
- hash_buffer[32] = '\0';
- frame_info.hash = hash_buffer;
-
- return frame_info;
-}
-
-
-long
-ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
-{
- switch (eyes) {
- case Eyes::BOTH:
- return frame * _info_size;
- case Eyes::LEFT:
- return frame * _info_size * 2;
- case Eyes::RIGHT:
- return frame * _info_size * 2 + _info_size;
- default:
- DCPOMATIC_ASSERT (false);
- }
-
- DCPOMATIC_ASSERT (false);
}
@@ -290,8 +269,8 @@ ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
}
/* Offset of the last dcp::FrameInfo in the info file */
- int const n = (dcp::filesystem::file_size(info_file->get().path()) / _info_size) - 1;
- LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, dcp::filesystem::file_size(info_file->get().path()), _info_size);
+ int const n = (dcp::filesystem::file_size(info_file->get().path()) / J2KFrameInfo::size_on_disk()) - 1;
+ LOG_GENERAL("The last FI is %1; info file is %2, info size %3", n, dcp::filesystem::file_size(info_file->get().path()), J2KFrameInfo::size_on_disk())
Frame first_nonexistent_frame;
if (film()->three_d()) {
@@ -321,13 +300,13 @@ ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
void
ReelWriter::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
{
- if (!_picture_asset_writer) {
+ if (!_j2k_picture_asset_writer) {
/* We're not writing any data */
return;
}
- auto fin = _picture_asset_writer->write (encoded->data(), encoded->size());
- write_frame_info (frame, eyes, fin);
+ auto fin = J2KFrameInfo(_j2k_picture_asset_writer->write(encoded->data(), encoded->size()));
+ fin.write(film()->info_file_handle(_period, false), frame, eyes);
_last_written[eyes] = encoded;
}
@@ -349,37 +328,50 @@ ReelWriter::write (shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metada
void
-ReelWriter::fake_write (int size)
+ReelWriter::write(shared_ptr<dcp::MonoMPEG2PictureFrame> image)
{
- if (!_picture_asset_writer) {
+ _mpeg2_picture_asset_writer->write(image->data(), image->size());
+}
+
+
+void
+ReelWriter::fake_write(dcp::J2KFrameInfo const& info)
+{
+ if (!_j2k_picture_asset_writer) {
/* We're not writing any data */
return;
}
- _picture_asset_writer->fake_write (size);
+ _j2k_picture_asset_writer->fake_write(info);
}
void
ReelWriter::repeat_write (Frame frame, Eyes eyes)
{
- if (!_picture_asset_writer) {
+ if (!_j2k_picture_asset_writer) {
/* We're not writing any data */
return;
}
- auto fin = _picture_asset_writer->write(_last_written[eyes]->data(), _last_written[eyes]->size());
- write_frame_info (frame, eyes, fin);
+ auto fin = J2KFrameInfo(_j2k_picture_asset_writer->write(_last_written[eyes]->data(), _last_written[eyes]->size()));
+ fin.write(film()->info_file_handle(_period, false), frame, eyes);
}
void
ReelWriter::finish (boost::filesystem::path output_dcp)
{
- if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
- /* Nothing was written to the picture asset */
- LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
- _picture_asset.reset ();
+ if (_j2k_picture_asset_writer && !_j2k_picture_asset_writer->finalize()) {
+ /* Nothing was written to the J2K picture asset */
+ LOG_GENERAL("Nothing was written to J2K asset for reel %1 of %2", _reel_index, _reel_count);
+ _j2k_picture_asset.reset();
+ }
+
+ if (_mpeg2_picture_asset_writer && !_mpeg2_picture_asset_writer->finalize()) {
+ /* Nothing was written to the MPEG2 picture asset */
+ LOG_GENERAL("Nothing was written to MPEG2 asset for reel %1 of %2", _reel_index, _reel_count);
+ _mpeg2_picture_asset.reset();
}
if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
@@ -387,42 +379,6 @@ ReelWriter::finish (boost::filesystem::path output_dcp)
_sound_asset.reset ();
}
- /* Hard-link any video asset file into the DCP */
- if (_picture_asset) {
- DCPOMATIC_ASSERT (_picture_asset->file());
- boost::filesystem::path video_from = _picture_asset->file().get();
- boost::filesystem::path video_to = output_dcp;
- video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
- /* There may be an existing "to" file if we are recreating a DCP in the same place without
- changing any video.
- */
- boost::system::error_code ec;
- dcp::filesystem::remove(video_to, ec);
-
- dcp::filesystem::create_hard_link(video_from, video_to, ec);
- if (ec) {
- LOG_WARNING("Hard-link failed (%1); copying instead", error_details(ec));
- auto job = _job.lock ();
- if (job) {
- job->sub (_("Copying video file into DCP"));
- try {
- copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
- } catch (exception& e) {
- LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
- throw FileError (e.what(), video_from);
- }
- } else {
- dcp::filesystem::copy_file(video_from, video_to, ec);
- if (ec) {
- LOG_ERROR("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), error_details(ec));
- throw FileError (ec.message(), video_from);
- }
- }
- }
-
- _picture_asset->set_file (video_to);
- }
-
/* Move the audio asset into the DCP */
if (_sound_asset) {
boost::filesystem::path audio_to = output_dcp;
@@ -544,17 +500,17 @@ ReelWriter::create_reel_picture (shared_ptr<dcp::Reel> reel, list<ReferencedReel
{
shared_ptr<dcp::ReelPictureAsset> reel_asset;
- if (_picture_asset) {
+ if (_j2k_picture_asset) {
/* We have made a picture asset of our own. Put it into the reel */
- auto mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
- if (mono) {
+ if (auto mono = dynamic_pointer_cast<dcp::MonoJ2KPictureAsset>(_j2k_picture_asset)) {
reel_asset = make_shared<dcp::ReelMonoPictureAsset>(mono, 0);
}
- auto stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
- if (stereo) {
+ if (auto stereo = dynamic_pointer_cast<dcp::StereoJ2KPictureAsset>(_j2k_picture_asset)) {
reel_asset = make_shared<dcp::ReelStereoPictureAsset>(stereo, 0);
}
+ } else if (_mpeg2_picture_asset) {
+ reel_asset = make_shared<dcp::ReelMonoPictureAsset>(_mpeg2_picture_asset, 0);
} else {
LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
/* We don't have a picture asset of our own; hopefully we have one to reference */
@@ -783,8 +739,11 @@ try
{
vector<shared_ptr<const dcp::Asset>> assets;
- if (_picture_asset) {
- assets.push_back(_picture_asset);
+ if (_j2k_picture_asset) {
+ assets.push_back(_j2k_picture_asset);
+ }
+ if (_mpeg2_picture_asset) {
+ assets.push_back(_mpeg2_picture_asset);
}
if (_sound_asset) {
assets.push_back(_sound_asset);
@@ -1012,7 +971,7 @@ ReelWriter::existing_picture_frame_ok (dcp::File& asset_file, shared_ptr<InfoFil
/* Read the data from the info file; for 3D we just check the left
frames until we find a good one.
*/
- auto const info = read_frame_info (info_file, frame, film()->three_d() ? Eyes::LEFT : Eyes::BOTH);
+ auto const info = J2KFrameInfo(info_file, frame, film()->three_d() ? Eyes::LEFT : Eyes::BOTH);
bool ok = true;
diff --git a/src/lib/reel_writer.h b/src/lib/reel_writer.h
index c9052c832..0b243b264 100644
--- a/src/lib/reel_writer.h
+++ b/src/lib/reel_writer.h
@@ -27,10 +27,14 @@
#include "player_text.h"
#include "referenced_reel_asset.h"
#include "render_text.h"
+#include "text_type.h"
#include "weak_film.h"
#include <dcp/atmos_asset_writer.h>
#include <dcp/file.h>
-#include <dcp/picture_asset_writer.h>
+#include <dcp/ffmpeg_image.h>
+#include <dcp/j2k_picture_asset_writer.h>
+#include <dcp/mono_mpeg2_picture_frame.h>
+#include <dcp/mpeg2_picture_asset_writer.h>
class AudioBuffers;
@@ -41,17 +45,18 @@ struct write_frame_info_test;
namespace dcp {
class AtmosAsset;
- class MonoPictureAsset;
- class MonoPictureAssetWriter;
- class PictureAsset;
- class PictureAssetWriter;
+ class MonoJ2KPictureAsset;
+ class MonoJ2KPictureAssetWriter;
+ class J2KPictureAsset;
+ class J2KPictureAssetWriter;
+ class MPEG2PictureAsset;
class Reel;
class ReelAsset;
class ReelPictureAsset;
class SoundAsset;
class SoundAssetWriter;
- class StereoPictureAsset;
- class StereoPictureAssetWriter;
+ class StereoJ2KPictureAsset;
+ class StereoJ2KPictureAssetWriter;
class SubtitleAsset;
}
@@ -65,15 +70,17 @@ public:
std::shared_ptr<Job> job,
int reel_index,
int reel_count,
- bool text_only
+ bool text_only,
+ boost::filesystem::path output_dir
);
void write (std::shared_ptr<const dcp::Data> encoded, Frame frame, Eyes eyes);
- void fake_write (int size);
+ void fake_write(dcp::J2KFrameInfo const& info);
void repeat_write (Frame frame, Eyes eyes);
void write (std::shared_ptr<const AudioBuffers> audio);
void write(PlayerText text, TextType type, boost::optional<DCPTextTrack> track, dcpomatic::DCPTimePeriod period, FontIdMap const& fonts, std::shared_ptr<dcpomatic::Font> chosen_interop_font);
void write (std::shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadata);
+ void write(std::shared_ptr<dcp::MonoMPEG2PictureFrame> image);
void finish (boost::filesystem::path output_dcp);
std::shared_ptr<dcp::Reel> create_reel (
@@ -94,14 +101,10 @@ public:
return _first_nonexistent_frame;
}
- dcp::FrameInfo read_frame_info (std::shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const;
-
private:
friend struct ::write_frame_info_test;
- void write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const;
- long frame_info_position (Frame frame, Eyes eyes) const;
Frame check_existing_picture_asset (boost::filesystem::path asset);
bool existing_picture_frame_ok (dcp::File& asset_file, std::shared_ptr<InfoFileHandle> info_file, Frame frame) const;
std::shared_ptr<dcp::SubtitleAsset> empty_text_asset (TextType type, boost::optional<DCPTextTrack> track, bool with_dummy) const;
@@ -119,9 +122,10 @@ private:
void create_reel_markers (std::shared_ptr<dcp::Reel> reel) const;
float convert_vertical_position(StringText const& subtitle, dcp::SubtitleStandard to) const;
+ boost::filesystem::path _output_dir;
dcpomatic::DCPTimePeriod _period;
/** the first picture frame index that does not already exist in our MXF */
- int _first_nonexistent_frame;
+ int _first_nonexistent_frame = 0;
/** the data of the last written frame, if there is one */
EnumIndexedVector<std::shared_ptr<const dcp::Data>, Eyes> _last_written;
/** index of this reel within the DCP (starting from 0) */
@@ -134,9 +138,11 @@ private:
dcp::ArrayData _default_font;
- std::shared_ptr<dcp::PictureAsset> _picture_asset;
+ std::shared_ptr<dcp::J2KPictureAsset> _j2k_picture_asset;
+ std::shared_ptr<dcp::MPEG2PictureAsset> _mpeg2_picture_asset;
/** picture asset writer, or 0 if we are not writing any picture because we already have one */
- std::shared_ptr<dcp::PictureAssetWriter> _picture_asset_writer;
+ std::shared_ptr<dcp::J2KPictureAssetWriter> _j2k_picture_asset_writer;
+ std::shared_ptr<dcp::MPEG2PictureAssetWriter> _mpeg2_picture_asset_writer;
std::shared_ptr<dcp::SoundAsset> _sound_asset;
std::shared_ptr<dcp::SoundAssetWriter> _sound_asset_writer;
std::shared_ptr<dcp::SubtitleAsset> _subtitle_asset;
@@ -145,6 +151,4 @@ private:
std::shared_ptr<dcp::AtmosAssetWriter> _atmos_asset_writer;
mutable FontMetrics _font_metrics;
-
- static int const _info_size;
};
diff --git a/src/lib/release_notes.cc b/src/lib/release_notes.cc
index d69738427..64556bb26 100644
--- a/src/lib/release_notes.cc
+++ b/src/lib/release_notes.cc
@@ -21,15 +21,46 @@
#include "config.h"
#include "release_notes.h"
+#include "variant.h"
#include "version.h"
+#include <dcp/raw_convert.h>
+#include <boost/algorithm/string.hpp>
#include "i18n.h"
using std::string;
+using std::vector;
using boost::optional;
+bool
+before(optional<string> last, string current)
+{
+ if (!last) {
+ return true;
+ }
+
+ vector<string> last_parts;
+ boost::split(last_parts, *last, boost::is_any_of("."));
+ vector<string> current_parts;
+ boost::split(current_parts, current, boost::is_any_of("."));
+
+ if (last_parts.size() != 3 || current_parts.size() != 3) {
+ /* One or other is a git version; don't bother reporting anything */
+ return false;
+ }
+
+ for (int i = 0; i < 3; ++i) {
+ if (dcp::raw_convert<int>(last_parts[i]) < dcp::raw_convert<int>(current_parts[i])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
optional<string>
find_release_notes(bool dark, optional<string> current)
{
@@ -43,19 +74,34 @@ find_release_notes(bool dark, optional<string> current)
Config::instance()->set_last_release_notes_version(*current);
+ vector<string> notes;
+ if (!last) {
+ notes.push_back(_("In this version there are changes to the way that subtitles are positioned. "
+ "Positioning should now be more correct, with respect to the standards, but you "
+ "should check any subtitles in your project to make sure that they are placed "
+ "where you want them."));
+ }
+
+ if (before(last, "2.17.19")) {
+ notes.push_back(_("The vertical offset control for some subtitles now works in the opposite direction "
+ "to how it was before. You should check any subtitles in your project to make sure "
+ "that they are placed where you want them."));
+ }
+
+ if (notes.empty()) {
+ return {};
+ }
+
string const colour = dark ? "white" : "black";
auto const span = String::compose("<span style=\"color: %1\">", colour);
- const string header = String::compose("<h1>%1DCP-o-matic %2 release notes</span></h1>", span, *current);
+ auto output = String::compose("<h1>%1%2 %3 release notes</span></h1><ul>", span, variant::dcpomatic(), *current);
- if (!last) {
- return header + span +
- _("In this version there are changes to the way that subtitles are positioned. "
- "Positioning should now be more correct, with respect to the standards, but you "
- "should check any subtitles in your project to make sure that they are placed "
- "where you want them.")
- + "</span>";
+ for (auto const& note: notes) {
+ output += string("<li>") + span + note + "</span>";
}
- return {};
+ output += "</ul>";
+
+ return output;
}
diff --git a/src/lib/remembered_asset.cc b/src/lib/remembered_asset.cc
new file mode 100644
index 000000000..20b65970c
--- /dev/null
+++ b/src/lib/remembered_asset.cc
@@ -0,0 +1,97 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcpomatic_assert.h"
+#include "remembered_asset.h"
+#include <dcp/filesystem.h>
+#include <dcp/raw_convert.h>
+#include <libcxml/cxml.h>
+LIBDCP_DISABLE_WARNINGS
+#include <libxml++/libxml++.h>
+LIBDCP_ENABLE_WARNINGS
+
+
+using std::string;
+using std::vector;
+
+
+RememberedAsset::RememberedAsset(cxml::ConstNodePtr node)
+{
+ _filename = node->string_child("Filename");
+ auto period_node = node->node_child("Period");
+ DCPOMATIC_ASSERT(period_node);
+
+ _period = {
+ dcpomatic::DCPTime(period_node->number_child<int64_t>("From")),
+ dcpomatic::DCPTime(period_node->number_child<int64_t>("To"))
+ };
+
+ _identifier = node->string_child("Identifier");
+}
+
+
+void
+RememberedAsset::as_xml(xmlpp::Element* parent) const
+{
+ cxml::add_text_child(parent, "Filename", _filename.string());
+ auto period_node = cxml::add_child(parent, "Period");
+ cxml::add_text_child(period_node, "From", dcp::raw_convert<string>(_period.from.get()));
+ cxml::add_text_child(period_node, "To", dcp::raw_convert<string>(_period.to.get()));
+ cxml::add_text_child(parent, "Identifier", _identifier);
+}
+
+
+boost::optional<boost::filesystem::path>
+find_asset(vector<RememberedAsset> const& assets, boost::filesystem::path directory, dcpomatic::DCPTimePeriod period, string identifier)
+{
+ for (auto path: dcp::filesystem::recursive_directory_iterator(directory)) {
+ auto iter = std::find_if(assets.begin(), assets.end(), [period, identifier, path](RememberedAsset const& asset) {
+ return asset.filename() == path.path().filename() && asset.period() == period && asset.identifier() == identifier;
+ });
+ if (iter != assets.end()) {
+ return path.path();
+ }
+ }
+
+ return {};
+}
+
+
+void
+clean_up_asset_directory(boost::filesystem::path directory)
+{
+ /* We could do something more advanced here (e.g. keep the last N assets) but for now
+ * let's just clean the whole thing out.
+ */
+ boost::system::error_code ec;
+ dcp::filesystem::remove_all(directory, ec);
+}
+
+
+void
+preserve_assets(boost::filesystem::path search, boost::filesystem::path assets_path)
+{
+ for (auto const& path: boost::filesystem::directory_iterator(search)) {
+ if (path.path().extension() == ".mxf") {
+ dcp::filesystem::rename(path.path(), assets_path / path.path().filename());
+ }
+ }
+}
diff --git a/src/lib/remembered_asset.h b/src/lib/remembered_asset.h
new file mode 100644
index 000000000..ec9344e24
--- /dev/null
+++ b/src/lib/remembered_asset.h
@@ -0,0 +1,79 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_REMEMBERED_ASSET_H
+#define DCPOMATIC_REMEMBERED_ASSET_H
+
+
+#include "dcpomatic_time.h"
+#include <libcxml/cxml.h>
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <libxml++/libxml++.h>
+LIBDCP_ENABLE_WARNINGS
+#include <boost/filesystem.hpp>
+
+
+class RememberedAsset
+{
+public:
+ explicit RememberedAsset(cxml::ConstNodePtr node);
+
+ RememberedAsset(boost::filesystem::path filename, dcpomatic::DCPTimePeriod period, std::string identifier)
+ : _filename(filename)
+ , _period(period)
+ , _identifier(std::move(identifier))
+ {}
+
+ void as_xml(xmlpp::Element* parent) const;
+
+ boost::filesystem::path filename() const {
+ return _filename;
+ }
+
+ dcpomatic::DCPTimePeriod period() const {
+ return _period;
+ }
+
+ std::string identifier() const {
+ return _identifier;
+ }
+
+private:
+ boost::filesystem::path _filename;
+ dcpomatic::DCPTimePeriod _period;
+ std::string _identifier;
+};
+
+
+boost::optional<boost::filesystem::path> find_asset(
+ std::vector<RememberedAsset> const& assets, boost::filesystem::path directory, dcpomatic::DCPTimePeriod period, std::string identifier
+ );
+
+
+void clean_up_asset_directory(boost::filesystem::path directory);
+
+
+void preserve_assets(boost::filesystem::path search, boost::filesystem::path assets_path);
+
+
+#endif
+
diff --git a/src/lib/remote_j2k_encoder_thread.cc b/src/lib/remote_j2k_encoder_thread.cc
new file mode 100644
index 000000000..49d80953d
--- /dev/null
+++ b/src/lib/remote_j2k_encoder_thread.cc
@@ -0,0 +1,84 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_video.h"
+#include "dcpomatic_log.h"
+#include "j2k_encoder.h"
+#include "remote_j2k_encoder_thread.h"
+#include "util.h"
+
+#include "i18n.h"
+
+
+using std::make_shared;
+using std::shared_ptr;
+
+
+RemoteJ2KEncoderThread::RemoteJ2KEncoderThread(J2KEncoder& encoder, EncodeServerDescription server)
+ : J2KSyncEncoderThread(encoder)
+ , _server(server)
+{
+
+}
+
+
+void
+RemoteJ2KEncoderThread::log_thread_start() const
+{
+ start_of_thread("RemoteJ2KEncoder");
+ LOG_TIMING("start-encoder-thread thread=%1 server=%2", thread_id(), _server.host_name());
+}
+
+
+shared_ptr<dcp::ArrayData>
+RemoteJ2KEncoderThread::encode(DCPVideo const& frame)
+{
+ shared_ptr<dcp::ArrayData> encoded;
+
+ try {
+ encoded = make_shared<dcp::ArrayData>(frame.encode_remotely(_server));
+ if (_remote_backoff > 0) {
+ LOG_GENERAL("%1 was lost, but now she is found; removing backoff", _server.host_name());
+ _remote_backoff = 0;
+ }
+ } catch (std::exception& e) {
+ LOG_ERROR(
+ N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"),
+ frame.index(), _server.host_name(), e.what(), _remote_backoff
+ );
+ } catch (...) {
+ LOG_ERROR(
+ N_("Remote encode of %1 on %2 failed; thread sleeping for %4s"),
+ frame.index(), _server.host_name(), _remote_backoff
+ );
+ }
+
+ if (!encoded) {
+ if (_remote_backoff < 60) {
+ /* back off more */
+ _remote_backoff += 10;
+ }
+ boost::this_thread::sleep(boost::posix_time::seconds(_remote_backoff));
+ }
+
+ return encoded;
+}
+
diff --git a/src/lib/remote_j2k_encoder_thread.h b/src/lib/remote_j2k_encoder_thread.h
new file mode 100644
index 000000000..f3fe7f94a
--- /dev/null
+++ b/src/lib/remote_j2k_encoder_thread.h
@@ -0,0 +1,21 @@
+#include "encode_server_description.h"
+#include "j2k_sync_encoder_thread.h"
+
+
+class RemoteJ2KEncoderThread : public J2KSyncEncoderThread
+{
+public:
+ RemoteJ2KEncoderThread(J2KEncoder& encoder, EncodeServerDescription server);
+
+ void log_thread_start() const override;
+ std::shared_ptr<dcp::ArrayData> encode(DCPVideo const& frame) override;
+
+ EncodeServerDescription server() const {
+ return _server;
+ }
+
+private:
+ EncodeServerDescription _server;
+ /** Number of seconds that we currently wait between attempts to connect to the server */
+ int _remote_backoff = 0;
+};
diff --git a/src/lib/render_text.cc b/src/lib/render_text.cc
index 870f3045d..3c3d4eed7 100644
--- a/src/lib/render_text.cc
+++ b/src/lib/render_text.cc
@@ -31,6 +31,7 @@
#include <cairomm/cairomm.h>
LIBDCP_DISABLE_WARNINGS
#include <pangomm.h>
+#include <pangommconfig.h>
LIBDCP_ENABLE_WARNINGS
#include <pango/pangocairo.h>
#include <boost/algorithm/string.hpp>
@@ -51,6 +52,15 @@ using boost::optional;
using namespace dcpomatic;
+#if CAIROMM_MAJOR_VERSION == 1 && CAIROMM_MINOR_VERSION <= 14
+#define DCPOMATIC_OLD_CAIROMM_API
+#endif
+
+#if PANGOMM_MAJOR_VERSION == 2 && PANGOMM_MINOR_VERSION <= 46
+#define DCPOMATIC_OLD_PANGOMM_API
+#endif
+
+
/** Create a Pango layout using a dummy context which we can use to calculate the size
* of the text we will render. Then we can transfer the layout over to the real context
* for the actual render.
@@ -62,11 +72,25 @@ create_layout(string font_name, string markup)
DCPOMATIC_ASSERT (c_font_map);
auto font_map = Glib::wrap (c_font_map);
auto c_context = pango_font_map_create_context (c_font_map);
+
+ cairo_font_options_t *options = cairo_font_options_create();
+ /* CAIRO_ANTIALIAS_BEST is totally broken here: see e.g.
+ * https://gitlab.freedesktop.org/cairo/cairo/-/issues/152
+ */
+ cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_GOOD);
+ cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_FULL);
+ pango_cairo_context_set_font_options(c_context, options);
+ cairo_font_options_destroy(options);
+
DCPOMATIC_ASSERT (c_context);
auto context = Glib::wrap (c_context);
auto layout = Pango::Layout::create(context);
- layout->set_alignment (Pango::ALIGN_LEFT);
+#ifdef DCPOMATIC_OLD_PANGOMM_API
+ layout->set_alignment(Pango::ALIGN_LEFT);
+#else
+ layout->set_alignment(Pango::Alignment::LEFT);
+#endif
Pango::FontDescription font (font_name);
layout->set_font_description (font);
layout->set_markup (markup);
@@ -124,7 +148,7 @@ marked_up(vector<StringText> subtitles, int target_height, float fade_factor, st
int dummy;
layout->get_pixel_size(space_width, dummy);
auto spacing = ((i.space_before() * i.size_in_pixels(target_height) - space_width) / 2) * pixels_to_1024ths_point;
- out += make_span(i, " ", "letter_spacing=\"" + dcp::raw_convert<string>(spacing) + "\"");
+ out += make_span(i, " ", "letter_spacing=\"" + dcp::raw_convert<string>(std::round(spacing)) + "\"");
}
out += make_span(i, i.text(), {});
@@ -163,11 +187,22 @@ create_surface (shared_ptr<Image> image)
DCPOMATIC_ASSERT (image->pixel_format() == AV_PIX_FMT_BGRA);
return Cairo::ImageSurface::create (
image->data()[0],
+#ifdef DCPOMATIC_OLD_CAIROMM_API
Cairo::FORMAT_ARGB32,
+#else
+ Cairo::ImageSurface::Format::ARGB32,
+#endif
image->size().width,
image->size().height,
/* Cairo ARGB32 means first byte blue, second byte green, third byte red, fourth byte alpha */
- Cairo::ImageSurface::format_stride_for_width (Cairo::FORMAT_ARGB32, image->size().width)
+ Cairo::ImageSurface::format_stride_for_width(
+#ifdef DCPOMATIC_OLD_CAIROMM_API
+ Cairo::FORMAT_ARGB32,
+#else
+ Cairo::ImageSurface::Format::ARGB32,
+#endif
+ image->size().width
+ )
);
}
@@ -394,24 +429,19 @@ render_line(vector<StringText> subtitles, dcp::Size target, DCPTime time, int fr
/* Border effect */
set_source_rgba (context, first.effect_colour(), fade_factor);
context->set_line_width (border_width);
+#ifdef DCPOMATIC_OLD_CAIROMM_API
context->set_line_join (Cairo::LINE_JOIN_ROUND);
+#else
+ context->set_line_join (Cairo::Context::LineJoin::ROUND);
+#endif
context->move_to (x_offset, y_offset);
layout.pango->add_to_cairo_context (context);
context->stroke ();
}
/* The actual subtitle */
-
- set_source_rgba (context, first.colour(), fade_factor);
-
- context->move_to (x_offset, y_offset);
- layout.pango->add_to_cairo_context (context);
- context->fill ();
-
- context->set_line_width (0.5);
context->move_to (x_offset, y_offset);
- layout.pango->add_to_cairo_context (context);
- context->stroke ();
+ layout.pango->show_in_cairo_context(context);
int const x = x_position(first.h_align(), first.h_position(), target.width, layout.size.width);
int const y = y_position(first.valign_standard, first.v_align(), first.v_position(), target.height, layout.baseline_to_bottom(border_width), layout.size.height);
diff --git a/src/lib/rgba.cc b/src/lib/rgba.cc
index 2e6a4fab6..2e0408d8a 100644
--- a/src/lib/rgba.cc
+++ b/src/lib/rgba.cc
@@ -41,12 +41,12 @@ RGBA::RGBA (cxml::ConstNodePtr node)
void
-RGBA::as_xml (xmlpp::Node* parent) const
+RGBA::as_xml(xmlpp::Element* parent) const
{
- parent->add_child("R")->add_child_text(lexical_cast<string>(int(r)));
- parent->add_child("G")->add_child_text(lexical_cast<string>(int(g)));
- parent->add_child("B")->add_child_text(lexical_cast<string>(int(b)));
- parent->add_child("A")->add_child_text(lexical_cast<string>(int(a)));
+ cxml::add_text_child(parent, "R", lexical_cast<string>(int(r)));
+ cxml::add_text_child(parent, "G", lexical_cast<string>(int(g)));
+ cxml::add_text_child(parent, "B", lexical_cast<string>(int(b)));
+ cxml::add_text_child(parent, "A", lexical_cast<string>(int(a)));
}
diff --git a/src/lib/rgba.h b/src/lib/rgba.h
index 96fed710e..a582a4ca1 100644
--- a/src/lib/rgba.h
+++ b/src/lib/rgba.h
@@ -44,7 +44,7 @@ public:
explicit RGBA (cxml::ConstNodePtr node);
- void as_xml (xmlpp::Node* parent) const;
+ void as_xml(xmlpp::Element* parent) const;
uint8_t r = 0;
uint8_t g = 0;
diff --git a/src/lib/screen.cc b/src/lib/screen.cc
index febf9085c..b77eb6b52 100644
--- a/src/lib/screen.cc
+++ b/src/lib/screen.cc
@@ -20,6 +20,7 @@
#include "cinema.h"
+#include "cinema_list.h"
#include "config.h"
#include "film.h"
#include "kdm_util.h"
@@ -39,29 +40,6 @@ using boost::optional;
using namespace dcpomatic;
-Screen::Screen (cxml::ConstNodePtr node)
- : KDMRecipient (node)
-{
- for (auto i: node->node_children ("TrustedDevice")) {
- if (boost::algorithm::starts_with(i->content(), "-----BEGIN CERTIFICATE-----")) {
- trusted_devices.push_back (TrustedDevice(dcp::Certificate(i->content())));
- } else {
- trusted_devices.push_back (TrustedDevice(i->content()));
- }
- }
-}
-
-
-void
-Screen::as_xml (xmlpp::Element* parent) const
-{
- KDMRecipient::as_xml (parent);
- for (auto i: trusted_devices) {
- parent->add_child("TrustedDevice")->add_child_text(i.as_string());
- }
-}
-
-
vector<string>
Screen::trusted_device_thumbprints () const
{
@@ -76,46 +54,40 @@ Screen::trusted_device_thumbprints () const
KDMWithMetadataPtr
kdm_for_screen (
std::function<dcp::DecryptedKDM (dcp::LocalTime, dcp::LocalTime)> make_kdm,
- shared_ptr<const dcpomatic::Screen> screen,
- boost::posix_time::ptime valid_from,
- boost::posix_time::ptime valid_to,
+ CinemaID cinema_id,
+ Cinema const& cinema,
+ Screen const& screen,
+ dcp::LocalTime valid_from,
+ dcp::LocalTime valid_to,
dcp::Formulation formulation,
bool disable_forensic_marking_picture,
optional<int> disable_forensic_marking_audio,
vector<KDMCertificatePeriod>& period_checks
)
{
- if (!screen->recipient) {
+ if (!screen.recipient) {
return {};
}
- auto cinema = screen->cinema;
- dcp::LocalTime const begin(valid_from, dcp::UTCOffset(cinema ? cinema->utc_offset_hour() : 0, cinema ? cinema->utc_offset_minute() : 0));
- dcp::LocalTime const end (valid_to, dcp::UTCOffset(cinema ? cinema->utc_offset_hour() : 0, cinema ? cinema->utc_offset_minute() : 0));
-
- period_checks.push_back(check_kdm_and_certificate_validity_periods(cinema ? cinema->name : "", screen->name, screen->recipient.get(), begin, end));
+ period_checks.push_back(check_kdm_and_certificate_validity_periods(cinema.name, screen.name, screen.recipient.get(), valid_from, valid_to));
auto signer = Config::instance()->signer_chain();
if (!signer->valid()) {
throw InvalidSignerError();
}
- auto kdm = make_kdm(begin, end).encrypt(
- signer, screen->recipient.get(), screen->trusted_device_thumbprints(), formulation, disable_forensic_marking_picture, disable_forensic_marking_audio
+ auto kdm = make_kdm(valid_from, valid_to).encrypt(
+ signer, screen.recipient.get(), screen.trusted_device_thumbprints(), formulation, disable_forensic_marking_picture, disable_forensic_marking_audio
);
dcp::NameFormat::Map name_values;
- if (cinema) {
- name_values['c'] = cinema->name;
- } else {
- name_values['c'] = "";
- }
- name_values['s'] = screen->name;
+ name_values['c'] = cinema.name;
+ name_values['s'] = screen.name;
name_values['f'] = kdm.content_title_text();
- name_values['b'] = begin.date() + " " + begin.time_of_day(true, false);
- name_values['e'] = end.date() + " " + end.time_of_day(true, false);
+ name_values['b'] = valid_from.date() + " " + valid_from.time_of_day(true, false);
+ name_values['e'] = valid_to.date() + " " + valid_to.time_of_day(true, false);
name_values['i'] = kdm.cpl_id();
- return make_shared<KDMWithMetadata>(name_values, cinema.get(), cinema ? cinema->emails : vector<string>(), kdm);
+ return make_shared<KDMWithMetadata>(name_values, cinema_id, cinema.emails, kdm);
}
diff --git a/src/lib/screen.h b/src/lib/screen.h
index 7f01cdf27..89ebc3ab4 100644
--- a/src/lib/screen.h
+++ b/src/lib/screen.h
@@ -23,12 +23,14 @@
#define DCPOMATIC_SCREEN_H
-#include "kdm_with_metadata.h"
+#include "cinema_list.h"
#include "kdm_recipient.h"
#include "kdm_util.h"
+#include "kdm_with_metadata.h"
#include "trusted_device.h"
#include <dcp/certificate.h>
#include <dcp/decrypted_kdm.h>
+#include <dcp/utc_offset.h>
#include <libcxml/cxml.h>
#include <boost/optional.hpp>
#include <string>
@@ -62,12 +64,7 @@ public:
, trusted_devices (trusted_devices_)
{}
- explicit Screen (cxml::ConstNodePtr);
-
- void as_xml (xmlpp::Element *) const override;
std::vector<std::string> trusted_device_thumbprints () const;
-
- std::shared_ptr<Cinema> cinema;
std::vector<TrustedDevice> trusted_devices;
};
@@ -77,9 +74,11 @@ public:
KDMWithMetadataPtr
kdm_for_screen (
std::function<dcp::DecryptedKDM (dcp::LocalTime, dcp::LocalTime)> make_kdm,
- std::shared_ptr<const dcpomatic::Screen> screen,
- boost::posix_time::ptime valid_from,
- boost::posix_time::ptime valid_to,
+ CinemaID cinema_id,
+ Cinema const& cinema,
+ dcpomatic::Screen const& screen,
+ dcp::LocalTime valid_from,
+ dcp::LocalTime valid_to,
dcp::Formulation formulation,
bool disable_forensic_marking_picture,
boost::optional<int> disable_forensic_marking_audio,
diff --git a/src/lib/send_problem_report_job.cc b/src/lib/send_problem_report_job.cc
index 9569aca3b..ed78112e9 100644
--- a/src/lib/send_problem_report_job.cc
+++ b/src/lib/send_problem_report_job.cc
@@ -19,15 +19,16 @@
*/
-#include "send_problem_report_job.h"
#include "compose.hpp"
-#include "film.h"
#include "cross.h"
+#include "email.h"
+#include "environment_info.h"
+#include "film.h"
#include "film.h"
#include "log.h"
+#include "send_problem_report_job.h"
+#include "variant.h"
#include "version.h"
-#include "email.h"
-#include "environment_info.h"
#include <libxml++/libxml++.h>
#include "i18n.h"
@@ -108,7 +109,7 @@ SendProblemReportJob::run ()
body += "---<8----\n";
}
- Email email(_from, {"carl@dcpomatic.com"}, "DCP-o-matic problem report", body);
+ Email email(_from, {"carl@dcpomatic.com"}, variant::insert_dcpomatic("%1 problem report"), body);
email.send("main.carlh.net", 2525, EmailProtocol::STARTTLS);
set_progress (1);
diff --git a/src/lib/server.cc b/src/lib/server.cc
index c1be5735e..359234044 100644
--- a/src/lib/server.cc
+++ b/src/lib/server.cc
@@ -41,13 +41,7 @@ Server::Server (int port, int timeout)
Server::~Server ()
{
- {
- boost::mutex::scoped_lock lm (_mutex);
- _terminate = true;
- }
-
- _acceptor.close ();
- stop ();
+ stop();
}
@@ -81,6 +75,8 @@ Server::handle_accept (shared_ptr<Socket> socket, boost::system::error_code cons
return;
}
+ _socket = socket;
+
handle (socket);
start_accept ();
}
@@ -89,5 +85,14 @@ Server::handle_accept (shared_ptr<Socket> socket, boost::system::error_code cons
void
Server::stop ()
{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _terminate = true;
+ }
+
+ _acceptor.close ();
+ if (auto s = _socket.lock()) {
+ s->close();
+ }
_io_service.stop ();
}
diff --git a/src/lib/server.h b/src/lib/server.h
index 0b1950aa7..7e42f6cbd 100644
--- a/src/lib/server.h
+++ b/src/lib/server.h
@@ -57,6 +57,7 @@ private:
boost::asio::io_service _io_service;
boost::asio::ip::tcp::acceptor _acceptor;
int _timeout;
+ std::weak_ptr<Socket> _socket;
};
diff --git a/src/lib/shuffler.cc b/src/lib/shuffler.cc
index 5a4faf4d1..a4ea0f5dc 100644
--- a/src/lib/shuffler.cc
+++ b/src/lib/shuffler.cc
@@ -40,8 +40,8 @@ int const Shuffler::_max_size = 64;
struct Comparator
{
bool operator()(Shuffler::Store const & a, Shuffler::Store const & b) {
- if (a.second.frame != b.second.frame) {
- return a.second.frame < b.second.frame;
+ if (a.second.time != b.second.time) {
+ return a.second.time < b.second.time;
}
return a.second.eyes < b.second.eyes;
}
@@ -51,7 +51,7 @@ struct Comparator
void
Shuffler::video (weak_ptr<Piece> weak_piece, ContentVideo video)
{
- LOG_DEBUG_THREE_D ("Shuffler::video frame=%1 eyes=%2 part=%3", video.frame, static_cast<int>(video.eyes), static_cast<int>(video.part));
+ LOG_DEBUG_THREE_D("Shuffler::video time=%1 eyes=%2 part=%3", to_string(video.time), static_cast<int>(video.eyes), static_cast<int>(video.part));
if (video.eyes != Eyes::LEFT && video.eyes != Eyes::RIGHT) {
/* Pass through anything that we don't care about */
@@ -79,13 +79,13 @@ Shuffler::video (weak_ptr<Piece> weak_piece, ContentVideo video)
!_store.empty() &&
_last &&
(
- (_store.front().second.frame == _last->frame && _store.front().second.eyes == Eyes::RIGHT && _last->eyes == Eyes::LEFT) ||
- (_store.front().second.frame >= (_last->frame + 1) && _store.front().second.eyes == Eyes::LEFT && _last->eyes == Eyes::RIGHT)
+ (_store.front().second.time == _last->time && _store.front().second.eyes == Eyes::RIGHT && _last->eyes == Eyes::LEFT) ||
+ (_store.front().second.time > _last->time && _store.front().second.eyes == Eyes::LEFT && _last->eyes == Eyes::RIGHT)
);
if (!store_front_in_sequence) {
- string const store = _store.empty() ? "store empty" : String::compose("store front frame=%1 eyes=%2", _store.front().second.frame, static_cast<int>(_store.front().second.eyes));
- string const last = _last ? String::compose("last frame=%1 eyes=%2", _last->frame, static_cast<int>(_last->eyes)) : "no last";
+ string const store = _store.empty() ? "store empty" : String::compose("store front time=%1 eyes=%2", to_string(_store.front().second.time), static_cast<int>(_store.front().second.eyes));
+ string const last = _last ? String::compose("last time=%1 eyes=%2", to_string(_last->time), static_cast<int>(_last->eyes)) : "no last";
LOG_DEBUG_THREE_D("Shuffler not in sequence: %1 %2", store, last);
}
@@ -98,10 +98,10 @@ Shuffler::video (weak_ptr<Piece> weak_piece, ContentVideo video)
}
if (_store.size() > _max_size) {
- LOG_WARNING ("Shuffler is full after receiving frame %1; 3D sync may be incorrect.", video.frame);
+ LOG_WARNING("Shuffler is full after receiving frame at %1; 3D sync may be incorrect.", to_string(video.time));
}
- LOG_DEBUG_THREE_D("Shuffler emits frame=%1 eyes=%2 store=%3", _store.front().second.frame, static_cast<int>(_store.front().second.eyes), _store.size());
+ LOG_DEBUG_THREE_D("Shuffler emits time=%1 eyes=%2 store=%3", to_string(_store.front().second.time), static_cast<int>(_store.front().second.eyes), _store.size());
Video (_store.front().first, _store.front().second);
_last = _store.front().second;
_store.pop_front ();
diff --git a/src/lib/spl.cc b/src/lib/spl.cc
index f49f11a7d..e87465cdc 100644
--- a/src/lib/spl.cc
+++ b/src/lib/spl.cc
@@ -62,10 +62,10 @@ SPL::write (boost::filesystem::path path) const
{
xmlpp::Document doc;
auto root = doc.create_root_node ("SPL");
- root->add_child("Id")->add_child_text (_id);
- root->add_child("Name")->add_child_text (_name);
+ cxml::add_text_child(root, "Id", _id);
+ cxml::add_text_child(root, "Name", _name);
for (auto i: _spl) {
- i.as_xml (root->add_child("Entry"));
+ i.as_xml(cxml::add_child(root, "Entry"));
}
doc.write_to_file_formatted (path.string());
}
diff --git a/src/lib/spl_entry.cc b/src/lib/spl_entry.cc
index f0b377a56..a77b0b7d5 100644
--- a/src/lib/spl_entry.cc
+++ b/src/lib/spl_entry.cc
@@ -53,5 +53,5 @@ SPLEntry::SPLEntry (shared_ptr<Content> c)
void
SPLEntry::as_xml (xmlpp::Element* e)
{
- e->add_child("Digest")->add_child_text(digest);
+ cxml::add_text_child(e, "Digest", digest);
}
diff --git a/src/lib/sqlite_statement.cc b/src/lib/sqlite_statement.cc
new file mode 100644
index 000000000..b3ec1fb81
--- /dev/null
+++ b/src/lib/sqlite_statement.cc
@@ -0,0 +1,111 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "exceptions.h"
+#include "sqlite_statement.h"
+
+
+using std::function;
+using std::string;
+
+
+SQLiteStatement::SQLiteStatement(sqlite3* db, string const& statement)
+ : _db(db)
+{
+#ifdef DCPOMATIC_HAVE_SQLITE3_PREPARE_V3
+ auto rc = sqlite3_prepare_v3(_db, statement.c_str(), -1, 0, &_stmt, nullptr);
+#else
+ auto rc = sqlite3_prepare_v2(_db, statement.c_str(), -1, &_stmt, nullptr);
+#endif
+ if (rc != SQLITE_OK) {
+ throw SQLError(_db, rc, statement);
+ }
+}
+
+
+SQLiteStatement::~SQLiteStatement()
+{
+ sqlite3_finalize(_stmt);
+}
+
+
+void
+SQLiteStatement::bind_text(int index, string const& value)
+{
+ auto rc = sqlite3_bind_text(_stmt, index, value.c_str(), -1, SQLITE_TRANSIENT);
+ if (rc != SQLITE_OK) {
+ throw SQLError(_db, rc);
+ }
+}
+
+
+void
+SQLiteStatement::bind_int64(int index, int64_t value)
+{
+ auto rc = sqlite3_bind_int64(_stmt, index, value);
+ if (rc != SQLITE_OK) {
+ throw SQLError(_db, rc);
+ }
+}
+
+
+void
+SQLiteStatement::execute(function<void(SQLiteStatement&)> row, function<void()> busy)
+{
+ while (true) {
+ auto const rc = sqlite3_step(_stmt);
+ switch (rc) {
+ case SQLITE_BUSY:
+ busy();
+ break;
+ case SQLITE_DONE:
+ return;
+ case SQLITE_ROW:
+ row(*this);
+ break;
+ case SQLITE_ERROR:
+ case SQLITE_MISUSE:
+ throw SQLError(_db, sqlite3_errmsg(_db));
+ }
+ }
+}
+
+
+int
+SQLiteStatement::data_count()
+{
+ return sqlite3_data_count(_stmt);
+}
+
+
+int64_t
+SQLiteStatement::column_int64(int index)
+{
+ return sqlite3_column_int64(_stmt, index);
+}
+
+
+string
+SQLiteStatement::column_text(int index)
+{
+ return reinterpret_cast<const char*>(sqlite3_column_text(_stmt, index));
+}
+
diff --git a/src/lib/sqlite_statement.h b/src/lib/sqlite_statement.h
new file mode 100644
index 000000000..3c2246efb
--- /dev/null
+++ b/src/lib/sqlite_statement.h
@@ -0,0 +1,50 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <sqlite3.h>
+#include <functional>
+#include <string>
+
+
+class SQLiteStatement
+{
+public:
+ SQLiteStatement(sqlite3* db, std::string const& statement);
+ ~SQLiteStatement();
+
+ SQLiteStatement(SQLiteStatement const&) = delete;
+ SQLiteStatement& operator=(SQLiteStatement const&) = delete;
+
+ void bind_text(int index, std::string const& value);
+ void bind_int64(int index, int64_t value);
+
+ int64_t column_int64(int index);
+ std::string column_text(int index);
+
+ void execute(std::function<void(SQLiteStatement&)> row = std::function<void(SQLiteStatement& statement)>(), std::function<void()> busy = std::function<void()>());
+
+ int data_count();
+
+private:
+ sqlite3* _db;
+ sqlite3_stmt* _stmt;
+};
+
diff --git a/src/lib/sqlite_table.cc b/src/lib/sqlite_table.cc
new file mode 100644
index 000000000..f00fa6b74
--- /dev/null
+++ b/src/lib/sqlite_table.cc
@@ -0,0 +1,79 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcpomatic_assert.h"
+#include "compose.hpp"
+#include "sqlite_table.h"
+#include "util.h"
+
+
+using std::string;
+using std::vector;
+
+
+void
+SQLiteTable::add_column(string const& name, string const& type)
+{
+ _columns.push_back(name);
+ _types.push_back(type);
+}
+
+
+string
+SQLiteTable::create() const
+{
+ DCPOMATIC_ASSERT(!_columns.empty());
+ DCPOMATIC_ASSERT(_columns.size() == _types.size());
+ vector<string> columns(_columns.size());
+ for (size_t i = 0; i < _columns.size(); ++i) {
+ columns[i] = _columns[i] + " " + _types[i];
+ }
+ return String::compose("CREATE TABLE IF NOT EXISTS %1 (id INTEGER PRIMARY KEY, %2)", _name, join_strings(columns, ", "));
+}
+
+
+string
+SQLiteTable::insert() const
+{
+ DCPOMATIC_ASSERT(!_columns.empty());
+ vector<string> placeholders(_columns.size(), "?");
+ return String::compose("INSERT INTO %1 (%2) VALUES (%3)", _name, join_strings(_columns, ", "), join_strings(placeholders, ", "));
+}
+
+
+string
+SQLiteTable::update(string const& condition) const
+{
+ DCPOMATIC_ASSERT(!_columns.empty());
+ vector<string> placeholders(_columns.size());
+ for (size_t i = 0; i < _columns.size(); ++i) {
+ placeholders[i] = _columns[i] + "=?";
+ }
+
+ return String::compose("UPDATE %1 SET %2 %3", _name, join_strings(placeholders, ", "), condition);
+}
+
+
+string
+SQLiteTable::select(string const& condition) const
+{
+ return String::compose("SELECT id,%1 FROM %2 %3", join_strings(_columns, ","), _name, condition);
+}
diff --git a/src/lib/sqlite_table.h b/src/lib/sqlite_table.h
new file mode 100644
index 000000000..43c9491ed
--- /dev/null
+++ b/src/lib/sqlite_table.h
@@ -0,0 +1,54 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_SQLITE_TABLE_H
+#define DCPOMATIC_SQLITE_TABLE_H
+
+#include <string>
+#include <vector>
+
+
+class SQLiteTable
+{
+public:
+ SQLiteTable(std::string name)
+ : _name(std::move(name))
+ {}
+
+ SQLiteTable(SQLiteTable const&) = default;
+ SQLiteTable(SQLiteTable&&) = default;
+
+ void add_column(std::string const& name, std::string const& type);
+
+ std::string create() const;
+ std::string insert() const;
+ std::string update(std::string const& condition) const;
+ std::string select(std::string const& condition) const;
+
+private:
+ std::string _name;
+ std::vector<std::string> _columns;
+ std::vector<std::string> _types;
+};
+
+
+#endif
+
diff --git a/src/lib/sqlite_transaction.cc b/src/lib/sqlite_transaction.cc
new file mode 100644
index 000000000..239d85020
--- /dev/null
+++ b/src/lib/sqlite_transaction.cc
@@ -0,0 +1,50 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "sqlite_statement.h"
+#include "sqlite_transaction.h"
+
+
+SQLiteTransaction::SQLiteTransaction(sqlite3* db)
+ : _db(db)
+{
+ SQLiteStatement statement(_db, "BEGIN TRANSACTION");
+ statement.execute();
+}
+
+
+SQLiteTransaction::~SQLiteTransaction()
+{
+ if (_rollback) {
+ SQLiteStatement rollback(_db, "ROLLBACK");
+ rollback.execute();
+ }
+}
+
+
+void
+SQLiteTransaction::commit()
+{
+ SQLiteStatement commit(_db, "COMMIT");
+ commit.execute();
+ _rollback = false;
+}
+
diff --git a/src/lib/sqlite_transaction.h b/src/lib/sqlite_transaction.h
new file mode 100644
index 000000000..0f6319243
--- /dev/null
+++ b/src/lib/sqlite_transaction.h
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <sqlite3.h>
+
+
+class SQLiteTransaction
+{
+public:
+ SQLiteTransaction(sqlite3* db);
+ ~SQLiteTransaction();
+
+ SQLiteTransaction(SQLiteTransaction const&) = delete;
+ SQLiteTransaction& operator=(SQLiteTransaction const&) = delete;
+
+ void commit();
+
+private:
+ sqlite3* _db;
+ bool _rollback = true;
+};
+
diff --git a/src/lib/state.cc b/src/lib/state.cc
index a8230385d..7a3232ee9 100644
--- a/src/lib/state.cc
+++ b/src/lib/state.cc
@@ -35,7 +35,7 @@ boost::optional<boost::filesystem::path> State::override_path;
/* List of config versions to look for in descending order of preference;
* i.e. look at the first one, and if that doesn't exist, try the second, etc.
*/
-static std::vector<std::string> config_versions = { "2.16" };
+static std::vector<std::string> config_versions = { "2.18", "2.16" };
static
diff --git a/src/lib/string_text_file_content.cc b/src/lib/string_text_file_content.cc
index d8c195be7..c6639936c 100644
--- a/src/lib/string_text_file_content.cc
+++ b/src/lib/string_text_file_content.cc
@@ -122,16 +122,16 @@ StringTextFileContent::technical_summary () const
void
-StringTextFileContent::as_xml (xmlpp::Node* node, bool with_paths) const
+StringTextFileContent::as_xml(xmlpp::Element* element, bool with_paths) const
{
- node->add_child("Type")->add_child_text("TextSubtitle");
- Content::as_xml (node, with_paths);
+ cxml::add_text_child(element, "Type", "TextSubtitle");
+ Content::as_xml(element, with_paths);
if (only_text()) {
- only_text()->as_xml(node);
+ only_text()->as_xml(element);
}
- node->add_child("Length")->add_child_text(raw_convert<string>(_length.get ()));
+ cxml::add_text_child(element, "Length", raw_convert<string>(_length.get()));
}
diff --git a/src/lib/string_text_file_content.h b/src/lib/string_text_file_content.h
index 30f543381..8a5d9946a 100644
--- a/src/lib/string_text_file_content.h
+++ b/src/lib/string_text_file_content.h
@@ -45,7 +45,7 @@ public:
void examine (std::shared_ptr<const Film> film, std::shared_ptr<Job>) override;
std::string summary () const override;
std::string technical_summary () const override;
- void as_xml (xmlpp::Node *, bool with_paths) const override;
+ void as_xml(xmlpp::Element*, bool with_paths) const override;
dcpomatic::DCPTime full_length (std::shared_ptr<const Film> film) const override;
dcpomatic::DCPTime approximate_length () const override;
std::string identifier () const override;
diff --git a/src/lib/subtitle_analysis.cc b/src/lib/subtitle_analysis.cc
index ff1969a3a..2968416c6 100644
--- a/src/lib/subtitle_analysis.cc
+++ b/src/lib/subtitle_analysis.cc
@@ -70,18 +70,18 @@ SubtitleAnalysis::write (boost::filesystem::path path) const
auto doc = make_shared<xmlpp::Document>();
xmlpp::Element* root = doc->create_root_node ("SubtitleAnalysis");
- root->add_child("Version")->add_child_text (raw_convert<string>(_current_state_version));
+ cxml::add_text_child(root, "Version", raw_convert<string>(_current_state_version));
if (_bounding_box) {
- auto bounding_box = root->add_child("BoundingBox");
- bounding_box->add_child("X")->add_child_text(raw_convert<string>(_bounding_box->x));
- bounding_box->add_child("Y")->add_child_text(raw_convert<string>(_bounding_box->y));
- bounding_box->add_child("Width")->add_child_text(raw_convert<string>(_bounding_box->width));
- bounding_box->add_child("Height")->add_child_text(raw_convert<string>(_bounding_box->height));
+ auto bounding_box = cxml::add_child(root, "BoundingBox");
+ cxml::add_text_child(bounding_box, "X", raw_convert<string>(_bounding_box->x));
+ cxml::add_text_child(bounding_box, "Y", raw_convert<string>(_bounding_box->y));
+ cxml::add_text_child(bounding_box, "Width", raw_convert<string>(_bounding_box->width));
+ cxml::add_text_child(bounding_box, "Height", raw_convert<string>(_bounding_box->height));
}
- root->add_child("AnalysisXOffset")->add_child_text(raw_convert<string>(_analysis_x_offset));
- root->add_child("AnalysisYOffset")->add_child_text(raw_convert<string>(_analysis_y_offset));
+ cxml::add_text_child(root, "AnalysisXOffset", raw_convert<string>(_analysis_x_offset));
+ cxml::add_text_child(root, "AnalysisYOffset", raw_convert<string>(_analysis_y_offset));
doc->write_to_file_formatted (path.string());
}
diff --git a/src/lib/subtitle_encoder.cc b/src/lib/subtitle_film_encoder.cc
index 8b1d9a15b..93ccc177b 100644
--- a/src/lib/subtitle_encoder.cc
+++ b/src/lib/subtitle_film_encoder.cc
@@ -23,7 +23,7 @@
#include "film.h"
#include "job.h"
#include "player.h"
-#include "subtitle_encoder.h"
+#include "subtitle_film_encoder.h"
#include <dcp/filesystem.h>
#include <dcp/interop_subtitle_asset.h>
#include <dcp/raw_convert.h>
@@ -51,8 +51,8 @@ using dcp::raw_convert;
* @param initial_name Hint that may be used to create filenames, if @ref output is a directory.
* @param include_font true to refer to and export any font file (for Interop; ignored for SMPTE).
*/
-SubtitleEncoder::SubtitleEncoder (shared_ptr<const Film> film, shared_ptr<Job> job, boost::filesystem::path output, string initial_name, bool split_reels, bool include_font)
- : Encoder (film, job)
+SubtitleFilmEncoder::SubtitleFilmEncoder(shared_ptr<const Film> film, shared_ptr<Job> job, boost::filesystem::path output, string initial_name, bool split_reels, bool include_font)
+ : FilmEncoder(film, job)
, _split_reels (split_reels)
, _include_font (include_font)
, _reel_index (0)
@@ -61,7 +61,7 @@ SubtitleEncoder::SubtitleEncoder (shared_ptr<const Film> film, shared_ptr<Job> j
_player.set_play_referenced();
_player.set_ignore_video();
_player.set_ignore_audio();
- _player.Text.connect(boost::bind(&SubtitleEncoder::text, this, _1, _2, _3, _4));
+ _player.Text.connect(boost::bind(&SubtitleFilmEncoder::text, this, _1, _2, _3, _4));
string const extension = film->interop() ? ".xml" : ".mxf";
@@ -91,7 +91,7 @@ SubtitleEncoder::SubtitleEncoder (shared_ptr<const Film> film, shared_ptr<Job> j
void
-SubtitleEncoder::go ()
+SubtitleFilmEncoder::go()
{
{
shared_ptr<Job> job = _job.lock ();
@@ -133,7 +133,7 @@ SubtitleEncoder::go ()
void
-SubtitleEncoder::text (PlayerText subs, TextType type, optional<DCPTextTrack> track, dcpomatic::DCPTimePeriod period)
+SubtitleFilmEncoder::text(PlayerText subs, TextType type, optional<DCPTextTrack> track, dcpomatic::DCPTimePeriod period)
{
if (type != TextType::OPEN_SUBTITLE) {
return;
@@ -193,7 +193,7 @@ SubtitleEncoder::text (PlayerText subs, TextType type, optional<DCPTextTrack> tr
Frame
-SubtitleEncoder::frames_done () const
+SubtitleFilmEncoder::frames_done() const
{
if (!_last) {
return 0;
diff --git a/src/lib/subtitle_encoder.h b/src/lib/subtitle_film_encoder.h
index 0815b1fff..54231794d 100644
--- a/src/lib/subtitle_encoder.h
+++ b/src/lib/subtitle_film_encoder.h
@@ -21,7 +21,7 @@
#include "dcp_text_track.h"
#include "dcpomatic_time.h"
-#include "encoder.h"
+#include "film_encoder.h"
#include "player_text.h"
@@ -33,13 +33,13 @@ namespace dcp {
class Film;
-/** @class SubtitleEncoder.
+/** @class SubtitleFilmEncoder.
* @brief An `encoder' which extracts a film's subtitles to DCP XML format.
*/
-class SubtitleEncoder : public Encoder
+class SubtitleFilmEncoder : public FilmEncoder
{
public:
- SubtitleEncoder (std::shared_ptr<const Film> film, std::shared_ptr<Job> job, boost::filesystem::path output, std::string initial_name, bool split_reels, bool include_font);
+ SubtitleFilmEncoder(std::shared_ptr<const Film> film, std::shared_ptr<Job> job, boost::filesystem::path output, std::string initial_name, bool split_reels, bool include_font);
void go () override;
diff --git a/src/lib/text_content.cc b/src/lib/text_content.cc
index 92a35b822..03336f15d 100644
--- a/src/lib/text_content.cc
+++ b/src/lib/text_content.cc
@@ -19,11 +19,12 @@
*/
-#include "text_content.h"
-#include "util.h"
+#include "content.h"
#include "exceptions.h"
#include "font.h"
-#include "content.h"
+#include "text_content.h"
+#include "util.h"
+#include "variant.h"
#include <dcp/raw_convert.h>
#include <libcxml/cxml.h>
#include <libxml++/libxml++.h>
@@ -32,13 +33,13 @@
#include "i18n.h"
-using std::string;
-using std::vector;
using std::cout;
+using std::dynamic_pointer_cast;
using std::list;
-using std::shared_ptr;
using std::make_shared;
-using std::dynamic_pointer_cast;
+using std::shared_ptr;
+using std::string;
+using std::vector;
using boost::optional;
using dcp::raw_convert;
using namespace dcpomatic;
@@ -235,17 +236,20 @@ TextContent::TextContent (Content* parent, cxml::ConstNodePtr node, int version,
if (lang) {
try {
_language = dcp::LanguageTag(lang->content());
- auto add = lang->optional_bool_attribute("Additional");
- _language_is_additional = add && *add;
+ auto additional = lang->optional_bool_attribute("Additional");
+ if (!additional) {
+ additional = lang->optional_bool_attribute("additional");
+ }
+ _language_is_additional = additional.get_value_or(false);
} catch (dcp::LanguageTagError&) {
/* The language tag can be empty or invalid if it was loaded from a
* 2.14.x metadata file; we'll just ignore it in that case.
*/
if (version <= 37) {
if (!lang->content().empty()) {
- notes.push_back (String::compose(
+ notes.push_back(String::compose(
_("A subtitle or closed caption file in this project is marked with the language '%1', "
- "which DCP-o-matic does not recognise. The file's language has been cleared."), lang->content()));
+ "which %2 does not recognise. The file's language has been cleared."), lang->content(), variant::dcpomatic()));
}
} else {
throw;
@@ -353,63 +357,63 @@ TextContent::TextContent (Content* parent, vector<shared_ptr<Content>> c)
/** _mutex must not be held on entry */
void
-TextContent::as_xml (xmlpp::Node* root) const
+TextContent::as_xml(xmlpp::Element* root) const
{
boost::mutex::scoped_lock lm (_mutex);
- auto text = root->add_child ("Text");
+ auto text = cxml::add_child(root, "Text");
- text->add_child("Use")->add_child_text (_use ? "1" : "0");
- text->add_child("Burn")->add_child_text (_burn ? "1" : "0");
- text->add_child("XOffset")->add_child_text (raw_convert<string> (_x_offset));
- text->add_child("YOffset")->add_child_text (raw_convert<string> (_y_offset));
- text->add_child("XScale")->add_child_text (raw_convert<string> (_x_scale));
- text->add_child("YScale")->add_child_text (raw_convert<string> (_y_scale));
+ cxml::add_text_child(text, "Use", _use ? "1" : "0");
+ cxml::add_text_child(text, "Burn", _burn ? "1" : "0");
+ cxml::add_text_child(text, "XOffset", raw_convert<string>(_x_offset));
+ cxml::add_text_child(text, "YOffset", raw_convert<string>(_y_offset));
+ cxml::add_text_child(text, "XScale", raw_convert<string>(_x_scale));
+ cxml::add_text_child(text, "YScale", raw_convert<string>(_y_scale));
if (_colour) {
- text->add_child("Red")->add_child_text (raw_convert<string> (_colour->r));
- text->add_child("Green")->add_child_text (raw_convert<string> (_colour->g));
- text->add_child("Blue")->add_child_text (raw_convert<string> (_colour->b));
+ cxml::add_text_child(text, "Red", raw_convert<string>(_colour->r));
+ cxml::add_text_child(text, "Green", raw_convert<string>(_colour->g));
+ cxml::add_text_child(text, "Blue", raw_convert<string>(_colour->b));
}
if (_effect) {
switch (*_effect) {
case dcp::Effect::NONE:
- text->add_child("Effect")->add_child_text("none");
+ cxml::add_text_child(text, "Effect", "none");
break;
case dcp::Effect::BORDER:
- text->add_child("Effect")->add_child_text("outline");
+ cxml::add_text_child(text, "Effect", "outline");
break;
case dcp::Effect::SHADOW:
- text->add_child("Effect")->add_child_text("shadow");
+ cxml::add_text_child(text, "Effect", "shadow");
break;
}
}
if (_effect_colour) {
- text->add_child("EffectRed")->add_child_text (raw_convert<string> (_effect_colour->r));
- text->add_child("EffectGreen")->add_child_text (raw_convert<string> (_effect_colour->g));
- text->add_child("EffectBlue")->add_child_text (raw_convert<string> (_effect_colour->b));
+ cxml::add_text_child(text, "EffectRed", raw_convert<string>(_effect_colour->r));
+ cxml::add_text_child(text, "EffectGreen", raw_convert<string>(_effect_colour->g));
+ cxml::add_text_child(text, "EffectBlue", raw_convert<string>(_effect_colour->b));
}
- text->add_child("LineSpacing")->add_child_text (raw_convert<string> (_line_spacing));
+ cxml::add_text_child(text, "LineSpacing", raw_convert<string>(_line_spacing));
if (_fade_in) {
- text->add_child("FadeIn")->add_child_text (raw_convert<string> (_fade_in->get()));
+ cxml::add_text_child(text, "FadeIn", raw_convert<string>(_fade_in->get()));
}
if (_fade_out) {
- text->add_child("FadeOut")->add_child_text (raw_convert<string> (_fade_out->get()));
+ cxml::add_text_child(text, "FadeOut", raw_convert<string>(_fade_out->get()));
}
- text->add_child("OutlineWidth")->add_child_text (raw_convert<string> (_outline_width));
+ cxml::add_text_child(text, "OutlineWidth", raw_convert<string>(_outline_width));
for (auto i: _fonts) {
- i->as_xml (text->add_child("Font"));
+ i->as_xml(cxml::add_child(text, "Font"));
}
- text->add_child("Type")->add_child_text (text_type_to_string(_type));
- text->add_child("OriginalType")->add_child_text (text_type_to_string(_original_type));
+ cxml::add_text_child(text, "Type", text_type_to_string(_type));
+ cxml::add_text_child(text, "OriginalType", text_type_to_string(_original_type));
if (_dcp_track) {
- _dcp_track->as_xml(text->add_child("DCPTrack"));
+ _dcp_track->as_xml(cxml::add_child(text, "DCPTrack"));
}
if (_language) {
- auto lang = text->add_child("Language");
+ auto lang = cxml::add_child(text, "Language");
lang->add_child_text (_language->to_string());
- lang->set_attribute ("Additional", _language_is_additional ? "1" : "0");
+ lang->set_attribute("additional", _language_is_additional ? "1" : "0");
}
}
diff --git a/src/lib/text_content.h b/src/lib/text_content.h
index 4d4bdc507..58014917b 100644
--- a/src/lib/text_content.h
+++ b/src/lib/text_content.h
@@ -72,7 +72,7 @@ public:
TextContent (Content* parent, std::vector<std::shared_ptr<Content>>);
TextContent (Content* parent, cxml::ConstNodePtr, int version, std::list<std::string>& notes);
- void as_xml (xmlpp::Node *) const;
+ void as_xml(xmlpp::Element*) const;
std::string identifier () const;
void take_settings_from (std::shared_ptr<const TextContent> c);
@@ -224,6 +224,10 @@ private:
double _x_scale;
/** y scale factor to apply to subtitles */
double _y_scale;
+ /** Fonts used by this content. They are added during content examination, then
+ * saved/loaded from metadata.xml. This is so the user can change the font in
+ * used by a piece of content.
+ */
std::list<std::shared_ptr<dcpomatic::Font>> _fonts;
boost::optional<dcp::Colour> _colour;
boost::optional<dcp::Effect> _effect;
diff --git a/src/lib/text_decoder.cc b/src/lib/text_decoder.cc
index 75fa33605..945ffaa03 100644
--- a/src/lib/text_decoder.cc
+++ b/src/lib/text_decoder.cc
@@ -20,6 +20,7 @@
#include "compose.hpp"
+#include "dcpomatic_log.h"
#include "log.h"
#include "text_content.h"
#include "text_decoder.h"
@@ -306,6 +307,10 @@ TextDecoder::emit_plain_start (ContentTime from, sub::Subtitle const & sub_subti
);
auto font = content()->get_font(block.font.get_value_or(""));
+ if (!font) {
+ LOG_WARNING("Could not find font '%1' in content; falling back to default", block.font.get_value_or(""));
+ font = std::make_shared<dcpomatic::Font>(block.font.get_value_or(""), default_font_file());
+ }
DCPOMATIC_ASSERT(font);
auto string_text = StringText(
diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc
index 12b9a2aa3..f1373fb4a 100644
--- a/src/lib/transcode_job.cc
+++ b/src/lib/transcode_job.cc
@@ -28,15 +28,16 @@
#include "compose.hpp"
#include "content.h"
#include "config.h"
-#include "dcp_encoder.h"
+#include "dcp_film_encoder.h"
#include "dcpomatic_log.h"
-#include "encoder.h"
#include "examine_content_job.h"
#include "film.h"
+#include "film_encoder.h"
#include "job_manager.h"
#include "log.h"
#include "transcode_job.h"
#include "upload_job.h"
+#include "variant.h"
#include <iomanip>
#include <iostream>
@@ -83,7 +84,7 @@ TranscodeJob::json_name () const
void
-TranscodeJob::set_encoder (shared_ptr<Encoder> e)
+TranscodeJob::set_encoder(shared_ptr<FilmEncoder> e)
{
_encoder = e;
}
@@ -110,7 +111,10 @@ TranscodeJob::run ()
return;
case ChangedBehaviour::STOP:
set_progress (1);
- set_error (_("Files have changed since they were added to the project."), _("Open the project in DCP-o-matic, check the settings, then save it before trying again."));
+ set_error(
+ _("Files have changed since they were added to the project."),
+ variant::insert_dcpomatic(_("Open the project in %1, check the settings, then save it before trying again."))
+ );
set_state (FINISHED_ERROR);
return;
default:
@@ -129,7 +133,7 @@ TranscodeJob::run ()
LOG_GENERAL(N_("Transcode job completed successfully: %1 fps"), dcp::locale_convert<string>(frames_per_second(), 2, true));
- if (dynamic_pointer_cast<DCPEncoder>(_encoder)) {
+ if (variant::count_created_dcps() && dynamic_pointer_cast<DCPFilmEncoder>(_encoder)) {
try {
Analytics::instance()->successful_dcp_encode();
} catch (FileError& e) {
@@ -148,6 +152,20 @@ TranscodeJob::run ()
}
+void
+TranscodeJob::pause()
+{
+ _encoder->pause();
+}
+
+
+void TranscodeJob::resume()
+{
+ _encoder->resume();
+ Job::resume();
+}
+
+
string
TranscodeJob::status () const
{
diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h
index b05b20a16..720d7f99b 100644
--- a/src/lib/transcode_job.h
+++ b/src/lib/transcode_job.h
@@ -35,7 +35,9 @@
#undef IGNORE
-class Encoder;
+class FilmEncoder;
+
+struct frames_not_lost_when_threads_disappear;
/** @class TranscodeJob
@@ -56,20 +58,24 @@ public:
std::string name () const override;
std::string json_name () const override;
void run () override;
+ void pause() override;
+ void resume() override;
std::string status () const override;
bool enable_notify () const override {
return true;
}
- void set_encoder (std::shared_ptr<Encoder> t);
+ void set_encoder(std::shared_ptr<FilmEncoder> encoder);
private:
+ friend struct ::frames_not_lost_when_threads_disappear;
+
virtual void post_transcode () {}
float frames_per_second() const;
int remaining_time () const override;
- std::shared_ptr<Encoder> _encoder;
+ std::shared_ptr<FilmEncoder> _encoder;
ChangedBehaviour _changed;
};
diff --git a/src/lib/types.h b/src/lib/types.h
index 36059401e..b4fcea959 100644
--- a/src/lib/types.h
+++ b/src/lib/types.h
@@ -43,8 +43,9 @@ class FFmpegContent;
*
* 64 - first version used
* 65 - v2.16.0 - checksums added to communication
+ * 66 - v2.17.x - J2KBandwidth -> VideoBitRate in metadata
*/
-#define SERVER_LINK_VERSION (64+1)
+#define SERVER_LINK_VERSION (64+2)
/** A film of F seconds at f FPS will be Ff frames;
Consider some delta FPS d, so if we run the same
@@ -107,7 +108,8 @@ enum class ReelType
{
SINGLE,
BY_VIDEO_CONTENT,
- BY_LENGTH
+ BY_LENGTH,
+ CUSTOM
};
diff --git a/src/lib/unzipper.cc b/src/lib/unzipper.cc
index f0170e7e0..8d468f24f 100644
--- a/src/lib/unzipper.cc
+++ b/src/lib/unzipper.cc
@@ -56,8 +56,20 @@ Unzipper::~Unzipper()
}
+bool
+Unzipper::contains(string const& filename) const
+{
+ auto file = zip_fopen(_zip, filename.c_str(), 0);
+ bool exists = file != nullptr;
+ if (file) {
+ zip_fclose(file);
+ }
+ return exists;
+}
+
+
string
-Unzipper::get(string const& filename)
+Unzipper::get(string const& filename) const
{
auto file = zip_fopen(_zip, filename.c_str(), 0);
if (!file) {
diff --git a/src/lib/unzipper.h b/src/lib/unzipper.h
index 7cab6e5f4..76b2fe45a 100644
--- a/src/lib/unzipper.h
+++ b/src/lib/unzipper.h
@@ -33,7 +33,8 @@ public:
Unzipper(Unzipper const&) = delete;
Unzipper& operator=(Unzipper const&) = delete;
- std::string get(std::string const& filename);
+ std::string get(std::string const& filename) const;
+ bool contains(std::string const& filename) const;
private:
struct zip* _zip;
diff --git a/src/lib/util.cc b/src/lib/util.cc
index fdc647fba..172b8d763 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -49,12 +49,14 @@
#include "string_text.h"
#include "text_decoder.h"
#include "util.h"
+#include "variant.h"
#include "video_content.h"
#include <dcp/atmos_asset.h>
#include <dcp/decrypted_kdm.h>
#include <dcp/file.h>
#include <dcp/filesystem.h>
#include <dcp/locale_convert.h>
+#include <dcp/mpeg2_picture_asset.h>
#include <dcp/picture_asset.h>
#include <dcp/raw_convert.h>
#include <dcp/scope_guard.h>
@@ -87,10 +89,11 @@ LIBDCP_ENABLE_WARNINGS
#include <dbghelp.h>
#endif
#include <signal.h>
+#include <climits>
#include <iomanip>
#include <iostream>
#include <fstream>
-#include <climits>
+#include <numeric>
#include <stdexcept>
#ifdef DCPOMATIC_POSIX
#include <execinfo.h>
@@ -102,6 +105,7 @@ LIBDCP_ENABLE_WARNINGS
using std::bad_alloc;
using std::cout;
+using std::dynamic_pointer_cast;
using std::endl;
using std::istream;
using std::list;
@@ -118,9 +122,6 @@ using std::vector;
using std::wstring;
using boost::thread;
using boost::optional;
-using boost::lexical_cast;
-using boost::bad_lexical_cast;
-using boost::scoped_array;
using dcp::Size;
using dcp::raw_convert;
using dcp::locale_convert;
@@ -243,6 +244,7 @@ addr2line (void const * const addr)
{
char addr2line_cmd[512] = { 0 };
sprintf (addr2line_cmd, "addr2line -f -p -e %.256s %p > %s", program_name.c_str(), addr, backtrace_file.string().c_str());
+ std::cout << addr2line_cmd << "\n";
return system(addr2line_cmd);
}
@@ -428,6 +430,11 @@ dcpomatic_setup ()
SetUnhandledExceptionFilter(exception_handler);
#endif
+#ifdef DCPOMATIC_GROK
+ /* This makes grok support work with CUDA 12.2 */
+ setenv("CUDA_MODULE_LOADING", "EAGER", 1);
+#endif
+
#ifdef DCPOMATIC_HAVE_AVREGISTER
LIBDCP_DISABLE_WARNINGS
av_register_all ();
@@ -509,7 +516,7 @@ mo_path ()
boost::filesystem::path
mo_path ()
{
- return "DCP-o-matic 2.app/Contents/Resources";
+ return variant::dcpomatic_app() + "/Contents/Resources";
}
#endif
@@ -741,9 +748,10 @@ asset_filename (shared_ptr<dcp::Asset> asset, string type, int reel_index, int r
string
-video_asset_filename (shared_ptr<dcp::PictureAsset> asset, int reel_index, int reel_count, optional<string> summary)
+video_asset_filename(shared_ptr<dcp::PictureAsset> asset, int reel_index, int reel_count, optional<string> summary)
{
- return asset_filename(asset, "j2c", reel_index, reel_count, summary, ".mxf");
+ string type = dynamic_pointer_cast<dcp::MPEG2PictureAsset>(asset) ? "mpeg2" : "j2c";
+ return asset_filename(asset, type, reel_index, reel_count, summary, ".mxf");
}
@@ -879,16 +887,6 @@ remap (shared_ptr<const AudioBuffers> input, int output_channels, AudioMapping m
return mapped;
}
-Eyes
-increment_eyes (Eyes e)
-{
- if (e == Eyes::LEFT) {
- return Eyes::RIGHT;
- }
-
- return Eyes::LEFT;
-}
-
size_t
utf8_strlen (string s)
@@ -1024,9 +1022,9 @@ decrypt_kdm_with_helpful_error (dcp::EncryptedKDM kdm)
}
}
if (!on_chain) {
- throw KDMError (_("This KDM was not made for DCP-o-matic's decryption certificate."), e.what());
+ throw KDMError(variant::insert_dcpomatic(_("This KDM was not made for %1's decryption certificate.")), e.what());
} else if (kdm_subject_name != dc->leaf().subject()) {
- throw KDMError (_("This KDM was made for DCP-o-matic but not for its leaf certificate."), e.what());
+ throw KDMError(variant::insert_dcpomatic(_("This KDM was made for %1 but not for its leaf certificate.")), e.what());
} else {
throw;
}
@@ -1126,6 +1124,34 @@ word_wrap(string input, int columns)
}
+#ifdef DCPOMATIC_GROK
+void
+setup_grok_library_path()
+{
+ static std::string old_path;
+ if (old_path.empty()) {
+ auto const old = getenv("LD_LIRARY_PATH");
+ if (old) {
+ old_path = old;
+ }
+ }
+ auto const grok = Config::instance()->grok();
+ if (!grok || grok->binary_location.empty()) {
+ setenv("LD_LIRARY_PATH", old_path.c_str(), 1);
+ return;
+ }
+
+ std::string new_path = old_path;
+ if (!new_path.empty()) {
+ new_path += ":";
+ }
+ new_path += grok->binary_location.string();
+
+ setenv("LD_LIBRARY_PATH", new_path.c_str(), 1);
+}
+#endif
+
+
string
screen_names_to_string(vector<string> names)
{
@@ -1153,3 +1179,23 @@ screen_names_to_string(vector<string> names)
return result.substr(0, result.length() - 2);
}
+
+string
+report_problem()
+{
+ return String::compose(_("Please report this problem by using Help -> Report a problem or via email to %1"), variant::report_problem_email());
+}
+
+
+string
+join_strings(vector<string> const& in, string const& separator)
+{
+ if (in.empty()) {
+ return {};
+ }
+
+ return std::accumulate(std::next(in.begin()), in.end(), in.front(), [separator](string a, string b) {
+ return a + separator + b;
+ });
+}
+
diff --git a/src/lib/util.h b/src/lib/util.h
index fd6fd6164..7c40c5ce8 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -32,6 +32,7 @@
#include "dcpomatic_time.h"
#include "pixel_quanta.h"
#include "types.h"
+#include <libcxml/cxml.h>
#include <dcp/atmos_asset.h>
#include <dcp/decrypted_kdm.h>
#include <dcp/util.h>
@@ -97,6 +98,24 @@ extern std::string error_details(boost::system::error_code ec);
extern bool contains_assetmap(boost::filesystem::path dir);
extern std::string word_wrap(std::string input, int columns);
extern void capture_ffmpeg_logs();
+#ifdef DCPOMATIC_GROK
+extern void setup_grok_library_path();
+#endif
+extern std::string join_strings(std::vector<std::string> const& in, std::string const& separator = " ");
+
+
+template <class T>
+T
+number_attribute(cxml::ConstNodePtr node, std::string name1, std::string name2)
+{
+ auto value = node->optional_number_attribute<T>(name1);
+ if (!value) {
+ value = node->number_attribute<T>(name2);
+ }
+ return *value;
+}
+
extern std::string screen_names_to_string(std::vector<std::string> names);
+extern std::string report_problem();
#endif
diff --git a/src/lib/variant.cc b/src/lib/variant.cc
new file mode 100644
index 000000000..a9dc56307
--- /dev/null
+++ b/src/lib/variant.cc
@@ -0,0 +1,179 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "variant.h"
+
+
+static char const* _dcpomatic = "DCP-o-matic";
+static char const* _dcpomatic_player = "DCP-o-matic Player";
+static char const* _dcpomatic_kdm_creator = "DCP-o-matic KDM Creator";
+static char const* _dcpomatic_verifier = "DCP-o-matic Verifier";
+static char const* _dcpomatic_app = "DCP-o-matic 2.app";
+static char const* _dcpomatic_player_app = "DCP-o-matic 2 Player.app";
+static char const* _dcpomatic_disk_writer = "DCP-o-matic Disk Writer";
+static char const* _dcpomatic_editor = "DCP-o-matic Editor";
+static char const* _dcpomatic_encode_server = "DCP-o-matic Encode Server";
+static char const* _dcpomatic_batch_converter_app = "DCP-o-matic 2 Batch Converter.app";
+static char const* _dcpomatic_playlist_editor = "DCP-o-matic Playlist Editor";
+static char const* _dcpomatic_combiner = "DCP-o-matic Combiner";
+static char const* _dcpomatic_batch_converter = "DCP-o-matic Batch Converter";
+
+static char const* _report_problem_email = "carl@dcpomatic.com";
+
+static bool const _show_tagline = true;
+static bool const _show_dcpomatic_website = true;
+static bool const _show_credits = true;
+static bool const _show_report_a_problem = true;
+static bool const _count_created_dcps = true;
+
+
+std::string
+variant::dcpomatic()
+{
+ return _dcpomatic;
+}
+
+std::string
+variant::dcpomatic_batch_converter()
+{
+ return _dcpomatic_batch_converter;
+}
+
+std::string
+variant::dcpomatic_combiner()
+{
+ return _dcpomatic_combiner;
+}
+
+std::string
+variant::dcpomatic_disk_writer()
+{
+ return _dcpomatic_disk_writer;
+}
+
+std::string
+variant::dcpomatic_editor()
+{
+ return _dcpomatic_editor;
+}
+
+std::string
+variant::dcpomatic_encode_server()
+{
+ return _dcpomatic_encode_server;
+}
+
+std::string
+variant::dcpomatic_kdm_creator()
+{
+ return _dcpomatic_kdm_creator;
+}
+
+std::string
+variant::dcpomatic_player()
+{
+ return _dcpomatic_player;
+}
+
+std::string
+variant::dcpomatic_playlist_editor()
+{
+ return _dcpomatic_playlist_editor;
+}
+
+std::string
+variant::dcpomatic_verifier()
+{
+ return _dcpomatic_verifier;
+}
+
+std::string
+variant::insert_dcpomatic(std::string const& s)
+{
+ return String::compose(s, _dcpomatic);
+}
+
+std::string
+variant::insert_dcpomatic_encode_server(std::string const& s)
+{
+ return String::compose(s, _dcpomatic_encode_server);
+}
+
+std::string
+variant::insert_dcpomatic_kdm_creator(std::string const& s)
+{
+ return String::compose(s, _dcpomatic_kdm_creator);
+}
+
+std::string
+variant::dcpomatic_app()
+{
+ return _dcpomatic_app;
+}
+
+std::string
+variant::dcpomatic_batch_converter_app()
+{
+ return _dcpomatic_batch_converter_app;
+}
+
+std::string
+variant::dcpomatic_player_app()
+{
+ return _dcpomatic_player_app;
+}
+
+bool
+variant::show_tagline()
+{
+ return _show_tagline;
+}
+
+bool
+variant::show_dcpomatic_website()
+{
+ return _show_dcpomatic_website;
+}
+
+bool
+variant::show_credits()
+{
+ return _show_credits;
+}
+
+bool
+variant::show_report_a_problem()
+{
+ return _show_report_a_problem;
+}
+
+bool
+variant::count_created_dcps()
+{
+ return _count_created_dcps;
+}
+
+std::string
+variant::report_problem_email()
+{
+ return _report_problem_email;
+}
+
diff --git a/src/lib/variant.h b/src/lib/variant.h
new file mode 100644
index 000000000..9a42f3eca
--- /dev/null
+++ b/src/lib/variant.h
@@ -0,0 +1,56 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "compose.hpp"
+
+
+namespace variant
+{
+
+std::string dcpomatic();
+std::string dcpomatic_batch_converter();
+std::string dcpomatic_combiner();
+std::string dcpomatic_disk_writer();
+std::string dcpomatic_editor();
+std::string dcpomatic_encode_server();
+std::string dcpomatic_kdm_creator();
+std::string dcpomatic_player();
+std::string dcpomatic_playlist_editor();
+std::string dcpomatic_verifier();
+
+std::string insert_dcpomatic(std::string const& s);
+std::string insert_dcpomatic_encode_server(std::string const& s);
+std::string insert_dcpomatic_kdm_creator(std::string const& s);
+
+std::string dcpomatic_app();
+std::string dcpomatic_batch_converter_app();
+std::string dcpomatic_player_app();
+
+std::string report_problem_email();
+
+bool show_tagline();
+bool show_dcpomatic_website();
+bool show_credits();
+bool show_report_a_problem();
+bool count_created_dcps();
+
+}
+
diff --git a/src/lib/verify_dcp_job.cc b/src/lib/verify_dcp_job.cc
index 5e50ec51d..668b1eab4 100644
--- a/src/lib/verify_dcp_job.cc
+++ b/src/lib/verify_dcp_job.cc
@@ -86,7 +86,7 @@ VerifyDCPJob::run ()
}
}
- _notes = dcp::verify(
+ _result = dcp::verify(
_directories,
decrypted_kdms,
bind(&VerifyDCPJob::update_stage, this, _1, _2),
@@ -96,7 +96,7 @@ VerifyDCPJob::run ()
);
bool failed = false;
- for (auto i: _notes) {
+ for (auto i: _result.notes) {
if (i.type() == dcp::VerificationNote::Type::ERROR) {
failed = true;
}
diff --git a/src/lib/verify_dcp_job.h b/src/lib/verify_dcp_job.h
index 61a347507..d7ac21d41 100644
--- a/src/lib/verify_dcp_job.h
+++ b/src/lib/verify_dcp_job.h
@@ -36,8 +36,8 @@ public:
std::string json_name () const override;
void run () override;
- std::vector<dcp::VerificationNote> notes () const {
- return _notes;
+ dcp::VerificationResult const& result() const {
+ return _result;
}
private:
@@ -45,5 +45,5 @@ private:
std::vector<boost::filesystem::path> _directories;
std::vector<boost::filesystem::path> _kdms;
- std::vector<dcp::VerificationNote> _notes;
+ dcp::VerificationResult _result;
};
diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc
index 6c027ff11..10dd5ff1e 100644
--- a/src/lib/video_content.cc
+++ b/src/lib/video_content.cc
@@ -273,37 +273,37 @@ VideoContent::VideoContent (Content* parent, vector<shared_ptr<Content> > c)
void
-VideoContent::as_xml (xmlpp::Node* node) const
+VideoContent::as_xml(xmlpp::Element* element) const
{
boost::mutex::scoped_lock lm (_mutex);
- node->add_child("Use")->add_child_text (_use ? "1" : "0");
- node->add_child("VideoLength")->add_child_text (raw_convert<string> (_length));
+ cxml::add_text_child(element, "Use", _use ? "1" : "0");
+ cxml::add_text_child(element, "VideoLength", raw_convert<string>(_length));
if (_size) {
- node->add_child("VideoWidth")->add_child_text(raw_convert<string>(_size->width));
- node->add_child("VideoHeight")->add_child_text(raw_convert<string>(_size->height));
+ cxml::add_text_child(element, "VideoWidth", raw_convert<string>(_size->width));
+ cxml::add_text_child(element, "VideoHeight", raw_convert<string>(_size->height));
}
- node->add_child("VideoFrameType")->add_child_text (video_frame_type_to_string (_frame_type));
+ cxml::add_text_child(element, "VideoFrameType", video_frame_type_to_string(_frame_type));
if (_sample_aspect_ratio) {
- node->add_child("SampleAspectRatio")->add_child_text (raw_convert<string> (_sample_aspect_ratio.get ()));
+ cxml::add_text_child(element, "SampleAspectRatio", raw_convert<string> (_sample_aspect_ratio.get ()));
}
- _crop.as_xml (node);
+ _crop.as_xml(element);
if (_custom_ratio) {
- node->add_child("CustomRatio")->add_child_text(raw_convert<string>(*_custom_ratio));
+ cxml::add_text_child(element, "CustomRatio", raw_convert<string>(*_custom_ratio));
}
if (_custom_size) {
- node->add_child("CustomWidth")->add_child_text(raw_convert<string>(_custom_size->width));
- node->add_child("CustomHeight")->add_child_text(raw_convert<string>(_custom_size->height));
+ cxml::add_text_child(element, "CustomWidth", raw_convert<string>(_custom_size->width));
+ cxml::add_text_child(element, "CustomHeight", raw_convert<string>(_custom_size->height));
}
if (_colour_conversion) {
- _colour_conversion.get().as_xml (node->add_child("ColourConversion"));
+ _colour_conversion.get().as_xml(cxml::add_child(element, "ColourConversion"));
}
- node->add_child("YUV")->add_child_text (_yuv ? "1" : "0");
- node->add_child("FadeIn")->add_child_text (raw_convert<string> (_fade_in));
- node->add_child("FadeOut")->add_child_text (raw_convert<string> (_fade_out));
- node->add_child("Range")->add_child_text(_range == VideoRange::FULL ? "full" : "video");
- _pixel_quanta.as_xml(node->add_child("PixelQuanta"));
+ cxml::add_text_child(element, "YUV", _yuv ? "1" : "0");
+ cxml::add_text_child(element, "FadeIn", raw_convert<string>(_fade_in));
+ cxml::add_text_child(element, "FadeOut", raw_convert<string>(_fade_out));
+ cxml::add_text_child(element, "Range", _range == VideoRange::FULL ? "full" : "video");
+ _pixel_quanta.as_xml(cxml::add_child(element, "PixelQuanta"));
if (_burnt_subtitle_language) {
- node->add_child("BurntSubtitleLanguage")->add_child_text(_burnt_subtitle_language->to_string());
+ cxml::add_text_child(element, "BurntSubtitleLanguage", _burnt_subtitle_language->to_string());
}
}
@@ -424,27 +424,29 @@ VideoContent::size_after_crop () const
}
-/** @param f Frame index within the whole (untrimmed) content.
+/** @param time Time within the whole (untrimmed) content.
* @return Fade factor (between 0 and 1) or unset if there is no fade.
*/
optional<double>
-VideoContent::fade (shared_ptr<const Film> film, Frame f) const
+VideoContent::fade(shared_ptr<const Film> film, ContentTime time) const
{
- DCPOMATIC_ASSERT (f >= 0);
+ DCPOMATIC_ASSERT(time.get() >= 0);
double const vfr = _parent->active_video_frame_rate(film);
- auto const ts = _parent->trim_start().frames_round(vfr);
- if ((f - ts) < fade_in()) {
- return double (f - ts) / fade_in();
+ auto const ts = _parent->trim_start();
+ auto const fade_in_time = ContentTime::from_frames(fade_in(), vfr);
+ if ((time - ts) < fade_in_time) {
+ return double(ContentTime(time - ts).get()) / fade_in_time.get();
}
- auto fade_out_start = length() - _parent->trim_end().frames_round(vfr) - fade_out();
- if (f >= fade_out_start) {
- return 1 - double (f - fade_out_start) / fade_out();
+ auto const fade_out_time = ContentTime::from_frames(fade_out(), vfr);
+ auto fade_out_start = ContentTime::from_frames(length(), vfr) - _parent->trim_end() - fade_out_time;
+ if (time >= fade_out_start) {
+ return 1 - double(ContentTime(time - fade_out_start).get()) / fade_out_time.get();
}
- return optional<double> ();
+ return {};
}
string
diff --git a/src/lib/video_content.h b/src/lib/video_content.h
index 495d000e1..eb106cc75 100644
--- a/src/lib/video_content.h
+++ b/src/lib/video_content.h
@@ -66,7 +66,7 @@ public:
VideoContent (Content* parent, cxml::ConstNodePtr node, int version, VideoRange video_range_hint);
VideoContent (Content* parent, std::vector<std::shared_ptr<Content>>);
- void as_xml (xmlpp::Node *) const;
+ void as_xml(xmlpp::Element*) const;
std::string technical_summary () const;
std::string identifier () const;
void take_settings_from (std::shared_ptr<const VideoContent> c);
@@ -208,7 +208,7 @@ public:
boost::optional<dcp::Size> size_after_crop() const;
boost::optional<dcp::Size> scaled_size(dcp::Size container_size);
- boost::optional<double> fade (std::shared_ptr<const Film> film, Frame) const;
+ boost::optional<double> fade(std::shared_ptr<const Film> film, dcpomatic::ContentTime time) const;
std::string processing_description (std::shared_ptr<const Film> film);
diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc
index cf21f885a..c628fddd9 100644
--- a/src/lib/video_decoder.cc
+++ b/src/lib/video_decoder.cc
@@ -20,7 +20,6 @@
#include "compose.hpp"
-#include "film.h"
#include "frame_interval_checker.h"
#include "image.h"
#include "j2k_image_proxy.h"
@@ -47,17 +46,9 @@ VideoDecoder::VideoDecoder (Decoder* parent, shared_ptr<const Content> c)
}
-/** Called by decoder classes when they have a video frame ready.
- * @param frame Frame index within the content; this does not take into account 3D
- * so for 3D_ALTERNATE this value goes:
- * 0: frame 0 left
- * 1: frame 0 right
- * 2: frame 1 left
- * 3: frame 1 right
- * and so on.
- */
+/** Called by decoder classes when they have a video frame ready */
void
-VideoDecoder::emit (shared_ptr<const Film> film, shared_ptr<const ImageProxy> image, Frame decoder_frame)
+VideoDecoder::emit(shared_ptr<const Film> film, shared_ptr<const ImageProxy> image, ContentTime time)
{
if (ignore ()) {
return;
@@ -66,14 +57,12 @@ VideoDecoder::emit (shared_ptr<const Film> film, shared_ptr<const ImageProxy> im
auto const afr = _content->active_video_frame_rate(film);
auto const vft = _content->video->frame_type();
- auto frame_time = ContentTime::from_frames (decoder_frame, afr);
-
/* Do some heuristics to try and spot the case where the user sets content to 3D
* when it is not. We try to tell this by looking at the differences in time between
* the first few frames. Real 3D content should have two frames for each timestamp.
*/
if (_frame_interval_checker) {
- _frame_interval_checker->feed (frame_time, afr);
+ _frame_interval_checker->feed(time, afr);
if (_frame_interval_checker->guess() == FrameIntervalChecker::PROBABLY_NOT_3D && vft == VideoFrameType::THREE_D) {
boost::throw_exception (
DecodeError(
@@ -91,94 +80,54 @@ VideoDecoder::emit (shared_ptr<const Film> film, shared_ptr<const ImageProxy> im
}
}
- Frame frame;
- Eyes eyes = Eyes::BOTH;
- if (!_position) {
- /* This is the first data we have received since initialisation or seek. Set
- the position based on the frame that was given. After this first time
- we just count frames, since (as with audio) it seems that ContentTimes
- are unreliable from FFmpegDecoder. They are much better than audio times
- but still we get the occasional one which is duplicated. In this case
- ffmpeg seems to carry on regardless, processing the video frame as normal.
- If we drop the frame with the duplicated timestamp we obviously lose sync.
- */
-
- if (vft == VideoFrameType::THREE_D_ALTERNATE) {
- frame = decoder_frame / 2;
- eyes = (decoder_frame % 2) ? Eyes::RIGHT : Eyes::LEFT;
- } else {
- frame = decoder_frame;
- if (vft == VideoFrameType::THREE_D) {
- auto j2k = dynamic_pointer_cast<const J2KImageProxy>(image);
- /* At the moment only DCP decoders producers VideoFrameType::THREE_D, so only the J2KImageProxy
- * knows which eye it is.
- */
- if (j2k && j2k->eye()) {
- eyes = j2k->eye().get() == dcp::Eye::LEFT ? Eyes::LEFT : Eyes::RIGHT;
- }
- }
- }
-
- _position = ContentTime::from_frames (frame, afr);
- } else {
- if (vft == VideoFrameType::THREE_D) {
- auto j2k = dynamic_pointer_cast<const J2KImageProxy>(image);
- if (j2k && j2k->eye()) {
- if (j2k->eye() == dcp::Eye::LEFT) {
- frame = _position->frames_round(afr) + 1;
- eyes = Eyes::LEFT;
- } else {
- frame = _position->frames_round(afr);
- eyes = Eyes::RIGHT;
- }
- } else {
- /* This should not happen; see above */
- frame = _position->frames_round(afr) + 1;
- }
- } else if (vft == VideoFrameType::THREE_D_ALTERNATE) {
- DCPOMATIC_ASSERT (_last_emitted_eyes);
- if (_last_emitted_eyes.get() == Eyes::RIGHT) {
- frame = _position->frames_round(afr) + 1;
- eyes = Eyes::LEFT;
- } else {
- frame = _position->frames_round(afr);
- eyes = Eyes::RIGHT;
- }
- } else {
- frame = _position->frames_round(afr) + 1;
- }
- }
-
switch (vft) {
case VideoFrameType::TWO_D:
+ Data(ContentVideo(image, time, Eyes::BOTH, Part::WHOLE));
+ break;
case VideoFrameType::THREE_D:
- Data (ContentVideo (image, frame, eyes, Part::WHOLE));
+ {
+ auto eyes = Eyes::LEFT;
+ auto j2k = dynamic_pointer_cast<const J2KImageProxy>(image);
+ if (j2k && j2k->eye()) {
+ eyes = *j2k->eye() == dcp::Eye::LEFT ? Eyes::LEFT : Eyes::RIGHT;
+ }
+
+ Data(ContentVideo(image, time, eyes, Part::WHOLE));
break;
+ }
case VideoFrameType::THREE_D_ALTERNATE:
{
- Data (ContentVideo (image, frame, eyes, Part::WHOLE));
+ Eyes eyes;
+ if (_last_emitted_eyes) {
+ eyes = _last_emitted_eyes.get() == Eyes::LEFT ? Eyes::RIGHT : Eyes::LEFT;
+ } else {
+ /* We don't know what eye this frame is, so just guess */
+ auto frame = time.frames_round(_content->video_frame_rate().get_value_or(24));
+ eyes = (frame % 2) ? Eyes::RIGHT : Eyes::LEFT;
+ }
+ Data(ContentVideo(image, time, eyes, Part::WHOLE));
_last_emitted_eyes = eyes;
break;
}
case VideoFrameType::THREE_D_LEFT_RIGHT:
- Data (ContentVideo (image, frame, Eyes::LEFT, Part::LEFT_HALF));
- Data (ContentVideo (image, frame, Eyes::RIGHT, Part::RIGHT_HALF));
+ Data(ContentVideo(image, time, Eyes::LEFT, Part::LEFT_HALF));
+ Data(ContentVideo(image, time, Eyes::RIGHT, Part::RIGHT_HALF));
break;
case VideoFrameType::THREE_D_TOP_BOTTOM:
- Data (ContentVideo (image, frame, Eyes::LEFT, Part::TOP_HALF));
- Data (ContentVideo (image, frame, Eyes::RIGHT, Part::BOTTOM_HALF));
+ Data(ContentVideo(image, time, Eyes::LEFT, Part::TOP_HALF));
+ Data(ContentVideo(image, time, Eyes::RIGHT, Part::BOTTOM_HALF));
break;
case VideoFrameType::THREE_D_LEFT:
- Data (ContentVideo (image, frame, Eyes::LEFT, Part::WHOLE));
+ Data(ContentVideo(image, time, Eyes::LEFT, Part::WHOLE));
break;
case VideoFrameType::THREE_D_RIGHT:
- Data (ContentVideo (image, frame, Eyes::RIGHT, Part::WHOLE));
+ Data(ContentVideo(image, time, Eyes::RIGHT, Part::WHOLE));
break;
default:
DCPOMATIC_ASSERT (false);
}
- _position = ContentTime::from_frames (frame, afr);
+ _position = time;
}
diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h
index f6ee17425..b609404c4 100644
--- a/src/lib/video_decoder.h
+++ b/src/lib/video_decoder.h
@@ -60,7 +60,7 @@ public:
}
void seek () override;
- void emit (std::shared_ptr<const Film> film, std::shared_ptr<const ImageProxy>, Frame frame);
+ void emit(std::shared_ptr<const Film> film, std::shared_ptr<const ImageProxy>, dcpomatic::ContentTime time);
boost::signals2::signal<void (ContentVideo)> Data;
diff --git a/src/lib/video_encoder.cc b/src/lib/video_encoder.cc
new file mode 100644
index 000000000..590dc7471
--- /dev/null
+++ b/src/lib/video_encoder.cc
@@ -0,0 +1,64 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "video_encoder.h"
+
+
+using std::shared_ptr;
+using boost::optional;
+
+
+VideoEncoder::VideoEncoder(shared_ptr<const Film> film, Writer& writer)
+ : _film(film)
+ , _writer(writer)
+ , _history(200)
+{
+
+}
+
+
+void
+VideoEncoder::encode(shared_ptr<PlayerVideo>, dcpomatic::DCPTime time)
+{
+ _last_player_video_time = time;
+}
+
+
+/** @return Number of video frames that have been queued for encoding */
+int
+VideoEncoder::video_frames_enqueued() const
+{
+ if (!_last_player_video_time) {
+ return 0;
+ }
+
+ return _last_player_video_time->frames_floor(_film->video_frame_rate());
+}
+
+
+/** @return an estimate of the current number of frames we are encoding per second,
+ * if known.
+ */
+optional<float>
+VideoEncoder::current_encoding_rate() const
+{
+ return _history.rate();
+}
diff --git a/src/lib/video_encoder.h b/src/lib/video_encoder.h
new file mode 100644
index 000000000..8cc33ef8a
--- /dev/null
+++ b/src/lib/video_encoder.h
@@ -0,0 +1,69 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_VIDEO_ENCODER_H
+#define DCPOMATIC_VIDEO_ENCODER_H
+
+
+#include "dcpomatic_time.h"
+#include "event_history.h"
+#include "film.h"
+#include "player_video.h"
+
+
+class Writer;
+
+
+class VideoEncoder
+{
+public:
+ VideoEncoder(std::shared_ptr<const Film> film, Writer& writer);
+ virtual ~VideoEncoder() {}
+
+ VideoEncoder(VideoEncoder const&) = delete;
+ VideoEncoder& operator=(VideoEncoder const&) = delete;
+
+ /** Called to indicate that a processing run is about to begin */
+ virtual void begin() {}
+
+ /** Called to pass a bit of video to be encoded as the next DCP frame */
+ virtual void encode(std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time);
+
+ virtual void pause() = 0;
+ virtual void resume() = 0;
+
+ /** Called when a processing run has finished */
+ virtual void end() = 0;
+
+ int video_frames_enqueued() const;
+ boost::optional<float> current_encoding_rate() const;
+
+protected:
+ /** Film that we are encoding */
+ std::shared_ptr<const Film> _film;
+ Writer& _writer;
+ EventHistory _history;
+ boost::optional<dcpomatic::DCPTime> _last_player_video_time;
+};
+
+
+#endif
+
diff --git a/src/lib/video_encoding.cc b/src/lib/video_encoding.cc
new file mode 100644
index 000000000..de68c6ae9
--- /dev/null
+++ b/src/lib/video_encoding.cc
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcpomatic_assert.h"
+#include "video_encoding.h"
+
+
+using std::string;
+
+
+string
+video_encoding_to_string(VideoEncoding encoding)
+{
+ switch (encoding) {
+ case VideoEncoding::JPEG2000:
+ return "jpeg2000";
+ case VideoEncoding::MPEG2:
+ return "mpeg2";
+ case VideoEncoding::COUNT:
+ DCPOMATIC_ASSERT(false);
+ }
+
+ DCPOMATIC_ASSERT(false);
+}
+
+
+VideoEncoding
+video_encoding_from_string(string const& encoding)
+{
+ if (encoding == "jpeg2000") {
+ return VideoEncoding::JPEG2000;
+ }
+
+ if (encoding == "mpeg2") {
+ return VideoEncoding::MPEG2;
+ }
+
+ DCPOMATIC_ASSERT(false);
+}
+
diff --git a/src/lib/video_encoding.h b/src/lib/video_encoding.h
new file mode 100644
index 000000000..7c240f06f
--- /dev/null
+++ b/src/lib/video_encoding.h
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_VIDEO_ENCODING_H
+#define DCPOMATIC_VIDEO_ENCODING_H
+
+
+#include <string>
+
+
+enum class VideoEncoding
+{
+ JPEG2000,
+ MPEG2,
+ COUNT
+};
+
+
+std::string video_encoding_to_string(VideoEncoding encoding);
+VideoEncoding video_encoding_from_string(std::string const& encoding);
+
+
+#endif
+
diff --git a/src/lib/video_mxf_content.cc b/src/lib/video_mxf_content.cc
index 9adca5a2d..eeee6222c 100644
--- a/src/lib/video_mxf_content.cc
+++ b/src/lib/video_mxf_content.cc
@@ -26,8 +26,8 @@
#include "film.h"
#include "compose.hpp"
#include <asdcp/KM_log.h>
-#include <dcp/mono_picture_asset.h>
-#include <dcp/stereo_picture_asset.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/stereo_j2k_picture_asset.h>
#include <dcp/exceptions.h>
#include <libxml++/libxml++.h>
@@ -61,7 +61,7 @@ VideoMXFContent::valid_mxf (boost::filesystem::path path)
Kumu::DefaultLogSink().UnsetFilterFlag(Kumu::LOG_ALLOW_ALL);
try {
- dcp::MonoPictureAsset mp (path);
+ dcp::MonoJ2KPictureAsset mp(path);
return true;
} catch (dcp::MXFFileError& e) {
@@ -71,7 +71,7 @@ VideoMXFContent::valid_mxf (boost::filesystem::path path)
try {
Kumu::DefaultLogSink().SetFilterFlag(0);
- dcp::StereoPictureAsset sp (path);
+ dcp::StereoJ2KPictureAsset sp (path);
return true;
} catch (dcp::MXFFileError& e) {
@@ -121,11 +121,11 @@ VideoMXFContent::identifier () const
void
-VideoMXFContent::as_xml (xmlpp::Node* node, bool with_paths) const
+VideoMXFContent::as_xml(xmlpp::Element* element, bool with_paths) const
{
- node->add_child("Type")->add_child_text("VideoMXF");
- Content::as_xml (node, with_paths);
- video->as_xml (node);
+ cxml::add_text_child(element, "Type", "VideoMXF");
+ Content::as_xml(element, with_paths);
+ video->as_xml(element);
}
diff --git a/src/lib/video_mxf_content.h b/src/lib/video_mxf_content.h
index 5a04c3da9..1731942a6 100644
--- a/src/lib/video_mxf_content.h
+++ b/src/lib/video_mxf_content.h
@@ -40,7 +40,7 @@ public:
std::string summary () const override;
std::string technical_summary () const override;
std::string identifier () const override;
- void as_xml (xmlpp::Node* node, bool with_paths) const override;
+ void as_xml(xmlpp::Element* element, bool with_paths) const override;
dcpomatic::DCPTime full_length (std::shared_ptr<const Film> film) const override;
dcpomatic::DCPTime approximate_length () const override;
void add_properties (std::shared_ptr<const Film> film, std::list<UserProperty>& p) const override;
diff --git a/src/lib/video_mxf_decoder.cc b/src/lib/video_mxf_decoder.cc
index 40d3a461a..3d1ed7f4e 100644
--- a/src/lib/video_mxf_decoder.cc
+++ b/src/lib/video_mxf_decoder.cc
@@ -24,10 +24,10 @@
#include "video_mxf_content.h"
#include "j2k_image_proxy.h"
#include "frame_interval_checker.h"
-#include <dcp/mono_picture_asset.h>
-#include <dcp/mono_picture_asset_reader.h>
-#include <dcp/stereo_picture_asset.h>
-#include <dcp/stereo_picture_asset_reader.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/mono_j2k_picture_asset_reader.h>
+#include <dcp/stereo_j2k_picture_asset.h>
+#include <dcp/stereo_j2k_picture_asset_reader.h>
#include <dcp/exceptions.h>
@@ -44,7 +44,7 @@ VideoMXFDecoder::VideoMXFDecoder (shared_ptr<const Film> film, shared_ptr<const
video = make_shared<VideoDecoder>(this, content);
try {
- auto mono = make_shared<dcp::MonoPictureAsset>(_content->path(0));
+ auto mono = make_shared<dcp::MonoJ2KPictureAsset>(_content->path(0));
_mono_reader = mono->start_read ();
_mono_reader->set_check_hmac (false);
_size = mono->size ();
@@ -55,7 +55,7 @@ VideoMXFDecoder::VideoMXFDecoder (shared_ptr<const Film> film, shared_ptr<const
/* maybe it's stereo */
}
- auto stereo = make_shared<dcp::StereoPictureAsset>(_content->path(0));
+ auto stereo = make_shared<dcp::StereoJ2KPictureAsset>(_content->path(0));
_stereo_reader = stereo->start_read ();
_stereo_reader->set_check_hmac (false);
_size = stereo->size ();
@@ -76,18 +76,18 @@ VideoMXFDecoder::pass ()
video->emit (
film(),
std::make_shared<J2KImageProxy>(_mono_reader->get_frame(frame), _size, AV_PIX_FMT_XYZ12LE, optional<int>()),
- frame
+ _next
);
} else {
video->emit (
film(),
std::make_shared<J2KImageProxy>(_stereo_reader->get_frame(frame), _size, dcp::Eye::LEFT, AV_PIX_FMT_XYZ12LE, optional<int>()),
- frame
+ _next
);
video->emit (
film(),
std::make_shared<J2KImageProxy>(_stereo_reader->get_frame(frame), _size, dcp::Eye::RIGHT, AV_PIX_FMT_XYZ12LE, optional<int>()),
- frame
+ _next
);
}
diff --git a/src/lib/video_mxf_decoder.h b/src/lib/video_mxf_decoder.h
index 774e269c6..16d8b112e 100644
--- a/src/lib/video_mxf_decoder.h
+++ b/src/lib/video_mxf_decoder.h
@@ -20,8 +20,8 @@
#include "decoder.h"
-#include <dcp/mono_picture_asset_reader.h>
-#include <dcp/stereo_picture_asset_reader.h>
+#include <dcp/mono_j2k_picture_asset_reader.h>
+#include <dcp/stereo_j2k_picture_asset_reader.h>
class VideoMXFContent;
@@ -42,7 +42,7 @@ private:
/** Time of next thing to return from pass */
dcpomatic::ContentTime _next;
- std::shared_ptr<dcp::MonoPictureAssetReader> _mono_reader;
- std::shared_ptr<dcp::StereoPictureAssetReader> _stereo_reader;
+ std::shared_ptr<dcp::MonoJ2KPictureAssetReader> _mono_reader;
+ std::shared_ptr<dcp::StereoJ2KPictureAssetReader> _stereo_reader;
dcp::Size _size;
};
diff --git a/src/lib/video_mxf_examiner.cc b/src/lib/video_mxf_examiner.cc
index 7a05f3369..276664a14 100644
--- a/src/lib/video_mxf_examiner.cc
+++ b/src/lib/video_mxf_examiner.cc
@@ -21,8 +21,8 @@
#include "video_mxf_content.h"
#include "video_mxf_examiner.h"
#include <dcp/exceptions.h>
-#include <dcp/mono_picture_asset.h>
-#include <dcp/stereo_picture_asset.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/stereo_j2k_picture_asset.h>
using std::shared_ptr;
using boost::optional;
@@ -30,7 +30,7 @@ using boost::optional;
VideoMXFExaminer::VideoMXFExaminer (shared_ptr<const VideoMXFContent> content)
{
try {
- _asset.reset (new dcp::MonoPictureAsset (content->path(0)));
+ _asset = std::make_shared<dcp::MonoJ2KPictureAsset>(content->path(0));
} catch (dcp::MXFFileError& e) {
/* maybe it's stereo */
} catch (dcp::ReadError& e) {
@@ -38,7 +38,7 @@ VideoMXFExaminer::VideoMXFExaminer (shared_ptr<const VideoMXFContent> content)
}
if (!_asset) {
- _asset.reset (new dcp::StereoPictureAsset (content->path(0)));
+ _asset = std::make_shared<dcp::StereoJ2KPictureAsset>(content->path(0));
}
}
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
index 7b9defd73..f9293ed09 100644
--- a/src/lib/writer.cc
+++ b/src/lib/writer.cc
@@ -30,6 +30,7 @@
#include "dcpomatic_log.h"
#include "film.h"
#include "film_util.h"
+#include "frame_info.h"
#include "job.h"
#include "log.h"
#include "ratio.h"
@@ -39,6 +40,7 @@
#include "version.h"
#include "writer.h"
#include <dcp/cpl.h>
+#include <dcp/mono_mpeg2_picture_frame.h>
#include <dcp/locale_convert.h>
#include <dcp/raw_convert.h>
#include <dcp/reel_closed_caption_asset.h>
@@ -77,9 +79,10 @@ using namespace dcpomatic;
/** @param weak_job Job to report progress to, or 0.
* @param text_only true to enable only the text (subtitle/ccap) parts of the writer.
*/
-Writer::Writer(weak_ptr<const Film> weak_film, weak_ptr<Job> weak_job, bool text_only)
- : WeakConstFilm (weak_film)
+Writer::Writer(weak_ptr<const Film> weak_film, weak_ptr<Job> weak_job, boost::filesystem::path output_dir, bool text_only)
+ : WeakConstFilm(weak_film)
, _job(weak_job)
+ , _output_dir(output_dir)
/* These will be reset to sensible values when J2KEncoder is created */
, _maximum_frames_in_memory (8)
, _maximum_queue_size (8)
@@ -90,7 +93,7 @@ Writer::Writer(weak_ptr<const Film> weak_film, weak_ptr<Job> weak_job, bool text
int reel_index = 0;
auto const reels = film()->reels();
for (auto p: reels) {
- _reels.push_back (ReelWriter(weak_film, p, job, reel_index++, reels.size(), text_only));
+ _reels.push_back(ReelWriter(weak_film, p, job, reel_index++, reels.size(), text_only, _output_dir));
}
_last_written.resize (reels.size());
@@ -172,6 +175,13 @@ Writer::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
}
+void
+Writer::write(shared_ptr<dcp::MonoMPEG2PictureFrame> image, Frame frame)
+{
+ _reels[video_reel(frame)].write(image);
+}
+
+
bool
Writer::can_repeat (Frame frame) const
{
@@ -233,11 +243,7 @@ Writer::fake_write (Frame frame, Eyes eyes)
QueueItem qi;
qi.type = QueueItem::Type::FAKE;
-
- {
- shared_ptr<InfoFileHandle> info_file = film()->info_file_handle(_reels[reel].period(), true);
- qi.size = _reels[reel].read_frame_info(info_file, frame_in_reel, eyes).size;
- }
+ qi.info = J2KFrameInfo(film()->info_file_handle(_reels[reel].period(), true), frame_in_reel, eyes);
DCPOMATIC_ASSERT((film()->three_d() && eyes != Eyes::BOTH) || (!film()->three_d() && eyes == Eyes::BOTH));
@@ -411,7 +417,7 @@ try
if (i.type == QueueItem::Type::FULL) {
LOG_WARNING (N_("- type FULL, frame %1, eyes %2"), i.frame, (int) i.eyes);
} else {
- LOG_WARNING (N_("- type FAKE, size %1, frame %2, eyes %3"), i.size, i.frame, (int) i.eyes);
+ LOG_WARNING (N_("- type FAKE, size %1, frame %2, eyes %3"), i.info.size, i.frame, (int) i.eyes);
}
}
}
@@ -442,7 +448,7 @@ try
break;
case QueueItem::Type::FAKE:
LOG_DEBUG_ENCODE (N_("Writer FAKE-writes %1"), qi.frame);
- reel.fake_write (qi.size);
+ reel.fake_write(qi.info);
++_fake_written;
break;
case QueueItem::Type::REPEAT:
@@ -583,9 +589,8 @@ Writer::calculate_digests ()
}
-/** @param output_dcp Path to DCP folder to write */
void
-Writer::finish (boost::filesystem::path output_dcp)
+Writer::finish()
{
if (_thread.joinable()) {
LOG_GENERAL_NC ("Terminating writer thread");
@@ -596,12 +601,12 @@ Writer::finish (boost::filesystem::path output_dcp)
for (auto& reel: _reels) {
write_hanging_text(reel);
- reel.finish(output_dcp);
+ reel.finish(_output_dir);
}
LOG_GENERAL_NC ("Writing XML");
- dcp::DCP dcp (output_dcp);
+ dcp::DCP dcp(_output_dir);
auto cpl = make_shared<dcp::CPL>(
film()->dcp_name(),
@@ -616,7 +621,7 @@ Writer::finish (boost::filesystem::path output_dcp)
/* Add reels */
for (auto& i: _reels) {
- cpl->add(i.create_reel(_reel_assets, output_dcp, _have_subtitles, _have_closed_captions));
+ cpl->add(i.create_reel(_reel_assets, _output_dir, _have_subtitles, _have_closed_captions));
}
/* Add metadata */
@@ -718,12 +723,12 @@ Writer::finish (boost::filesystem::path output_dcp)
N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
);
- write_cover_sheet (output_dcp);
+ write_cover_sheet();
}
void
-Writer::write_cover_sheet (boost::filesystem::path output_dcp)
+Writer::write_cover_sheet()
{
auto const cover = film()->file("COVER_SHEET.txt");
dcp::File file(cover, "w");
@@ -756,7 +761,7 @@ Writer::write_cover_sheet (boost::filesystem::path output_dcp)
boost::uintmax_t size = 0;
for (
- auto i = dcp::filesystem::recursive_directory_iterator(output_dcp);
+ auto i = dcp::filesystem::recursive_directory_iterator(_output_dir);
i != dcp::filesystem::recursive_directory_iterator();
++i) {
if (dcp::filesystem::is_regular_file(i->path())) {
diff --git a/src/lib/writer.h b/src/lib/writer.h
index f0f1fe69a..3e93c9b7b 100644
--- a/src/lib/writer.h
+++ b/src/lib/writer.h
@@ -38,6 +38,8 @@
#include "types.h"
#include "weak_film.h"
#include <dcp/atmos_frame.h>
+#include <dcp/frame_info.h>
+#include <dcp/mono_mpeg2_picture_frame.h>
#include <boost/thread.hpp>
#include <boost/thread/condition.hpp>
#include <list>
@@ -75,8 +77,8 @@ public:
/** encoded data for FULL */
std::shared_ptr<const dcp::Data> encoded;
- /** size of data for FAKE */
- int size = 0;
+ /** info for FAKE */
+ dcp::J2KFrameInfo info;
/** reel index */
size_t reel = 0;
/** frame index within the reel */
@@ -104,7 +106,7 @@ bool operator== (QueueItem const & a, QueueItem const & b);
class Writer : public ExceptionStore, public WeakConstFilm
{
public:
- Writer (std::weak_ptr<const Film>, std::weak_ptr<Job>, bool text_only = false);
+ Writer(std::weak_ptr<const Film>, std::weak_ptr<Job>, boost::filesystem::path output_dir, bool text_only = false);
~Writer ();
Writer (Writer const &) = delete;
@@ -123,7 +125,8 @@ public:
void write (std::vector<std::shared_ptr<dcpomatic::Font>> fonts);
void write (ReferencedReelAsset asset);
void write (std::shared_ptr<const dcp::AtmosFrame> atmos, dcpomatic::DCPTime time, AtmosMetadata metadata);
- void finish (boost::filesystem::path output_dcp);
+ void write (std::shared_ptr<dcp::MonoMPEG2PictureFrame> image, Frame frame);
+ void finish();
void set_encoder_threads (int threads);
@@ -139,7 +142,7 @@ private:
bool have_sequenced_image_at_queue_head ();
size_t video_reel (int frame) const;
void set_digest_progress(Job* job, int id, int64_t done, int64_t size);
- void write_cover_sheet (boost::filesystem::path output_dcp);
+ void write_cover_sheet();
void calculate_referenced_digests(std::function<void (int64_t, int64_t)> set_progress);
void write_hanging_text (ReelWriter& reel);
void calculate_digests ();
@@ -151,6 +154,7 @@ private:
std::map<DCPTextTrack, std::vector<ReelWriter>::iterator> _caption_reels;
std::vector<ReelWriter>::iterator _atmos_reel;
+ boost::filesystem::path _output_dir;
/** our thread */
boost::thread _thread;
/** true if our thread should finish */
diff --git a/src/lib/wscript b/src/lib/wscript
index 87a1ca787..dfe3ce487 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -49,7 +49,7 @@ sources = """
text_decoder.cc
case_insensitive_sorter.cc
check_content_job.cc
- cinema.cc
+ cinema_list.cc
cinema_sound_processor.cc
change_signaller.cc
collator.cc
@@ -59,6 +59,7 @@ sources = """
content_factory.cc
combine_dcp_job.cc
copy_dcp_details_to_film.cc
+ cpu_j2k_encoder_thread.cc
create_cli.cc
crop.cc
cross_common.cc
@@ -67,9 +68,9 @@ sources = """
dcp_content.cc
dcp_content_type.cc
dcp_decoder.cc
- dcp_encoder.cc
dcp_examiner.cc
dcp_digest_file.cc
+ dcp_film_encoder.cc
dcp_subtitle.cc
dcp_subtitle_content.cc
dcp_subtitle_decoder.cc
@@ -84,11 +85,11 @@ sources = """
decoder_part.cc
digester.cc
dkdm_recipient.cc
+ dkdm_recipient_list.cc
dkdm_wrapper.cc
dolby_cp750.cc
email.cc
empty.cc
- encoder.cc
encode_server.cc
encode_server_finder.cc
encoded_log_entry.cc
@@ -98,6 +99,7 @@ sources = """
examine_ffmpeg_subtitles_job.cc
exceptions.cc
export_config.cc
+ frame_info.cc
file_group.cc
file_log.cc
filter_graph.cc
@@ -106,14 +108,15 @@ sources = """
ffmpeg_audio_stream.cc
ffmpeg_content.cc
ffmpeg_decoder.cc
- ffmpeg_encoder.cc
ffmpeg_examiner.cc
ffmpeg_file_encoder.cc
+ ffmpeg_film_encoder.cc
ffmpeg_image_proxy.cc
ffmpeg_stream.cc
ffmpeg_subtitle_stream.cc
ffmpeg_wrapper.cc
film.cc
+ film_encoder.cc
film_util.cc
filter.cc
font.cc
@@ -124,6 +127,8 @@ sources = """
frame_rate_change.cc
guess_crop.cc
hints.cc
+ http_server.cc
+ id.cc
internet.cc
image.cc
image_content.cc
@@ -134,10 +139,13 @@ sources = """
image_png.cc
image_proxy.cc
image_store.cc
+ internal_player_server.cc
j2k_image_proxy.cc
job.cc
job_manager.cc
j2k_encoder.cc
+ j2k_encoder_thread.cc
+ j2k_sync_encoder_thread.cc
json_server.cc
kdm_cli.cc
kdm_recipient.cc
@@ -150,6 +158,7 @@ sources = """
maths_util.cc
memory_util.cc
mid_side_decoder.cc
+ mpeg2_encoder.cc
named_channel.cc
overlaps.cc
pixel_quanta.cc
@@ -162,7 +171,9 @@ sources = """
reel_writer.cc
referenced_reel_asset.cc
release_notes.cc
+ remembered_asset.cc
render_text.cc
+ remote_j2k_encoder_thread.cc
resampler.cc
resolution.cc
rgba.cc
@@ -178,12 +189,15 @@ sources = """
state.cc
spl.cc
spl_entry.cc
+ sqlite_statement.cc
+ sqlite_table.cc
+ sqlite_transaction.cc
string_log_entry.cc
string_text_file.cc
string_text_file_content.cc
string_text_file_decoder.cc
subtitle_analysis.cc
- subtitle_encoder.cc
+ subtitle_film_encoder.cc
territory_type.cc
text_ring_buffers.cc
text_type.cc
@@ -202,9 +216,12 @@ sources = """
upmixer_b.cc
usl.cc
util.cc
+ variant.cc
verify_dcp_job.cc
video_content.cc
video_decoder.cc
+ video_encoder.cc
+ video_encoding.cc
video_filter_graph.cc
video_filter_graph_set.cc
video_frame_type.cc
@@ -230,7 +247,7 @@ def build(bld):
BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 BOOST_REGEX
SAMPLERATE POSTPROC TIFF SSH DCP CXML GLIB LZMA XML++
CURL ZIP BZ2 FONTCONFIG PANGOMM CAIROMM XMLSEC SUB ICU NETTLE PNG JPEG LEQM_NRT
- LIBZ
+ LIBZ SQLITE3
"""
if bld.env.TARGET_OSX:
@@ -244,6 +261,9 @@ def build(bld):
if bld.env.TARGET_LINUX:
obj.uselib += ' POLKIT'
+ if bld.env.ENABLE_GROK:
+ obj.source += ' grok_j2k_encoder_thread.cc'
+
if bld.env.TARGET_WINDOWS_64 or bld.env.TARGET_WINDOWS_32:
obj.uselib += ' WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE SETUPAPI OLE32 UUID'
obj.source += ' cross_windows.cc'
diff --git a/src/tools/dcpomatic.cc b/src/tools/dcpomatic.cc
index 584ebd27d..0a6728731 100644
--- a/src/tools/dcpomatic.cc
+++ b/src/tools/dcpomatic.cc
@@ -26,6 +26,7 @@
#include "wx/about_dialog.h"
#include "wx/content_panel.h"
+#include "wx/dcp_referencing_dialog.h"
#include "wx/dkdm_dialog.h"
#include "wx/export_subtitles_dialog.h"
#include "wx/export_video_file_dialog.h"
@@ -41,6 +42,7 @@
#include "wx/id.h"
#include "wx/job_manager_view.h"
#include "wx/kdm_dialog.h"
+#include "wx/load_config_from_zip_dialog.h"
#include "wx/nag_dialog.h"
#include "wx/paste_dialog.h"
#include "wx/recreate_chain_dialog.h"
@@ -55,6 +57,7 @@
#include "wx/video_waveform_dialog.h"
#include "wx/wx_signal_manager.h"
#include "wx/wx_util.h"
+#include "wx/wx_variant.h"
#include "lib/analytics.h"
#include "lib/audio_content.h"
#include "lib/check_content_job.h"
@@ -73,9 +76,12 @@
#include "lib/email.h"
#include "lib/encode_server_finder.h"
#include "lib/exceptions.h"
-#include "lib/ffmpeg_encoder.h"
+#include "lib/ffmpeg_film_encoder.h"
#include "lib/film.h"
#include "lib/font_config.h"
+#ifdef DCPOMATIC_GROK
+#include "lib/grok/context.h"
+#endif
#include "lib/hints.h"
#include "lib/job_manager.h"
#include "lib/kdm_with_metadata.h"
@@ -85,15 +91,18 @@
#include "lib/screen.h"
#include "lib/send_kdm_email_job.h"
#include "lib/signal_manager.h"
-#include "lib/subtitle_encoder.h"
+#include "lib/subtitle_film_encoder.h"
#include "lib/text_content.h"
#include "lib/transcode_job.h"
+#include "lib/unzipper.h"
#include "lib/update_checker.h"
+#include "lib/variant.h"
#include "lib/version.h"
#include "lib/video_content.h"
#include <dcp/exceptions.h>
#include <dcp/filesystem.h>
#include <dcp/raw_convert.h>
+#include <dcp/scope_guard.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/cmdline.h>
@@ -207,6 +216,7 @@ private:
#define NEEDS_SELECTED_VIDEO_CONTENT 0x20
#define NEEDS_CLIPBOARD 0x40
#define NEEDS_ENCRYPTION 0x80
+#define NEEDS_DCP_CONTENT 0x100
map<wxMenuItem*, int> menu_items;
@@ -236,6 +246,7 @@ enum {
ID_jobs_open_dcp_in_player,
ID_view_closed_captions,
ID_view_video_waveform,
+ ID_tools_version_file,
ID_tools_hints,
ID_tools_encoding_servers,
ID_tools_manage_templates,
@@ -318,7 +329,7 @@ public:
#endif
_config_changed_connection = Config::instance()->Changed.connect(boost::bind(&DOMFrame::config_changed, this, _1));
- config_changed (Config::OTHER);
+ config_changed(Config::OTHER);
_analytics_message_connection = Analytics::instance()->Message.connect(boost::bind(&DOMFrame::analytics_message, this, _1, _2));
@@ -347,6 +358,7 @@ public:
Bind (wxEVT_MENU, boost::bind (&DOMFrame::jobs_open_dcp_in_player, this), ID_jobs_open_dcp_in_player);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_closed_captions, this), ID_view_closed_captions);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_video_waveform, this), ID_view_video_waveform);
+ Bind (wxEVT_MENU, boost::bind (&DOMFrame::tools_version_file, this), ID_tools_version_file);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::tools_hints, this), ID_tools_hints);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::tools_encoding_servers, this), ID_tools_encoding_servers);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::tools_manage_templates, this), ID_tools_manage_templates);
@@ -442,12 +454,13 @@ public:
}
}
+ /** Make a new film in the given path, using template_name as a template
+ * (or the default template if it's empty).
+ */
void new_film (boost::filesystem::path path, optional<string> template_name)
{
auto film = make_shared<Film>(path);
- if (template_name) {
- film->use_template (template_name.get());
- }
+ film->use_template(template_name);
film->set_name (path.filename().generic_string());
film->write_metadata ();
set_film (film);
@@ -458,6 +471,7 @@ public:
{
auto film = make_shared<Film>(file);
auto const notes = film->read_metadata ();
+ film->read_ui_state();
if (film->state_version() == 4) {
error_dialog (
@@ -479,8 +493,10 @@ public:
auto const dir = e.file().parent_path();
if (dcp::filesystem::exists(dir / "ASSETMAP") || dcp::filesystem::exists(dir / "ASSETMAP.xml")) {
error_dialog (
- this, _("Could not open this folder as a DCP-o-matic project."),
- _("It looks like you are trying to open a DCP. File -> Open is for loading DCP-o-matic projects, not DCPs. To import a DCP, create a new project with File -> New and then click the \"Add DCP...\" button.")
+ this, variant::wx::insert_dcpomatic(_("Could not open this folder as a %s project.")),
+ variant::wx::insert_dcpomatic(
+ _("It looks like you are trying to open a DCP. File -> Open is for loading %s projects, not DCPs. "
+ "To import a DCP, create a new project with File -> New and then click the \"Add DCP...\" button."))
);
} else {
auto const p = std_to_wx(file.string ());
@@ -564,7 +580,7 @@ private:
if (!found_bad_chars.empty()) {
message += wxString::Format (_("Try removing the %s characters from your folder name."), std_to_wx(found_bad_chars).data());
} else {
- message += _("Please check that you do not have Windows controlled folder access enabled for DCP-o-matic.");
+ message += variant::wx::insert_dcpomatic(_("Please check that you do not have Windows controlled folder access enabled for %s."));
}
error_dialog (this, message, std_to_wx(e.what()));
#else
@@ -611,7 +627,11 @@ private:
SaveTemplateDialog dialog(this);
if (dialog.ShowModal() == wxID_OK) {
try {
- Config::instance()->save_template(_film, dialog.name());
+ if (dialog.name()) {
+ Config::instance()->save_template(_film, *dialog.name());
+ } else {
+ Config::instance()->save_default_template(_film);
+ }
} catch (exception& e) {
error_dialog(this, _("Could not save template."), std_to_wx(e.what()));
}
@@ -780,24 +800,31 @@ private:
{
FileDialog dialog(this, _("Specify ZIP file"), wxT("ZIP files (*.zip)|*.zip"), wxFD_OPEN, "Preferences");
- if (dialog.show()) {
- Config::instance()->load_from_zip(dialog.path());
+ if (!dialog.show()) {
+ return;
}
+
+ auto action = Config::CinemasAction::WRITE_TO_CURRENT_PATH;
+
+ if (Config::zip_contains_cinemas(dialog.path()) && Config::cinemas_file_from_zip(dialog.path()) != Config::instance()->cinemas_file()) {
+ LoadConfigFromZIPDialog how(this, dialog.path());
+ if (how.ShowModal() == wxID_CANCEL) {
+ return;
+ }
+
+ action = how.action();
+ }
+
+ Config::instance()->load_from_zip(dialog.path(), action);
}
void jobs_make_dcp ()
{
double required;
double available;
- bool can_hard_link;
- if (!_film->should_be_enough_disk_space (required, available, can_hard_link)) {
- wxString message;
- if (can_hard_link) {
- message = wxString::Format (_("The DCP for this film will take up about %.1f GB, and the disk that you are using only has %.1f GB available. Do you want to continue anyway?"), required, available);
- } else {
- message = wxString::Format (_("The DCP and intermediate files for this film will take up about %.1f GB, and the disk that you are using only has %.1f GB available. You would need half as much space if the filesystem supported hard links, but it does not. Do you want to continue anyway?"), required, available);
- }
+ if (!_film->should_be_enough_disk_space(required, available)) {
+ auto const message = wxString::Format(_("The DCP for this film will take up about %.1f GB, and the disk that you are using only has %.1f GB available. Do you want to continue anyway?"), required, available);
if (!confirm_dialog (this, message)) {
return;
}
@@ -827,6 +854,8 @@ private:
if (!confirm_dialog (this, wxString::Format (_("Do you want to overwrite the existing DCP %s?"), std_to_wx(dcp_dir.string()).data()))) {
return;
}
+
+ preserve_assets(dcp_dir, _film->assets_path());
dcp::filesystem::remove_all(dcp_dir);
}
@@ -1014,7 +1043,7 @@ private:
auto job = make_shared<TranscodeJob>(_film, TranscodeJob::ChangedBehaviour::EXAMINE_THEN_STOP);
job->set_encoder (
- make_shared<FFmpegEncoder> (
+ make_shared<FFmpegFilmEncoder>(
_film, job, dialog.path(), dialog.format(), dialog.mixdown_to_stereo(), dialog.split_reels(), dialog.split_streams(), dialog.x264_crf())
);
JobManager::instance()->add (job);
@@ -1029,7 +1058,7 @@ private:
}
auto job = make_shared<TranscodeJob>(_film, TranscodeJob::ChangedBehaviour::EXAMINE_THEN_STOP);
job->set_encoder(
- make_shared<SubtitleEncoder>(_film, job, dialog.path(), _film->isdcf_name(true), dialog.split_reels(), dialog.include_font())
+ make_shared<SubtitleFilmEncoder>(_film, job, dialog.path(), _film->isdcf_name(true), dialog.split_reels(), dialog.include_font())
);
JobManager::instance()->add(job);
}
@@ -1071,6 +1100,17 @@ private:
_system_information_dialog->Show ();
}
+ void tools_version_file()
+ {
+ if (_dcp_referencing_dialog) {
+ _dcp_referencing_dialog->Destroy();
+ _dcp_referencing_dialog = nullptr;
+ }
+
+ _dcp_referencing_dialog = new DCPReferencingDialog(this, _film);
+ _dcp_referencing_dialog->Show();
+ }
+
void tools_hints ()
{
if (!_hints_dialog) {
@@ -1170,6 +1210,7 @@ private:
FontConfig::drop();
ev.Skip ();
+ JobManager::drop ();
}
void active_jobs_changed()
@@ -1193,6 +1234,13 @@ private:
bool const have_single_selected_content = _film_editor->content_panel()->selected().size() == 1;
bool const have_selected_content = !_film_editor->content_panel()->selected().empty();
bool const have_selected_video_content = !_film_editor->content_panel()->selected_video().empty();
+ vector<shared_ptr<Content>> content;
+ if (_film) {
+ content = _film->content();
+ }
+ bool const have_dcp_content = std::find_if(content.begin(), content.end(), [](shared_ptr<const Content> content) {
+ return static_cast<bool>(dynamic_pointer_cast<const DCPContent>(content));
+ }) != content.end();
for (auto j: menu_items) {
@@ -1230,6 +1278,10 @@ private:
enabled = false;
}
+ if ((j.second & NEEDS_DCP_CONTENT) && !have_dcp_content) {
+ enabled = false;
+ }
+
j.first->Enable (enabled);
}
}
@@ -1340,7 +1392,7 @@ private:
add_item (jobs_menu, _("Make &KDMs...\tCtrl-K"), ID_jobs_make_kdms, NEEDS_FILM);
/* [Shortcut] Ctrl+D:Make DKDMs */
add_item (jobs_menu, _("Make &DKDMs...\tCtrl-D"), ID_jobs_make_dkdms, NEEDS_FILM);
- add_item (jobs_menu, _("Make DKDM for DCP-o-matic..."), ID_jobs_make_self_dkdm, NEEDS_FILM | NEEDS_ENCRYPTION);
+ add_item(jobs_menu, variant::wx::insert_dcpomatic(_("Make DKDM for %s...")), ID_jobs_make_self_dkdm, NEEDS_FILM | NEEDS_ENCRYPTION);
jobs_menu->AppendSeparator ();
/* [Shortcut] Ctrl+E:Export video file */
add_item (jobs_menu, _("Export video file...\tCtrl-E"), ID_jobs_export_video_file, NEEDS_FILM);
@@ -1363,6 +1415,7 @@ private:
add_item (view, _("Video waveform..."), ID_view_video_waveform, NEEDS_FILM);
auto tools = new wxMenu;
+ add_item (tools, _("Version File (VF)..."), ID_tools_version_file, NEEDS_FILM | NEEDS_DCP_CONTENT);
add_item (tools, _("Hints..."), ID_tools_hints, NEEDS_FILM);
add_item (tools, _("Encoding servers..."), ID_tools_encoding_servers, 0);
add_item (tools, _("Manage templates..."), ID_tools_manage_templates, 0);
@@ -1376,11 +1429,13 @@ private:
wxMenu* help = new wxMenu;
#ifdef __WXOSX__
- add_item (help, _("About DCP-o-matic"), wxID_ABOUT, ALWAYS);
+ add_item(help, variant::wx::insert_dcpomatic(_("About %s")), wxID_ABOUT, ALWAYS);
#else
add_item (help, _("About"), wxID_ABOUT, ALWAYS);
#endif
- add_item (help, _("Report a problem..."), ID_help_report_a_problem, NEEDS_FILM);
+ if (variant::show_report_a_problem()) {
+ add_item(help, _("Report a problem..."), ID_help_report_a_problem, NEEDS_FILM);
+ }
m->Append (_file_menu, _("&File"));
m->Append (edit, _("&Edit"));
@@ -1390,48 +1445,19 @@ private:
m->Append (help, _("&Help"));
}
- void config_changed (Config::Property what)
+ void config_changed(Config::Property what)
{
/* Instantly save any config changes when using the DCP-o-matic GUI */
- switch (what) {
- case Config::CINEMAS:
- try {
- Config::instance()->write_cinemas();
- } catch (exception& e) {
- error_dialog (
- this,
- wxString::Format (
- _("Could not write to cinemas file at %s. Your changes have not been saved."),
- std_to_wx (Config::instance()->cinemas_file().string()).data()
- )
- );
- }
- break;
- case Config::DKDM_RECIPIENTS:
- try {
- Config::instance()->write_dkdm_recipients();
- } catch (exception& e) {
- error_dialog (
- this,
- wxString::Format (
- _("Could not write to DKDM recipients file at %s. Your changes have not been saved."),
- std_to_wx(Config::instance()->dkdm_recipients_file().string()).data()
- )
- );
- }
- break;
- default:
- try {
- Config::instance()->write_config();
- } catch (exception& e) {
- error_dialog (
- this,
- wxString::Format (
- _("Could not write to config file at %s. Your changes have not been saved."),
- std_to_wx (Config::instance()->cinemas_file().string()).data()
- )
- );
- }
+ try {
+ Config::instance()->write_config();
+ } catch (exception& e) {
+ error_dialog (
+ this,
+ wxString::Format (
+ _("Could not write to config file at %s. Your changes have not been saved."),
+ std_to_wx (Config::instance()->cinemas_file().string()).data()
+ )
+ );
}
for (int i = 0; i < _history_items; ++i) {
@@ -1467,6 +1493,14 @@ private:
_history_items = history.size ();
dcpomatic_log->set_types (Config::instance()->log_types());
+
+#ifdef DCPOMATIC_GROK
+ if (what == Config::GROK) {
+ setup_grok_library_path();
+ }
+#else
+ LIBDCP_UNUSED(what);
+#endif
}
void update_checker_state_changed ()
@@ -1488,9 +1522,9 @@ private:
UpdateDialog dialog(this, uc->stable(), uc->test());
dialog.ShowModal();
} else if (uc->state() == UpdateChecker::State::FAILED) {
- error_dialog (this, _("The DCP-o-matic download server could not be contacted."));
+ error_dialog(this, variant::wx::insert_dcpomatic(_("The %s download server could not be contacted.")));
} else {
- error_dialog (this, _("There are no new versions of DCP-o-matic available."));
+ error_dialog(this, variant::wx::insert_dcpomatic(_("There are no new versions of %s available.")));
}
_update_news_requested = false;
@@ -1528,7 +1562,7 @@ private:
void set_title ()
{
- auto s = wx_to_std(_("DCP-o-matic"));
+ auto s = variant::dcpomatic();
if (_film) {
if (_film->directory()) {
s += " - " + _film->directory()->string();
@@ -1548,6 +1582,7 @@ private:
StandardControls* _controls;
wx_ptr<VideoWaveformDialog> _video_waveform_dialog;
SystemInformationDialog* _system_information_dialog = nullptr;
+ DCPReferencingDialog* _dcp_referencing_dialog = nullptr;
HintsDialog* _hints_dialog = nullptr;
ServersListDialog* _servers_list_dialog = nullptr;
wxPreferencesEditor* _config_dialog = nullptr;
@@ -1571,7 +1606,7 @@ static const wxCmdLineEntryDesc command_line_description[] = {
{ wxCMD_LINE_SWITCH, "n", "new", "create new film", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_OPTION, "c", "content", "add content file / directory", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_OPTION, "d", "dcp", "add content DCP", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
- { wxCMD_LINE_SWITCH, "v", "version", "show DCP-o-matic version", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
+ { wxCMD_LINE_SWITCH, "v", "version", "show version", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_OPTION, "", "config", "directory containing config.xml and cinemas.xml", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
@@ -1615,7 +1650,7 @@ private:
setvbuf(hf_in, NULL, _IONBF, 128);
*stdin = *hf_in;
- cout << "DCP-o-matic is starting." << "\n";
+ cout << variant::insert_dcpomatic("%1 is starting.") << "\n";
}
#endif
wxInitAllImageHandlers ();
@@ -1625,7 +1660,7 @@ private:
_splash = maybe_show_splash ();
- SetAppName (_("DCP-o-matic"));
+ SetAppName(variant::wx::dcpomatic());
if (!wxApp::OnInit()) {
return false;
@@ -1664,7 +1699,7 @@ private:
*/
Config::Bad.connect (boost::bind(&App::config_bad, this, _1));
- _frame = new DOMFrame (_("DCP-o-matic"));
+ _frame = new DOMFrame(variant::wx::dcpomatic());
SetTopWindow (_frame);
_frame->Maximize ();
close_splash ();
@@ -1672,7 +1707,14 @@ private:
if (running_32_on_64 ()) {
NagDialog::maybe_nag (
_frame, Config::NAG_32_ON_64,
- _("You are running the 32-bit version of DCP-o-matic on a 64-bit version of Windows. This will limit the memory available to DCP-o-matic and may cause errors. You are strongly advised to install the 64-bit version of DCP-o-matic."),
+ wxString::Format(
+ _("You are running the 32-bit version of %s on a 64-bit version of Windows. "
+ "This will limit the memory available to %s and may cause errors. You are "
+ "strongly advised to install the 64-bit version of %s."),
+ variant::wx::dcpomatic(),
+ variant::wx::dcpomatic(),
+ variant::wx::dcpomatic()
+ ),
false);
}
@@ -1715,11 +1757,16 @@ private:
notes.Centre();
notes.ShowModal();
}
+
+#ifdef DCPOMATIC_GROK
+ grk_plugin::setMessengerLogger(new grk_plugin::GrokLogger("[GROK] "));
+ setup_grok_library_path();
+#endif
}
catch (exception& e)
{
close_splash();
- error_dialog (nullptr, wxString::Format ("DCP-o-matic could not start."), std_to_wx(e.what()));
+ error_dialog(nullptr, variant::wx::insert_dcpomatic(_("%s could not start.")), std_to_wx(e.what()));
}
return true;
@@ -1772,31 +1819,34 @@ private:
error_dialog (
nullptr,
wxString::Format(
- _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what()),
- std_to_wx (e.file().string().c_str())
+ _("An exception occurred: %s (%s)\n\n%s"),
+ std_to_wx(e.what()),
+ std_to_wx(e.file().string().c_str()),
+ wx::report_problem()
)
);
} catch (boost::filesystem::filesystem_error& e) {
error_dialog (
nullptr,
wxString::Format(
- _("An exception occurred: %s (%s) (%s)\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what()),
- std_to_wx (e.path1().string()),
- std_to_wx (e.path2().string())
+ _("An exception occurred: %s (%s) (%s)\n\n%s"),
+ std_to_wx(e.what()),
+ std_to_wx(e.path1().string()),
+ std_to_wx(e.path2().string()),
+ wx::report_problem()
)
);
} catch (exception& e) {
error_dialog (
nullptr,
wxString::Format(
- _("An exception occurred: %s.\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what ())
+ _("An exception occurred: %s.\n\n%s"),
+ std_to_wx(e.what()),
+ wx::report_problem()
)
);
} catch (...) {
- error_dialog (0, _("An unknown exception occurred.") + " " + REPORT_PROBLEM);
+ error_dialog(nullptr, _("An unknown exception occurred.") + " " + wx::report_problem());
}
}
@@ -1862,9 +1912,11 @@ private:
}
RecreateChainDialog dialog(
_frame, _("Recreate signing certificates"),
- _("The certificate chain that DCP-o-matic uses for signing DCPs and KDMs contains a small error\n"
- "which will prevent DCPs from being validated correctly on some systems. Do you want to re-create\n"
- "the certificate chain for signing DCPs and KDMs?"),
+ variant::wx::insert_dcpomatic(
+ _("The certificate chain that %s uses for signing DCPs and KDMs contains a small error\n"
+ "which will prevent DCPs from being validated correctly on some systems. Do you want to re-create\n"
+ "the certificate chain for signing DCPs and KDMs?")
+ ),
_("Do nothing"),
Config::NAG_BAD_SIGNER_CHAIN_UTF8
);
@@ -1877,9 +1929,11 @@ private:
}
RecreateChainDialog dialog(
_frame, _("Recreate signing certificates"),
- _("The certificate chain that DCP-o-matic uses for signing DCPs and KDMs has a validity period\n"
- "that is too long. This will cause problems playing back DCPs on some systems.\n"
- "Do you want to re-create the certificate chain for signing DCPs and KDMs?"),
+ variant::wx::insert_dcpomatic(
+ _("The certificate chain that %s uses for signing DCPs and KDMs has a validity period\n"
+ "that is too long. This will cause problems playing back DCPs on some systems.\n"
+ "Do you want to re-create the certificate chain for signing DCPs and KDMs?")
+ ),
_("Do nothing"),
Config::NAG_BAD_SIGNER_CHAIN_VALIDITY
);
@@ -1889,10 +1943,14 @@ private:
{
RecreateChainDialog dialog(
_frame, _("Recreate signing certificates"),
- _("The certificate chain that DCP-o-matic uses for signing DCPs and KDMs is inconsistent and\n"
- "cannot be used. DCP-o-matic cannot start unless you re-create it. Do you want to re-create\n"
- "the certificate chain for signing DCPs and KDMs?"),
- _("Close DCP-o-matic")
+ wxString::Format(
+ _("The certificate chain that %s uses for signing DCPs and KDMs is inconsistent and\n"
+ "cannot be used. %s cannot start unless you re-create it. Do you want to re-create\n"
+ "the certificate chain for signing DCPs and KDMs?"),
+ variant::wx::dcpomatic(),
+ variant::wx::dcpomatic()
+ ),
+ variant::wx::insert_dcpomatic(_("Close %s"))
);
if (dialog.ShowModal() != wxID_OK) {
exit (EXIT_FAILURE);
@@ -1903,11 +1961,15 @@ private:
{
RecreateChainDialog dialog(
_frame, _("Recreate KDM decryption chain"),
- _("The certificate chain that DCP-o-matic uses for decrypting KDMs is inconsistent and\n"
- "cannot be used. DCP-o-matic cannot start unless you re-create it. Do you want to re-create\n"
- "the certificate chain for decrypting KDMs? You may want to say \"No\" here and back up your\n"
- "configuration before continuing."),
- _("Close DCP-o-matic")
+ wxString::Format(
+ _("The certificate chain that %s uses for decrypting KDMs is inconsistent and\n"
+ "cannot be used. %s cannot start unless you re-create it. Do you want to re-create\n"
+ "the certificate chain for decrypting KDMs? You may want to say \"No\" here and back up your\n"
+ "configuration before continuing."),
+ variant::wx::dcpomatic(),
+ variant::wx::dcpomatic()
+ ),
+ variant::wx::insert_dcpomatic(_("Close %s"))
);
if (dialog.ShowModal() != wxID_OK) {
exit (EXIT_FAILURE);
@@ -1918,10 +1980,14 @@ private:
{
RecreateChainDialog dialog(
_frame, _("Recreate signing certificates"),
- _("The certificate chain that DCP-o-matic uses for signing DCPs and KDMs contains a small error\n"
- "which will prevent DCPs from being validated correctly on some systems. This error was caused\n"
- "by a bug in DCP-o-matic which has now been fixed. Do you want to re-create the certificate chain\n"
- "for signing DCPs and KDMs?"),
+ wxString::Format(
+ _("The certificate chain that %s uses for signing DCPs and KDMs contains a small error\n"
+ "which will prevent DCPs from being validated correctly on some systems. This error was caused\n"
+ "by a bug in %s which has now been fixed. Do you want to re-create the certificate chain\n"
+ "for signing DCPs and KDMs?"),
+ variant::wx::dcpomatic(),
+ variant::wx::dcpomatic()
+ ),
_("Do nothing"),
Config::NAG_BAD_SIGNER_DN_QUALIFIER
);
diff --git a/src/tools/dcpomatic_batch.cc b/src/tools/dcpomatic_batch.cc
index e476c2163..51d700d20 100644
--- a/src/tools/dcpomatic_batch.cc
+++ b/src/tools/dcpomatic_batch.cc
@@ -28,10 +28,14 @@
#include "wx/wx_ptr.h"
#include "wx/wx_signal_manager.h"
#include "wx/wx_util.h"
+#include "wx/wx_variant.h"
#include "lib/compose.hpp"
#include "lib/config.h"
#include "lib/dcpomatic_socket.h"
#include "lib/film.h"
+#ifdef DCPOMATIC_GROK
+#include "lib/grok/context.h"
+#endif
#include "lib/job.h"
#include "lib/job_manager.h"
#include "lib/make_dcp.h"
@@ -216,9 +220,8 @@ public:
double total_required;
double available;
- bool can_hard_link;
- film->should_be_enough_disk_space (total_required, available, can_hard_link);
+ film->should_be_enough_disk_space(total_required, available);
set<shared_ptr<const Film>> films;
@@ -235,7 +238,7 @@ public:
}
double required;
- i->should_be_enough_disk_space (required, available, can_hard_link);
+ i->should_be_enough_disk_space(required, available);
total_required += (1 - progress) * required;
}
@@ -289,6 +292,7 @@ private:
}
ev.Skip ();
+ JobManager::drop ();
}
void file_add_film ()
@@ -353,31 +357,25 @@ private:
void config_changed (Config::Property what)
{
/* Instantly save any config changes when using the DCP-o-matic GUI */
- if (what == Config::CINEMAS) {
- try {
- Config::instance()->write_cinemas();
- } catch (exception& e) {
- error_dialog (
- this,
- wxString::Format(
- _("Could not write to cinemas file at %s. Your changes have not been saved."),
- std_to_wx (Config::instance()->cinemas_file().string()).data()
- )
- );
- }
- } else {
- try {
- Config::instance()->write_config();
- } catch (exception& e) {
- error_dialog (
- this,
- wxString::Format(
- _("Could not write to config file at %s. Your changes have not been saved."),
- std_to_wx (Config::instance()->cinemas_file().string()).data()
- )
- );
- }
+ try {
+ Config::instance()->write_config();
+ } catch (exception& e) {
+ error_dialog (
+ this,
+ wxString::Format(
+ _("Could not write to config file at %s. Your changes have not been saved."),
+ std_to_wx (Config::instance()->cinemas_file().string()).data()
+ )
+ );
+ }
+
+#ifdef DCPOMATIC_GROK
+ if (what == Config::GROK) {
+ setup_grok_library_path();
}
+#else
+ LIBDCP_UNUSED(what);
+#endif
}
boost::optional<boost::filesystem::path> _last_parent;
@@ -426,7 +424,7 @@ class App : public wxApp
{
wxInitAllImageHandlers ();
- SetAppName (_("DCP-o-matic Batch Converter"));
+ SetAppName(variant::wx::dcpomatic_batch_converter());
is_batch_converter = true;
Config::FailedToLoad.connect(boost::bind(&App::config_failed_to_load, this, _1));
@@ -462,7 +460,7 @@ class App : public wxApp
*/
Config::drop ();
- _frame = new DOMFrame (_("DCP-o-matic Batch Converter"));
+ _frame = new DOMFrame(variant::wx::dcpomatic_batch_converter());
SetTopWindow (_frame);
_frame->Maximize ();
if (splash) {
@@ -475,7 +473,11 @@ class App : public wxApp
server->StartJob.connect(bind(&DOMFrame::start_job, _frame, _1));
new thread (boost::bind (&JobServer::run, server));
} catch (boost::system::system_error& e) {
- error_dialog(_frame, _("Could not listen for new batch jobs. Perhaps another instance of the DCP-o-matic Batch Converter is running."));
+ error_dialog(
+ _frame,
+ variant::wx::insert_dcpomatic_batch_converter(
+ _("Could not listen for new batch jobs. Perhaps another instance of the %s is running.")
+ ));
}
signal_manager = new wxSignalManager (this);
@@ -498,6 +500,11 @@ class App : public wxApp
}
}
+#ifdef DCPOMATIC_GROK
+ grk_plugin::setMessengerLogger(new grk_plugin::GrokLogger("[GROK] "));
+ setup_grok_library_path();
+#endif
+
return true;
}
diff --git a/src/tools/dcpomatic_cli.cc b/src/tools/dcpomatic_cli.cc
index 5a7ec3c72..8b55d6205 100644
--- a/src/tools/dcpomatic_cli.cc
+++ b/src/tools/dcpomatic_cli.cc
@@ -25,9 +25,12 @@
#include "lib/cross.h"
#include "lib/dcpomatic_log.h"
#include "lib/encode_server_finder.h"
-#include "lib/ffmpeg_encoder.h"
+#include "lib/ffmpeg_film_encoder.h"
#include "lib/film.h"
#include "lib/filter.h"
+#ifdef DCPOMATIC_GROK
+#include "lib/grok/context.h"
+#endif
#include "lib/hints.h"
#include "lib/job_manager.h"
#include "lib/json_server.h"
@@ -37,6 +40,7 @@
#include "lib/signal_manager.h"
#include "lib/transcode_job.h"
#include "lib/util.h"
+#include "lib/variant.h"
#include "lib/version.h"
#include "lib/video_content.h"
#include <dcp/filesystem.h>
@@ -63,7 +67,7 @@ static void
help (string n)
{
cerr << "Syntax: " << n << " [OPTION] [<FILM>]\n"
- << " -v, --version show DCP-o-matic version\n"
+ << variant::insert_dcpomatic(" -v, --version show %1 version\n")
<< " -h, --help show this help\n"
<< " -f, --flags show flags passed to C++ compiler on build\n"
<< " -n, --no-progress do not print progress to stdout\n"
@@ -72,7 +76,7 @@ help (string n)
<< " -j, --json <port> run a JSON server on the specified port\n"
<< " -k, --keep-going keep running even when the job is complete\n"
<< " -s, --servers <file> specify servers to use in a text file\n"
- << " -l, --list-servers just display a list of encoding servers that DCP-o-matic is configured to use; don't encode\n"
+ << variant::insert_dcpomatic(" -l, --list-servers just display a list of encoding servers that %1 is configured to use; don't encode\n")
<< " -d, --dcp-path echo DCP's path to stdout on successful completion (implies -n)\n"
<< " -c, --config <dir> directory containing config.xml and cinemas.xml\n"
<< " --dump just dump a summary of the film's settings; don't encode\n"
@@ -90,7 +94,7 @@ print_dump (shared_ptr<Film> film)
{
cout << film->dcp_name (true) << "\n"
<< film->container()->container_nickname() << " at " << ((film->resolution() == Resolution::TWO_K) ? "2K" : "4K") << "\n"
- << (film->j2k_bandwidth() / 1000000) << "Mbit/s" << "\n"
+ << (film->video_bit_rate(film->video_encoding()) / 1000000) << "Mbit/s" << "\n"
<< "Duration " << (film->length().timecode(film->video_frame_rate())) << "\n"
<< "Output " << film->video_frame_rate() << "fps " << (film->three_d() ? "3D" : "2D") << " " << (film->audio_frame_rate() / 1000) << "kHz\n"
<< (film->interop() ? "Inter-Op" : "SMPTE") << " " << (film->encrypted() ? "encrypted" : "unencrypted") << "\n";
@@ -417,7 +421,7 @@ main (int argc, char* argv[])
signal_manager = new SignalManager ();
if (no_remote || export_format) {
- EncodeServerFinder::instance()->stop ();
+ EncodeServerFinder::drop();
}
if (json_port) {
@@ -500,6 +504,11 @@ main (int argc, char* argv[])
}
}
+#ifdef DCPOMATIC_GROK
+ grk_plugin::setMessengerLogger(new grk_plugin::GrokLogger("[GROK] "));
+ setup_grok_library_path();
+#endif
+
if (progress) {
if (export_format) {
cout << "\nExporting " << film->name() << "\n";
@@ -513,7 +522,7 @@ main (int argc, char* argv[])
if (export_format) {
auto job = std::make_shared<TranscodeJob>(film, behaviour);
job->set_encoder (
- std::make_shared<FFmpegEncoder> (
+ std::make_shared<FFmpegFilmEncoder>(
film, job, *export_filename, *export_format == "mp4" ? ExportFormat::H264_AAC : ExportFormat::PRORES_HQ, false, false, false, 23
)
);
diff --git a/src/tools/dcpomatic_combiner.cc b/src/tools/dcpomatic_combiner.cc
index c135184e6..26ca0022b 100644
--- a/src/tools/dcpomatic_combiner.cc
+++ b/src/tools/dcpomatic_combiner.cc
@@ -23,11 +23,14 @@
#include "wx/dir_picker_ctrl.h"
#include "wx/editable_list.h"
#include "wx/wx_signal_manager.h"
+#include "wx/wx_util.h"
+#include "wx/wx_variant.h"
#include "lib/combine_dcp_job.h"
#include "lib/config.h"
#include "lib/constants.h"
#include "lib/cross.h"
#include "lib/job_manager.h"
+#include "lib/util.h"
#include <dcp/combine.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/filepicker.h>
@@ -175,7 +178,7 @@ private:
auto jm = JobManager::instance ();
jm->add (make_shared<CombineDCPJob>(_inputs, output, wx_to_std(_annotation_text->GetValue())));
- bool const ok = display_progress(_("DCP-o-matic Combiner"), _("Combining DCPs"));
+ bool const ok = display_progress(variant::wx::dcpomatic_combiner(), _("Combining DCPs"));
if (!ok) {
return;
}
@@ -219,7 +222,7 @@ public:
Config::FailedToLoad.connect(boost::bind(&App::config_failed_to_load, this, _1));
Config::Warning.connect (boost::bind (&App::config_warning, this, _1));
- SetAppName (_("DCP-o-matic Combiner"));
+ SetAppName(variant::wx::dcpomatic_combiner());
if (!wxApp::OnInit()) {
return false;
@@ -253,7 +256,7 @@ public:
*/
Config::drop ();
- _frame = new DOMFrame(_("DCP-o-matic Combiner"));
+ _frame = new DOMFrame(variant::wx::dcpomatic_combiner());
SetTopWindow (_frame);
_frame->Show ();
@@ -263,7 +266,7 @@ public:
}
catch (exception& e)
{
- error_dialog(nullptr, wxString::Format("DCP-o-matic Combiner could not start."), std_to_wx(e.what()));
+ error_dialog(nullptr, wxString::Format(_("%s could not start (%s)"), variant::wx::dcpomatic_combiner()), std_to_wx(e.what()));
return false;
}
@@ -294,21 +297,23 @@ public:
error_dialog (
0,
wxString::Format(
- _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what()),
- std_to_wx (e.file().string().c_str ())
+ _("An exception occurred: %s (%s)\n\n%s"),
+ std_to_wx(e.what()),
+ std_to_wx(e.file().string().c_str()),
+ wx::report_problem()
)
);
} catch (exception& e) {
error_dialog (
0,
wxString::Format(
- _("An exception occurred: %s.\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what ())
+ _("An exception occurred: %s\n\n%s"),
+ std_to_wx(e.what()),
+ wx::report_problem()
)
);
} catch (...) {
- error_dialog (nullptr, _("An unknown exception occurred.") + " " + REPORT_PROBLEM);
+ error_dialog(nullptr, _("An unknown exception occurred.") + " " + wx::report_problem());
}
}
diff --git a/src/tools/dcpomatic_disk.cc b/src/tools/dcpomatic_disk.cc
index 395b60a88..a20a062e3 100644
--- a/src/tools/dcpomatic_disk.cc
+++ b/src/tools/dcpomatic_disk.cc
@@ -29,6 +29,7 @@
#include "wx/wx_util.h"
#include "wx/wx_signal_manager.h"
#include "wx/wx_util.h"
+#include "wx/wx_variant.h"
#include "lib/config.h"
#include "lib/constants.h"
#include "lib/copy_to_drive_job.h"
@@ -43,6 +44,7 @@
#include <dcp/filesystem.h>
#include <dcp/warnings.h>
#include <wx/cmdline.h>
+#include <wx/progdlg.h>
#include <wx/wx.h>
LIBDCP_DISABLE_WARNINGS
#include <boost/process.hpp>
@@ -130,7 +132,7 @@ public:
auto grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
int r = 0;
- add_label_to_sizer (grid, overall_panel, _("DCP"), true, wxGBPosition(r, 0));
+ add_label_to_sizer(grid, overall_panel, _("DCPs"), true, wxGBPosition(r, 0));
auto dcp_sizer = new wxBoxSizer (wxHORIZONTAL);
auto dcps = new EditableList<boost::filesystem::path, DirDialogWrapper>(
overall_panel,
@@ -149,9 +151,9 @@ public:
add_label_to_sizer (grid, overall_panel, _("Drive"), true, wxGBPosition(r, 0));
auto drive_sizer = new wxBoxSizer (wxHORIZONTAL);
_drive = new wxChoice (overall_panel, wxID_ANY);
- drive_sizer->Add (_drive, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
+ drive_sizer->Add(_drive, 1, wxTOP, 2);
_drive_refresh = new wxButton (overall_panel, wxID_ANY, _("Refresh"));
- drive_sizer->Add (_drive_refresh, 0);
+ drive_sizer->Add(_drive_refresh, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
grid->Add (drive_sizer, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
++r;
@@ -159,7 +161,7 @@ public:
grid->Add (_jobs, wxGBPosition(r, 0), wxGBSpan(6, 2), wxEXPAND);
r += 6;
- _copy = new wxButton (overall_panel, wxID_ANY, _("Copy DCP"));
+ _copy = new wxButton(overall_panel, wxID_ANY, _("Copy DCPs"));
grid->Add (_copy, wxGBPosition(r, 0), wxGBSpan(1, 2), wxEXPAND);
++r;
@@ -172,7 +174,7 @@ public:
_sizer->Add (grid, 1, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER);
overall_panel->SetSizer (_sizer);
Fit ();
- SetSize (1024, GetSize().GetHeight() + 32);
+ SetSize(768, GetSize().GetHeight() + 32);
/* XXX: this is a hack, but I expect we'll need logs and I'm not sure if there's
* a better place to put them.
@@ -181,7 +183,17 @@ public:
dcpomatic_log->set_types (dcpomatic_log->types() | LogEntry::TYPE_DISK);
LOG_DISK("dcpomatic_disk %1 started", dcpomatic_git_commit);
- drive_refresh ();
+ {
+ int constexpr seconds_to_look = 3;
+ wxProgressDialog find_drives_progress(_("Disk Writer"), _("Finding disks"), seconds_to_look * 4, this);
+ for (auto i = 0; i < seconds_to_look * 4; ++i) {
+ if (!find_drives_progress.Update(i)) {
+ break;
+ }
+ drive_refresh();
+ dcpomatic_sleep_milliseconds(250);
+ }
+ }
Bind (wxEVT_SIZE, boost::bind(&DOMFrame::sized, this, _1));
Bind (wxEVT_CLOSE_WINDOW, boost::bind(&DOMFrame::close, this, _1));
@@ -270,6 +282,7 @@ private:
}
ev.Skip ();
+ JobManager::drop ();
}
void copy ()
@@ -313,7 +326,7 @@ private:
#if defined(DCPOMATIC_WINDOWS)
auto m = make_wx<MessageDialog>(
this,
- _("DCP-o-matic Disk Writer"),
+ variant::wx::dcpomatic_disk_writer(),
_("Do you see a 'User Account Control' dialogue asking about dcpomatic2_disk_writer.exe? If so, click 'Yes', then try again.")
);
m->ShowModal ();
@@ -321,8 +334,8 @@ private:
#elif defined(DCPOMATIC_OSX)
auto m = make_wx<MessageDialog>(
this,
- _("DCP-o-matic Disk Writer"),
- _("Did you install the DCP-o-matic Disk Writer.pkg from the .dmg? Please check and try again.")
+ variant::wx::dcpomatic_disk_writer(),
+ variant::wx::insert_dcpomatic(_("Did you install the %s Disk Writer.pkg from the .dmg? Please check and try again."))
);
m->ShowModal ();
return;
@@ -354,7 +367,7 @@ private:
if (!reply || reply->type() != DiskWriterBackEndResponse::Type::OK) {
auto m = make_wx<MessageDialog>(
this,
- _("DCP-o-matic Disk Writer"),
+ variant::wx::dcpomatic_disk_writer(),
wxString::Format(
_("The drive %s could not be unmounted.\nClose any application that is using it, then try again. (%s)"),
std_to_wx(drive.description()),
@@ -442,7 +455,7 @@ public:
Config::FailedToLoad.connect (boost::bind (&App::config_failed_to_load, this));
Config::Warning.connect (boost::bind (&App::config_warning, this, _1));
- SetAppName (_("DCP-o-matic Disk Writer"));
+ SetAppName(variant::wx::dcpomatic_disk_writer());
if (!wxApp::OnInit()) {
return false;
@@ -483,12 +496,17 @@ public:
return false;
}
if (!warning->confirmed()) {
- message_dialog(nullptr, _("You did not correctly confirm that you read the warning that was just shown. DCP-o-matic Disk Writer will close now. Please try again."));
+ message_dialog(
+ nullptr,
+ variant::wx::insert_dcpomatic_disk_writer(
+ _("You did not correctly confirm that you read the warning that was just shown. %s will close now. Please try again.")
+ )
+ );
return false;
}
}
- _frame = new DOMFrame (_("DCP-o-matic Disk Writer"));
+ _frame = new DOMFrame(variant::wx::dcpomatic_disk_writer());
SetTopWindow (_frame);
_frame->Show ();
@@ -502,7 +520,7 @@ public:
}
catch (exception& e)
{
- error_dialog (0, wxString::Format ("DCP-o-matic could not start."), std_to_wx(e.what()));
+ error_dialog(nullptr, wxString::Format(_("%s could not start"), variant::wx::dcpomatic_disk_writer()), std_to_wx(e.what()));
return false;
}
@@ -551,21 +569,23 @@ public:
error_dialog (
0,
wxString::Format (
- _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what()),
- std_to_wx (e.file().string().c_str ())
+ _("An exception occurred: %s (%s)\n\n%s"),
+ std_to_wx(e.what()),
+ std_to_wx(e.file().string().c_str()),
+ wx::report_problem()
)
);
} catch (exception& e) {
error_dialog (
0,
wxString::Format (
- _("An exception occurred: %s.\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what ())
+ _("An exception occurred: %s.\n\n%s"),
+ std_to_wx(e.what()),
+ wx::report_problem()
)
);
} catch (...) {
- error_dialog (0, _("An unknown exception occurred.") + " " + REPORT_PROBLEM);
+ error_dialog(nullptr, _("An unknown exception occurred.") + " " + wx::report_problem());
}
}
diff --git a/src/tools/dcpomatic_editor.cc b/src/tools/dcpomatic_editor.cc
index 14ff6da7f..47c7bac14 100644
--- a/src/tools/dcpomatic_editor.cc
+++ b/src/tools/dcpomatic_editor.cc
@@ -24,10 +24,13 @@
#include "wx/id.h"
#include "wx/wx_signal_manager.h"
#include "wx/wx_util.h"
+#include "wx/wx_variant.h"
#include "lib/constants.h"
#include "lib/cross.h"
#include "lib/dcpomatic_log.h"
#include "lib/null_log.h"
+#include "lib/util.h"
+#include "lib/variant.h"
#include <dcp/cpl.h>
#include <dcp/dcp.h>
#include <dcp/reel.h>
@@ -290,14 +293,14 @@ class DOMFrame : public wxFrame
{
public:
DOMFrame ()
- : wxFrame(nullptr, -1, _("DCP-o-matic Editor"))
+ : wxFrame(nullptr, -1, variant::wx::dcpomatic_editor())
, _main_sizer(new wxBoxSizer(wxVERTICAL))
{
dcpomatic_log = make_shared<NullLog>();
#if defined(DCPOMATIC_WINDOWS)
maybe_open_console();
- std::cout << "DCP-o-matic Editor is starting." << "\n";
+ std::cout << variant::dcpomatic_editor() << " is starting." << "\n";
#endif
auto bar = new wxMenuBar;
@@ -360,7 +363,7 @@ private:
auto help = new wxMenu;
#ifdef __WXOSX__
- help->Append (wxID_ABOUT, _("About DCP-o-matic"));
+ help->Append(wxID_ABOUT, variant::wx::insert_dcpomatic_editor(_("About %s")));
#else
help->Append (wxID_ABOUT, _("About"));
#endif
@@ -444,7 +447,7 @@ private:
splash = maybe_show_splash ();
- SetAppName (_("DCP-o-matic Editor"));
+ SetAppName(variant::wx::dcpomatic_editor());
if (!wxApp::OnInit()) {
return false;
@@ -495,7 +498,7 @@ private:
if (splash) {
splash->Destroy ();
}
- error_dialog (0, _("DCP-o-matic Editor could not start."), std_to_wx(e.what()));
+ error_dialog(nullptr, variant::wx::insert_dcpomatic_editor(_("%s could not start.")), std_to_wx(e.what()));
}
return true;
@@ -524,21 +527,23 @@ private:
error_dialog (
0,
wxString::Format (
- _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what()),
- std_to_wx (e.file().string().c_str ())
+ _("An exception occurred: %s (%s)\n\n%s"),
+ std_to_wx(e.what()),
+ std_to_wx(e.file().string().c_str()),
+ wx::report_problem()
)
);
} catch (exception& e) {
error_dialog (
0,
wxString::Format (
- _("An exception occurred: %s.\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what ())
+ _("An exception occurred: %s\n\n%s"),
+ std_to_wx(e.what()),
+ wx::report_problem()
)
);
} catch (...) {
- error_dialog (0, _("An unknown exception occurred.") + " " + REPORT_PROBLEM);
+ error_dialog(nullptr, _("An unknown exception occurred.") + " " + wx::report_problem());
}
}
diff --git a/src/tools/dcpomatic_kdm.cc b/src/tools/dcpomatic_kdm.cc
index 9adef0ab0..af16f57aa 100644
--- a/src/tools/dcpomatic_kdm.cc
+++ b/src/tools/dcpomatic_kdm.cc
@@ -37,6 +37,7 @@
#include "wx/static_text.h"
#include "wx/wx_signal_manager.h"
#include "wx/wx_util.h"
+#include "wx/wx_variant.h"
#include "lib/cinema.h"
#include "lib/collator.h"
#include "lib/compose.hpp"
@@ -52,6 +53,8 @@
#include "lib/kdm_with_metadata.h"
#include "lib/screen.h"
#include "lib/send_kdm_email_job.h"
+#include "lib/util.h"
+#include "lib/variant.h"
#include <dcp/encrypted_kdm.h>
#include <dcp/decrypted_kdm.h>
#include <dcp/exceptions.h>
@@ -125,7 +128,7 @@ public:
setvbuf(hf_in, NULL, _IONBF, 128);
*stdin = *hf_in;
- std::cout << "DCP-o-matic KDM creator is starting." << "\n";
+ std::cout << variant::insert_dcpomatic_kdm_creator("%1 is starting.\n");
}
#endif
@@ -234,7 +237,7 @@ public:
/* Instantly save any config changes when using a DCP-o-matic GUI */
Config::instance()->Changed.connect (boost::bind (&Config::write, Config::instance ()));
- _screens->ScreensChanged.connect (boost::bind (&DOMFrame::setup_sensitivity, this));
+ _screens->ScreensChanged.connect(boost::bind(&DOMFrame::screens_changed, this));
_create->Bind (wxEVT_BUTTON, bind (&DOMFrame::create_kdms, this));
_dkdm->Bind(wxEVT_TREE_SEL_CHANGED, boost::bind(&DOMFrame::dkdm_selection_changed, this));
_dkdm->Bind (wxEVT_TREE_BEGIN_DRAG, boost::bind (&DOMFrame::dkdm_begin_drag, this, _1));
@@ -302,11 +305,13 @@ private:
wxMenu* help = new wxMenu;
#ifdef __WXOSX__
- help->Append (wxID_ABOUT, _("About DCP-o-matic"));
+ help->Append(wxID_ABOUT, variant::wx::insert_dcpomatic_kdm_creator(_("About %s")));
#else
help->Append (wxID_ABOUT, _("About"));
#endif
- help->Append (ID_help_report_a_problem, _("Report a problem..."));
+ if (variant::show_report_a_problem()) {
+ help->Append(ID_help_report_a_problem, _("Report a problem..."));
+ }
m->Append (file, _("&File"));
#ifndef __WXOSX__
@@ -400,11 +405,15 @@ private:
return kdm;
};
+ CinemaList cinemas;
+
for (auto i: _screens->screens()) {
auto kdm = kdm_for_screen(
make_kdm,
- i,
+ i.first,
+ *cinemas.cinema(i.first),
+ *cinemas.screen(i.second),
_timing->from(),
_timing->until(),
_output->formulation(),
@@ -460,7 +469,13 @@ private:
if (e.starts_too_early()) {
error_dialog(this, _("The KDM start period is before (or close to) the start of the signing certificate's validity period. Use a later start time for this KDM."));
} else {
- error_dialog(this, _("The KDM end period is after (or close to) the end of the signing certificates' validity period. Either use an earlier end time for this KDM or re-create your signing certificates in the DCP-o-matic preferences window."));
+ error_dialog(
+ this,
+ variant::wx::insert_dcpomatic_kdm_creator(
+ _("The KDM end period is after (or close to) the end of the signing certificates' validity period. "
+ "Either use an earlier end time for this KDM or re-create your signing certificates in the %s preferences window.")
+ )
+ );
}
return;
} catch (dcp::NotEncryptedError& e) {
@@ -788,6 +803,12 @@ private:
update_dkdm_view();
}
+ void screens_changed()
+ {
+ _timing->suggest_utc_offset(_screens->best_utc_offset());
+ setup_sensitivity();
+ }
+
wxPreferencesEditor* _config_dialog;
ScreensPanel* _screens;
KDMTimingPanel* _timing;
@@ -841,7 +862,7 @@ private:
splash = maybe_show_splash ();
- SetAppName (_("DCP-o-matic KDM Creator"));
+ SetAppName(variant::wx::dcpomatic_kdm_creator());
if (!wxApp::OnInit()) {
return false;
@@ -875,7 +896,7 @@ private:
*/
Config::drop ();
- _frame = new DOMFrame (_("DCP-o-matic KDM Creator"));
+ _frame = new DOMFrame(variant::wx::dcpomatic_kdm_creator());
SetTopWindow (_frame);
_frame->Maximize ();
if (splash) {
@@ -892,7 +913,7 @@ private:
if (splash) {
splash->Destroy ();
}
- error_dialog (0, _("DCP-o-matic could not start"), std_to_wx(e.what()));
+ error_dialog(nullptr, variant::wx::insert_dcpomatic_kdm_creator(_("%s could not start")), std_to_wx(e.what()));
}
return true;
@@ -905,23 +926,25 @@ private:
throw;
} catch (FileError& e) {
error_dialog (
- 0,
+ nullptr,
wxString::Format (
- _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what()),
- std_to_wx (e.file().string().c_str ())
+ _("An exception occurred: %s (%s)\n\n%s"),
+ std_to_wx(e.what()),
+ std_to_wx(e.file().string().c_str()),
+ wx::report_problem()
)
);
} catch (exception& e) {
error_dialog (
nullptr,
wxString::Format (
- _("An exception occurred: %s.\n\n") + " " + REPORT_PROBLEM,
- std_to_wx(e.what())
+ _("An exception occurred: %s.\n\n%s"),
+ std_to_wx(e.what()),
+ wx::report_problem()
)
);
} catch (...) {
- error_dialog (0, _("An unknown exception occurred.") + " " + REPORT_PROBLEM);
+ error_dialog(nullptr, _("An unknown exception occurred.") + " " + wx::report_problem());
}
/* This will terminate the program */
@@ -930,7 +953,7 @@ private:
void OnUnhandledException () override
{
- error_dialog (nullptr, _("An unknown exception occurred.") + " " + REPORT_PROBLEM);
+ error_dialog(nullptr, _("An unknown exception occurred.") + " " + wx::report_problem());
}
void idle ()
diff --git a/src/tools/dcpomatic_player.cc b/src/tools/dcpomatic_player.cc
index e65839521..55cd00027 100644
--- a/src/tools/dcpomatic_player.cc
+++ b/src/tools/dcpomatic_player.cc
@@ -32,11 +32,12 @@
#include "wx/system_information_dialog.h"
#include "wx/timer_display.h"
#include "wx/update_dialog.h"
-#include "wx/verify_dcp_dialog.h"
#include "wx/verify_dcp_progress_dialog.h"
+#include "wx/verify_dcp_result_dialog.h"
#include "wx/wx_ptr.h"
#include "wx/wx_signal_manager.h"
#include "wx/wx_util.h"
+#include "wx/wx_variant.h"
#include "lib/compose.hpp"
#include "lib/config.h"
#include "lib/constants.h"
@@ -50,9 +51,11 @@
#include "lib/file_log.h"
#include "lib/film.h"
#include "lib/font_config.h"
+#include "lib/http_server.h"
#include "lib/image.h"
#include "lib/image_jpeg.h"
#include "lib/image_png.h"
+#include "lib/internal_player_server.h"
#include "lib/internet.h"
#include "lib/job.h"
#include "lib/job_manager.h"
@@ -64,6 +67,7 @@
#include "lib/server.h"
#include "lib/text_content.h"
#include "lib/update_checker.h"
+#include "lib/variant.h"
#include "lib/verify_dcp_job.h"
#include "lib/video_content.h"
#include <dcp/cpl.h>
@@ -198,7 +202,7 @@ public:
DOMFrame ()
- : wxFrame (nullptr, -1, _("DCP-o-matic Player"))
+ : wxFrame(nullptr, -1, variant::wx::dcpomatic_player())
, _mode (Config::instance()->player_mode())
/* Use a panel as the only child of the Frame so that we avoid
the dark-grey background on Windows.
@@ -211,7 +215,7 @@ public:
#if defined(DCPOMATIC_WINDOWS)
maybe_open_console ();
- cout << "DCP-o-matic Player is starting." << "\n";
+ cout << variant::dcpomatic_player() << " is starting." << "\n";
#endif
auto bar = new wxMenuBar;
@@ -259,7 +263,6 @@ public:
}
_controls->set_film(_viewer.film());
_viewer.set_dcp_decode_reduction(Config::instance()->decode_reduction());
- _viewer.set_optimise_for_j2k(true);
_viewer.PlaybackPermitted.connect(bind(&DOMFrame::playback_permitted, this));
_viewer.TooManyDropped.connect(bind(&DOMFrame::too_many_frames_dropped, this));
_info = new PlayerInformation (_overall_panel, _viewer);
@@ -309,11 +312,15 @@ public:
_stress.LoadDCP.connect (boost::bind(&DOMFrame::load_dcp, this, _1));
+ setup_internal_player_server();
+ setup_http_server();
+
SetDropTarget(new DCPDropTarget(this));
}
~DOMFrame ()
{
+ stop_http_server();
/* It's important that this is stopped before our frame starts destroying its children,
* otherwise UI elements that it depends on will disappear from under it.
*/
@@ -398,7 +405,7 @@ public:
auto job = make_shared<ExamineContentJob>(_film, dcp);
_examine_job_connection = job->Finished.connect(bind(&DOMFrame::add_dcp_to_film, this, weak_ptr<Job>(job), weak_ptr<Content>(dcp)));
JobManager::instance()->add (job);
- bool const ok = display_progress (_("DCP-o-matic Player"), _("Loading content"));
+ bool const ok = display_progress(variant::wx::dcpomatic_player(), _("Loading content"));
if (!ok || !report_errors_from_last_job(this)) {
return;
}
@@ -406,14 +413,26 @@ public:
if (dcp->video_frame_rate()) {
_film->set_video_frame_rate(dcp->video_frame_rate().get(), true);
}
+ switch (dcp->video_encoding()) {
+ case VideoEncoding::JPEG2000:
+ _viewer.set_optimisation(Optimisation::JPEG2000);
+ break;
+ case VideoEncoding::MPEG2:
+ _viewer.set_optimisation(Optimisation::MPEG2);
+ break;
+ case VideoEncoding::COUNT:
+ DCPOMATIC_ASSERT(false);
+ }
} catch (ProjectFolderError &) {
error_dialog (
this,
wxString::Format(_("Could not load a DCP from %s"), std_to_wx(dir.string())),
- _(
- "This looks like a DCP-o-matic project folder, which cannot be loaded into the player. "
- "Choose the DCP folder inside the DCP-o-matic project folder if that's what you want to play."
- )
+ wxString::Format(
+ _("This looks like a %s project folder, which cannot be loaded into the player. "
+ "Choose the DCP folder inside the %s project folder if that's what you want to play."),
+ variant::wx::dcpomatic(),
+ variant::wx::dcpomatic()
+ )
);
} catch (dcp::ReadError& e) {
error_dialog (this, wxString::Format(_("Could not load a DCP from %s"), std_to_wx(dir.string())), std_to_wx(e.what()));
@@ -523,6 +542,25 @@ public:
_stress.load_script (path);
}
+ void idle()
+ {
+ if (_http_server) {
+ struct timeval now;
+ gettimeofday(&now, 0);
+ auto time_since_last_update = (now.tv_sec + now.tv_usec / 1e6) - (_last_http_server_update.tv_sec + _last_http_server_update.tv_usec / 1e6);
+ if (time_since_last_update > 0.25) {
+ _http_server->set_playing(_viewer.playing());
+ if (auto dcp = _viewer.dcp()) {
+ _http_server->set_dcp_name(dcp->name());
+ } else {
+ _http_server->set_dcp_name("");
+ }
+ _http_server->set_position(_viewer.position());
+ _last_http_server_update = now;
+ }
+ }
+ }
+
private:
void examine_content ()
@@ -609,19 +647,23 @@ private:
auto help = new wxMenu;
#ifdef __WXOSX__
- help->Append (wxID_ABOUT, _("About DCP-o-matic"));
+ help->Append(wxID_ABOUT, variant::wx::insert_dcpomatic_player(_("About %s")));
#else
help->Append (wxID_ABOUT, _("About"));
#endif
- help->Append (ID_help_report_a_problem, _("Report a problem..."));
+ if (variant::show_report_a_problem()) {
+ help->Append(ID_help_report_a_problem, _("Report a problem..."));
+ }
m->Append (_file_menu, _("&File"));
+ if (!Config::instance()->player_restricted_menus()) {
#ifndef __WXOSX__
- m->Append (edit, _("&Edit"));
+ m->Append (edit, _("&Edit"));
#endif
- m->Append (view, _("&View"));
- m->Append (tools, _("&Tools"));
- m->Append (help, _("&Help"));
+ m->Append (view, _("&View"));
+ m->Append (tools, _("&Tools"));
+ m->Append (help, _("&Help"));
+ }
}
void file_open ()
@@ -680,7 +722,7 @@ private:
DCPOMATIC_ASSERT (dcp);
dcp->add_ov (wx_to_std(c->GetPath()));
JobManager::instance()->add(make_shared<ExamineContentJob>(_film, dcp));
- bool const ok = display_progress (_("DCP-o-matic Player"), _("Loading content"));
+ bool const ok = display_progress(variant::wx::dcpomatic_player(), _("Loading content"));
if (!ok || !report_errors_from_last_job(this)) {
return;
}
@@ -925,12 +967,12 @@ private:
DCPOMATIC_ASSERT (dcp);
auto job = make_shared<VerifyDCPJob>(dcp->directories(), _kdms);
- VerifyDCPProgressDialog progress(this, _("DCP-o-matic Player"));
+ VerifyDCPProgressDialog progress(this, variant::wx::dcpomatic_player());
bool const completed = progress.run(job);
progress.Close();
if (completed) {
- VerifyDCPDialog dialog(this, job);
+ VerifyDCPResultDialog dialog(this, job);
dialog.ShowModal();
}
}
@@ -989,9 +1031,9 @@ private:
auto dialog = make_wx<UpdateDialog>(this, uc->stable (), uc->test ());
dialog->ShowModal ();
} else if (uc->state() == UpdateChecker::State::FAILED) {
- error_dialog (this, _("The DCP-o-matic download server could not be contacted."));
+ error_dialog(this, variant::wx::insert_dcpomatic(_("The %s download server could not be contacted.")));
} else {
- error_dialog (this, _("There are no new versions of DCP-o-matic available."));
+ error_dialog(this, variant::wx::insert_dcpomatic(_("There are no new versions of %s available.")));
}
_update_news_requested = false;
@@ -1020,6 +1062,48 @@ private:
}
update_from_config (prop);
+
+ setup_http_server();
+ }
+
+ void stop_http_server()
+ {
+ if (_http_server) {
+ _http_server->stop();
+ _http_server_thread.join();
+ _http_server.reset();
+ }
+ }
+
+ void setup_http_server()
+ {
+ stop_http_server();
+
+ auto config = Config::instance();
+ try {
+ if (config->enable_player_http_server()) {
+ _http_server.reset(new HTTPServer(config->player_http_server_port()));
+ _http_server->Play.connect(boost::bind(&FilmViewer::start, &_viewer));
+ _http_server->Stop.connect(boost::bind(&FilmViewer::stop, &_viewer));
+ _http_server_thread = boost::thread(boost::bind(&HTTPServer::run, _http_server.get()));
+ }
+ } catch (std::exception& e) {
+ LOG_DEBUG_PLAYER("Failed to start player HTTP server (%1)", e.what());
+ }
+ }
+
+ void setup_internal_player_server()
+ {
+ try {
+ auto server = new InternalPlayerServer();
+ server->LoadDCP.connect(boost::bind(&DOMFrame::load_dcp, this, _1));
+ new thread(boost::bind(&InternalPlayerServer::run, server));
+ } catch (std::exception& e) {
+ /* This is not the end of the world; probably a failure to bind the server socket
+ * because there's already another player running.
+ */
+ LOG_DEBUG_PLAYER("Failed to start internal player server (%1)", e.what());
+ }
}
void update_from_config (Config::Property prop)
@@ -1138,6 +1222,9 @@ private:
PlayerStressTester _stress;
/** KDMs that have been loaded, so that we can pass them to the verifier */
std::vector<boost::filesystem::path> _kdms;
+ boost::thread _http_server_thread;
+ std::unique_ptr<HTTPServer> _http_server;
+ struct timeval _last_http_server_update = { 0, 0 };
};
static const wxCmdLineEntryDesc command_line_description[] = {
@@ -1147,34 +1234,6 @@ static const wxCmdLineEntryDesc command_line_description[] = {
{ wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
};
-class PlayServer : public Server
-{
-public:
- explicit PlayServer (DOMFrame* frame)
- : Server (PLAYER_PLAY_PORT)
- , _frame (frame)
- {}
-
- void handle (shared_ptr<Socket> socket) override
- {
- try {
- uint32_t const length = socket->read_uint32 ();
- if (length > 65536) {
- return;
- }
- scoped_array<char> buffer (new char[length]);
- socket->read (reinterpret_cast<uint8_t*> (buffer.get()), length);
- string s (buffer.get());
- signal_manager->when_idle (bind (&DOMFrame::load_dcp, _frame, s));
- socket->write (reinterpret_cast<uint8_t const *> ("OK"), 3);
- } catch (...) {
-
- }
- }
-
-private:
- DOMFrame* _frame;
-};
/** @class App
* @brief The magic App class for wxWidgets.
@@ -1203,7 +1262,7 @@ private:
splash = maybe_show_splash ();
- SetAppName (_("DCP-o-matic Player"));
+ SetAppName(variant::wx::dcpomatic_player());
if (!wxApp::OnInit()) {
return false;
@@ -1248,16 +1307,6 @@ private:
}
_frame->Show ();
- try {
- auto server = new PlayServer (_frame);
- new thread (boost::bind (&PlayServer::run, server));
- } catch (std::exception& e) {
- /* This is not the end of the world; probably a failure to bind the server socket
- * because there's already another player running.
- */
- LOG_DEBUG_PLAYER ("Failed to start play server (%1)", e.what());
- }
-
if (!_dcp_to_load.empty() && dcp::filesystem::is_directory(_dcp_to_load)) {
try {
_frame->load_dcp (_dcp_to_load);
@@ -1285,7 +1334,7 @@ private:
if (splash) {
splash->Destroy ();
}
- error_dialog (0, _("DCP-o-matic Player could not start."), std_to_wx(e.what()));
+ error_dialog(nullptr, variant::wx::insert_dcpomatic_player(_("%s could not start")), std_to_wx(e.what()));
}
return true;
@@ -1323,21 +1372,23 @@ private:
error_dialog (
0,
wxString::Format (
- _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what()),
- std_to_wx (e.file().string().c_str ())
+ _("An exception occurred: %s (%s)\n\n%s"),
+ std_to_wx(e.what()),
+ std_to_wx(e.file().string().c_str()),
+ wx::report_problem()
)
);
} catch (exception& e) {
error_dialog (
0,
wxString::Format (
- _("An exception occurred: %s.\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what ())
+ _("An exception occurred: %s\n\n%s"),
+ std_to_wx(e.what()),
+ wx::report_problem()
)
);
} catch (...) {
- error_dialog (0, _("An unknown exception occurred.") + " " + REPORT_PROBLEM);
+ error_dialog(nullptr, _("An unknown exception occurred.") + " " + wx::report_problem());
}
}
@@ -1357,6 +1408,9 @@ private:
void idle ()
{
signal_manager->ui_idle ();
+ if (_frame) {
+ _frame->idle();
+ }
}
void config_failed_to_load ()
diff --git a/src/tools/dcpomatic_playlist.cc b/src/tools/dcpomatic_playlist.cc
index a373d81e6..a50fab37d 100644
--- a/src/tools/dcpomatic_playlist.cc
+++ b/src/tools/dcpomatic_playlist.cc
@@ -25,6 +25,7 @@
#include "wx/playlist_editor_config_dialog.h"
#include "wx/wx_signal_manager.h"
#include "wx/wx_util.h"
+#include "wx/wx_variant.h"
#include "lib/config.h"
#include "lib/constants.h"
#include "lib/cross.h"
@@ -567,34 +568,36 @@ private:
void setup_menu (wxMenuBar* m)
{
- auto file = new wxMenu;
-#ifdef __WXOSX__
- file->Append(wxID_PREFERENCES, _("&Preferences...\tCtrl-,"));
- file->Append (wxID_EXIT, _("&Exit"));
+#ifdef DCPOMATIC_OSX
+ auto help = new wxMenu;
+ /* These just need to be appended somewhere, it seems - they magically
+ * get moved to the right place.
+ */
+ if (!Config::instance()->playlist_editor_restricted_menus()) {
+ help->Append(wxID_PREFERENCES, _("&Preferences...\tCtrl-,"));
+ }
+ help->Append(wxID_EXIT, _("&Exit"));
+ help->Append(wxID_ABOUT, variant::wx::insert_dcpomatic_playlist_editor(_("About %s")));
+
+ m->Append(help, _("&Help"));
#else
- file->Append (wxID_EXIT, _("&Quit"));
-#endif
+ auto file = new wxMenu;
+ file->Append(wxID_EXIT, _("&Quit"));
-#ifndef __WXOSX__
auto edit = new wxMenu;
- edit->Append (wxID_PREFERENCES, _("&Preferences...\tCtrl-P"));
-#endif
+ edit->Append(wxID_PREFERENCES, _("&Preferences...\tCtrl-P"));
auto help = new wxMenu;
-#ifdef __WXOSX__
- help->Append (wxID_ABOUT, _("About DCP-o-matic"));
-#else
- help->Append (wxID_ABOUT, _("About"));
-#endif
+ help->Append(wxID_ABOUT, _("About"));
- m->Append (file, _("&File"));
-#ifndef __WXOSX__
- m->Append (edit, _("&Edit"));
+ m->Append(file, _("&File"));
+ if (!Config::instance()->playlist_editor_restricted_menus()) {
+ m->Append(edit, _("&Edit"));
+ m->Append(help, _("&Help"));
+ }
#endif
- m->Append (help, _("&Help"));
}
-
void config_changed ()
{
try {
@@ -635,7 +638,7 @@ private:
try
{
wxInitAllImageHandlers ();
- SetAppName (_("DCP-o-matic Playlist Editor"));
+ SetAppName(variant::wx::dcpomatic_playlist_editor());
if (!wxApp::OnInit()) {
return false;
@@ -669,7 +672,7 @@ private:
*/
Config::drop ();
- _frame = new DOMFrame (_("DCP-o-matic Playlist Editor"));
+ _frame = new DOMFrame(variant::wx::dcpomatic_playlist_editor());
SetTopWindow (_frame);
_frame->Maximize ();
_frame->Show ();
@@ -681,7 +684,7 @@ private:
}
catch (exception& e)
{
- error_dialog (0, _("DCP-o-matic could not start"), std_to_wx(e.what()));
+ error_dialog(nullptr, variant::wx::insert_dcpomatic_playlist_editor(_("%s could not start %s")), std_to_wx(e.what()));
return true;
}
@@ -694,21 +697,23 @@ private:
error_dialog (
0,
wxString::Format (
- _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
- std_to_wx (e.what()),
- std_to_wx (e.file().string().c_str ())
+ _("An exception occurred: %s (%s)\n\n%s") + wx::report_problem(),
+ std_to_wx(e.what()),
+ std_to_wx(e.file().string().c_str()),
+ wx::report_problem()
)
);
} catch (exception& e) {
error_dialog (
- 0,
- wxString::Format (
- _("An exception occurred: %s.\n\n") + " " + REPORT_PROBLEM,
- std_to_wx (e.what ())
+ nullptr,
+ wxString::Format(
+ _("An exception occurred: %s\n\n%s"),
+ std_to_wx(e.what()),
+ wx::report_problem()
)
);
} catch (...) {
- error_dialog (0, _("An unknown exception occurred.") + " " + REPORT_PROBLEM);
+ error_dialog(nullptr, _("An unknown exception occurred.") + " " + wx::report_problem());
}
/* This will terminate the program */
@@ -717,7 +722,7 @@ private:
void OnUnhandledException () override
{
- error_dialog (0, _("An unknown exception occurred.") + " " + REPORT_PROBLEM);
+ error_dialog(nullptr, _("An unknown exception occurred.") + " " + wx::report_problem());
}
void idle ()
diff --git a/src/tools/dcpomatic_server.cc b/src/tools/dcpomatic_server.cc
index 9bdc688c8..5aad282fd 100644
--- a/src/tools/dcpomatic_server.cc
+++ b/src/tools/dcpomatic_server.cc
@@ -22,7 +22,13 @@
#include "wx/static_text.h"
#include "wx/wx_signal_manager.h"
#include "wx/wx_util.h"
+#include "wx/wx_variant.h"
#include "lib/config.h"
+#ifdef DCPOMATIC_GROK
+#include "lib/grok/context.h"
+#endif
+#include "lib/log.h"
+#include "lib/signaller.h"
#include "lib/cross.h"
#include "lib/dcpomatic_log.h"
#include "lib/encode_server.h"
@@ -149,7 +155,7 @@ class StatusDialog : public wxDialog
public:
StatusDialog ()
: wxDialog (
- nullptr, wxID_ANY, _("DCP-o-matic Encode Server"),
+ nullptr, wxID_ANY, variant::wx::dcpomatic_encode_server(),
wxDefaultPosition, wxDefaultSize,
#ifdef DCPOMATIC_OSX
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxSTAY_ON_TOP
@@ -327,6 +333,11 @@ private:
SetExitOnFrameDelete (false);
+#ifdef DCPOMATIC_GROK
+ grk_plugin::setMessengerLogger(new grk_plugin::GrokLogger("[GROK] "));
+ setup_grok_library_path();
+#endif
+
return true;
}
@@ -352,7 +363,7 @@ private:
error_dialog (nullptr, std_to_wx(e.what()));
wxTheApp->ExitMainLoop ();
} catch (...) {
- error_dialog (nullptr, _("An unknown error has occurred with the DCP-o-matic server."));
+ error_dialog(nullptr, variant::wx::insert_dcpomatic_encode_server(_("An unknown error has occurred with the %s.")));
wxTheApp->ExitMainLoop ();
}
}
diff --git a/src/tools/dcpomatic_server_cli.cc b/src/tools/dcpomatic_server_cli.cc
index 023099034..ea78d41a5 100644
--- a/src/tools/dcpomatic_server_cli.cc
+++ b/src/tools/dcpomatic_server_cli.cc
@@ -19,22 +19,26 @@
*/
#include "lib/config.h"
+#include "lib/config.h"
#include "lib/dcp_video.h"
+#include "lib/dcpomatic_log.h"
+#include "lib/encode_server.h"
#include "lib/exceptions.h"
-#include "lib/util.h"
-#include "lib/config.h"
-#include "lib/image.h"
#include "lib/file_log.h"
+#ifdef DCPOMATIC_GROK
+#include "lib/grok/context.h"
+#endif
+#include "lib/image.h"
#include "lib/null_log.h"
+#include "lib/util.h"
+#include "lib/variant.h"
#include "lib/version.h"
-#include "lib/encode_server.h"
-#include "lib/dcpomatic_log.h"
+#include <boost/algorithm/string.hpp>
#include <boost/array.hpp>
#include <boost/asio.hpp>
-#include <boost/algorithm/string.hpp>
#include <boost/thread.hpp>
-#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>
+#include <boost/thread/mutex.hpp>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
@@ -44,15 +48,15 @@
#include <vector>
using std::cerr;
-using std::string;
using std::cout;
using std::shared_ptr;
+using std::string;
static void
help (string n)
{
cerr << "Syntax: " << n << " [OPTION]\n"
- << " -v, --version show DCP-o-matic version\n"
+ << variant::insert_dcpomatic(" -v, --version show %1 version\n")
<< " -h, --help show this help\n"
<< " -t, --threads number of parallel encoding threads to use\n"
<< " --verbose be verbose to stdout\n"
@@ -112,13 +116,18 @@ main (int argc, char* argv[])
dcpomatic_log.reset (new FileLog("dcpomatic_server_cli.log"));
}
+#ifdef DCPOMATIC_GROK
+ setMessengerLogger(new grk_plugin::GrokLogger("[GROK] "));
+ setup_grok_library_path();
+#endif
+
EncodeServer server (verbose, num_threads);
try {
server.run ();
} catch (boost::system::system_error& e) {
if (e.code() == boost::system::errc::address_in_use) {
- cerr << program_name << ": address already in use. Is another DCP-o-matic server instance already running?\n";
+ cerr << program_name << variant::insert_dcpomatic(": address already in use. Is another %1 server instance already running?\n");
exit (EXIT_FAILURE);
}
cerr << program_name << ": " << e.what() << "\n";
diff --git a/src/tools/dcpomatic_verifier.cc b/src/tools/dcpomatic_verifier.cc
new file mode 100644
index 000000000..834ebefa1
--- /dev/null
+++ b/src/tools/dcpomatic_verifier.cc
@@ -0,0 +1,251 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+/** @file src/tools/dcpomatic_verify.cc
+ * @brief A DCP verify GUI.
+ */
+
+
+#include "wx/check_box.h"
+#include "wx/dcpomatic_button.h"
+#include "wx/dir_picker_ctrl.h"
+#include "wx/verify_dcp_progress_panel.h"
+#include "wx/verify_dcp_result_panel.h"
+#include "wx/wx_util.h"
+#include "wx/wx_variant.h"
+#include "lib/constants.h"
+#include "lib/cross.h"
+#include "lib/job_manager.h"
+#include "lib/verify_dcp_job.h"
+#include "lib/util.h"
+#include <dcp/verify_report.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/evtloop.h>
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+#ifdef __WXGTK__
+#include <X11/Xlib.h>
+#endif
+
+
+using std::exception;
+using std::make_shared;
+
+
+class DOMFrame : public wxFrame
+{
+public:
+ explicit DOMFrame(wxString const& title)
+ : wxFrame(nullptr, -1, title)
+ {
+#ifdef DCPOMATIC_WINDOWS
+ SetIcon(wxIcon(std_to_wx("id")));
+#endif
+ auto overall_sizer = new wxBoxSizer(wxVERTICAL);
+
+ auto dcp_sizer = new wxBoxSizer(wxHORIZONTAL);
+ add_label_to_sizer(dcp_sizer, this, _("DCP"), true, 0, wxALIGN_CENTER_VERTICAL);
+ _dcp = new DirPickerCtrl(this, true);
+ dcp_sizer->Add(_dcp, 1, wxEXPAND);
+ overall_sizer->Add(dcp_sizer, 0, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+
+ auto options_sizer = new wxBoxSizer(wxVERTICAL);
+ _write_log = new CheckBox(this, _("Write log to DCP folder"));
+ options_sizer->Add(_write_log, 0, wxBOTTOM, DCPOMATIC_SIZER_GAP);
+ overall_sizer->Add(options_sizer, 0, wxLEFT, DCPOMATIC_DIALOG_BORDER);
+
+ _verify = new Button(this, _("Verify"));
+ overall_sizer->Add(_verify, 0, wxEXPAND | wxLEFT | wxRIGHT, DCPOMATIC_DIALOG_BORDER);
+
+ _progress_panel = new VerifyDCPProgressPanel(this);
+ overall_sizer->Add(_progress_panel, 0, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+
+ _result_panel = new VerifyDCPResultPanel(this);
+ overall_sizer->Add(_result_panel, 0, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+
+ SetSizerAndFit(overall_sizer);
+
+ _dcp->Changed.connect(boost::bind(&DOMFrame::setup_sensitivity, this));
+ _verify->bind(&DOMFrame::verify_clicked, this);
+
+ setup_sensitivity();
+ }
+
+private:
+ void setup_sensitivity()
+ {
+ _verify->Enable(!_dcp->GetPath().IsEmpty());
+ }
+
+ void verify_clicked()
+ {
+ auto dcp = boost::filesystem::path(wx_to_std(_dcp->GetPath()));
+ if (dcp.empty()) {
+ return;
+ }
+
+ auto job_manager = JobManager::instance();
+ auto job = make_shared<VerifyDCPJob>(std::vector<boost::filesystem::path>{dcp}, std::vector<boost::filesystem::path>());
+ job_manager->add(job);
+
+ while (job_manager->work_to_do()) {
+ wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
+ dcpomatic_sleep_seconds(1);
+
+ _progress_panel->update(job);
+ }
+
+ _result_panel->fill(job);
+ if (_write_log->get()) {
+ dcp::TextFormatter formatter(dcp / "REPORT.txt");
+ dcp::verify_report(job->result(), formatter);
+ }
+ }
+
+ DirPickerCtrl* _dcp;
+ CheckBox* _write_log;
+ Button* _verify;
+ VerifyDCPProgressPanel* _progress_panel;
+ VerifyDCPResultPanel* _result_panel;
+};
+
+
+/** @class App
+ * @brief The magic App class for wxWidgets.
+ */
+class App : public wxApp
+{
+public:
+ App()
+ : wxApp()
+ {
+ dcpomatic_setup_path_encoding();
+#ifdef DCPOMATIC_LINUX
+ XInitThreads();
+#endif
+ }
+
+private:
+ bool OnInit() override
+ {
+ try {
+ SetAppName(variant::wx::dcpomatic_verifier());
+
+ if (!wxApp::OnInit()) {
+ return false;
+ }
+
+#ifdef DCPOMATIC_LINUX
+ unsetenv("UBUNTU_MENUPROXY");
+#endif
+
+#ifdef DCPOMATIC_OSX
+ dcpomatic_sleep_seconds(1);
+ make_foreground_application();
+#endif
+
+ /* Enable i18n; this will create a Config object
+ to look for a force-configured language. This Config
+ object will be wrong, however, because dcpomatic_setup
+ hasn't yet been called and there aren't any filters etc.
+ set up yet.
+ */
+ dcpomatic_setup_i18n();
+
+ /* Set things up, including filters etc.
+ which will now be internationalised correctly.
+ */
+ dcpomatic_setup();
+
+ /* Force the configuration to be re-loaded correctly next
+ time it is needed.
+ */
+ Config::drop();
+
+ _frame = new DOMFrame(variant::wx::dcpomatic_verifier());
+ SetTopWindow(_frame);
+ _frame->SetSize({480, 640});
+ _frame->Show();
+ }
+ catch (exception& e)
+ {
+ error_dialog(nullptr, variant::wx::insert_dcpomatic_verifier("%s could not start."), std_to_wx(e.what()));
+ }
+
+ return true;
+ }
+
+ void report_exception()
+ {
+ try {
+ throw;
+ } catch (FileError& e) {
+ error_dialog(
+ nullptr,
+ wxString::Format(
+ _("An exception occurred: %s (%s)\n\n%s"),
+ std_to_wx(e.what()),
+ std_to_wx(e.file().string().c_str()),
+ wx::report_problem()
+ )
+ );
+ } catch (boost::filesystem::filesystem_error& e) {
+ error_dialog(
+ nullptr,
+ wxString::Format(
+ _("An exception occurred: %s (%s) (%s)\n\n%s"),
+ std_to_wx(e.what()),
+ std_to_wx(e.path1().string()),
+ std_to_wx(e.path2().string()),
+ wx::report_problem()
+ )
+ );
+ } catch (exception& e) {
+ error_dialog(
+ nullptr,
+ wxString::Format(
+ _("An exception occurred: %s.\n\n%s"),
+ std_to_wx(e.what()),
+ wx::report_problem()
+ )
+ );
+ } catch (...) {
+ error_dialog(nullptr, _("An unknown exception occurred.") + " " + wx::report_problem());
+ }
+ }
+
+ /* An unhandled exception has occurred inside the main event loop */
+ bool OnExceptionInMainLoop() override
+ {
+ report_exception();
+ return false;
+ }
+
+ void OnUnhandledException() override
+ {
+ report_exception();
+ }
+
+ DOMFrame* _frame = nullptr;
+};
+
+
+IMPLEMENT_APP(App)
diff --git a/src/tools/wscript b/src/tools/wscript
index c3b2b5fe0..e6d7c4be1 100644
--- a/src/tools/wscript
+++ b/src/tools/wscript
@@ -17,11 +17,79 @@
# along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
#
+from __future__ import print_function
import os
import glob
from waflib import Logs
import i18n
+
+def description(tool, variant):
+ descriptions = {
+ 'dcpomatic': 'DCP-o-matic',
+ 'dcpomatic_batch': 'DCP-o-matic Batch Converter',
+ 'dcpomatic_server': 'DCP-o-matic Encode Server',
+ 'dcpomatic_kdm': 'DCP-o-matic KDM Creator',
+ 'dcpomatic_player': 'DCP-o-matic Player',
+ 'dcpomatic_playlist': 'DCP-o-matic Playlist Editor',
+ 'dcpomatic_combiner': 'DCP-o-matic Combiner',
+ 'dcpomatic_verifier': 'DCP-o-matic Verifier',
+ }
+ return descriptions[tool] if tool in descriptions else tool
+
+
+def make_rc(tool, icon, variant):
+ filename = 'build/src/tools/%s.rc' % tool
+ with open(filename, 'w') as rc:
+ if tool == 'dcpomatic_disk_writer':
+ print('#include "winuser.h"', file=rc)
+ print('1 RT_MANIFEST "dcpomatic2_disk_writer.exe.manifest"', file=rc)
+ with open("build/src/tools/dcpomatic2_disk_writer.exe.manifest", "w") as manifest:
+ print("""
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="dcpomatic2_disk_writer" type="win32"/>
+ <description>DCP-o-matic Disk Writer</description>
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+</assembly>""", file=manifest)
+ else:
+ print("""
+id ICON "../../graphics/windows/%s"
+#include "wx-3.1/wx/msw/wx.rc"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 0,0,0,2
+ PRODUCTVERSION 0,0,0,2
+ FILEFLAGSMASK 0x3fL
+ #ifdef _DEBUG
+ FILEFLAGS 0x1L
+ #else
+ FILEFLAGS 0x0L
+ #endif
+ FILEOS 0x4L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+ {
+ BLOCK "StringFileInfo"
+ {
+ BLOCK "040904b0"
+ {
+ VALUE "FileDescription", "%s\\0"
+ }
+ }
+ BLOCK "VarFileInfo"
+ {
+ VALUE "Translation", 0x409, 1200
+ }
+ }
+ """ % (icon, description(tool, variant)), file=rc)
+ return filename
+
def configure(conf):
if conf.env.TARGET_WINDOWS_64 or conf.env.TARGET_WINDOWS_32:
conf.env.append_value('CXXFLAGS', ['-mconsole'])
@@ -30,7 +98,7 @@ def configure(conf):
def build(bld):
uselib = 'BOOST_THREAD BOOST_DATETIME DCP XMLSEC CXML XMLPP AVFORMAT AVFILTER AVCODEC '
uselib += 'AVUTIL SWSCALE SWRESAMPLE POSTPROC CURL BOOST_FILESYSTEM SSH ZIP CAIROMM FONTCONFIG PANGOMM SUB '
- uselib += 'SNDFILE SAMPLERATE BOOST_REGEX ICU NETTLE RTAUDIO PNG JPEG LEQM_NRT '
+ uselib += 'SNDFILE SAMPLERATE BOOST_REGEX ICU NETTLE RTAUDIO PNG JPEG LEQM_NRT SQLITE3 '
if bld.env.ENABLE_DISK:
if bld.env.TARGET_LINUX:
@@ -46,6 +114,11 @@ def build(bld):
if bld.env.ENABLE_DISK and not bld.env.DISABLE_GUI:
cli_tools.append('dcpomatic_disk_writer')
+ try:
+ os.makedirs('build/src/tools')
+ except:
+ pass
+
for t in cli_tools:
obj = bld(features='cxx cxxprogram')
obj.uselib = uselib
@@ -53,7 +126,7 @@ def build(bld):
obj.use = ['libdcpomatic2']
obj.source = '%s.cc' % t
if (bld.env.TARGET_WINDOWS_64 or bld.env.TARGET_WINDOWS_32) and t == 'dcpomatic_disk_writer':
- obj.source += ' ../../platform/windows/%s.rc' % t
+ obj.source += ' ../../%s' % (make_rc(t, None, bld.env.VARIANT))
# Prevent a console window opening when we start dcpomatic2_disk_writer
obj.env.append_value('LINKFLAGS', '-Wl,-subsystem,windows')
obj.target = t.replace('dcpomatic', 'dcpomatic2')
@@ -62,7 +135,15 @@ def build(bld):
gui_tools = []
if not bld.env.DISABLE_GUI:
- gui_tools = ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server', 'dcpomatic_kdm', 'dcpomatic_player', 'dcpomatic_playlist', 'dcpomatic_combiner', 'dcpomatic_editor']
+ gui_tools = ['dcpomatic',
+ 'dcpomatic_batch',
+ 'dcpomatic_server',
+ 'dcpomatic_kdm',
+ 'dcpomatic_player',
+ 'dcpomatic_playlist',
+ 'dcpomatic_combiner',
+ 'dcpomatic_editor',
+ 'dcpomatic_verifier']
if bld.env.ENABLE_DISK:
gui_tools.append('dcpomatic_disk')
@@ -82,7 +163,7 @@ def build(bld):
obj.use = ['libdcpomatic2', 'libdcpomatic2-wx']
obj.source = '%s.cc' % t
if bld.env.TARGET_WINDOWS_64 or bld.env.TARGET_WINDOWS_32:
- obj.source += ' ../../platform/windows/%s.rc' % t
+ obj.source += ' ../../%s' % make_rc(t, t.replace("dcpomatic", "dcpomatic2") + ".ico", bld.env.VARIANT)
obj.target = t.replace('dcpomatic', 'dcpomatic2')
i18n.po_to_mo(os.path.join('src', 'tools'), 'dcpomatic2', bld)
diff --git a/src/wx/about_dialog.cc b/src/wx/about_dialog.cc
index 59fad0878..0c3905097 100644
--- a/src/wx/about_dialog.cc
+++ b/src/wx/about_dialog.cc
@@ -26,7 +26,9 @@
#include "about_dialog.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/compose.hpp"
+#include "lib/variant.h"
#include "lib/version.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
@@ -38,7 +40,7 @@ using std::vector;
AboutDialog::AboutDialog (wxWindow* parent)
- : wxDialog (parent, wxID_ANY, _("About DCP-o-matic"))
+ : wxDialog(parent, wxID_ANY, variant::wx::insert_dcpomatic(_("About %s")))
{
auto overall_sizer = new wxBoxSizer (wxVERTICAL);
auto sizer = new wxBoxSizer (wxVERTICAL);
@@ -53,7 +55,7 @@ AboutDialog::AboutDialog (wxWindow* parent)
wxFont version_font (*wxNORMAL_FONT);
version_font.SetWeight (wxFONTWEIGHT_BOLD);
- auto t = new StaticText (this, _("DCP-o-matic"));
+ auto t = new StaticText(this, variant::wx::dcpomatic());
t->SetFont (title_font);
sizer->Add (t, wxSizerFlags().Centre().Border(wxALL, 16));
@@ -67,178 +69,187 @@ AboutDialog::AboutDialog (wxWindow* parent)
sizer->Add (t, wxSizerFlags().Centre().Border(wxALL, 2));
sizer->AddSpacer (12);
- t = new StaticText (
- this,
- _("Free, open-source DCP creation from almost anything."),
- wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER
- );
- t->SetFont (subtitle_font);
-
- sizer->Add (t, wxSizerFlags().Centre().Border(wxALL, 8));
-
- auto h = new wxHyperlinkCtrl (
- this, wxID_ANY,
- wxT ("dcpomatic.com"),
- wxT ("https://dcpomatic.com")
- );
-
- sizer->Add (h, wxSizerFlags().Centre().Border(wxALL, 8));
-
- t = new StaticText (
- this,
- _("(C) 2012-2024 Carl Hetherington, Terrence Meiczinger\n Ole Laursen"),
- wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER
- );
-
- sizer->Add (t, wxSizerFlags().Centre().Border(wxLEFT | wxRIGHT, 16));
-
- _notebook = new wxNotebook (this, wxID_ANY);
-
- wxArrayString written_by;
- written_by.Add (wxT ("Carl Hetherington"));
- written_by.Add (wxT ("Terrence Meiczinger"));
- written_by.Add (wxT ("Mart Jansink"));
- written_by.Add (wxT ("Ole Laursen"));
- written_by.Add (wxT ("Benjamin Radel"));
- add_section (_("Written by"), written_by);
-
- wxArrayString with_help_from;
- with_help_from.Add (wxT ("David Vignoni"));
- with_help_from.Add (wxT ("Dennis Couzin"));
- with_help_from.Add (wxT ("Carsten Kurz"));
- with_help_from.Add (wxT ("Gérald Maruccia"));
- with_help_from.Add (wxT ("Julian van Mil"));
- with_help_from.Add (wxT ("Lilian Lefranc"));
- add_section (_("With help from"), with_help_from);
-
- wxArrayString translated_by;
- translated_by.Add (wxT ("Manuel AC"));
- translated_by.Add (wxT ("Max Aeschlimann"));
- translated_by.Add (wxT ("Gökhan Aksoy"));
- translated_by.Add (wxT ("Thiago Andre"));
- translated_by.Add (wxT ("Felice D'Andrea"));
- translated_by.Add (wxT ("Németh Áron"));
- translated_by.Add (wxT ("Grégoire Ausina"));
- translated_by.Add (wxT ("Tomáš Begeni"));
- translated_by.Add (wxT ("Fabio \"Zak\" Belli"));
- translated_by.Add (wxT ("Cherif Ben Brahim"));
- translated_by.Add (wxT ("Massimiliano Broggi"));
- translated_by.Add (wxT ("Dan Cohen"));
- translated_by.Add (wxT ("Akivili Collindort"));
- translated_by.Add (wxT ("Davide Dall'AraCiao"));
- translated_by.Add (wxT ("Uwe Dittes"));
- translated_by.Add (wxT ("Михаил Эпштейн"));
- translated_by.Add (wxT ("William Fanelli"));
- translated_by.Add (wxT ("Max M. Fuhlendorf"));
- translated_by.Add (wxT ("Tomáš Hlaváč"));
- translated_by.Add (wxT ("Thierry Journet"));
- translated_by.Add (wxT ("Adam Klotblixt"));
- translated_by.Add (wxT ("Theo Kooijmans"));
- translated_by.Add (wxT ("Carsten Kurz"));
- translated_by.Add (wxT ("Lilian Lefranc"));
- translated_by.Add (wxT ("Gérald Maruccia"));
- translated_by.Add (wxT ("Mattias Mattsson"));
- translated_by.Add (wxT ("Mike Mazur"));
- translated_by.Add (wxT ("Rob van Nieuwkerk"));
- translated_by.Add (wxT ("Anders Uhl Pedersen"));
- translated_by.Add (wxT ("David Perrenoud"));
- translated_by.Add (wxT ("Olivier Perriere"));
- translated_by.Add (wxT ("Markus Raab"));
- translated_by.Add (wxT ("Soleyman Rahmani"));
- translated_by.Add (wxT ("Tiago Casal Ribeiro"));
- translated_by.Add (wxT ("Davide Sanvito"));
- translated_by.Add (wxT ("Marek Skrzelowski"));
- translated_by.Add (wxT ("Danbo Song"));
- translated_by.Add (wxT ("Martin Srebotnjak"));
- translated_by.Add (wxT ("Michał Tomaszewski"));
- translated_by.Add (wxT ("Igor Voytovich"));
- translated_by.Add (wxT ("Rov (若文)"));
- translated_by.Add (wxT ("刘汉源"));
- translated_by.Add (wxT ("poppinzhang"));
- add_section (_("Translated by"), translated_by);
-
- wxArrayString patrons;
- patrons.Add ("Lightbender Post");
- add_section (_("Patrons"), patrons);
-
- wxArrayString subscribers;
- #include "subscribers.cc"
- add_section (_("Subscribers"), subscribers);
-
- wxArrayString supported_by;
- #include "supporters.cc"
- add_section (_("Also supported by"), supported_by);
-
- wxArrayString tested_by;
- tested_by.Add (wxT ("Manuel AC"));
- tested_by.Add (wxT ("Trever Anderson"));
- tested_by.Add (wxT ("Mohamad W. Ali"));
- tested_by.Add (wxT ("JP Beauviala"));
- tested_by.Add (wxT ("Mike Blakesley"));
- tested_by.Add (wxT ("David Booty"));
- tested_by.Add (wxT ("Antonio Casado"));
- tested_by.Add (wxT ("Roop Chand"));
- tested_by.Add (wxT ("Daniel Chauvet"));
- tested_by.Add (wxT ("Adam Colt"));
- tested_by.Add (wxT ("John Convertino"));
- tested_by.Add (wxT ("Daniel Courville"));
- tested_by.Add (wxT ("Marek Dudzik"));
- tested_by.Add (wxT ("Andreas Eli"));
- tested_by.Add (wxT ("Leo Enticknap"));
- tested_by.Add (wxT ("Jose Angel Velasco Fernandez"));
- tested_by.Add (wxT ("Maurizio Giampà"));
- tested_by.Add (wxT ("Luke Granger-Brown"));
- tested_by.Add (wxT ("Sumit Guha"));
- tested_by.Add (wxT ("Steve Guttag"));
- tested_by.Add (wxT ("Patrick Haderer"));
- tested_by.Add (wxT ("Bill Hamell"));
- tested_by.Add (wxT ("Groet Han"));
- tested_by.Add (wxT ("Jonathan Jensen"));
- tested_by.Add (wxT ("Thierry Journet"));
- tested_by.Add (wxT ("Markus Kalb"));
- tested_by.Add (wxT ("Ada de Kamper"));
- tested_by.Add (wxT ("Stefan Karner"));
- tested_by.Add (wxT ("Adam Keay"));
- tested_by.Add (wxT ("Simon Kesselman"));
- tested_by.Add (wxT ("Pepijn Klijs"));
- tested_by.Add (wxT ("Denzil Kriekenbeek"));
- tested_by.Add (wxT ("Carsten Kurz"));
- tested_by.Add (wxT ("Bill Lam"));
- tested_by.Add (wxT ("David Lankes"));
- tested_by.Add (wxT ("Lilian Lefranc"));
- tested_by.Add (wxT ("Sebastian Leitner"));
- tested_by.Add (wxT ("Olivier Lemaire"));
- tested_by.Add (wxT ("Gavin Lewarne"));
- tested_by.Add (wxT ("Gérald Maruccia"));
- tested_by.Add (wxT ("George Mazarakis"));
- tested_by.Add (wxT ("Mattias Mattsson"));
- tested_by.Add (wxT ("Will Meadows"));
- tested_by.Add (wxT ("Brad Miller"));
- tested_by.Add (wxT ("Ash Mitchell"));
- tested_by.Add (wxT ("Rob van Nieuwkerk"));
- tested_by.Add (wxT ("Anders Nordentoft-Madsen"));
- tested_by.Add (wxT ("Mauro Ottonello"));
- tested_by.Add (wxT ("Peter Puchner"));
- tested_by.Add (wxT ("Markus Raab"));
- tested_by.Add (wxT ("Michael Reckert"));
- tested_by.Add (wxT ("Greg Rooke"));
- tested_by.Add (wxT ("Elad Saad"));
- tested_by.Add (wxT ("Karim Senoucci"));
- tested_by.Add (wxT ("Hordur Valgardsson"));
- tested_by.Add (wxT ("Xenophon the Vampire"));
- tested_by.Add (wxT ("Simon Vannarath"));
- tested_by.Add (wxT ("Igor Voytovich"));
- tested_by.Add (wxT ("Andrew Walls"));
- tested_by.Add (wxT ("Andreas Weiss"));
- tested_by.Add (wxT ("Paul Willmott"));
- tested_by.Add (wxT ("Wolfgang Woehl"));
- tested_by.Add (wxT ("Benno Zwanenburg"));
- tested_by.Add (wxT ("Дима Агатов"));
- add_section (_("Tested by"), tested_by);
-
- sizer->Add (_notebook, wxSizerFlags().Centre().Border(wxALL, 16));
-
- overall_sizer->Add (sizer);
+ if (variant::show_tagline())
+ {
+ t = new StaticText(
+ this,
+ _("Free, open-source DCP creation from almost anything."),
+ wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER
+ );
+ t->SetFont(subtitle_font);
+ sizer->Add(t, wxSizerFlags().Centre().Border(wxALL, 8));
+ }
+
+ if (variant::show_dcpomatic_website())
+ {
+ auto h = new wxHyperlinkCtrl(
+ this, wxID_ANY,
+ wxT("dcpomatic.com"),
+ wxT("https://dcpomatic.com")
+ );
+ sizer->Add(h, wxSizerFlags().Centre().Border(wxALL, 8));
+ }
+
+ if (variant::show_credits())
+ {
+ t = new StaticText (
+ this,
+ _("(C) 2012-2024 Carl Hetherington, Terrence Meiczinger\nOle Laursen, Aaron Boxer"),
+ wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER
+ );
+
+ sizer->Add (t, wxSizerFlags().Centre().Border(wxLEFT | wxRIGHT, 16));
+
+ _notebook = new wxNotebook (this, wxID_ANY);
+
+ wxArrayString written_by;
+ written_by.Add (wxT ("Carl Hetherington"));
+ written_by.Add (wxT ("Terrence Meiczinger"));
+ written_by.Add (wxT ("Mart Jansink"));
+ written_by.Add (wxT ("Ole Laursen"));
+ written_by.Add (wxT ("Aaron Boxer"));
+ written_by.Add (wxT ("Benjamin Radel"));
+ add_section (_("Written by"), written_by);
+
+ wxArrayString with_help_from;
+ with_help_from.Add (wxT ("David Vignoni"));
+ with_help_from.Add (wxT ("Dennis Couzin"));
+ with_help_from.Add (wxT ("Carsten Kurz"));
+ with_help_from.Add (wxT ("Gérald Maruccia"));
+ with_help_from.Add (wxT ("Julian van Mil"));
+ with_help_from.Add (wxT ("Lilian Lefranc"));
+ add_section (_("With help from"), with_help_from);
+
+ wxArrayString translated_by;
+ translated_by.Add (wxT ("Manuel AC"));
+ translated_by.Add (wxT ("Max Aeschlimann"));
+ translated_by.Add (wxT ("Gökhan Aksoy"));
+ translated_by.Add (wxT ("Thiago Andre"));
+ translated_by.Add (wxT ("Felice D'Andrea"));
+ translated_by.Add (wxT ("Németh Áron"));
+ translated_by.Add (wxT ("Grégoire Ausina"));
+ translated_by.Add (wxT ("Tomáš Begeni"));
+ translated_by.Add (wxT ("Fabio \"Zak\" Belli"));
+ translated_by.Add (wxT ("Cherif Ben Brahim"));
+ translated_by.Add (wxT ("Massimiliano Broggi"));
+ translated_by.Add (wxT ("Dan Cohen"));
+ translated_by.Add (wxT ("Akivili Collindort"));
+ translated_by.Add (wxT ("Davide Dall'AraCiao"));
+ translated_by.Add (wxT ("Uwe Dittes"));
+ translated_by.Add (wxT ("Михаил Эпштейн"));
+ translated_by.Add (wxT ("William Fanelli"));
+ translated_by.Add (wxT ("Max M. Fuhlendorf"));
+ translated_by.Add (wxT ("Tomáš Hlaváč"));
+ translated_by.Add (wxT ("Thierry Journet"));
+ translated_by.Add (wxT ("Adam Klotblixt"));
+ translated_by.Add (wxT ("Theo Kooijmans"));
+ translated_by.Add (wxT ("Carsten Kurz"));
+ translated_by.Add (wxT ("Lilian Lefranc"));
+ translated_by.Add (wxT ("Gérald Maruccia"));
+ translated_by.Add (wxT ("Mattias Mattsson"));
+ translated_by.Add (wxT ("Mike Mazur"));
+ translated_by.Add (wxT ("Rob van Nieuwkerk"));
+ translated_by.Add (wxT ("Anders Uhl Pedersen"));
+ translated_by.Add (wxT ("David Perrenoud"));
+ translated_by.Add (wxT ("Olivier Perriere"));
+ translated_by.Add (wxT ("Markus Raab"));
+ translated_by.Add (wxT ("Soleyman Rahmani"));
+ translated_by.Add (wxT ("Tiago Casal Ribeiro"));
+ translated_by.Add (wxT ("Davide Sanvito"));
+ translated_by.Add (wxT ("Marek Skrzelowski"));
+ translated_by.Add (wxT ("Danbo Song"));
+ translated_by.Add (wxT ("Martin Srebotnjak"));
+ translated_by.Add (wxT ("Michał Tomaszewski"));
+ translated_by.Add (wxT ("Igor Voytovich"));
+ translated_by.Add (wxT ("Rov (若文)"));
+ translated_by.Add (wxT ("刘汉源"));
+ translated_by.Add (wxT ("poppinzhang"));
+ add_section (_("Translated by"), translated_by);
+
+ wxArrayString patrons;
+ patrons.Add ("Lightbender Post");
+ add_section (_("Patrons"), patrons);
+
+ wxArrayString subscribers;
+ #include "subscribers.cc"
+ add_section (_("Subscribers"), subscribers);
+
+ wxArrayString supported_by;
+ #include "supporters.cc"
+ add_section (_("Also supported by"), supported_by);
+
+ wxArrayString tested_by;
+ tested_by.Add (wxT ("Manuel AC"));
+ tested_by.Add (wxT ("Trever Anderson"));
+ tested_by.Add (wxT ("Mohamad W. Ali"));
+ tested_by.Add (wxT ("JP Beauviala"));
+ tested_by.Add (wxT ("Mike Blakesley"));
+ tested_by.Add (wxT ("David Booty"));
+ tested_by.Add (wxT ("Antonio Casado"));
+ tested_by.Add (wxT ("Roop Chand"));
+ tested_by.Add (wxT ("Daniel Chauvet"));
+ tested_by.Add (wxT ("Adam Colt"));
+ tested_by.Add (wxT ("John Convertino"));
+ tested_by.Add (wxT ("Daniel Courville"));
+ tested_by.Add (wxT ("Marek Dudzik"));
+ tested_by.Add (wxT ("Andreas Eli"));
+ tested_by.Add (wxT ("Leo Enticknap"));
+ tested_by.Add (wxT ("Jose Angel Velasco Fernandez"));
+ tested_by.Add (wxT ("Maurizio Giampà"));
+ tested_by.Add (wxT ("Luke Granger-Brown"));
+ tested_by.Add (wxT ("Sumit Guha"));
+ tested_by.Add (wxT ("Steve Guttag"));
+ tested_by.Add (wxT ("Patrick Haderer"));
+ tested_by.Add (wxT ("Bill Hamell"));
+ tested_by.Add (wxT ("Groet Han"));
+ tested_by.Add (wxT ("Jonathan Jensen"));
+ tested_by.Add (wxT ("Thierry Journet"));
+ tested_by.Add (wxT ("Markus Kalb"));
+ tested_by.Add (wxT ("Ada de Kamper"));
+ tested_by.Add (wxT ("Stefan Karner"));
+ tested_by.Add (wxT ("Adam Keay"));
+ tested_by.Add (wxT ("Simon Kesselman"));
+ tested_by.Add (wxT ("Pepijn Klijs"));
+ tested_by.Add (wxT ("Denzil Kriekenbeek"));
+ tested_by.Add (wxT ("Carsten Kurz"));
+ tested_by.Add (wxT ("Bill Lam"));
+ tested_by.Add (wxT ("David Lankes"));
+ tested_by.Add (wxT ("Lilian Lefranc"));
+ tested_by.Add (wxT ("Sebastian Leitner"));
+ tested_by.Add (wxT ("Olivier Lemaire"));
+ tested_by.Add (wxT ("Gavin Lewarne"));
+ tested_by.Add (wxT ("Gérald Maruccia"));
+ tested_by.Add (wxT ("George Mazarakis"));
+ tested_by.Add (wxT ("Mattias Mattsson"));
+ tested_by.Add (wxT ("Will Meadows"));
+ tested_by.Add (wxT ("Brad Miller"));
+ tested_by.Add (wxT ("Ash Mitchell"));
+ tested_by.Add (wxT ("Rob van Nieuwkerk"));
+ tested_by.Add (wxT ("Anders Nordentoft-Madsen"));
+ tested_by.Add (wxT ("Mauro Ottonello"));
+ tested_by.Add (wxT ("Peter Puchner"));
+ tested_by.Add (wxT ("Markus Raab"));
+ tested_by.Add (wxT ("Michael Reckert"));
+ tested_by.Add (wxT ("Greg Rooke"));
+ tested_by.Add (wxT ("Elad Saad"));
+ tested_by.Add (wxT ("Karim Senoucci"));
+ tested_by.Add (wxT ("Hordur Valgardsson"));
+ tested_by.Add (wxT ("Xenophon the Vampire"));
+ tested_by.Add (wxT ("Simon Vannarath"));
+ tested_by.Add (wxT ("Igor Voytovich"));
+ tested_by.Add (wxT ("Andrew Walls"));
+ tested_by.Add (wxT ("Andreas Weiss"));
+ tested_by.Add (wxT ("Paul Willmott"));
+ tested_by.Add (wxT ("Wolfgang Woehl"));
+ tested_by.Add (wxT ("Benno Zwanenburg"));
+ tested_by.Add (wxT ("Дима Агатов"));
+ add_section (_("Tested by"), tested_by);
+
+ sizer->Add (_notebook, wxSizerFlags().Centre().Border(wxALL, 16));
+ overall_sizer->Add (sizer);
+ } else {
+ overall_sizer->Add(sizer, 0, wxALL, DCPOMATIC_DIALOG_BORDER);
+ }
wxSizer* buttons = CreateButtonSizer (wxOK);
if (buttons) {
diff --git a/src/wx/audio_dialog.cc b/src/wx/audio_dialog.cc
index 714bf78e4..1f7f3ad03 100644
--- a/src/wx/audio_dialog.cc
+++ b/src/wx/audio_dialog.cc
@@ -24,6 +24,7 @@
#include "check_box.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/analyse_audio_job.h"
#include "lib/audio_analysis.h"
#include "lib/audio_content.h"
@@ -166,9 +167,9 @@ AudioDialog::AudioDialog (wxWindow* parent, shared_ptr<Film> film, FilmViewer& v
_film_content_connection = film->ContentChange.connect(boost::bind(&AudioDialog::content_change, this, _1, _3));
DCPOMATIC_ASSERT (film->directory());
if (content) {
- SetTitle(wxString::Format(_("DCP-o-matic audio - %s"), std_to_wx(content->path(0).string())));
+ SetTitle(wxString::Format(_("%s audio - %s"), variant::wx::dcpomatic(), std_to_wx(content->path(0).string())));
} else {
- SetTitle(wxString::Format(_("DCP-o-matic audio - %s"), std_to_wx(film->directory().get().string())));
+ SetTitle(wxString::Format(_("%s audio - %s"), variant::wx::dcpomatic(), std_to_wx(film->directory().get().string())));
}
if (content) {
diff --git a/src/wx/audio_panel.cc b/src/wx/audio_panel.cc
index fcadd0c68..34923fb02 100644
--- a/src/wx/audio_panel.cc
+++ b/src/wx/audio_panel.cc
@@ -71,14 +71,6 @@ AudioPanel::AudioPanel (ContentPanel* p)
void
AudioPanel::create ()
{
- _reference = new CheckBox (this, _("Use this DCP's audio as OV and make VF"));
- _reference_note = new StaticText (this, wxT(""));
- _reference_note->Wrap (200);
- auto font = _reference_note->GetFont();
- font.SetStyle(wxFONTSTYLE_ITALIC);
- font.SetPointSize(font.GetPointSize() - 1);
- _reference_note->SetFont(font);
-
_show = new Button (this, _("Show graph of audio levels..."));
_peak = new StaticText (this, wxT (""));
@@ -121,6 +113,9 @@ AudioPanel::create ()
_description = new StaticText (this, wxT(" \n"), wxDefaultPosition, wxDefaultSize);
_sizer->Add (_description, 0, wxALL, 12);
+ auto font = _description->GetFont();
+ font.SetStyle(wxFONTSTYLE_ITALIC);
+ font.SetPointSize(font.GetPointSize() - 1);
_description->SetFont (font);
_gain->wrapped()->SetRange (-60, 60);
@@ -133,7 +128,6 @@ AudioPanel::create ()
film_changed(FilmProperty::VIDEO_FRAME_RATE);
film_changed(FilmProperty::REEL_TYPE);
- _reference->bind(&AudioPanel::reference_clicked, this);
_show->Bind (wxEVT_BUTTON, boost::bind (&AudioPanel::show_clicked, this));
_gain_calculate_button->Bind (wxEVT_BUTTON, boost::bind (&AudioPanel::gain_calculate_button_clicked, this));
@@ -155,12 +149,6 @@ AudioPanel::add_to_grid ()
{
int r = 0;
- auto reference_sizer = new wxBoxSizer (wxVERTICAL);
- reference_sizer->Add (_reference, 0);
- reference_sizer->Add (_reference_note, 0);
- _grid->Add (reference_sizer, wxGBPosition(r, 0), wxGBSpan(1, 4));
- ++r;
-
_grid->Add (_show, wxGBPosition (r, 0), wxGBSpan (1, 2));
_grid->Add (_peak, wxGBPosition (r, 2), wxGBSpan (1, 2), wxALIGN_CENTER_VERTICAL);
++r;
@@ -258,15 +246,6 @@ AudioPanel::film_content_changed (int property)
/* This is a bit aggressive but probably not so bad */
_peak_cache.clear();
setup_peak ();
- } else if (property == DCPContentProperty::REFERENCE_AUDIO) {
- if (ac.size() == 1) {
- shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (ac.front ());
- checked_set (_reference, dcp ? dcp->reference_audio () : false);
- } else {
- checked_set (_reference, false);
- }
-
- setup_sensitivity ();
} else if (property == ContentProperty::VIDEO_FRAME_RATE) {
setup_description ();
} else if (property == AudioContentProperty::FADE_IN) {
@@ -384,19 +363,8 @@ AudioPanel::setup_sensitivity ()
dcp = dynamic_pointer_cast<DCPContent> (sel.front ());
}
- string why_not;
- bool const can_reference = dcp && dcp->can_reference_audio (_parent->film(), why_not);
- wxString cannot;
- if (why_not.empty()) {
- cannot = _("Cannot reference this DCP's audio.");
- } else {
- cannot = _("Cannot reference this DCP's audio: ") + std_to_wx(why_not);
- }
- setup_refer_button (_reference, _reference_note, dcp, can_reference, cannot);
-
- auto const ref = _reference->GetValue();
+ auto const ref = dcp && dcp->reference_audio();
auto const single = sel.size() == 1;
-
auto const all_have_video = std::all_of(sel.begin(), sel.end(), [](shared_ptr<const Content> c) { return static_cast<bool>(c->video); });
_gain->wrapped()->Enable (!ref);
@@ -501,23 +469,6 @@ AudioPanel::active_jobs_changed (optional<string> old_active, optional<string> n
void
-AudioPanel::reference_clicked ()
-{
- auto c = _parent->selected ();
- if (c.size() != 1) {
- return;
- }
-
- auto d = dynamic_pointer_cast<DCPContent>(c.front());
- if (!d) {
- return;
- }
-
- d->set_reference_audio (_reference->GetValue ());
-}
-
-
-void
AudioPanel::set_film (shared_ptr<Film>)
{
/* We are changing film, so destroy any audio dialog for the old one */
diff --git a/src/wx/audio_panel.h b/src/wx/audio_panel.h
index 61dd2783a..d25f7e247 100644
--- a/src/wx/audio_panel.h
+++ b/src/wx/audio_panel.h
@@ -54,15 +54,12 @@ private:
void setup_peak ();
void active_jobs_changed (boost::optional<std::string>, boost::optional<std::string>);
void setup_sensitivity ();
- void reference_clicked ();
void add_to_grid () override;
boost::optional<float> peak () const;
void fade_in_changed ();
void fade_out_changed ();
void use_same_fades_as_video_changed ();
- CheckBox* _reference;
- wxStaticText* _reference_note;
wxButton* _show;
wxStaticText* _gain_label;
wxStaticText* _gain_db_label;
diff --git a/src/wx/check_box.cc b/src/wx/check_box.cc
index baec651c1..c18ceba47 100644
--- a/src/wx/check_box.cc
+++ b/src/wx/check_box.cc
@@ -53,3 +53,10 @@ CheckBox::get() const
return GetValue();
}
+
+void
+CheckBox::set(bool state)
+{
+ SetValue(state);
+}
+
diff --git a/src/wx/check_box.h b/src/wx/check_box.h
index 4b3fddb9f..d9feb00c9 100644
--- a/src/wx/check_box.h
+++ b/src/wx/check_box.h
@@ -39,6 +39,7 @@ public:
void set_text (wxString text) override;
wxString get_text () const override;
bool get() const;
+ void set(bool state);
template <typename... Args>
void bind(Args... args) {
diff --git a/src/wx/cinema_dialog.cc b/src/wx/cinema_dialog.cc
index 84fde5f41..3b8265e6f 100644
--- a/src/wx/cinema_dialog.cc
+++ b/src/wx/cinema_dialog.cc
@@ -20,6 +20,7 @@
#include "cinema_dialog.h"
+#include "dcpomatic_choice.h"
#include "wx_util.h"
#include "lib/dcpomatic_assert.h"
#include "lib/util.h"
@@ -36,7 +37,7 @@ using namespace boost::placeholders;
#endif
-CinemaDialog::CinemaDialog(wxWindow* parent, wxString title, string name, vector<string> emails, string notes, int utc_offset_hour, int utc_offset_minute)
+CinemaDialog::CinemaDialog(wxWindow* parent, wxString title, string name, vector<string> emails, string notes, dcp::UTCOffset utc_offset)
: wxDialog (parent, wxID_ANY, title)
{
auto overall_sizer = new wxBoxSizer (wxVERTICAL);
@@ -50,9 +51,9 @@ CinemaDialog::CinemaDialog(wxWindow* parent, wxString title, string name, vector
sizer->Add (_name, wxGBPosition(r, 1));
++r;
- add_label_to_sizer (sizer, this, _("UTC offset (time zone)"), true, wxGBPosition(r, 0));
- _utc_offset = new wxChoice (this, wxID_ANY);
- sizer->Add (_utc_offset, wxGBPosition(r, 1));
+ add_label_to_sizer(sizer, this, _("UTC offset (time zone)"), true, wxGBPosition(r, 0));
+ _utc_offset = new Choice(this);
+ sizer->Add(_utc_offset, wxGBPosition(r, 1));
++r;
add_label_to_sizer (sizer, this, _("Notes"), true, wxGBPosition(r, 0));
@@ -84,15 +85,15 @@ CinemaDialog::CinemaDialog(wxWindow* parent, wxString title, string name, vector
}
/* Default to UTC */
- size_t sel = get_offsets (_offsets);
+ size_t sel = get_offsets(_offsets);
for (size_t i = 0; i < _offsets.size(); ++i) {
- _utc_offset->Append (_offsets[i].name);
- if (_offsets[i].hour == utc_offset_hour && _offsets[i].minute == utc_offset_minute) {
+ _utc_offset->add_entry(_offsets[i].name);
+ if (_offsets[i].offset == utc_offset) {
sel = i;
}
}
- _utc_offset->SetSelection (sel);
+ _utc_offset->set(sel);
overall_sizer->Layout ();
overall_sizer->SetSizeHints (this);
@@ -122,32 +123,20 @@ CinemaDialog::emails() const
}
-int
-CinemaDialog::utc_offset_hour () const
+string
+CinemaDialog::notes () const
{
- int const sel = _utc_offset->GetSelection();
- if (sel < 0 || sel > int(_offsets.size())) {
- return 0;
- }
-
- return _offsets[sel].hour;
+ return wx_to_std (_notes->GetValue());
}
-int
-CinemaDialog::utc_offset_minute () const
+dcp::UTCOffset
+CinemaDialog::utc_offset() const
{
- int const sel = _utc_offset->GetSelection();
+ auto const sel = _utc_offset->GetSelection();
if (sel < 0 || sel > int(_offsets.size())) {
- return 0;
+ return {};
}
- return _offsets[sel].minute;
-}
-
-
-string
-CinemaDialog::notes () const
-{
- return wx_to_std (_notes->GetValue());
+ return _offsets[sel].offset;
}
diff --git a/src/wx/cinema_dialog.h b/src/wx/cinema_dialog.h
index 0b8362843..873657772 100644
--- a/src/wx/cinema_dialog.h
+++ b/src/wx/cinema_dialog.h
@@ -30,6 +30,9 @@ LIBDCP_ENABLE_WARNINGS
#include <vector>
+class Choice;
+
+
class CinemaDialog : public wxDialog
{
public:
@@ -39,15 +42,13 @@ public:
std::string name = "",
std::vector<std::string> emails = std::vector<std::string>(),
std::string notes = "",
- int utc_offset_hour = 0,
- int utc_offset_minute = 0
+ dcp::UTCOffset = {}
);
std::string name () const;
std::string notes () const;
std::vector<std::string> emails () const;
- int utc_offset_hour () const;
- int utc_offset_minute () const;
+ dcp::UTCOffset utc_offset() const;
private:
void set_emails (std::vector<std::string>);
@@ -56,6 +57,6 @@ private:
wxTextCtrl* _notes;
EditableList<std::string, EmailDialog>* _email_list;
std::vector<std::string> _emails;
- wxChoice* _utc_offset;
+ Choice* _utc_offset;
std::vector<Offset> _offsets;
};
diff --git a/src/wx/colours.h b/src/wx/colours.h
new file mode 100644
index 000000000..0d33208e0
--- /dev/null
+++ b/src/wx/colours.h
@@ -0,0 +1,25 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#define VIDEO_CONTENT_COLOUR (wxColour(242, 92, 120, 255))
+#define AUDIO_CONTENT_COLOUR (wxColour(149, 121, 232, 255))
+#define TEXT_CONTENT_COLOUR (wxColour(163, 255, 154, 255))
+#define ATMOS_CONTENT_COLOUR (wxColour(149, 121, 232, 255))
diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc
index b4adc855e..05c3f281c 100644
--- a/src/wx/config_dialog.cc
+++ b/src/wx/config_dialog.cc
@@ -25,7 +25,9 @@
#include "dcpomatic_button.h"
#include "nag_dialog.h"
#include "static_text.h"
+#include "wx_variant.h"
#include "lib/constants.h"
+#include "lib/util.h"
#include <dcp/file.h>
#include <dcp/filesystem.h>
#include <dcp/raw_convert.h>
@@ -148,7 +150,7 @@ GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
++r;
auto restart = add_label_to_sizer (
- table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
+ table, _panel, variant::wx::insert_dcpomatic(_("(restart %s to see language changes)")), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
);
wxFont font = restart->GetFont();
font.SetStyle (wxFONTSTYLE_ITALIC);
@@ -792,7 +794,7 @@ KeysPage::import_decryption_chain_and_key ()
if (new_chain->chain_valid() && new_chain->private_key_valid()) {
Config::instance()->set_decryption_chain(new_chain);
} else {
- error_dialog(_panel, _("Invalid DCP-o-matic export file"));
+ error_dialog(_panel, variant::wx::insert_dcpomatic(_("Invalid %s export file")));
}
}
diff --git a/src/wx/content_menu.cc b/src/wx/content_menu.cc
index 642457d93..fdf43ba7c 100644
--- a/src/wx/content_menu.cc
+++ b/src/wx/content_menu.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2024 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
@@ -23,13 +23,13 @@
#include "content_advanced_dialog.h"
#include "content_menu.h"
#include "content_properties_dialog.h"
+#include "content_timeline_audio_view.h"
+#include "content_timeline_video_view.h"
#include "dir_dialog.h"
#include "file_dialog.h"
#include "film_viewer.h"
#include "id.h"
#include "repeat_dialog.h"
-#include "timeline_video_content_view.h"
-#include "timeline_audio_content_view.h"
#include "wx_util.h"
#include "lib/audio_content.h"
#include "lib/config.h"
@@ -82,6 +82,7 @@ enum {
ID_advanced,
ID_re_examine,
ID_auto_crop,
+ ID_copy_settings,
ID_kdm,
ID_ov,
ID_choose_cpl,
@@ -102,6 +103,7 @@ ContentMenu::ContentMenu(wxWindow* p, FilmViewer& viewer)
_find_missing = _menu->Append (ID_find_missing, _("Find missing..."));
_re_examine = _menu->Append (ID_re_examine, _("Re-examine..."));
_auto_crop = _menu->Append (ID_auto_crop, _("Auto-crop..."));
+ _copy_settings = _menu->Append(ID_copy_settings, _("Copy settings from another project..."));
_properties = _menu->Append (ID_properties, _("Properties..."));
_advanced = _menu->Append (ID_advanced, _("Advanced settings..."));
_menu->AppendSeparator ();
@@ -121,6 +123,7 @@ ContentMenu::ContentMenu(wxWindow* p, FilmViewer& viewer)
_parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::advanced, this), ID_advanced);
_parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::re_examine, this), ID_re_examine);
_parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::auto_crop, this), ID_auto_crop);
+ _parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::copy_settings, this), ID_copy_settings);
_parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::kdm, this), ID_kdm);
_parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::ov, this), ID_ov);
_parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::set_dcp_settings, this), ID_set_dcp_settings);
@@ -157,6 +160,7 @@ ContentMenu::popup (weak_ptr<Film> film, ContentList c, TimelineContentViewList
_advanced->Enable (_content.size() == 1);
_re_examine->Enable (!_content.empty ());
_auto_crop->Enable (_content.size() == 1);
+ _copy_settings->Enable(_content.size() == 1);
if (_content.size() == 1) {
auto dcp = dynamic_pointer_cast<DCPContent> (_content.front());
@@ -314,12 +318,12 @@ ContentMenu::remove ()
continue;
}
- shared_ptr<TimelineVideoContentView> video;
- shared_ptr<TimelineAudioContentView> audio;
+ shared_ptr<ContentTimelineVideoView> video;
+ shared_ptr<ContentTimelineAudioView> audio;
for (auto j: _views) {
- auto v = dynamic_pointer_cast<TimelineVideoContentView>(j);
- auto a = dynamic_pointer_cast<TimelineAudioContentView>(j);
+ auto v = dynamic_pointer_cast<ContentTimelineVideoView>(j);
+ auto a = dynamic_pointer_cast<ContentTimelineAudioView>(j);
if (v && v->content() == fc) {
video = v;
} else if (a && a->content() == fc) {
@@ -603,3 +607,36 @@ ContentMenu::auto_crop ()
update_viewer (crop);
});
}
+
+
+void
+ContentMenu::copy_settings()
+{
+ DCPOMATIC_ASSERT(_content.size() == 1);
+
+ auto film = _film.lock();
+ DCPOMATIC_ASSERT (film);
+
+ DirDialog dialog(_parent, _("Film to copy settings from"), wxDD_DIR_MUST_EXIST, "CopySettingsPath", add_files_override_path(film));
+
+ if (!dialog.show()) {
+ return;
+ }
+
+ try {
+ auto copy_film = std::make_shared<Film>(dialog.path());
+ copy_film->read_metadata();
+ auto copy_content = copy_film->content();
+ auto iter = std::find_if(copy_content.begin(), copy_content.end(), [this](shared_ptr<const Content> content) {
+ return content->paths() == _content[0]->paths();
+ });
+ if (iter == copy_content.end()) {
+ error_dialog(_parent, wxString::Format(_("Could not find this content in the project \"%s\"."), std_to_wx(dialog.path().filename().string())));
+ } else {
+ _content[0]->take_settings_from(*iter);
+ }
+ } catch (std::exception& e) {
+ error_dialog(_parent, std_to_wx(e.what()));
+ }
+}
+
diff --git a/src/wx/content_menu.h b/src/wx/content_menu.h
index 2f3250284..e7f095390 100644
--- a/src/wx/content_menu.h
+++ b/src/wx/content_menu.h
@@ -58,6 +58,7 @@ private:
void advanced ();
void re_examine ();
void auto_crop ();
+ void copy_settings();
void kdm ();
void ov ();
void set_dcp_settings ();
@@ -81,6 +82,7 @@ private:
wxMenuItem* _advanced;
wxMenuItem* _re_examine;
wxMenuItem* _auto_crop;
+ wxMenuItem* _copy_settings;
wxMenuItem* _kdm;
wxMenuItem* _ov;
wxMenuItem* _choose_cpl;
diff --git a/src/wx/content_panel.cc b/src/wx/content_panel.cc
index 9e73900fc..5624e8797 100644
--- a/src/wx/content_panel.cc
+++ b/src/wx/content_panel.cc
@@ -21,16 +21,17 @@
#include "audio_panel.h"
#include "content_panel.h"
+#include "content_timeline_dialog.h"
#include "dcpomatic_button.h"
#include "dir_dialog.h"
#include "file_dialog.h"
#include "film_viewer.h"
#include "image_sequence_dialog.h"
#include "text_panel.h"
-#include "timeline_dialog.h"
#include "timing_panel.h"
#include "video_panel.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/audio_content.h"
#include "lib/case_insensitive_sorter.h"
#include "lib/compose.hpp"
@@ -210,26 +211,35 @@ public:
wxString OnGetItemText(long item, long) const override
{
- DCPOMATIC_ASSERT(item >= 0 && item < static_cast<long>(_items.size()));
+ if (out_of_range(item)) {
+ /* wxWidgets sometimes asks for things that are already gone */
+ return {};
+ }
wxClientDC dc(const_cast<wxWindow*>(static_cast<wxWindow const*>(this)));
return wxControl::Ellipsize(_items[item].text, dc, wxELLIPSIZE_MIDDLE, GetSize().GetWidth());
}
wxListItemAttr* OnGetItemAttr(long item) const override
{
- DCPOMATIC_ASSERT(item >= 0 && item < static_cast<long>(_items.size()));
+ if (out_of_range(item)) {
+ return nullptr;
+ }
return _items[item].error ? const_cast<wxListItemAttr*>(&_red) : nullptr;
}
weak_ptr<Content> content_at_index(long index)
{
- if (index < 0 || index >= static_cast<long>(_items.size())) {
+ if (out_of_range(index)) {
return {};
}
return _items[index].content;
}
private:
+ bool out_of_range(long index) const {
+ return index < 0 || index >= static_cast<long>(_items.size());
+ }
+
std::vector<Item> _items;
wxListItemAttr _red;
};
@@ -673,10 +683,14 @@ ContentPanel::add_dcp(boost::filesystem::path dcp)
} catch (ProjectFolderError &) {
error_dialog (
_parent,
- _(
- "This looks like a DCP-o-matic project folder, which cannot be added to a different project. "
- "Choose the DCP folder inside the DCP-o-matic project folder if that's what you want to import."
- )
+ wxString::Format(
+ _(
+ "This looks like a %s project folder, which cannot be added to a different project. "
+ "Choose the DCP folder inside the %s project folder if that's what you want to import."
+ ),
+ variant::wx::dcpomatic(),
+ variant::wx::dcpomatic()
+ )
);
} catch (exception& e) {
error_dialog(_parent, e.what());
diff --git a/src/wx/content_panel.h b/src/wx/content_panel.h
index ca0d49719..38b634a62 100644
--- a/src/wx/content_panel.h
+++ b/src/wx/content_panel.h
@@ -33,12 +33,12 @@ LIBDCP_ENABLE_WARNINGS
class AudioPanel;
class ContentListCtrl;
class ContentSubPanel;
+class ContentTimelineDialog;
class Film;
class FilmEditor;
class FilmViewer;
class LimitedContentPanelSplitter;
class TextPanel;
-class TimelineDialog;
class TimingPanel;
class VideoPanel;
class wxListCtrl;
@@ -132,7 +132,7 @@ private:
EnumIndexedVector<TextPanel*, TextType> _text_panel;
TimingPanel* _timing_panel;
ContentMenu* _menu;
- wx_ptr<TimelineDialog> _timeline_dialog;
+ wx_ptr<ContentTimelineDialog> _timeline_dialog;
wxNotebook* _parent;
wxWindow* _last_selected_tab = nullptr;
diff --git a/src/wx/content_sub_panel.cc b/src/wx/content_sub_panel.cc
index 6bbc9a51f..10fdfa232 100644
--- a/src/wx/content_sub_panel.cc
+++ b/src/wx/content_sub_panel.cc
@@ -53,29 +53,6 @@ ContentSubPanel::ContentSubPanel (ContentPanel* p, wxString name)
}
void
-ContentSubPanel::setup_refer_button (wxCheckBox* button, wxStaticText* note, shared_ptr<DCPContent> dcp, bool can_reference, wxString cannot)
-{
- button->Enable (can_reference);
-
- if (dcp && !can_reference) {
- note->SetLabel (cannot);
- } else {
- note->SetLabel (wxT(""));
- }
-
- note->Wrap (400);
-
- if (cannot.IsEmpty()) {
- note->Hide ();
- } else {
- note->Show ();
- }
-
- layout ();
-}
-
-
-void
ContentSubPanel::layout ()
{
int x;
diff --git a/src/wx/content_sub_panel.h b/src/wx/content_sub_panel.h
index a3916817e..48970e6a9 100644
--- a/src/wx/content_sub_panel.h
+++ b/src/wx/content_sub_panel.h
@@ -55,7 +55,6 @@ public:
protected:
- void setup_refer_button (wxCheckBox* button, wxStaticText* note, std::shared_ptr<DCPContent> dcp, bool can_reference, wxString cannot);
void layout ();
virtual void add_to_grid () = 0;
diff --git a/src/wx/content_timeline.cc b/src/wx/content_timeline.cc
new file mode 100644
index 000000000..7200bf076
--- /dev/null
+++ b/src/wx/content_timeline.cc
@@ -0,0 +1,1021 @@
+/*
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "content_panel.h"
+#include "content_timeline.h"
+#include "film_editor.h"
+#include "film_viewer.h"
+#include "content_timeline_atmos_view.h"
+#include "content_timeline_audio_view.h"
+#include "content_timeline_text_view.h"
+#include "content_timeline_video_view.h"
+#include "timeline_labels_view.h"
+#include "timeline_reels_view.h"
+#include "timeline_time_axis_view.h"
+#include "wx_util.h"
+#include "lib/atmos_mxf_content.h"
+#include "lib/audio_content.h"
+#include "lib/constants.h"
+#include "lib/film.h"
+#include "lib/image_content.h"
+#include "lib/playlist.h"
+#include "lib/text_content.h"
+#include "lib/timer.h"
+#include "lib/video_content.h"
+#include <dcp/scope_guard.h>
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/graphics.h>
+LIBDCP_ENABLE_WARNINGS
+#include <iterator>
+#include <list>
+
+
+using std::abs;
+using std::dynamic_pointer_cast;
+using std::list;
+using std::make_shared;
+using std::max;
+using std::min;
+using std::shared_ptr;
+using std::weak_ptr;
+using boost::bind;
+using boost::optional;
+using namespace dcpomatic;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
+
+int const ContentTimeline::_minimum_pixels_per_track = 16;
+
+
+ContentTimeline::ContentTimeline(wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, FilmViewer& viewer)
+ : Timeline(parent)
+ , _labels_canvas (new wxScrolledCanvas (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE))
+ , _main_canvas (new wxScrolledCanvas (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE))
+ , _content_panel (cp)
+ , _film (film)
+ , _viewer (viewer)
+ , _time_axis_view (new TimelineTimeAxisView (*this, 64))
+ , _reels_view (new TimelineReelsView (*this, 32))
+ , _labels_view (new TimelineLabelsView (*this))
+ , _tracks (0)
+ , _left_down (false)
+ , _down_view_position (0)
+ , _first_move (false)
+ , _menu (this, viewer)
+ , _snap (true)
+ , _tool (SELECT)
+ , _x_scroll_rate (16)
+ , _y_scroll_rate (16)
+ , _pixels_per_track (48)
+ , _first_resize (true)
+ , _timer (this)
+{
+#ifndef __WXOSX__
+ _labels_canvas->SetDoubleBuffered (true);
+ _main_canvas->SetDoubleBuffered (true);
+#endif
+
+ auto sizer = new wxBoxSizer (wxHORIZONTAL);
+ sizer->Add (_labels_canvas, 0, wxEXPAND);
+ _labels_canvas->SetMinSize (wxSize (_labels_view->bbox().width, -1));
+ sizer->Add (_main_canvas, 1, wxEXPAND);
+ SetSizer (sizer);
+
+ _labels_canvas->Bind(wxEVT_PAINT, boost::bind(&ContentTimeline::paint_labels, this));
+ _main_canvas->Bind (wxEVT_PAINT, boost::bind(&ContentTimeline::paint_main, this));
+ _main_canvas->Bind (wxEVT_LEFT_DOWN, boost::bind(&ContentTimeline::left_down, this, _1));
+ _main_canvas->Bind (wxEVT_LEFT_UP, boost::bind(&ContentTimeline::left_up, this, _1));
+ _main_canvas->Bind (wxEVT_RIGHT_DOWN, boost::bind(&ContentTimeline::right_down, this, _1));
+ _main_canvas->Bind (wxEVT_MOTION, boost::bind(&ContentTimeline::mouse_moved, this, _1));
+ _main_canvas->Bind (wxEVT_SIZE, boost::bind(&ContentTimeline::resized, this));
+ _main_canvas->Bind (wxEVT_MOUSEWHEEL, boost::bind(&ContentTimeline::mouse_wheel_turned, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_TOP, boost::bind(&ContentTimeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_BOTTOM, boost::bind(&ContentTimeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_LINEUP, boost::bind(&ContentTimeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_LINEDOWN, boost::bind(&ContentTimeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_PAGEUP, boost::bind(&ContentTimeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_PAGEDOWN, boost::bind(&ContentTimeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_THUMBTRACK, boost::bind(&ContentTimeline::scrolled, this, _1));
+
+ film_change(ChangeType::DONE, FilmProperty::CONTENT);
+
+ SetMinSize (wxSize (640, 4 * pixels_per_track() + 96));
+
+ _film_changed_connection = film->Change.connect(bind(&ContentTimeline::film_change, this, _1, _2));
+ _film_content_change_connection = film->ContentChange.connect(bind(&ContentTimeline::film_content_change, this, _1, _3, _4));
+
+ Bind(wxEVT_TIMER, boost::bind(&ContentTimeline::update_playhead, this));
+ _timer.Start (200, wxTIMER_CONTINUOUS);
+
+ setup_scrollbars ();
+ _labels_canvas->ShowScrollbars (wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
+}
+
+
+void
+ContentTimeline::mouse_wheel_turned(wxMouseEvent& event)
+{
+ auto const rotation = event.GetWheelRotation();
+
+ if (event.ControlDown()) {
+ /* On my mouse one click of the scroll wheel is 120, and it's -ve when
+ * scrolling the wheel towards me.
+ */
+ auto const scale = rotation > 0 ?
+ (1.0 / (rotation / 90.0)) :
+ (-rotation / 90.0);
+
+ int before_start_x;
+ int before_start_y;
+ _main_canvas->GetViewStart(&before_start_x, &before_start_y);
+
+ auto const before_pps = _pixels_per_second.get_value_or(1);
+ auto const before_pos = _last_mouse_wheel_x && *_last_mouse_wheel_x == event.GetX() ?
+ *_last_mouse_wheel_time :
+ (before_start_x * _x_scroll_rate + event.GetX()) / before_pps;
+
+ set_pixels_per_second(before_pps * scale);
+ setup_scrollbars();
+
+ auto after_left = std::max(0.0, before_pos * _pixels_per_second.get_value_or(1) - event.GetX());
+ _main_canvas->Scroll(after_left / _x_scroll_rate, before_start_y);
+ _labels_canvas->Scroll(0, before_start_y);
+ Refresh();
+
+ if (!_last_mouse_wheel_x || *_last_mouse_wheel_x != event.GetX()) {
+ _last_mouse_wheel_x = event.GetX();
+ _last_mouse_wheel_time = before_pos;
+ }
+ } else if (event.ShiftDown()) {
+ int before_start_x;
+ int before_start_y;
+ _main_canvas->GetViewStart(&before_start_x, &before_start_y);
+ auto const width = _main_canvas->GetSize().GetWidth();
+ _main_canvas->Scroll(std::max(0.0, before_start_x - rotation * 100.0 / width), before_start_y);
+ }
+}
+
+
+void
+ContentTimeline::update_playhead()
+{
+ Refresh ();
+}
+
+
+void
+ContentTimeline::paint_labels()
+{
+ wxPaintDC dc (_labels_canvas);
+
+ auto film = _film.lock();
+ if (film->content().empty()) {
+ return;
+ }
+
+ auto gc = wxGraphicsContext::Create (dc);
+ if (!gc) {
+ return;
+ }
+
+ dcp::ScopeGuard sg = [gc]() { delete gc; };
+
+ int vsx, vsy;
+ _labels_canvas->GetViewStart (&vsx, &vsy);
+ gc->Translate (-vsx * _x_scroll_rate, -vsy * _y_scroll_rate + tracks_y_offset());
+
+ _labels_view->paint (gc, {});
+}
+
+
+void
+ContentTimeline::paint_main()
+{
+ wxPaintDC dc (_main_canvas);
+ dc.Clear();
+
+ auto film = _film.lock();
+ if (film->content().empty()) {
+ return;
+ }
+
+ _main_canvas->DoPrepareDC (dc);
+
+ auto gc = wxGraphicsContext::Create (dc);
+ if (!gc) {
+ return;
+ }
+
+ dcp::ScopeGuard sg = [gc]() { delete gc; };
+
+ gc->SetAntialiasMode (wxANTIALIAS_DEFAULT);
+
+ for (auto i: _views) {
+
+ auto ic = dynamic_pointer_cast<TimelineContentView> (i);
+
+ /* Find areas of overlap with other content views, so that we can plot them */
+ list<dcpomatic::Rect<int>> overlaps;
+ for (auto j: _views) {
+ auto jc = dynamic_pointer_cast<TimelineContentView> (j);
+ /* No overlap with non-content views, views on different tracks, audio views or non-active views */
+ if (!ic || !jc || i == j || ic->track() != jc->track() || ic->track().get_value_or(2) >= 2 || !ic->active() || !jc->active()) {
+ continue;
+ }
+
+ auto r = j->bbox().intersection(i->bbox());
+ if (r) {
+ overlaps.push_back (r.get ());
+ }
+ }
+
+ i->paint (gc, overlaps);
+ }
+
+ if (_zoom_point) {
+ gc->SetPen(gui_is_dark() ? *wxWHITE_PEN : *wxBLACK_PEN);
+ gc->SetBrush (*wxTRANSPARENT_BRUSH);
+ gc->DrawRectangle (
+ min (_down_point.x, _zoom_point->x),
+ min (_down_point.y, _zoom_point->y),
+ abs (_down_point.x - _zoom_point->x),
+ abs (_down_point.y - _zoom_point->y)
+ );
+ }
+
+ /* Playhead */
+
+ gc->SetPen (*wxRED_PEN);
+ auto path = gc->CreatePath ();
+ double const ph = _viewer.position().seconds() * pixels_per_second().get_value_or(0);
+ path.MoveToPoint (ph, 0);
+ path.AddLineToPoint (ph, pixels_per_track() * _tracks + 32);
+ gc->StrokePath (path);
+}
+
+
+void
+ContentTimeline::film_change(ChangeType type, FilmProperty p)
+{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
+ if (p == FilmProperty::CONTENT || p == FilmProperty::REEL_TYPE || p == FilmProperty::REEL_LENGTH) {
+ ensure_ui_thread ();
+ recreate_views ();
+ } else if (p == FilmProperty::CONTENT_ORDER) {
+ Refresh ();
+ }
+}
+
+
+void
+ContentTimeline::recreate_views()
+{
+ auto film = _film.lock ();
+ if (!film) {
+ return;
+ }
+
+ _views.clear ();
+ _views.push_back (_time_axis_view);
+ _views.push_back (_reels_view);
+
+ for (auto i: film->content ()) {
+ if (i->video) {
+ _views.push_back(make_shared<ContentTimelineVideoView>(*this, i));
+ }
+
+ if (i->has_mapped_audio()) {
+ _views.push_back(make_shared<ContentTimelineAudioView>(*this, i));
+ }
+
+ for (auto j: i->text) {
+ _views.push_back(make_shared<ContentTimelineTextView>(*this, i, j));
+ }
+
+ if (i->atmos) {
+ _views.push_back(make_shared<ContentTimelineAtmosView>(*this, i));
+ }
+ }
+
+ assign_tracks ();
+ setup_scrollbars ();
+ Refresh ();
+}
+
+
+void
+ContentTimeline::film_content_change(ChangeType type, int property, bool frequent)
+{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
+ ensure_ui_thread ();
+
+ if (property == AudioContentProperty::STREAMS || property == VideoContentProperty::FRAME_TYPE) {
+ recreate_views ();
+ } else if (property == ContentProperty::POSITION || property == ContentProperty::LENGTH) {
+ _reels_view->force_redraw ();
+ } else if (!frequent) {
+ setup_scrollbars ();
+ Refresh ();
+ }
+}
+
+
+template <class T>
+int
+place(shared_ptr<const Film> film, ContentTimelineViewList& views, int& tracks)
+{
+ int const base = tracks;
+
+ for (auto i: views) {
+ if (!dynamic_pointer_cast<T>(i)) {
+ continue;
+ }
+
+ auto cv = dynamic_pointer_cast<TimelineContentView> (i);
+ DCPOMATIC_ASSERT(cv);
+
+ int t = base;
+
+ auto content = cv->content();
+ DCPTimePeriod const content_period = content->period(film);
+
+ while (true) {
+ auto j = views.begin();
+ while (j != views.end()) {
+ auto test = dynamic_pointer_cast<T> (*j);
+ if (!test) {
+ ++j;
+ continue;
+ }
+
+ auto test_content = test->content();
+ if (
+ test->track() && test->track().get() == t &&
+ content_period.overlap(test_content->period(film))
+ ) {
+ /* we have an overlap on track `t' */
+ ++t;
+ break;
+ }
+
+ ++j;
+ }
+
+ if (j == views.end ()) {
+ /* no overlap on `t' */
+ break;
+ }
+ }
+
+ cv->set_track (t);
+ tracks = max (tracks, t + 1);
+ }
+
+ return tracks - base;
+}
+
+
+/** Compare the mapped output channels of two TimelineViews, so we can into
+ * order of first mapped DCP channel.
+ */
+struct AudioMappingComparator {
+ bool operator()(shared_ptr<ContentTimelineView> a, shared_ptr<ContentTimelineView> b) {
+ int la = -1;
+ auto cva = dynamic_pointer_cast<ContentTimelineAudioView>(a);
+ if (cva) {
+ auto oc = cva->content()->audio->mapping().mapped_output_channels();
+ la = *min_element(boost::begin(oc), boost::end(oc));
+ }
+ int lb = -1;
+ auto cvb = dynamic_pointer_cast<ContentTimelineAudioView>(b);
+ if (cvb) {
+ auto oc = cvb->content()->audio->mapping().mapped_output_channels();
+ lb = *min_element(boost::begin(oc), boost::end(oc));
+ }
+ return la < lb;
+ }
+};
+
+
+void
+ContentTimeline::assign_tracks()
+{
+ /* Tracks are:
+ Video 1
+ Video 2
+ Video N
+ Text 1
+ Text 2
+ Text N
+ Atmos
+ Audio 1
+ Audio 2
+ Audio N
+ */
+
+ auto film = _film.lock ();
+ DCPOMATIC_ASSERT (film);
+
+ _tracks = 0;
+
+ for (auto i: _views) {
+ auto c = dynamic_pointer_cast<TimelineContentView>(i);
+ if (c) {
+ c->unset_track ();
+ }
+ }
+
+ int const video_tracks = place<ContentTimelineVideoView>(film, _views, _tracks);
+ int const text_tracks = place<ContentTimelineTextView>(film, _views, _tracks);
+
+ /* Atmos */
+
+ bool have_atmos = false;
+ for (auto i: _views) {
+ auto cv = dynamic_pointer_cast<ContentTimelineAtmosView>(i);
+ if (cv) {
+ cv->set_track (_tracks);
+ have_atmos = true;
+ }
+ }
+
+ if (have_atmos) {
+ ++_tracks;
+ }
+
+ /* Audio. We're sorting the views so that we get the audio views in order of increasing
+ DCP channel index.
+ */
+
+ auto views = _views;
+ sort(views.begin(), views.end(), AudioMappingComparator());
+ int const audio_tracks = place<ContentTimelineAudioView>(film, views, _tracks);
+
+ _labels_view->set_video_tracks (video_tracks);
+ _labels_view->set_audio_tracks (audio_tracks);
+ _labels_view->set_text_tracks (text_tracks);
+ _labels_view->set_atmos (have_atmos);
+
+ _time_axis_view->set_y (tracks());
+ _reels_view->set_y (8);
+}
+
+
+int
+ContentTimeline::tracks() const
+{
+ return _tracks;
+}
+
+
+void
+ContentTimeline::setup_scrollbars()
+{
+ auto film = _film.lock ();
+ if (!film || !_pixels_per_second) {
+ return;
+ }
+
+ int const h = tracks() * pixels_per_track() + tracks_y_offset() + _time_axis_view->bbox().height;
+
+ _labels_canvas->SetVirtualSize (_labels_view->bbox().width, h);
+ _labels_canvas->SetScrollRate (_x_scroll_rate, _y_scroll_rate);
+ _main_canvas->SetVirtualSize (*_pixels_per_second * film->length().seconds(), h);
+ _main_canvas->SetScrollRate (_x_scroll_rate, _y_scroll_rate);
+}
+
+
+shared_ptr<ContentTimelineView>
+ContentTimeline::event_to_view(wxMouseEvent& ev)
+{
+ /* Search backwards through views so that we find the uppermost one first */
+ auto i = _views.rbegin();
+
+ int vsx, vsy;
+ _main_canvas->GetViewStart (&vsx, &vsy);
+ Position<int> const p (ev.GetX() + vsx * _x_scroll_rate, ev.GetY() + vsy * _y_scroll_rate);
+
+ while (i != _views.rend() && !(*i)->bbox().contains (p)) {
+ ++i;
+ }
+
+ if (i == _views.rend ()) {
+ return {};
+ }
+
+ return *i;
+}
+
+
+void
+ContentTimeline::left_down(wxMouseEvent& ev)
+{
+ _left_down = true;
+ _down_point = ev.GetPosition ();
+
+ switch (_tool) {
+ case SELECT:
+ left_down_select (ev);
+ break;
+ case ZOOM:
+ case ZOOM_ALL:
+ case SNAP:
+ case SEQUENCE:
+ /* Nothing to do */
+ break;
+ }
+}
+
+
+void
+ContentTimeline::left_down_select(wxMouseEvent& ev)
+{
+ auto view = event_to_view (ev);
+ auto content_view = dynamic_pointer_cast<TimelineContentView>(view);
+
+ _down_view.reset ();
+
+ if (content_view) {
+ _down_view = content_view;
+ _down_view_position = content_view->content()->position ();
+ }
+
+ if (dynamic_pointer_cast<TimelineTimeAxisView>(view)) {
+ int vsx, vsy;
+ _main_canvas->GetViewStart(&vsx, &vsy);
+ _viewer.seek(DCPTime::from_seconds((ev.GetPosition().x + vsx * _x_scroll_rate) / _pixels_per_second.get_value_or(1)), true);
+ }
+
+ for (auto i: _views) {
+ auto cv = dynamic_pointer_cast<TimelineContentView>(i);
+ if (!cv) {
+ continue;
+ }
+
+ if (!ev.ShiftDown ()) {
+ cv->set_selected (view == i);
+ }
+ }
+
+ if (content_view && ev.ShiftDown ()) {
+ content_view->set_selected (!content_view->selected ());
+ }
+
+ _first_move = false;
+
+ if (_down_view) {
+ /* Pre-compute the points that we might snap to */
+ for (auto i: _views) {
+ auto cv = dynamic_pointer_cast<TimelineContentView>(i);
+ if (!cv || cv == _down_view || cv->content() == _down_view->content()) {
+ continue;
+ }
+
+ auto film = _film.lock ();
+ DCPOMATIC_ASSERT (film);
+
+ _start_snaps.push_back (cv->content()->position());
+ _end_snaps.push_back (cv->content()->position());
+ _start_snaps.push_back (cv->content()->end(film));
+ _end_snaps.push_back (cv->content()->end(film));
+
+ for (auto i: cv->content()->reel_split_points(film)) {
+ _start_snaps.push_back (i);
+ }
+ }
+
+ /* Tell everyone that things might change frequently during the drag */
+ _down_view->content()->set_change_signals_frequent (true);
+ }
+}
+
+
+void
+ContentTimeline::left_up(wxMouseEvent& ev)
+{
+ _left_down = false;
+
+ switch (_tool) {
+ case SELECT:
+ left_up_select (ev);
+ break;
+ case ZOOM:
+ left_up_zoom (ev);
+ break;
+ case ZOOM_ALL:
+ case SNAP:
+ case SEQUENCE:
+ break;
+ }
+}
+
+
+void
+ContentTimeline::left_up_select(wxMouseEvent& ev)
+{
+ if (_down_view) {
+ _down_view->content()->set_change_signals_frequent (false);
+ }
+
+ _content_panel->set_selection (selected_content ());
+ /* Since we may have just set change signals back to `not-frequent', we have to
+ make sure this position change is signalled, even if the position value has
+ not changed since the last time it was set (with frequent=true). This is
+ a bit of a hack.
+ */
+ set_position_from_event (ev, true);
+
+ /* Clear up up the stuff we don't do during drag */
+ assign_tracks ();
+ setup_scrollbars ();
+ Refresh ();
+
+ _start_snaps.clear ();
+ _end_snaps.clear ();
+}
+
+
+void
+ContentTimeline::left_up_zoom(wxMouseEvent& ev)
+{
+ _zoom_point = ev.GetPosition ();
+
+ int vsx, vsy;
+ _main_canvas->GetViewStart (&vsx, &vsy);
+
+ wxPoint top_left(min(_down_point.x, _zoom_point->x), min(_down_point.y, _zoom_point->y));
+ wxPoint bottom_right(max(_down_point.x, _zoom_point->x), max(_down_point.y, _zoom_point->y));
+
+ if ((bottom_right.x - top_left.x) < 8 || (bottom_right.y - top_left.y) < 8) {
+ /* Very small zoom rectangle: we assume it wasn't intentional */
+ _zoom_point = optional<wxPoint> ();
+ Refresh ();
+ return;
+ }
+
+ auto const time_left = DCPTime::from_seconds((top_left.x + vsx) / *_pixels_per_second);
+ auto const time_right = DCPTime::from_seconds((bottom_right.x + vsx) / *_pixels_per_second);
+ set_pixels_per_second (double(GetSize().GetWidth()) / (time_right.seconds() - time_left.seconds()));
+
+ double const tracks_top = double(top_left.y - tracks_y_offset()) / _pixels_per_track;
+ double const tracks_bottom = double(bottom_right.y - tracks_y_offset()) / _pixels_per_track;
+ set_pixels_per_track (lrint(GetSize().GetHeight() / (tracks_bottom - tracks_top)));
+
+ setup_scrollbars ();
+ int const y = (tracks_top * _pixels_per_track + tracks_y_offset()) / _y_scroll_rate;
+ _main_canvas->Scroll (time_left.seconds() * *_pixels_per_second / _x_scroll_rate, y);
+ _labels_canvas->Scroll (0, y);
+
+ _zoom_point = optional<wxPoint> ();
+ Refresh ();
+}
+
+
+void
+ContentTimeline::set_pixels_per_track(int h)
+{
+ _pixels_per_track = max(_minimum_pixels_per_track, h);
+}
+
+
+void
+ContentTimeline::mouse_moved(wxMouseEvent& ev)
+{
+ switch (_tool) {
+ case SELECT:
+ mouse_moved_select (ev);
+ break;
+ case ZOOM:
+ mouse_moved_zoom (ev);
+ break;
+ case ZOOM_ALL:
+ case SNAP:
+ case SEQUENCE:
+ break;
+ }
+}
+
+
+void
+ContentTimeline::mouse_moved_select(wxMouseEvent& ev)
+{
+ if (!_left_down) {
+ return;
+ }
+
+ set_position_from_event (ev);
+}
+
+
+void
+ContentTimeline::mouse_moved_zoom(wxMouseEvent& ev)
+{
+ if (!_left_down) {
+ return;
+ }
+
+ _zoom_point = ev.GetPosition ();
+ setup_scrollbars();
+ Refresh ();
+}
+
+
+void
+ContentTimeline::right_down(wxMouseEvent& ev)
+{
+ switch (_tool) {
+ case SELECT:
+ right_down_select (ev);
+ break;
+ case ZOOM:
+ /* Zoom out */
+ set_pixels_per_second (*_pixels_per_second / 2);
+ set_pixels_per_track (_pixels_per_track / 2);
+ setup_scrollbars ();
+ Refresh ();
+ break;
+ case ZOOM_ALL:
+ case SNAP:
+ case SEQUENCE:
+ break;
+ }
+}
+
+
+void
+ContentTimeline::right_down_select(wxMouseEvent& ev)
+{
+ auto view = event_to_view (ev);
+ auto cv = dynamic_pointer_cast<TimelineContentView> (view);
+ if (!cv) {
+ return;
+ }
+
+ if (!cv->selected ()) {
+ clear_selection ();
+ cv->set_selected (true);
+ }
+
+ _menu.popup (_film, selected_content (), selected_views (), ev.GetPosition ());
+}
+
+
+void
+ContentTimeline::maybe_snap(DCPTime a, DCPTime b, optional<DCPTime>& nearest_distance) const
+{
+ auto const d = a - b;
+ if (!nearest_distance || d.abs() < nearest_distance.get().abs()) {
+ nearest_distance = d;
+ }
+}
+
+
+void
+ContentTimeline::set_position_from_event(wxMouseEvent& ev, bool force_emit)
+{
+ if (!_pixels_per_second) {
+ return;
+ }
+
+ double const pps = _pixels_per_second.get ();
+
+ auto const p = ev.GetPosition();
+
+ if (!_first_move) {
+ /* We haven't moved yet; in that case, we must move the mouse some reasonable distance
+ before the drag is considered to have started.
+ */
+ int const dist = sqrt (pow (p.x - _down_point.x, 2) + pow (p.y - _down_point.y, 2));
+ if (dist < 8) {
+ return;
+ }
+ _first_move = true;
+ }
+
+ if (!_down_view) {
+ return;
+ }
+
+ auto new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / pps);
+
+ auto film = _film.lock ();
+ DCPOMATIC_ASSERT (film);
+
+ if (_snap) {
+ auto const new_end = new_position + _down_view->content()->length_after_trim(film);
+ /* Signed `distance' to nearest thing (i.e. negative is left on the timeline,
+ positive is right).
+ */
+ optional<DCPTime> nearest_distance;
+
+ /* Find the nearest snap point */
+
+ for (auto i: _start_snaps) {
+ maybe_snap (i, new_position, nearest_distance);
+ }
+
+ for (auto i: _end_snaps) {
+ maybe_snap (i, new_end, nearest_distance);
+ }
+
+ if (nearest_distance) {
+ /* Snap if it's close; `close' means within a proportion of the time on the timeline */
+ if (nearest_distance.get().abs() < DCPTime::from_seconds ((width() / pps) / SNAP_SUBDIVISION)) {
+ new_position += nearest_distance.get ();
+ }
+ }
+ }
+
+ if (new_position < DCPTime ()) {
+ new_position = DCPTime ();
+ }
+
+ _down_view->content()->set_position (film, new_position, force_emit);
+
+ film->set_sequence (false);
+}
+
+
+void
+ContentTimeline::force_redraw(dcpomatic::Rect<int> const & r)
+{
+ _main_canvas->RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
+}
+
+
+shared_ptr<const Film>
+ContentTimeline::film() const
+{
+ return _film.lock ();
+}
+
+
+void
+ContentTimeline::resized()
+{
+ if (_main_canvas->GetSize().GetWidth() > 0 && _first_resize) {
+ zoom_all ();
+ _first_resize = false;
+ }
+ setup_scrollbars ();
+}
+
+
+void
+ContentTimeline::clear_selection()
+{
+ for (auto i: _views) {
+ shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView>(i);
+ if (cv) {
+ cv->set_selected (false);
+ }
+ }
+}
+
+
+TimelineContentViewList
+ContentTimeline::selected_views() const
+{
+ TimelineContentViewList sel;
+
+ for (auto i: _views) {
+ auto cv = dynamic_pointer_cast<TimelineContentView>(i);
+ if (cv && cv->selected()) {
+ sel.push_back (cv);
+ }
+ }
+
+ return sel;
+}
+
+
+ContentList
+ContentTimeline::selected_content() const
+{
+ ContentList sel;
+
+ for (auto i: selected_views()) {
+ sel.push_back(i->content());
+ }
+
+ return sel;
+}
+
+
+void
+ContentTimeline::set_selection(ContentList selection)
+{
+ for (auto i: _views) {
+ auto cv = dynamic_pointer_cast<TimelineContentView> (i);
+ if (cv) {
+ cv->set_selected (find (selection.begin(), selection.end(), cv->content ()) != selection.end ());
+ }
+ }
+}
+
+
+int
+ContentTimeline::tracks_y_offset() const
+{
+ return _reels_view->bbox().height + 4;
+}
+
+
+int
+ContentTimeline::width() const
+{
+ return _main_canvas->GetVirtualSize().GetWidth();
+}
+
+
+void
+ContentTimeline::scrolled(wxScrollWinEvent& ev)
+{
+ if (ev.GetOrientation() == wxVERTICAL) {
+ int x, y;
+ _main_canvas->GetViewStart (&x, &y);
+ _labels_canvas->Scroll (0, y);
+ }
+ ev.Skip ();
+}
+
+
+void
+ContentTimeline::tool_clicked(Tool t)
+{
+ switch (t) {
+ case ZOOM:
+ case SELECT:
+ _tool = t;
+ break;
+ case ZOOM_ALL:
+ zoom_all ();
+ break;
+ case SNAP:
+ case SEQUENCE:
+ break;
+ }
+}
+
+
+void
+ContentTimeline::zoom_all()
+{
+ auto film = _film.lock ();
+ DCPOMATIC_ASSERT (film);
+ set_pixels_per_second((_main_canvas->GetSize().GetWidth() - 32) / std::max(1.0, film->length().seconds()));
+ set_pixels_per_track((_main_canvas->GetSize().GetHeight() - tracks_y_offset() - _time_axis_view->bbox().height - 32) / std::max(1, _tracks));
+ setup_scrollbars ();
+ _main_canvas->Scroll (0, 0);
+ _labels_canvas->Scroll (0, 0);
+ Refresh ();
+}
+
+
+void
+ContentTimeline::keypress(wxKeyEvent const& event)
+{
+ if (event.GetKeyCode() == WXK_DELETE) {
+ auto film = _film.lock();
+ DCPOMATIC_ASSERT(film);
+ film->remove_content(selected_content());
+ } else {
+ switch (event.GetRawKeyCode()) {
+ case '+':
+ set_pixels_per_second(_pixels_per_second.get_value_or(1) * 2);
+ setup_scrollbars();
+ break;
+ case '-':
+ set_pixels_per_second(_pixels_per_second.get_value_or(1) / 2);
+ setup_scrollbars();
+ break;
+ }
+ }
+}
+
diff --git a/src/wx/content_timeline.h b/src/wx/content_timeline.h
new file mode 100644
index 000000000..10f880191
--- /dev/null
+++ b/src/wx/content_timeline.h
@@ -0,0 +1,149 @@
+/*
+ Copyright (C) 2013-2019 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "content_menu.h"
+#include "timeline.h"
+#include "timeline_content_view.h"
+#include "lib/film_property.h"
+#include "lib/rect.h"
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+#include <boost/signals2.hpp>
+
+
+class ContentPanel;
+class ContentTimelineView;
+class Film;
+class FilmViewer;
+class TimelineLabelsView;
+class TimelineReelsView;
+class TimelineTimeAxisView;
+
+
+class ContentTimeline : public Timeline
+{
+public:
+ ContentTimeline(wxWindow *, ContentPanel *, std::shared_ptr<Film>, FilmViewer& viewer);
+
+ std::shared_ptr<const Film> film () const;
+
+ void force_redraw (dcpomatic::Rect<int> const &);
+
+ int width () const;
+
+ int pixels_per_track () const {
+ return _pixels_per_track;
+ }
+
+ int tracks () const;
+
+ void set_snap (bool s) {
+ _snap = s;
+ }
+
+ bool snap () const {
+ return _snap;
+ }
+
+ void set_selection (ContentList selection);
+
+ enum Tool {
+ SELECT,
+ ZOOM,
+ ZOOM_ALL,
+ SNAP,
+ SEQUENCE
+ };
+
+ void tool_clicked (Tool t);
+
+ int tracks_y_offset () const;
+
+ void keypress(wxKeyEvent const &);
+
+private:
+ void paint_labels ();
+ void paint_main ();
+ void left_down (wxMouseEvent &);
+ void left_down_select (wxMouseEvent &);
+ void left_up (wxMouseEvent &);
+ void left_up_select (wxMouseEvent &);
+ void left_up_zoom (wxMouseEvent &);
+ void right_down (wxMouseEvent &);
+ void right_down_select (wxMouseEvent &);
+ void mouse_moved (wxMouseEvent &);
+ void mouse_moved_select (wxMouseEvent &);
+ void mouse_moved_zoom (wxMouseEvent &);
+ void film_change(ChangeType type, FilmProperty);
+ void film_content_change (ChangeType type, int, bool frequent);
+ void resized ();
+ void assign_tracks ();
+ void set_position_from_event (wxMouseEvent& ev, bool force_emit = false);
+ void clear_selection ();
+ void recreate_views ();
+ void setup_scrollbars ();
+ void scrolled (wxScrollWinEvent& ev);
+ void set_pixels_per_track (int h);
+ void zoom_all ();
+ void update_playhead ();
+ void mouse_wheel_turned(wxMouseEvent& event);
+
+ std::shared_ptr<ContentTimelineView> event_to_view(wxMouseEvent &);
+ TimelineContentViewList selected_views () const;
+ ContentList selected_content () const;
+ void maybe_snap (dcpomatic::DCPTime a, dcpomatic::DCPTime b, boost::optional<dcpomatic::DCPTime>& nearest_distance) const;
+
+ wxScrolledCanvas* _labels_canvas;
+ wxScrolledCanvas* _main_canvas;
+ ContentPanel* _content_panel;
+ std::weak_ptr<Film> _film;
+ FilmViewer& _viewer;
+ ContentTimelineViewList _views;
+ std::shared_ptr<TimelineTimeAxisView> _time_axis_view;
+ std::shared_ptr<TimelineReelsView> _reels_view;
+ std::shared_ptr<TimelineLabelsView> _labels_view;
+ int _tracks;
+ bool _left_down;
+ wxPoint _down_point;
+ boost::optional<wxPoint> _zoom_point;
+ std::shared_ptr<TimelineContentView> _down_view;
+ dcpomatic::DCPTime _down_view_position;
+ bool _first_move;
+ ContentMenu _menu;
+ bool _snap;
+ std::list<dcpomatic::DCPTime> _start_snaps;
+ std::list<dcpomatic::DCPTime> _end_snaps;
+ Tool _tool;
+ int _x_scroll_rate;
+ int _y_scroll_rate;
+ int _pixels_per_track;
+ bool _first_resize;
+ wxTimer _timer;
+ boost::optional<int> _last_mouse_wheel_x;
+ boost::optional<double> _last_mouse_wheel_time;
+
+ static int const _minimum_pixels_per_track;
+
+ boost::signals2::scoped_connection _film_changed_connection;
+ boost::signals2::scoped_connection _film_content_change_connection;
+};
diff --git a/src/wx/timeline_atmos_content_view.cc b/src/wx/content_timeline_atmos_view.cc
index ec0f06ec1..2352ba5dc 100644
--- a/src/wx/timeline_atmos_content_view.cc
+++ b/src/wx/content_timeline_atmos_view.cc
@@ -19,17 +19,18 @@
*/
-#include "timeline_atmos_content_view.h"
+#include "colours.h"
+#include "content_timeline_atmos_view.h"
using std::shared_ptr;
-/** @class TimelineAtmosContentView
- * @brief Timeline view for AtmosContent.
+/** @class ContentTimelineContentView
+ * @brief Content timeline view for AtmosContent.
*/
-TimelineAtmosContentView::TimelineAtmosContentView (Timeline& tl, shared_ptr<Content> c)
+ContentTimelineAtmosView::ContentTimelineAtmosView(ContentTimeline& tl, shared_ptr<Content> c)
: TimelineContentView (tl, c)
{
@@ -37,14 +38,14 @@ TimelineAtmosContentView::TimelineAtmosContentView (Timeline& tl, shared_ptr<Con
wxColour
-TimelineAtmosContentView::background_colour () const
+ContentTimelineAtmosView::background_colour() const
{
- return wxColour (149, 121, 232, 255);
+ return ATMOS_CONTENT_COLOUR;
}
wxColour
-TimelineAtmosContentView::foreground_colour () const
+ContentTimelineAtmosView::foreground_colour() const
{
return wxColour (0, 0, 0, 255);
}
diff --git a/src/wx/timeline_atmos_content_view.h b/src/wx/content_timeline_atmos_view.h
index 15da14edc..d5fea9248 100644
--- a/src/wx/timeline_atmos_content_view.h
+++ b/src/wx/content_timeline_atmos_view.h
@@ -22,13 +22,13 @@
#include "timeline_content_view.h"
-/** @class TimelineAtmosContentView
- * @brief Timeline view for AtmosContent.
+/** @class ContentTimelineAtmosContentView
+ * @brief Content timeline view for AtmosContent.
*/
-class TimelineAtmosContentView : public TimelineContentView
+class ContentTimelineAtmosView : public TimelineContentView
{
public:
- TimelineAtmosContentView (Timeline& tl, std::shared_ptr<Content> c);
+ ContentTimelineAtmosView(ContentTimeline& tl, std::shared_ptr<Content> c);
private:
bool active () const override {
diff --git a/src/wx/timeline_audio_content_view.cc b/src/wx/content_timeline_audio_view.cc
index 0e9701034..cff0b5ed5 100644
--- a/src/wx/timeline_audio_content_view.cc
+++ b/src/wx/content_timeline_audio_view.cc
@@ -18,39 +18,44 @@
*/
-#include "timeline_audio_content_view.h"
+
+#include "colours.h"
+#include "content_timeline_audio_view.h"
#include "wx_util.h"
#include "lib/audio_content.h"
#include "lib/util.h"
+
+using std::dynamic_pointer_cast;
using std::list;
using std::shared_ptr;
-using std::dynamic_pointer_cast;
-/** @class TimelineAudioContentView
- * @brief Timeline view for AudioContent.
+
+/** @class ContentTimelineAudioView
+ * @brief Content timeline view for AudioContent.
*/
-TimelineAudioContentView::TimelineAudioContentView (Timeline& tl, shared_ptr<Content> c)
+
+ContentTimelineAudioView::ContentTimelineAudioView(ContentTimeline& tl, shared_ptr<Content> c)
: TimelineContentView (tl, c)
{
}
wxColour
-TimelineAudioContentView::background_colour () const
+ContentTimelineAudioView::background_colour () const
{
- return wxColour (149, 121, 232, 255);
+ return AUDIO_CONTENT_COLOUR;
}
wxColour
-TimelineAudioContentView::foreground_colour () const
+ContentTimelineAudioView::foreground_colour () const
{
return wxColour (0, 0, 0, 255);
}
wxString
-TimelineAudioContentView::label () const
+ContentTimelineAudioView::label () const
{
wxString s = TimelineContentView::label ();
shared_ptr<AudioContent> ac = content()->audio;
diff --git a/src/wx/timeline_audio_content_view.h b/src/wx/content_timeline_audio_view.h
index 5b8d6cdc2..719c2536d 100644
--- a/src/wx/timeline_audio_content_view.h
+++ b/src/wx/content_timeline_audio_view.h
@@ -22,13 +22,13 @@
#include "timeline_content_view.h"
-/** @class TimelineAudioContentView
- * @brief Timeline view for AudioContent.
+/** @class ContentTimelineAudioView
+ * @brief Content timeline view for AudioContent.
*/
-class TimelineAudioContentView : public TimelineContentView
+class ContentTimelineAudioView : public TimelineContentView
{
public:
- TimelineAudioContentView (Timeline& tl, std::shared_ptr<Content> c);
+ ContentTimelineAudioView(ContentTimeline& tl, std::shared_ptr<Content> c);
private:
bool active () const override {
diff --git a/src/wx/timeline_dialog.cc b/src/wx/content_timeline_dialog.cc
index e0e1689a8..697e6561f 100644
--- a/src/wx/timeline_dialog.cc
+++ b/src/wx/content_timeline_dialog.cc
@@ -20,8 +20,8 @@
#include "content_panel.h"
+#include "content_timeline_dialog.h"
#include "film_editor.h"
-#include "timeline_dialog.h"
#include "wx_util.h"
#include "lib/compose.hpp"
#include "lib/cross.h"
@@ -43,7 +43,7 @@ using namespace boost::placeholders;
#endif
-TimelineDialog::TimelineDialog(ContentPanel* cp, shared_ptr<Film> film, FilmViewer& viewer)
+ContentTimelineDialog::ContentTimelineDialog(ContentPanel* cp, shared_ptr<Film> film, FilmViewer& viewer)
: wxDialog (
cp->window(),
wxID_ANY,
@@ -73,14 +73,14 @@ TimelineDialog::TimelineDialog(ContentPanel* cp, shared_ptr<Film> film, FilmView
_toolbar = new wxToolBar (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL);
_toolbar->SetMargins (4, 4);
_toolbar->SetToolBitmapSize (wxSize(32, 32));
- _toolbar->AddRadioTool ((int) Timeline::SELECT, _("Select"), select, wxNullBitmap, _("Select and move content"));
- _toolbar->AddRadioTool ((int) Timeline::ZOOM, _("Zoom"), zoom, wxNullBitmap, _("Zoom in / out"));
- _toolbar->AddTool ((int) Timeline::ZOOM_ALL, _("Zoom all"), zoom_all, _("Zoom out to whole film"));
- _toolbar->AddCheckTool ((int) Timeline::SNAP, _("Snap"), snap, wxNullBitmap, _("Snap"));
- _toolbar->AddCheckTool ((int) Timeline::SEQUENCE, _("Sequence"), sequence, wxNullBitmap, _("Keep video and subtitles in sequence"));
+ _toolbar->AddRadioTool(static_cast<int>(ContentTimeline::SELECT), _("Select"), select, wxNullBitmap, _("Select and move content"));
+ _toolbar->AddRadioTool(static_cast<int>(ContentTimeline::ZOOM), _("Zoom"), zoom, wxNullBitmap, _("Zoom in / out"));
+ _toolbar->AddTool(static_cast<int>(ContentTimeline::ZOOM_ALL), _("Zoom all"), zoom_all, _("Zoom out to whole film"));
+ _toolbar->AddCheckTool(static_cast<int>(ContentTimeline::SNAP), _("Snap"), snap, wxNullBitmap, _("Snap"));
+ _toolbar->AddCheckTool(static_cast<int>(ContentTimeline::SEQUENCE), _("Sequence"), sequence, wxNullBitmap, _("Keep video and subtitles in sequence"));
_toolbar->Realize ();
- _toolbar->Bind (wxEVT_TOOL, bind (&TimelineDialog::tool_clicked, this, _1));
+ _toolbar->Bind(wxEVT_TOOL, bind(&ContentTimelineDialog::tool_clicked, this, _1));
sizer->Add (_toolbar, 0, wxALL, 12);
sizer->Add (&_timeline, 1, wxEXPAND | wxALL, 12);
@@ -96,17 +96,17 @@ TimelineDialog::TimelineDialog(ContentPanel* cp, shared_ptr<Film> film, FilmView
sizer->Layout ();
sizer->SetSizeHints (this);
- Bind(wxEVT_CHAR_HOOK, boost::bind(&TimelineDialog::keypress, this, _1));
+ Bind(wxEVT_CHAR_HOOK, boost::bind(&ContentTimelineDialog::keypress, this, _1));
- _toolbar->ToggleTool ((int) Timeline::SNAP, _timeline.snap ());
+ _toolbar->ToggleTool(static_cast<int>(ContentTimeline::SNAP), _timeline.snap ());
film_change(ChangeType::DONE, FilmProperty::SEQUENCE);
- _film_changed_connection = film->Change.connect (bind (&TimelineDialog::film_change, this, _1, _2));
+ _film_changed_connection = film->Change.connect(bind(&ContentTimelineDialog::film_change, this, _1, _2));
}
void
-TimelineDialog::film_change(ChangeType type, FilmProperty p)
+ContentTimelineDialog::film_change(ChangeType type, FilmProperty p)
{
if (type != ChangeType::DONE) {
return;
@@ -118,26 +118,26 @@ TimelineDialog::film_change(ChangeType type, FilmProperty p)
}
if (p == FilmProperty::SEQUENCE) {
- _toolbar->ToggleTool ((int) Timeline::SEQUENCE, film->sequence ());
+ _toolbar->ToggleTool(static_cast<int>(ContentTimeline::SEQUENCE), film->sequence());
}
}
void
-TimelineDialog::set_selection (ContentList selection)
+ContentTimelineDialog::set_selection(ContentList selection)
{
_timeline.set_selection (selection);
}
void
-TimelineDialog::tool_clicked (wxCommandEvent& ev)
+ContentTimelineDialog::tool_clicked(wxCommandEvent& ev)
{
- Timeline::Tool t = static_cast<Timeline::Tool>(ev.GetId());
+ auto t = static_cast<ContentTimeline::Tool>(ev.GetId());
_timeline.tool_clicked (t);
- if (t == Timeline::SNAP) {
+ if (t == ContentTimeline::SNAP) {
_timeline.set_snap (_toolbar->GetToolState(static_cast<int>(t)));
- } else if (t == Timeline::SEQUENCE) {
+ } else if (t == ContentTimeline::SEQUENCE) {
auto film = _film.lock ();
if (film) {
film->set_sequence (_toolbar->GetToolState(static_cast<int>(t)));
@@ -147,7 +147,7 @@ TimelineDialog::tool_clicked (wxCommandEvent& ev)
void
-TimelineDialog::keypress(wxKeyEvent const& event)
+ContentTimelineDialog::keypress(wxKeyEvent const& event)
{
_timeline.keypress(event);
}
diff --git a/src/wx/timeline_dialog.h b/src/wx/content_timeline_dialog.h
index 8134aa6db..2babf0437 100644
--- a/src/wx/timeline_dialog.h
+++ b/src/wx/content_timeline_dialog.h
@@ -19,7 +19,7 @@
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
@@ -29,10 +29,10 @@ LIBDCP_ENABLE_WARNINGS
class Playlist;
-class TimelineDialog : public wxDialog
+class ContentTimelineDialog : public wxDialog
{
public:
- TimelineDialog(ContentPanel *, std::shared_ptr<Film>, FilmViewer& viewer);
+ ContentTimelineDialog(ContentPanel *, std::shared_ptr<Film>, FilmViewer& viewer);
void set_selection (ContentList selection);
@@ -42,7 +42,7 @@ private:
void keypress(wxKeyEvent const& event);
std::weak_ptr<Film> _film;
- Timeline _timeline;
+ ContentTimeline _timeline;
wxToolBar* _toolbar;
boost::signals2::scoped_connection _film_changed_connection;
};
diff --git a/src/wx/timeline_text_content_view.cc b/src/wx/content_timeline_text_view.cc
index a57398599..f22f47850 100644
--- a/src/wx/timeline_text_content_view.cc
+++ b/src/wx/content_timeline_text_view.cc
@@ -19,7 +19,8 @@
*/
-#include "timeline_text_content_view.h"
+#include "colours.h"
+#include "content_timeline_text_view.h"
#include "lib/text_content.h"
#include "lib/content.h"
@@ -27,7 +28,7 @@
using std::shared_ptr;
-TimelineTextContentView::TimelineTextContentView (Timeline& tl, shared_ptr<Content> c, shared_ptr<TextContent> caption)
+ContentTimelineTextView::ContentTimelineTextView(ContentTimeline& tl, shared_ptr<Content> c, shared_ptr<TextContent> caption)
: TimelineContentView (tl, c)
, _caption (caption)
{
@@ -35,17 +36,17 @@ TimelineTextContentView::TimelineTextContentView (Timeline& tl, shared_ptr<Conte
}
wxColour
-TimelineTextContentView::background_colour () const
+ContentTimelineTextView::background_colour() const
{
if (!active ()) {
return wxColour (210, 210, 210, 128);
}
- return wxColour (163, 255, 154, 255);
+ return TEXT_CONTENT_COLOUR;
}
wxColour
-TimelineTextContentView::foreground_colour () const
+ContentTimelineTextView::foreground_colour() const
{
if (!active ()) {
return wxColour (180, 180, 180, 128);
@@ -55,7 +56,7 @@ TimelineTextContentView::foreground_colour () const
}
bool
-TimelineTextContentView::active () const
+ContentTimelineTextView::active() const
{
return _caption->use();
}
diff --git a/src/wx/timeline_text_content_view.h b/src/wx/content_timeline_text_view.h
index 046f5b3e6..8d37ad489 100644
--- a/src/wx/timeline_text_content_view.h
+++ b/src/wx/content_timeline_text_view.h
@@ -18,18 +18,20 @@
*/
+
#include "timeline_content_view.h"
+
class TextContent;
-class TextContent;
-/** @class TimelineTextContentView
- * @brief Timeline view for TextContent.
+
+/** @class ContentTimelineTextView
+ * @brief Content timeline view for TextContent.
*/
-class TimelineTextContentView : public TimelineContentView
+class ContentTimelineTextView : public TimelineContentView
{
public:
- TimelineTextContentView (Timeline& tl, std::shared_ptr<Content>, std::shared_ptr<TextContent>);
+ ContentTimelineTextView(ContentTimeline& tl, std::shared_ptr<Content>, std::shared_ptr<TextContent>);
private:
bool active () const override;
diff --git a/src/wx/timeline_video_content_view.cc b/src/wx/content_timeline_video_view.cc
index b0f4b4f5d..d907627cd 100644
--- a/src/wx/timeline_video_content_view.cc
+++ b/src/wx/content_timeline_video_view.cc
@@ -18,31 +18,35 @@
*/
+
+#include "colours.h"
+#include "content_timeline_video_view.h"
#include "lib/image_content.h"
#include "lib/video_content.h"
-#include "timeline_video_content_view.h"
+
using std::dynamic_pointer_cast;
using std::shared_ptr;
-TimelineVideoContentView::TimelineVideoContentView (Timeline& tl, shared_ptr<Content> c)
+
+ContentTimelineVideoView::ContentTimelineVideoView(ContentTimeline& tl, shared_ptr<Content> c)
: TimelineContentView (tl, c)
{
}
wxColour
-TimelineVideoContentView::background_colour () const
+ContentTimelineVideoView::background_colour() const
{
if (!active()) {
return wxColour (210, 210, 210, 128);
}
- return wxColour (242, 92, 120, 255);
+ return VIDEO_CONTENT_COLOUR;
}
wxColour
-TimelineVideoContentView::foreground_colour () const
+ContentTimelineVideoView::foreground_colour() const
{
if (!active()) {
return wxColour (180, 180, 180, 128);
@@ -52,9 +56,9 @@ TimelineVideoContentView::foreground_colour () const
}
bool
-TimelineVideoContentView::active () const
+ContentTimelineVideoView::active() const
{
- shared_ptr<Content> c = _content.lock ();
+ auto c = _content.lock();
DCPOMATIC_ASSERT (c);
return c->video && c->video->use();
}
diff --git a/src/wx/timeline_video_content_view.h b/src/wx/content_timeline_video_view.h
index fa8ddf54c..940a1a305 100644
--- a/src/wx/timeline_video_content_view.h
+++ b/src/wx/content_timeline_video_view.h
@@ -22,13 +22,13 @@
#include "timeline_content_view.h"
-/** @class TimelineVideoContentView
- * @brief Timeline view for VideoContent.
+/** @class ContentTimelineVideoView
+ * @brief Content timeline view for VideoContent.
*/
-class TimelineVideoContentView : public TimelineContentView
+class ContentTimelineVideoView : public TimelineContentView
{
public:
- TimelineVideoContentView (Timeline& tl, std::shared_ptr<Content> c);
+ ContentTimelineVideoView(ContentTimeline& tl, std::shared_ptr<Content> c);
private:
bool active () const override;
diff --git a/src/wx/content_timeline_view.cc b/src/wx/content_timeline_view.cc
new file mode 100644
index 000000000..127e440fa
--- /dev/null
+++ b/src/wx/content_timeline_view.cc
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "content_timeline.h"
+#include "content_timeline_view.h"
+
+
+using std::list;
+using namespace dcpomatic;
+
+
+ContentTimelineView::ContentTimelineView(ContentTimeline& timeline)
+ : TimelineView(timeline)
+{
+
+}
+
+int
+ContentTimelineView::y_pos(int t) const
+{
+ return t * _timeline.pixels_per_track() + _timeline.tracks_y_offset();
+}
+
+
diff --git a/src/wx/content_timeline_view.h b/src/wx/content_timeline_view.h
new file mode 100644
index 000000000..450d19df4
--- /dev/null
+++ b/src/wx/content_timeline_view.h
@@ -0,0 +1,60 @@
+/*
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_CONTENT_TIMELINE_VIEW_H
+#define DCPOMATIC_CONTENT_TIMELINE_VIEW_H
+
+
+#include "timeline_view.h"
+#include "lib/rect.h"
+#include "lib/dcpomatic_time.h"
+
+
+class wxGraphicsContext;
+class ContentTimeline;
+
+
+/** @class ContentTimelineView
+ * @brief Parent class for components of the content timeline (e.g. a piece of content or an axis).
+ */
+class ContentTimelineView : public TimelineView<ContentTimeline>
+{
+public:
+ explicit ContentTimelineView(ContentTimeline& t);
+ virtual ~ContentTimelineView () = default;
+
+ void paint(wxGraphicsContext* gc, std::list<dcpomatic::Rect<int>> overlaps)
+ {
+ _last_paint_bbox = bbox();
+ do_paint(gc, overlaps);
+ }
+
+protected:
+ virtual void do_paint(wxGraphicsContext *, std::list<dcpomatic::Rect<int>> overlaps) = 0;
+
+ int y_pos(int t) const;
+};
+
+
+typedef std::vector<std::shared_ptr<ContentTimelineView>> ContentTimelineViewList;
+
+
+#endif
diff --git a/src/wx/content_view.cc b/src/wx/content_view.cc
index b9fe7ce00..b2d6f86d1 100644
--- a/src/wx/content_view.cc
+++ b/src/wx/content_view.cc
@@ -21,6 +21,7 @@
#include "content_view.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/config.h"
#include "lib/content_factory.h"
#include "lib/cross.h"
@@ -86,7 +87,7 @@ ContentView::update ()
dir = home_directory ();
}
- wxProgressDialog progress (_("DCP-o-matic"), _("Reading content directory"));
+ wxProgressDialog progress(variant::wx::dcpomatic(), _("Reading content directory"));
auto jm = JobManager::instance ();
list<shared_ptr<ExamineContentJob>> jobs;
diff --git a/src/wx/dcp_panel.cc b/src/wx/dcp_panel.cc
index f4ba74cde..4d2ee12b9 100644
--- a/src/wx/dcp_panel.cc
+++ b/src/wx/dcp_panel.cc
@@ -23,6 +23,7 @@
#include "check_box.h"
#include "check_box.h"
#include "dcp_panel.h"
+#include "dcp_timeline_dialog.h"
#include "dcpomatic_button.h"
#include "dcpomatic_choice.h"
#include "dcpomatic_spin_ctrl.h"
@@ -97,10 +98,6 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr<Film> film, FilmViewer& viewer)
wxALIGN_CENTRE_HORIZONTAL | wxST_NO_AUTORESIZE | wxST_ELLIPSIZE_MIDDLE
);
- _enable_audio_language = new CheckBox(_panel, _("Audio language"));
- _audio_language = new wxStaticText (_panel, wxID_ANY, wxT(""));
- _edit_audio_language = new Button (_panel, _("Edit..."));
-
_dcp_content_type_label = create_label (_panel, _("Content Type"), true);
_dcp_content_type = new Choice(_panel);
@@ -110,18 +107,12 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr<Film> film, FilmViewer& viewer)
auto size = dc.GetTextExtent (wxT ("GGGGGGGG..."));
size.SetHeight (-1);
- _reels_label = create_label (_panel, _("Reels"), true);
- _reel_type = new Choice(_panel);
-
- _reel_length_label = create_label (_panel, _("Reel length"), true);
- _reel_length = new SpinCtrl (_panel, DCPOMATIC_SPIN_CTRL_WIDTH);
- _reel_length_gb_label = create_label (_panel, _("GB"), false);
-
_standard_label = create_label (_panel, _("Standard"), true);
_standard = new Choice(_panel);
_markers = new Button (_panel, _("Markers..."));
_metadata = new Button (_panel, _("Metadata..."));
+ _reels = new Button(_panel, _("Reels..."));
_notebook = new wxNotebook (_panel, wxID_ANY);
_sizer->Add (_notebook, 1, wxEXPAND | wxTOP, 6);
@@ -134,28 +125,17 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr<Film> film, FilmViewer& viewer)
_copy_isdcf_name_button->Bind(wxEVT_BUTTON, boost::bind(&DCPPanel::copy_isdcf_name_button_clicked, this));
_dcp_content_type->Bind (wxEVT_CHOICE, boost::bind(&DCPPanel::dcp_content_type_changed, this));
_encrypted->bind(&DCPPanel::encrypted_toggled, this);
- _reel_type->Bind (wxEVT_CHOICE, boost::bind(&DCPPanel::reel_type_changed, this));
- _reel_length->Bind (wxEVT_SPINCTRL, boost::bind(&DCPPanel::reel_length_changed, this));
_standard->Bind (wxEVT_CHOICE, boost::bind(&DCPPanel::standard_changed, this));
_markers->Bind (wxEVT_BUTTON, boost::bind(&DCPPanel::markers_clicked, this));
_metadata->Bind (wxEVT_BUTTON, boost::bind(&DCPPanel::metadata_clicked, this));
- _enable_audio_language->bind(&DCPPanel::enable_audio_language_toggled, this);
- _edit_audio_language->Bind (wxEVT_BUTTON, boost::bind(&DCPPanel::edit_audio_language_clicked, this));
+ _reels->Bind(wxEVT_BUTTON, boost::bind(&DCPPanel::reels_clicked, this));
for (auto i: DCPContentType::all()) {
- _dcp_content_type->add(i->pretty_name());
+ _dcp_content_type->add_entry(i->pretty_name());
}
- _reel_type->add(_("Single reel"));
- _reel_type->add(_("Split by video content"));
- /// TRANSLATORS: translate the word "Custom" here; do not include the "Reel|" prefix
- _reel_type->add(S_("Reel|Custom"));
- _reel_type->SetToolTip(_("How the DCP should be split into parts internally. If in doubt, choose 'Single reel'"));
-
- _reel_length->SetRange (1, 64);
-
add_standards();
- _standard->SetToolTip(_("Which standard the DCP should use. Interop is older and SMPTE is the modern standard. If in doubt, choose 'SMPTE'"));
+ _standard->SetToolTip(_("The standard that the DCP should use. Interop is older, and SMPTE is the newer (current) standard. If in doubt, choose 'SMPTE'"));
Config::instance()->Changed.connect (boost::bind(&DCPPanel::config_changed, this, _1));
@@ -166,11 +146,12 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr<Film> film, FilmViewer& viewer)
void
DCPPanel::add_standards()
{
- _standard->add(_("SMPTE"), N_("smpte"));
+ _standard->add_entry(_("SMPTE"), N_("smpte"));
if (Config::instance()->allow_smpte_bv20() || (_film && _film->limit_to_smpte_bv20())) {
- _standard->add(_("SMPTE (Bv2.0 only)"), N_("smpte-bv20"));
+ _standard->add_entry(_("SMPTE (Bv2.0 only)"), N_("smpte-bv20"));
}
- _standard->add(_("Interop"), N_("interop"));
+ _standard->add_entry(_("Interop"), N_("interop"));
+ _standard->add_entry(_("MPEG2 Interop"), N_("mpeg2-interop"));
_sizer->Layout();
}
@@ -182,7 +163,11 @@ DCPPanel::set_standard()
DCPOMATIC_ASSERT(!_film->limit_to_smpte_bv20() || _standard->GetCount() == 3);
if (_film->interop()) {
- checked_set(_standard, "interop");
+ if (_film->video_encoding() == VideoEncoding::JPEG2000) {
+ checked_set(_standard, "interop");
+ } else {
+ checked_set(_standard, "mpeg2-interop");
+ }
} else {
checked_set(_standard, _film->limit_to_smpte_bv20() ? "smpte-bv20" : "smpte");
}
@@ -204,12 +189,18 @@ DCPPanel::standard_changed ()
if (*data == N_("interop")) {
_film->set_interop(true);
_film->set_limit_to_smpte_bv20(false);
+ _film->set_video_encoding(VideoEncoding::JPEG2000);
} else if (*data == N_("smpte")) {
_film->set_interop(false);
_film->set_limit_to_smpte_bv20(false);
+ _film->set_video_encoding(VideoEncoding::JPEG2000);
} else if (*data == N_("smpte-bv20")) {
_film->set_interop(false);
_film->set_limit_to_smpte_bv20(true);
+ _film->set_video_encoding(VideoEncoding::JPEG2000);
+ } else if (*data == N_("mpeg2-interop")) {
+ _film->set_interop(true);
+ _film->set_video_encoding(VideoEncoding::MPEG2);
}
}
@@ -241,15 +232,6 @@ DCPPanel::add_to_grid ()
_grid->Add (_dcp_name, wxGBPosition(r, 0), wxGBSpan(1, 2), wxALIGN_CENTER_VERTICAL | wxEXPAND);
++r;
- {
- auto s = new wxBoxSizer (wxHORIZONTAL);
- s->Add (_enable_audio_language, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_GAP);
- s->Add (_audio_language, 1, wxALIGN_CENTER_VERTICAL | wxBOTTOM, DCPOMATIC_CHECKBOX_BOTTOM_PAD);
- s->Add (_edit_audio_language, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM, DCPOMATIC_CHECKBOX_BOTTOM_PAD);
- _grid->Add (s, wxGBPosition(r, 0), wxGBSpan(1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL);
- }
- ++r;
-
add_label_to_sizer (_grid, _dcp_content_type_label, true, wxGBPosition(r, 0));
_grid->Add (_dcp_content_type, wxGBPosition(r, 1));
++r;
@@ -257,19 +239,6 @@ DCPPanel::add_to_grid ()
_grid->Add (_encrypted, wxGBPosition(r, 0), wxGBSpan(1, 2));
++r;
- add_label_to_sizer (_grid, _reels_label, true, wxGBPosition(r, 0));
- _grid->Add (_reel_type, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
- ++r;
-
- add_label_to_sizer (_grid, _reel_length_label, true, wxGBPosition(r, 0));
- {
- auto s = new wxBoxSizer (wxHORIZONTAL);
- s->Add (_reel_length);
- add_label_to_sizer (s, _reel_length_gb_label, false, 0, wxLEFT | wxALIGN_CENTER_VERTICAL);
- _grid->Add (s, wxGBPosition(r, 1));
- }
- ++r;
-
add_label_to_sizer (_grid, _standard_label, true, wxGBPosition(r, 0));
_grid->Add (_standard, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
++r;
@@ -277,6 +246,7 @@ DCPPanel::add_to_grid ()
auto extra = new wxBoxSizer (wxHORIZONTAL);
extra->Add (_markers, 1, wxRIGHT, DCPOMATIC_SIZER_X_GAP);
extra->Add (_metadata, 1, wxRIGHT, DCPOMATIC_SIZER_X_GAP);
+ extra->Add(_reels, 1, wxRIGHT, DCPOMATIC_SIZER_X_GAP);
_grid->Add (extra, wxGBPosition(r, 0), wxGBSpan(1, 2));
++r;
}
@@ -294,13 +264,13 @@ DCPPanel::name_changed ()
void
-DCPPanel::j2k_bandwidth_changed ()
+DCPPanel::video_bit_rate_changed()
{
if (!_film) {
return;
}
- _film->set_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
+ _film->set_video_bit_rate(_film->video_encoding(), _video_bit_rate->GetValue() * 1000000);
}
@@ -390,6 +360,14 @@ DCPPanel::metadata_clicked ()
void
+DCPPanel::reels_clicked()
+{
+ _dcp_timeline.reset(_panel, _film);
+ _dcp_timeline->Show();
+}
+
+
+void
DCPPanel::film_changed(FilmProperty p)
{
switch (p) {
@@ -418,8 +396,8 @@ DCPPanel::film_changed(FilmProperty p)
setup_container ();
setup_dcp_name ();
break;
- case FilmProperty::J2K_BANDWIDTH:
- checked_set (_j2k_bandwidth, _film->j2k_bandwidth() / 1000000);
+ case FilmProperty::VIDEO_BIT_RATE:
+ checked_set(_video_bit_rate, _film->video_bit_rate(_film->video_encoding()) / 1000000);
break;
case FilmProperty::USE_ISDCF_NAME:
{
@@ -429,10 +407,7 @@ DCPPanel::film_changed(FilmProperty p)
in case the user has clicked 'Copy as name' then re-ticked 'Use ISDCF name' (#1513).
*/
string const name = _film->name ();
- string::size_type const u = name.find("_");
- if (u != string::npos) {
- _film->set_name (name.substr(0, u));
- }
+ _film->set_name(name.substr(0, name.find("_")));
}
setup_dcp_name ();
break;
@@ -478,6 +453,12 @@ DCPPanel::film_changed(FilmProperty p)
setup_dcp_name ();
_markers->Enable (!_film->interop());
break;
+ case FilmProperty::VIDEO_ENCODING:
+ set_standard();
+ setup_container();
+ setup_sensitivity();
+ film_changed(FilmProperty::VIDEO_BIT_RATE);
+ break;
case FilmProperty::LIMIT_TO_SMPTE_BV20:
set_standard();
break;
@@ -490,13 +471,6 @@ DCPPanel::film_changed(FilmProperty p)
setup_audio_channels_choice (_audio_channels, minimum_allowed_audio_channels());
film_changed (FilmProperty::AUDIO_CHANNELS);
break;
- case FilmProperty::REEL_TYPE:
- checked_set (_reel_type, static_cast<int>(_film->reel_type()));
- _reel_length->Enable (_film->reel_type() == ReelType::BY_LENGTH);
- break;
- case FilmProperty::REEL_LENGTH:
- checked_set (_reel_length, _film->reel_length() / 1000000000LL);
- break;
case FilmProperty::CONTENT:
setup_dcp_name ();
setup_sensitivity ();
@@ -511,6 +485,7 @@ DCPPanel::film_changed(FilmProperty p)
checked_set (_audio_language, al ? std_to_wx(al->to_string()) : wxT(""));
setup_dcp_name ();
setup_sensitivity ();
+ _audio_panel_sizer->Layout();
break;
}
case FilmProperty::AUDIO_FRAME_RATE:
@@ -565,24 +540,28 @@ DCPPanel::film_content_changed (int property)
void
DCPPanel::setup_container ()
{
- int n = 0;
- auto ratios = Ratio::containers ();
- auto i = ratios.begin ();
- while (i != ratios.end() && *i != _film->container()) {
- ++i;
- ++n;
+ auto ratios = Ratio::containers();
+ if (std::find(ratios.begin(), ratios.end(), _film->container()) == ratios.end()) {
+ ratios.push_back(_film->container());
}
- if (i == ratios.end()) {
- checked_set (_container, -1);
- checked_set (_container_size, wxT(""));
- } else {
- checked_set (_container, n);
- auto const size = fit_ratio_within (_film->container()->ratio(), _film->full_frame ());
- checked_set (_container_size, wxString::Format("%dx%d", size.width, size.height));
+ wxArrayString new_ratios;
+ for (auto ratio: ratios) {
+ new_ratios.Add(std_to_wx(ratio->container_nickname()));
}
+ _container->set_entries(new_ratios);
+
+ auto iter = std::find_if(ratios.begin(), ratios.end(), [this](Ratio const* ratio) { return ratio == _film->container(); });
+ DCPOMATIC_ASSERT(iter != ratios.end());
+
+ checked_set(_container, iter - ratios.begin());
+ auto const size = fit_ratio_within(_film->container()->ratio(), _film->full_frame ());
+ checked_set(_container_size, wxString::Format("%dx%d", size.width, size.height));
+
setup_dcp_name ();
+
+ _video_grid->Layout();
}
@@ -644,7 +623,7 @@ DCPPanel::set_film (shared_ptr<Film> film)
film_changed(FilmProperty::CONTAINER);
film_changed(FilmProperty::RESOLUTION);
film_changed(FilmProperty::ENCRYPTED);
- film_changed(FilmProperty::J2K_BANDWIDTH);
+ film_changed(FilmProperty::VIDEO_BIT_RATE);
film_changed(FilmProperty::VIDEO_FRAME_RATE);
film_changed(FilmProperty::AUDIO_CHANNELS);
film_changed(FilmProperty::SEQUENCE);
@@ -673,6 +652,8 @@ DCPPanel::set_general_sensitivity (bool s)
void
DCPPanel::setup_sensitivity ()
{
+ auto const mpeg2 = _film && _film->video_encoding() == VideoEncoding::MPEG2;
+
_name->Enable (_generally_sensitive);
_use_isdcf_name->Enable (_generally_sensitive);
_dcp_content_type->Enable (_generally_sensitive);
@@ -681,16 +662,15 @@ DCPPanel::setup_sensitivity ()
_audio_language->Enable (_enable_audio_language->GetValue());
_edit_audio_language->Enable (_enable_audio_language->GetValue());
_encrypted->Enable (_generally_sensitive);
- _reel_type->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !_film->references_dcp_audio());
- _reel_length->Enable (_generally_sensitive && _film && _film->reel_type() == ReelType::BY_LENGTH);
_markers->Enable (_generally_sensitive && _film && !_film->interop());
_metadata->Enable (_generally_sensitive);
+ _reels->Enable (_generally_sensitive && _film);
_frame_rate_choice->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !_film->contains_atmos_content());
_frame_rate_spin->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !_film->contains_atmos_content());
_audio_channels->Enable (_generally_sensitive && _film && !_film->references_dcp_audio());
_audio_processor->Enable (_generally_sensitive && _film && !_film->references_dcp_audio());
- _j2k_bandwidth->Enable (_generally_sensitive && _film && !_film->references_dcp_video());
- _container->Enable (_generally_sensitive && _film && !_film->references_dcp_video());
+ _video_bit_rate->Enable (_generally_sensitive && _film && !_film->references_dcp_video());
+ _container->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !mpeg2);
_best_frame_rate->Enable (
_generally_sensitive &&
_film &&
@@ -698,8 +678,8 @@ DCPPanel::setup_sensitivity ()
!_film->references_dcp_video() &&
!_film->contains_atmos_content()
);
- _resolution->Enable (_generally_sensitive && _film && !_film->references_dcp_video());
- _three_d->Enable (_generally_sensitive && _film && !_film->references_dcp_video());
+ _resolution->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !mpeg2);
+ _three_d->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !mpeg2);
_standard->Enable (
_generally_sensitive &&
@@ -772,7 +752,8 @@ DCPPanel::reencode_j2k_changed ()
void
DCPPanel::config_changed (Config::Property p)
{
- _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000);
+ VideoEncoding const encoding = _film ? _film->video_encoding() : VideoEncoding::JPEG2000;
+ _video_bit_rate->SetRange(1, Config::instance()->maximum_video_bit_rate(encoding) / 1000000);
setup_frame_rate_widget ();
if (p == Config::SHOW_EXPERIMENTAL_AUDIO_PROCESSORS) {
@@ -790,6 +771,8 @@ DCPPanel::config_changed (Config::Property p)
}
} else if (p == Config::ISDCF_NAME_PART_LENGTH) {
setup_dcp_name();
+ } else if (p == Config::ALLOW_ANY_CONTAINER) {
+ setup_container();
}
}
@@ -831,8 +814,8 @@ DCPPanel::make_video_panel ()
_three_d = new CheckBox (panel, _("3D"));
- _j2k_bandwidth_label = create_label (panel, _("JPEG2000 bandwidth\nfor newly-encoded data"), true);
- _j2k_bandwidth = new SpinCtrl (panel, DCPOMATIC_SPIN_CTRL_WIDTH);
+ _video_bit_rate_label = create_label(panel, _("Video bit rate\nfor newly-encoded data"), true);
+ _video_bit_rate = new SpinCtrl(panel, DCPOMATIC_SPIN_CTRL_WIDTH);
_mbits_label = create_label (panel, _("Mbit/s"), false);
_reencode_j2k = new CheckBox (panel, _("Re-encode JPEG2000 data from input"));
@@ -841,26 +824,23 @@ DCPPanel::make_video_panel ()
_frame_rate_choice->Bind (wxEVT_CHOICE, boost::bind(&DCPPanel::frame_rate_choice_changed, this));
_frame_rate_spin->Bind (wxEVT_SPINCTRL, boost::bind(&DCPPanel::frame_rate_spin_changed, this));
_best_frame_rate->Bind (wxEVT_BUTTON, boost::bind(&DCPPanel::best_frame_rate_clicked, this));
- _j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind(&DCPPanel::j2k_bandwidth_changed, this));
+ _video_bit_rate->Bind (wxEVT_SPINCTRL, boost::bind(&DCPPanel::video_bit_rate_changed, this));
/* Also listen to wxEVT_TEXT so that typing numbers directly in is always noticed */
- _j2k_bandwidth->Bind (wxEVT_TEXT, boost::bind(&DCPPanel::j2k_bandwidth_changed, this));
+ _video_bit_rate->Bind (wxEVT_TEXT, boost::bind(&DCPPanel::video_bit_rate_changed, this));
_resolution->Bind (wxEVT_CHOICE, boost::bind(&DCPPanel::resolution_changed, this));
_three_d->bind(&DCPPanel::three_d_changed, this);
_reencode_j2k->bind(&DCPPanel::reencode_j2k_changed, this);
- for (auto i: Ratio::containers()) {
- _container->add(i->container_nickname());
- }
-
for (auto i: Config::instance()->allowed_dcp_frame_rates()) {
- _frame_rate_choice->add(boost::lexical_cast<string>(i));
+ _frame_rate_choice->add_entry(boost::lexical_cast<string>(i));
}
- _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000);
+ VideoEncoding const encoding = _film ? _film->video_encoding() : VideoEncoding::JPEG2000;
+ _video_bit_rate->SetRange(1, Config::instance()->maximum_video_bit_rate(encoding) / 1000000);
_frame_rate_spin->SetRange (1, 480);
- _resolution->add(_("2K"));
- _resolution->add(_("4K"));
+ _resolution->add_entry(_("2K"));
+ _resolution->add_entry(_("4K"));
add_video_panel_to_grid ();
setup_frame_rate_widget();
@@ -900,9 +880,9 @@ DCPPanel::add_video_panel_to_grid ()
_video_grid->Add (_three_d, wxGBPosition (r, 0), wxGBSpan (1, 2));
++r;
- add_label_to_sizer (_video_grid, _j2k_bandwidth_label, true, wxGBPosition (r, 0));
+ add_label_to_sizer(_video_grid, _video_bit_rate_label, true, wxGBPosition (r, 0));
auto s = new wxBoxSizer (wxHORIZONTAL);
- s->Add (_j2k_bandwidth, 0, wxALIGN_CENTER_VERTICAL);
+ s->Add(_video_bit_rate, 0, wxALIGN_CENTER_VERTICAL);
add_label_to_sizer (s, _mbits_label, false, 0, wxLEFT | wxALIGN_CENTER_VERTICAL);
_video_grid->Add (s, wxGBPosition(r, 1), wxDefaultSpan);
++r;
@@ -953,6 +933,10 @@ DCPPanel::make_audio_panel ()
_audio_processor = new Choice(panel);
add_audio_processors ();
+ _enable_audio_language = new CheckBox(panel, _("Language"));
+ _audio_language = new wxStaticText(panel, wxID_ANY, wxT(""));
+ _edit_audio_language = new Button(panel, _("Edit..."));
+
_show_audio = new Button (panel, _("Show graph of audio levels..."));
_audio_channels->Bind (wxEVT_CHOICE, boost::bind (&DCPPanel::audio_channels_changed, this));
@@ -960,6 +944,10 @@ DCPPanel::make_audio_panel ()
_audio_sample_rate->Bind (wxEVT_CHOICE, boost::bind(&DCPPanel::audio_sample_rate_changed, this));
}
_audio_processor->Bind (wxEVT_CHOICE, boost::bind (&DCPPanel::audio_processor_changed, this));
+
+ _enable_audio_language->bind(&DCPPanel::enable_audio_language_toggled, this);
+ _edit_audio_language->Bind(wxEVT_BUTTON, boost::bind(&DCPPanel::edit_audio_language_clicked, this));
+
_show_audio->Bind (wxEVT_BUTTON, boost::bind (&DCPPanel::show_audio_clicked, this));
if (_audio_sample_rate) {
@@ -992,6 +980,16 @@ DCPPanel::add_audio_panel_to_grid ()
_audio_grid->Add (_audio_processor, wxGBPosition (r, 1));
++r;
+ {
+ auto s = new wxBoxSizer (wxHORIZONTAL);
+ s->Add(_enable_audio_language, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_GAP);
+ s->Add(_audio_language, 1, wxALIGN_CENTER_VERTICAL | wxBOTTOM, DCPOMATIC_CHECKBOX_BOTTOM_PAD);
+ s->Add(DCPOMATIC_SIZER_X_GAP, 0);
+ s->Add(_edit_audio_language, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM, DCPOMATIC_CHECKBOX_BOTTOM_PAD);
+ _audio_grid->Add(s, wxGBPosition(r, 0), wxGBSpan(1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL);
+ }
+ ++r;
+
_audio_grid->Add (_show_audio, wxGBPosition (r, 0), wxGBSpan (1, 2));
++r;
}
@@ -1000,6 +998,13 @@ DCPPanel::add_audio_panel_to_grid ()
void
DCPPanel::copy_isdcf_name_button_clicked ()
{
+ auto name = _film->name();
+ if (name.length() > 20 && std::count(name.begin(), name.end(), '_') > 6) {
+ /* At a guess, the existing film name is itself an ISDCF name, so chop
+ * off the actual name part first.
+ */
+ _film->set_name(name.substr(0, name.find("_")));
+ }
_film->set_name (_film->isdcf_name (true));
_film->set_use_isdcf_name (false);
}
@@ -1030,33 +1035,11 @@ DCPPanel::show_audio_clicked ()
void
-DCPPanel::reel_type_changed ()
-{
- if (!_film || !_reel_type->get()) {
- return;
- }
-
- _film->set_reel_type(static_cast<ReelType>(*_reel_type->get()));
-}
-
-
-void
-DCPPanel::reel_length_changed ()
-{
- if (!_film) {
- return;
- }
-
- _film->set_reel_length (_reel_length->GetValue() * 1000000000LL);
-}
-
-
-void
DCPPanel::add_audio_processors ()
{
- _audio_processor->add(_("None"), new wxStringClientData(N_("none")));
+ _audio_processor->add_entry(_("None"), new wxStringClientData(N_("none")));
for (auto ap: AudioProcessor::visible()) {
- _audio_processor->add(std_to_wx(ap->name()), new wxStringClientData(std_to_wx(ap->id())));
+ _audio_processor->add_entry(std_to_wx(ap->name()), new wxStringClientData(std_to_wx(ap->id())));
}
_audio_panel_sizer->Layout();
}
diff --git a/src/wx/dcp_panel.h b/src/wx/dcp_panel.h
index 849fe185c..c686a9c55 100644
--- a/src/wx/dcp_panel.h
+++ b/src/wx/dcp_panel.h
@@ -39,6 +39,7 @@ class wxGridBagSizer;
class AudioDialog;
class Choice;
+class DCPTimelineDialog;
class Film;
class FilmViewer;
class InteropMetadataDialog;
@@ -70,7 +71,7 @@ private:
void copy_isdcf_name_button_clicked ();
void container_changed ();
void dcp_content_type_changed ();
- void j2k_bandwidth_changed ();
+ void video_bit_rate_changed();
void frame_rate_choice_changed ();
void frame_rate_spin_changed ();
void best_frame_rate_clicked ();
@@ -82,10 +83,9 @@ private:
void encrypted_toggled ();
void audio_processor_changed ();
void show_audio_clicked ();
- void reel_type_changed ();
- void reel_length_changed ();
void markers_clicked ();
void metadata_clicked ();
+ void reels_clicked();
void reencode_j2k_changed ();
void enable_audio_language_toggled ();
void edit_audio_language_clicked ();
@@ -129,9 +129,9 @@ private:
Choice* _container;
wxStaticText* _container_size;
wxButton* _copy_isdcf_name_button;
- wxStaticText* _j2k_bandwidth_label;
+ wxStaticText* _video_bit_rate_label;
wxStaticText* _mbits_label;
- wxSpinCtrl* _j2k_bandwidth;
+ wxSpinCtrl* _video_bit_rate;
wxStaticText* _dcp_content_type_label;
Choice* _dcp_content_type;
wxStaticText* _frame_rate_label;
@@ -153,19 +153,16 @@ private:
wxStaticText* _standard_label;
Choice* _standard;
CheckBox* _encrypted;
- wxStaticText* _reels_label;
- Choice* _reel_type;
- wxStaticText* _reel_length_label;
- wxStaticText* _reel_length_gb_label;
- wxSpinCtrl* _reel_length;
wxButton* _markers;
wxButton* _metadata;
+ Button* _reels;
wxSizer* _audio_panel_sizer;
wx_ptr<AudioDialog> _audio_dialog;
wx_ptr<MarkersDialog> _markers_dialog;
wx_ptr<InteropMetadataDialog> _interop_metadata_dialog;
wx_ptr<SMPTEMetadataDialog> _smpte_metadata_dialog;
+ wx_ptr<DCPTimelineDialog> _dcp_timeline;
std::shared_ptr<Film> _film;
FilmViewer& _viewer;
diff --git a/src/wx/dcp_referencing_dialog.cc b/src/wx/dcp_referencing_dialog.cc
new file mode 100644
index 000000000..853391bd3
--- /dev/null
+++ b/src/wx/dcp_referencing_dialog.cc
@@ -0,0 +1,231 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "check_box.h"
+#include "dcp_referencing_dialog.h"
+#include "static_text.h"
+#include "wx_util.h"
+#include "lib/dcp_content.h"
+#include "lib/film.h"
+#include "lib/types.h"
+#include <wx/gbsizer.h>
+#include <wx/wx.h>
+
+
+using std::dynamic_pointer_cast;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+using std::weak_ptr;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
+
+DCPReferencingDialog::DCPReferencingDialog(wxWindow* parent, shared_ptr<const Film> film)
+ : wxDialog(parent, wxID_ANY, _("Version file (VF) setup"))
+ , _film(film)
+ , _dcp_grid(new wxGridBagSizer(DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP))
+ , _overall_sizer(new wxBoxSizer(wxVERTICAL))
+{
+ _film_connection = film->Change.connect(boost::bind(&DCPReferencingDialog::film_changed, this, _1, _2));
+ _film_content_connection = film->ContentChange.connect(boost::bind(&DCPReferencingDialog::film_content_changed, this, _1, _3));
+
+ _overall_sizer->Add(_dcp_grid, 1, wxALL, DCPOMATIC_DIALOG_BORDER);
+ SetSizer(_overall_sizer);
+
+ auto buttons = CreateSeparatedButtonSizer(wxOK);
+ if (buttons) {
+ _overall_sizer->Add(buttons, wxSizerFlags().Expand().DoubleBorder());
+ }
+
+ setup();
+}
+
+
+void
+DCPReferencingDialog::film_changed(ChangeType type, FilmProperty property)
+{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
+ if (
+ property == FilmProperty::INTEROP ||
+ property == FilmProperty::RESOLUTION ||
+ property == FilmProperty::CONTAINER ||
+ property == FilmProperty::REEL_TYPE ||
+ property == FilmProperty::VIDEO_FRAME_RATE ||
+ property == FilmProperty::CONTENT) {
+ setup();
+ }
+}
+
+
+void
+DCPReferencingDialog::film_content_changed(ChangeType type, int property)
+{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
+ if (
+ property != DCPContentProperty::REFERENCE_VIDEO &&
+ property != DCPContentProperty::REFERENCE_AUDIO &&
+ property != DCPContentProperty::REFERENCE_TEXT) {
+ setup();
+ }
+}
+
+
+void
+DCPReferencingDialog::setup()
+{
+ _dcps.clear();
+ _dcp_grid->Clear(true);
+
+ int column = 0;
+ for (auto const& heading: { _("DCP"), _("Picture"), _("Sound"), _("Subtitles"), _("Closed captions") }) {
+ auto text = new StaticText(this, heading);
+ wxFont font(*wxNORMAL_FONT);
+ font.SetWeight(wxFONTWEIGHT_BOLD);
+ text->SetFont(font);
+ _dcp_grid->Add(text, wxGBPosition(0, column), wxDefaultSpan, wxALL, DCPOMATIC_SIZER_GAP);
+ ++column;
+ };
+
+ auto const all_parts = { Part::VIDEO, Part::AUDIO, Part::SUBTITLES, Part::CLOSED_CAPTIONS };
+
+ int row = 1;
+ for (auto& content: _film->content()) {
+ auto dcp_content = dynamic_pointer_cast<DCPContent>(content);
+ if (!dcp_content) {
+ continue;
+ }
+
+ DCP record;
+ record.content = dcp_content;
+ _dcp_grid->Add(new StaticText(this, std_to_wx(dcp_content->name())), wxGBPosition(row, 0));
+ column = 1;
+ for (auto const& part: all_parts) {
+ record.check_box[part] = new CheckBox(this, wxT(""));
+ switch (part) {
+ case Part::VIDEO:
+ record.check_box[part]->set(dcp_content->reference_video());
+ break;
+ case Part::AUDIO:
+ record.check_box[part]->set(dcp_content->reference_audio());
+ break;
+ case Part::SUBTITLES:
+ record.check_box[part]->set(dcp_content->reference_text(TextType::OPEN_SUBTITLE));
+ break;
+ case Part::CLOSED_CAPTIONS:
+ record.check_box[part]->set(dcp_content->reference_text(TextType::CLOSED_CAPTION));
+ break;
+ default:
+ DCPOMATIC_ASSERT(false);
+ }
+ record.check_box[part]->bind(&DCPReferencingDialog::checkbox_changed, this, weak_ptr<DCPContent>(dcp_content), record.check_box[part], part);
+ _dcp_grid->Add(record.check_box[part], wxGBPosition(row, column++), wxDefaultSpan, wxALIGN_CENTER);
+ }
+ ++row;
+
+ auto add_problem = [this, &row](wxString const& cannot, string why_not) {
+ auto reason = new StaticText(this, cannot + wxT(": ") + std_to_wx(why_not));
+ wxFont font(*wxNORMAL_FONT);
+ font.SetStyle(wxFONTSTYLE_ITALIC);
+ reason->SetFont(font);
+ _dcp_grid->Add(reason, wxGBPosition(row, 0), wxGBSpan(1, 5), wxLEFT, DCPOMATIC_SIZER_X_GAP * 4);
+ ++row;
+ };
+
+ string why_not;
+
+ if (!dcp_content->can_reference_anything(_film, why_not)) {
+ for (auto const& part: all_parts) {
+ record.check_box[part]->Enable(false);
+ }
+ add_problem(_("Cannot reference this DCP"), why_not);
+ } else {
+ if (!dcp_content->can_reference_video(_film, why_not)) {
+ record.check_box[Part::VIDEO]->Enable(false);
+ if (dcp_content->video) {
+ add_problem(_("Cannot reference this DCP's video"), why_not);
+ }
+ }
+
+ if (!dcp_content->can_reference_audio(_film, why_not)) {
+ record.check_box[Part::AUDIO]->Enable(false);
+ if (dcp_content->audio) {
+ add_problem(_("Cannot reference this DCP's audio"), why_not);
+ }
+ }
+
+ if (!dcp_content->can_reference_text(_film, TextType::OPEN_SUBTITLE, why_not)) {
+ record.check_box[Part::SUBTITLES]->Enable(false);
+ if (dcp_content->text_of_original_type(TextType::OPEN_SUBTITLE)) {
+ add_problem(_("Cannot reference this DCP's subtitles"), why_not);
+ }
+ }
+
+ if (!dcp_content->can_reference_text(_film, TextType::CLOSED_CAPTION, why_not)) {
+ record.check_box[Part::CLOSED_CAPTIONS]->Enable(false);
+ if (dcp_content->text_of_original_type(TextType::CLOSED_CAPTION)) {
+ add_problem(_("Cannot reference this DCP's closed captions"), why_not);
+ }
+ }
+ }
+
+ _dcps.push_back(record);
+ }
+
+ _dcp_grid->Layout();
+ _overall_sizer->Layout();
+ _overall_sizer->SetSizeHints(this);
+}
+
+
+void
+DCPReferencingDialog::checkbox_changed(weak_ptr<DCPContent> weak_content, CheckBox* check_box, Part part)
+{
+ auto content = weak_content.lock();
+ if (!content) {
+ return;
+ }
+
+ switch (part) {
+ case Part::VIDEO:
+ content->set_reference_video(check_box->get());
+ break;
+ case Part::AUDIO:
+ content->set_reference_audio(check_box->get());
+ break;
+ case Part::SUBTITLES:
+ content->set_reference_text(TextType::OPEN_SUBTITLE, check_box->get());
+ break;
+ case Part::CLOSED_CAPTIONS:
+ content->set_reference_text(TextType::CLOSED_CAPTION, check_box->get());
+ break;
+ default:
+ DCPOMATIC_ASSERT(false);
+ }
+}
+
diff --git a/src/wx/dcp_referencing_dialog.h b/src/wx/dcp_referencing_dialog.h
new file mode 100644
index 000000000..e49292c22
--- /dev/null
+++ b/src/wx/dcp_referencing_dialog.h
@@ -0,0 +1,70 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "lib/enum_indexed_vector.h"
+#include "lib/film.h"
+#include <wx/wx.h>
+#include <memory>
+#include <vector>
+
+
+class CheckBox;
+class DCPContent;
+class Film;
+class wxBoxSizer;
+class wxGridBagSizer;
+
+
+class DCPReferencingDialog : public wxDialog
+{
+public:
+ DCPReferencingDialog(wxWindow* parent, std::shared_ptr<const Film> film);
+
+private:
+ enum class Part {
+ VIDEO,
+ AUDIO,
+ SUBTITLES,
+ CLOSED_CAPTIONS,
+ COUNT
+ };
+
+ void setup();
+ void checkbox_changed(std::weak_ptr<DCPContent> content, CheckBox* check_box, Part part);
+ void film_changed(ChangeType type, FilmProperty property);
+ void film_content_changed(ChangeType type, int property);
+
+ std::shared_ptr<const Film> _film;
+
+ wxGridBagSizer* _dcp_grid;
+ wxBoxSizer* _overall_sizer;
+
+ struct DCP {
+ std::shared_ptr<DCPContent> content;
+ EnumIndexedVector<CheckBox*, Part> check_box;
+ };
+
+ std::vector<DCP> _dcps;
+
+ boost::signals2::scoped_connection _film_connection;
+ boost::signals2::scoped_connection _film_content_connection;
+};
+
diff --git a/src/wx/dcp_timeline.cc b/src/wx/dcp_timeline.cc
new file mode 100644
index 000000000..93e15f6cc
--- /dev/null
+++ b/src/wx/dcp_timeline.cc
@@ -0,0 +1,617 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "check_box.h"
+#include "colours.h"
+#include "dcp_timeline.h"
+#include "dcp_timeline_reel_marker_view.h"
+#include "dcpomatic_choice.h"
+#include "dcpomatic_spin_ctrl.h"
+#include "id.h"
+#include "timecode.h"
+#include "wx_util.h"
+#include "lib/atmos_content.h"
+#include "lib/audio_content.h"
+#include "lib/constants.h"
+#include "lib/film.h"
+#include "lib/text_content.h"
+#include "lib/video_content.h"
+#include <dcp/scope_guard.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/graphics.h>
+LIBDCP_ENABLE_WARNINGS
+
+
+using std::dynamic_pointer_cast;
+using std::make_shared;
+using std::shared_ptr;
+using std::vector;
+using boost::optional;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+using namespace dcpomatic;
+
+
+auto constexpr reel_marker_y_pos = 48;
+auto constexpr content_y_pos = 112;
+auto constexpr content_type_height = 12;
+
+enum {
+ ID_add_reel_boundary = DCPOMATIC_DCP_TIMELINE_MENU
+};
+
+
+class ReelBoundary
+{
+public:
+ ReelBoundary(wxWindow* parent, wxGridBagSizer* sizer, int index, DCPTime maximum, int fps, DCPTimeline& timeline, bool editable)
+ : _label(new wxStaticText(parent, wxID_ANY, wxString::Format(_("Reel %d to reel %d"), index + 1, index + 2)))
+ , _timecode(new Timecode<DCPTime>(parent, true))
+ , _index(index)
+ , _view(timeline, reel_marker_y_pos)
+ , _fps(fps)
+ {
+ sizer->Add(_label, wxGBPosition(index, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
+ sizer->Add(_timecode, wxGBPosition(index, 1));
+
+ _timecode->set_maximum(maximum.split(fps));
+ _timecode->set_editable(editable);
+ _timecode->Changed.connect(boost::bind(&ReelBoundary::timecode_changed, this));
+ }
+
+ ~ReelBoundary()
+ {
+ if (_label) {
+ _label->Destroy();
+ }
+
+ if (_timecode) {
+ _timecode->Destroy();
+ }
+ }
+
+ ReelBoundary(ReelBoundary const&) = delete;
+ ReelBoundary& operator=(ReelBoundary const&) = delete;
+
+ ReelBoundary(ReelBoundary&& other) = delete;
+ ReelBoundary& operator=(ReelBoundary&& other) = delete;
+
+ void set_time(DCPTime time)
+ {
+ if (_timecode) {
+ _timecode->set(time, _fps);
+ }
+ _view.set_time(time);
+ }
+
+ dcpomatic::DCPTime time() const {
+ return _view.time();
+ }
+
+ int index() const {
+ return _index;
+ }
+
+ DCPTimelineReelMarkerView& view() {
+ return _view;
+ }
+
+ DCPTimelineReelMarkerView const& view() const {
+ return _view;
+ }
+
+ boost::signals2::signal<void (int, dcpomatic::DCPTime)> Changed;
+
+private:
+ void timecode_changed() {
+ set_time(_timecode->get(_fps));
+ Changed(_index, time());
+ }
+
+ wxStaticText* _label = nullptr;
+ Timecode<dcpomatic::DCPTime>* _timecode = nullptr;
+ int _index = 0;
+ DCPTimelineReelMarkerView _view;
+ int _fps;
+};
+
+
+DCPTimeline::DCPTimeline(wxWindow* parent, shared_ptr<Film> film)
+ : Timeline(parent)
+ , _film(film)
+ , _canvas(new wxScrolledCanvas(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE))
+ , _reel_settings(new wxPanel(this, wxID_ANY))
+ , _reel_detail(new wxPanel(this, wxID_ANY))
+ , _reel_detail_sizer(new wxGridBagSizer(DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP))
+{
+#ifndef __WXOSX__
+ _canvas->SetDoubleBuffered(true);
+#endif
+ _reel_detail->SetSizer(_reel_detail_sizer);
+
+ auto sizer = new wxBoxSizer(wxVERTICAL);
+ sizer->Add(_reel_settings, 0);
+ sizer->Add(_canvas, 0, wxEXPAND);
+ sizer->Add(_reel_detail, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+ SetSizer(sizer);
+
+ SetMinSize(wxSize(640, 480));
+ _canvas->SetMinSize({-1, content_y_pos + content_type_height * 4});
+
+ _canvas->Bind(wxEVT_PAINT, boost::bind(&DCPTimeline::paint, this));
+ _canvas->Bind(wxEVT_SIZE, boost::bind(&DCPTimeline::setup_pixels_per_second, this));
+ _canvas->Bind(wxEVT_LEFT_DOWN, boost::bind(&DCPTimeline::left_down, this, _1));
+ _canvas->Bind(wxEVT_RIGHT_DOWN, boost::bind(&DCPTimeline::right_down, this, _1));
+ _canvas->Bind(wxEVT_LEFT_UP, boost::bind(&DCPTimeline::left_up, this, _1));
+ _canvas->Bind(wxEVT_MOTION, boost::bind(&DCPTimeline::mouse_moved, this, _1));
+
+ _film_connection = film->Change.connect(boost::bind(&DCPTimeline::film_changed, this, _1, _2));
+
+ _menu = new wxMenu;
+ _add_reel_boundary = _menu->Append(ID_add_reel_boundary, _("Add reel"));
+ _canvas->Bind(wxEVT_MENU, boost::bind(&DCPTimeline::add_reel_boundary, this));
+
+ setup_reel_settings();
+ setup_reel_boundaries();
+
+ sizer->Layout();
+ setup_pixels_per_second();
+ setup_sensitivity();
+}
+
+
+void
+DCPTimeline::add_reel_boundary()
+{
+ auto boundaries = film()->custom_reel_boundaries();
+ boundaries.push_back(DCPTime::from_seconds(_right_down_position.x / _pixels_per_second.get_value_or(1)));
+ film()->set_custom_reel_boundaries(boundaries);
+}
+
+
+void
+DCPTimeline::film_changed(ChangeType type, FilmProperty property)
+{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
+ switch (property) {
+ case FilmProperty::REEL_TYPE:
+ case FilmProperty::REEL_LENGTH:
+ case FilmProperty::CUSTOM_REEL_BOUNDARIES:
+ setup_sensitivity();
+ setup_reel_boundaries();
+ break;
+ case FilmProperty::CONTENT:
+ case FilmProperty::CONTENT_ORDER:
+ setup_pixels_per_second();
+ Refresh();
+ break;
+ default:
+ break;
+ }
+}
+
+
+void
+DCPTimeline::setup_sensitivity()
+{
+ _snap->Enable(editable());
+ _maximum_reel_size->Enable(film()->reel_type() == ReelType::BY_LENGTH);
+ _add_reel_boundary->Enable(film()->reel_type() == ReelType::CUSTOM);
+}
+
+
+void
+DCPTimeline::setup_reel_settings()
+{
+ auto sizer = new wxGridBagSizer(DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ _reel_settings->SetSizer(sizer);
+
+ int r = 0;
+ add_label_to_sizer(sizer, _reel_settings, _("Reel mode"), true, wxGBPosition(r, 0));
+ _reel_type = new Choice(_reel_settings);
+ _reel_type->add_entry(_("Single reel"));
+ _reel_type->add_entry(_("Split by video content"));
+ _reel_type->add_entry(_("Split by maximum reel size"));
+ _reel_type->add_entry(_("Custom"));
+ sizer->Add(_reel_type, wxGBPosition(r, 1));
+ ++r;
+
+ add_label_to_sizer(sizer, _reel_settings, _("Maximum reel size"), true, wxGBPosition(r, 0));
+ _maximum_reel_size = new SpinCtrl(_reel_settings, DCPOMATIC_SPIN_CTRL_WIDTH);
+ _maximum_reel_size->SetRange(1, 1000);
+ {
+ auto s = new wxBoxSizer(wxHORIZONTAL);
+ s->Add(_maximum_reel_size, 0);
+ add_label_to_sizer(s, _reel_settings, _("GB"), false, 0, wxALIGN_CENTER_VERTICAL | wxLEFT);
+ sizer->Add(s, wxGBPosition(r, 1));
+ }
+ ++r;
+
+ _snap = new CheckBox(_reel_settings, _("Snap when dragging"));
+ sizer->Add(_snap, wxGBPosition(r, 1));
+ ++r;
+
+ _reel_type->set(static_cast<int>(film()->reel_type()));
+ _maximum_reel_size->SetValue(film()->reel_length() / 1000000000LL);
+
+ _reel_type->bind(&DCPTimeline::reel_mode_changed, this);
+ _maximum_reel_size->Bind(wxEVT_SPINCTRL, boost::bind(&DCPTimeline::maximum_reel_size_changed, this));
+}
+
+
+void
+DCPTimeline::reel_mode_changed()
+{
+ film()->set_reel_type(static_cast<ReelType>(*_reel_type->get()));
+}
+
+
+void
+DCPTimeline::maximum_reel_size_changed()
+{
+ film()->set_reel_length(_maximum_reel_size->GetValue() * 1000000000LL);
+}
+
+
+void
+DCPTimeline::set_reel_boundary(int index, DCPTime time)
+{
+ auto boundaries = film()->custom_reel_boundaries();
+ DCPOMATIC_ASSERT(index >= 0 && index < static_cast<int>(boundaries.size()));
+ boundaries[index] = time.round(film()->video_frame_rate());
+ film()->set_custom_reel_boundaries(boundaries);
+}
+
+
+void
+DCPTimeline::setup_reel_boundaries()
+{
+ auto const reels = film()->reels();
+ if (reels.empty()) {
+ _reel_boundaries.clear();
+ return;
+ }
+
+ size_t const boundaries = reels.size() - 1;
+ auto const maximum = film()->length();
+ for (size_t i = _reel_boundaries.size(); i < boundaries; ++i) {
+ auto boundary = std::make_shared<ReelBoundary>(
+ _reel_detail, _reel_detail_sizer, i, maximum, film()->video_frame_rate(), *this, editable()
+ );
+
+ boundary->Changed.connect(boost::bind(&DCPTimeline::set_reel_boundary, this, _1, _2));
+ _reel_boundaries.push_back(boundary);
+ }
+
+ _reel_boundaries.resize(boundaries);
+
+ auto const active = editable();
+ for (size_t i = 0; i < boundaries; ++i) {
+ _reel_boundaries[i]->set_time(reels[i].to);
+ _reel_boundaries[i]->view().set_active(active);
+ }
+
+ _reel_detail_sizer->Layout();
+ _canvas->Refresh();
+}
+
+
+void
+DCPTimeline::paint()
+{
+ wxPaintDC dc(_canvas);
+ dc.Clear();
+
+ if (film()->content().empty()) {
+ return;
+ }
+
+ _canvas->DoPrepareDC(dc);
+
+ auto gc = wxGraphicsContext::Create(dc);
+ if (!gc) {
+ return;
+ }
+
+ dcp::ScopeGuard sg = [gc]() { delete gc; };
+
+ gc->SetAntialiasMode(wxANTIALIAS_DEFAULT);
+
+ paint_reels(gc);
+ paint_content(gc);
+}
+
+
+void
+DCPTimeline::paint_reels(wxGraphicsContext* gc)
+{
+ constexpr int x_offset = 2;
+
+ for (auto const& boundary: _reel_boundaries) {
+ boundary->view().paint(gc);
+ }
+
+ gc->SetFont(gc->CreateFont(*wxNORMAL_FONT, wxColour(0, 0, 0)));
+ gc->SetPen(*wxThePenList->FindOrCreatePen(wxColour(0, 0, 0), 2, wxPENSTYLE_SOLID));
+
+ auto const pps = pixels_per_second().get_value_or(1);
+
+ auto start = gc->CreatePath();
+ start.MoveToPoint(x_offset, reel_marker_y_pos);
+ start.AddLineToPoint(x_offset, reel_marker_y_pos + DCPTimelineReelMarkerView::HEIGHT);
+ gc->StrokePath(start);
+
+ auto const length = film()->length().seconds() * pps;
+ auto end = gc->CreatePath();
+ end.MoveToPoint(x_offset + length, reel_marker_y_pos);
+ end.AddLineToPoint(x_offset + length, reel_marker_y_pos + DCPTimelineReelMarkerView::HEIGHT);
+ gc->StrokePath(end);
+
+ auto const y = reel_marker_y_pos + DCPTimelineReelMarkerView::HEIGHT * 3 / 4;
+
+ auto paint_reel = [gc](double from, double to, int index) {
+ auto path = gc->CreatePath();
+ path.MoveToPoint(from, y);
+ path.AddLineToPoint(to, y);
+ gc->StrokePath(path);
+
+ auto str = wxString::Format(wxT("#%d"), index + 1);
+ wxDouble str_width;
+ wxDouble str_height;
+ wxDouble str_descent;
+ wxDouble str_leading;
+ gc->GetTextExtent(str, &str_width, &str_height, &str_descent, &str_leading);
+
+ if (str_width < (to - from)) {
+ gc->DrawText(str, (from + to - str_width) / 2, y - str_height - 2);
+ }
+ };
+
+ gc->SetPen(*wxThePenList->FindOrCreatePen(wxColour(0, 0, 255), 2, wxPENSTYLE_DOT));
+ int index = 0;
+ DCPTime last;
+ for (auto const& boundary: _reel_boundaries) {
+ paint_reel(last.seconds() * pps + 2, boundary->time().seconds() * pps, index++);
+ last = boundary->time();
+ }
+
+ paint_reel(last.seconds() * pps + 2, film()->length().seconds() * pps, index);
+}
+
+
+void
+DCPTimeline::paint_content(wxGraphicsContext* gc)
+{
+ auto const pps = pixels_per_second().get_value_or(1);
+ auto const film = this->film();
+
+ auto const& solid_pen = *wxThePenList->FindOrCreatePen(wxColour(0, 0, 0), 1, wxPENSTYLE_SOLID);
+ auto const& dotted_pen = *wxThePenList->FindOrCreatePen(wxColour(0, 0, 0), 1, wxPENSTYLE_DOT);
+
+ auto const& video_brush = *wxTheBrushList->FindOrCreateBrush(VIDEO_CONTENT_COLOUR, wxBRUSHSTYLE_SOLID);
+ auto const& audio_brush = *wxTheBrushList->FindOrCreateBrush(AUDIO_CONTENT_COLOUR, wxBRUSHSTYLE_SOLID);
+ auto const& text_brush = *wxTheBrushList->FindOrCreateBrush(TEXT_CONTENT_COLOUR, wxBRUSHSTYLE_SOLID);
+ auto const& atmos_brush = *wxTheBrushList->FindOrCreateBrush(ATMOS_CONTENT_COLOUR, wxBRUSHSTYLE_SOLID);
+
+ auto maybe_draw =
+ [gc, film, pps, solid_pen, dotted_pen]
+ (shared_ptr<Content> content, shared_ptr<ContentPart> part, wxBrush brush, int offset) {
+ if (part) {
+ auto const y = content_y_pos + offset * content_type_height + 1;
+ gc->SetPen(solid_pen);
+ gc->SetBrush(brush);
+ gc->DrawRectangle(
+ content->position().seconds() * pps,
+ y,
+ content->length_after_trim(film).seconds() * pps,
+ content_type_height - 2
+ );
+
+ gc->SetPen(dotted_pen);
+ for (auto split: content->reel_split_points(film)) {
+ if (split != content->position()) {
+ auto path = gc->CreatePath();
+ path.MoveToPoint(split.seconds() * pps, y);
+ path.AddLineToPoint(split.seconds() * pps, y + content_type_height - 2);
+ gc->StrokePath(path);
+ }
+ }
+ }
+ };
+
+ for (auto content: film->content()) {
+ maybe_draw(content, dynamic_pointer_cast<ContentPart>(content->video), video_brush, 0);
+ maybe_draw(content, dynamic_pointer_cast<ContentPart>(content->audio), audio_brush, 1);
+ for (auto text: content->text) {
+ maybe_draw(content, dynamic_pointer_cast<ContentPart>(text), text_brush, 2);
+ }
+ maybe_draw(content, dynamic_pointer_cast<ContentPart>(content->atmos), atmos_brush, 3);
+ }
+}
+
+
+void
+DCPTimeline::setup_pixels_per_second()
+{
+ set_pixels_per_second((_canvas->GetSize().GetWidth() - 4) / std::max(1.0, film()->length().seconds()));
+}
+
+
+shared_ptr<ReelBoundary>
+DCPTimeline::event_to_reel_boundary(wxMouseEvent& ev) const
+{
+ Position<int> const position(ev.GetX(), ev.GetY());
+ auto iter = std::find_if(_reel_boundaries.begin(), _reel_boundaries.end(), [position](shared_ptr<const ReelBoundary> boundary) {
+ return boundary->view().bbox().contains(position);
+ });
+
+ if (iter == _reel_boundaries.end()) {
+ return {};
+ }
+
+ return *iter;
+}
+
+
+void
+DCPTimeline::left_down(wxMouseEvent& ev)
+{
+ if (!editable()) {
+ return;
+ }
+
+ if (auto boundary = event_to_reel_boundary(ev)) {
+ auto const snap_distance = DCPTime::from_seconds((_canvas->GetSize().GetWidth() / _pixels_per_second.get_value_or(1)) / SNAP_SUBDIVISION);
+ _drag = DCPTimeline::Drag(
+ boundary,
+ _reel_boundaries,
+ film(),
+ static_cast<int>(ev.GetX() - boundary->time().seconds() * _pixels_per_second.get_value_or(0)),
+ _snap->get(),
+ snap_distance
+ );
+ } else {
+ _drag = boost::none;
+ }
+}
+
+
+void
+DCPTimeline::right_down(wxMouseEvent& ev)
+{
+ _right_down_position = ev.GetPosition();
+ _canvas->PopupMenu(_menu, _right_down_position);
+}
+
+
+void
+DCPTimeline::left_up(wxMouseEvent&)
+{
+ if (!_drag) {
+ return;
+ }
+
+ set_reel_boundary(_drag->reel_boundary->index(), _drag->time());
+ _drag = boost::none;
+}
+
+
+void
+DCPTimeline::mouse_moved(wxMouseEvent& ev)
+{
+ if (!_drag) {
+ return;
+ }
+
+ auto time = DCPTime::from_seconds((ev.GetPosition().x - _drag->offset) / _pixels_per_second.get_value_or(1));
+ time = std::max(_drag->previous ? _drag->previous->time() : DCPTime(), time);
+ time = std::min(_drag->next ? _drag->next->time() : film()->length(), time);
+ _drag->set_time(time);
+ _canvas->RefreshRect({0, reel_marker_y_pos - 2, _canvas->GetSize().GetWidth(), DCPTimelineReelMarkerView::HEIGHT + 4}, true);
+}
+
+
+void
+DCPTimeline::force_redraw(dcpomatic::Rect<int> const & r)
+{
+ _canvas->RefreshRect(wxRect(r.x, r.y, r.width, r.height), false);
+}
+
+
+shared_ptr<Film>
+DCPTimeline::film() const
+{
+ auto film = _film.lock();
+ DCPOMATIC_ASSERT(film);
+ return film;
+}
+
+
+bool
+DCPTimeline::editable() const
+{
+ return film()->reel_type() == ReelType::CUSTOM;
+}
+
+
+DCPTimeline::Drag::Drag(
+ shared_ptr<ReelBoundary> reel_boundary_,
+ vector<shared_ptr<ReelBoundary>> const& reel_boundaries,
+ shared_ptr<const Film> film,
+ int offset_,
+ bool snap,
+ DCPTime snap_distance
+ )
+ : reel_boundary(reel_boundary_)
+ , offset(offset_)
+ , _snap_distance(snap_distance)
+{
+ auto iter = std::find(reel_boundaries.begin(), reel_boundaries.end(), reel_boundary);
+ auto index = std::distance(reel_boundaries.begin(), iter);
+
+ if (index > 0) {
+ previous = reel_boundaries[index - 1];
+ }
+ if (index < static_cast<int>(reel_boundaries.size() - 1)) {
+ next = reel_boundaries[index + 1];
+ }
+
+ if (snap) {
+ for (auto content: film->content()) {
+ for (auto split: content->reel_split_points(film)) {
+ _snaps.push_back(split);
+ }
+ }
+ }
+}
+
+
+void
+DCPTimeline::Drag::set_time(DCPTime time)
+{
+ optional<DCPTime> nearest_distance;
+ optional<DCPTime> nearest_time;
+ for (auto snap: _snaps) {
+ auto const distance = time - snap;
+ if (!nearest_distance || distance.abs() < nearest_distance->abs()) {
+ nearest_distance = distance.abs();
+ nearest_time = snap;
+ }
+ }
+
+ if (nearest_distance && *nearest_distance < _snap_distance) {
+ reel_boundary->set_time(*nearest_time);
+ } else {
+ reel_boundary->set_time(time);
+ }
+}
+
+
+DCPTime
+DCPTimeline::Drag::time() const
+{
+ return reel_boundary->time();
+}
+
diff --git a/src/wx/dcp_timeline.h b/src/wx/dcp_timeline.h
new file mode 100644
index 000000000..23644c03f
--- /dev/null
+++ b/src/wx/dcp_timeline.h
@@ -0,0 +1,125 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_DCP_TIMELINE_H
+#define DCPOMATIC_DCP_TIMELINE_H
+
+
+#include "timecode.h"
+#include "timeline.h"
+#include "lib/change_signaller.h"
+#include "lib/film_property.h"
+#include "lib/rect.h"
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+#include <memory>
+
+
+class CheckBox;
+class Choice;
+class Film;
+class ReelBoundary;
+class SpinCtrl;
+class wxGridBagSizer;
+
+
+class DCPTimeline : public Timeline
+{
+public:
+ DCPTimeline(wxWindow* parent, std::shared_ptr<Film> film);
+
+ void force_redraw(dcpomatic::Rect<int> const &);
+
+private:
+ void paint();
+ void paint_reels(wxGraphicsContext* gc);
+ void paint_content(wxGraphicsContext* gc);
+ void setup_pixels_per_second();
+ void left_down(wxMouseEvent& ev);
+ void right_down(wxMouseEvent& ev);
+ void left_up(wxMouseEvent& ev);
+ void mouse_moved(wxMouseEvent& ev);
+ void reel_mode_changed();
+ void maximum_reel_size_changed();
+ void film_changed(ChangeType type, FilmProperty property);
+ std::shared_ptr<Film> film() const;
+ void setup_sensitivity();
+
+ void add_reel_boundary();
+ void setup_reel_settings();
+ void setup_reel_boundaries();
+ std::shared_ptr<ReelBoundary> event_to_reel_boundary(wxMouseEvent& ev) const;
+ void set_reel_boundary(int index, dcpomatic::DCPTime time);
+ bool editable() const;
+
+ std::weak_ptr<Film> _film;
+
+ wxScrolledCanvas* _canvas;
+
+ class Drag
+ {
+ public:
+ Drag(
+ std::shared_ptr<ReelBoundary> reel_boundary_,
+ std::vector<std::shared_ptr<ReelBoundary>> const& reel_boundaries,
+ std::shared_ptr<const Film> film,
+ int offset_,
+ bool snap,
+ dcpomatic::DCPTime snap_distance
+ );
+
+ std::shared_ptr<ReelBoundary> reel_boundary;
+ std::shared_ptr<ReelBoundary> previous;
+ std::shared_ptr<ReelBoundary> next;
+ int offset = 0;
+
+ void set_time(dcpomatic::DCPTime time);
+ dcpomatic::DCPTime time() const;
+
+ private:
+ std::vector<dcpomatic::DCPTime> _snaps;
+ dcpomatic::DCPTime _snap_distance;
+ };
+
+ boost::optional<Drag> _drag;
+
+ wxPoint _right_down_position;
+
+ wxPanel* _reel_settings;
+ Choice* _reel_type;
+ SpinCtrl* _maximum_reel_size;
+ CheckBox* _snap;
+ wxPanel* _reel_detail;
+ wxGridBagSizer* _reel_detail_sizer;
+
+ wxMenu* _menu;
+ wxMenuItem* _add_reel_boundary;
+
+ boost::signals2::scoped_connection _film_connection;
+
+ std::vector<std::shared_ptr<ReelBoundary>> _reel_boundaries;
+};
+
+
+#endif
+
diff --git a/src/wx/dcp_timeline_dialog.cc b/src/wx/dcp_timeline_dialog.cc
new file mode 100644
index 000000000..2cf6a74f1
--- /dev/null
+++ b/src/wx/dcp_timeline_dialog.cc
@@ -0,0 +1,78 @@
+/*
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_panel.h"
+#include "dcp_timeline_dialog.h"
+#include "film_editor.h"
+#include "wx_util.h"
+#include "lib/compose.hpp"
+#include "lib/cross.h"
+#include "lib/film.h"
+#include "lib/playlist.h"
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/graphics.h>
+LIBDCP_ENABLE_WARNINGS
+#include <list>
+
+
+using std::list;
+using std::shared_ptr;
+using std::string;
+using std::weak_ptr;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
+
+DCPTimelineDialog::DCPTimelineDialog(wxWindow* parent, shared_ptr<Film> film)
+ : wxDialog(
+ parent,
+ wxID_ANY,
+ _("Reels"),
+ wxDefaultPosition,
+ wxSize(640, 512),
+#ifdef DCPOMATIC_OSX
+ /* I can't get wxFRAME_FLOAT_ON_PARENT to work on OS X, and although wxSTAY_ON_TOP keeps
+ the window above all others (and not just our own) it's better than nothing for now.
+ */
+ wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxSTAY_ON_TOP
+#else
+ wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxFRAME_FLOAT_ON_PARENT
+#endif
+ )
+ , _timeline(this, film)
+{
+ auto sizer = new wxBoxSizer(wxVERTICAL);
+ sizer->Add (&_timeline, 1, wxEXPAND | wxALL, 12);
+
+#ifdef DCPOMATIC_LINUX
+ auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
+ if (buttons) {
+ sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+ }
+#endif
+
+ SetSizer(sizer);
+ sizer->Layout();
+ sizer->SetSizeHints(this);
+}
+
diff --git a/src/wx/dcp_timeline_dialog.h b/src/wx/dcp_timeline_dialog.h
new file mode 100644
index 000000000..d1293ca26
--- /dev/null
+++ b/src/wx/dcp_timeline_dialog.h
@@ -0,0 +1,39 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_timeline.h"
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+#include <memory>
+
+
+class DCPTimelineDialog : public wxDialog
+{
+public:
+ DCPTimelineDialog(wxWindow* parent, std::shared_ptr<Film> film);
+
+private:
+ std::weak_ptr<Film> _film;
+ DCPTimeline _timeline;
+};
+
diff --git a/src/wx/dcp_timeline_reel_marker_view.cc b/src/wx/dcp_timeline_reel_marker_view.cc
new file mode 100644
index 000000000..1c97ca175
--- /dev/null
+++ b/src/wx/dcp_timeline_reel_marker_view.cc
@@ -0,0 +1,71 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_timeline_reel_marker_view.h"
+LIBDCP_DISABLE_WARNINGS
+#include <wx/graphics.h>
+LIBDCP_ENABLE_WARNINGS
+
+
+using namespace std;
+using namespace dcpomatic;
+
+
+DCPTimelineReelMarkerView::DCPTimelineReelMarkerView(DCPTimeline& timeline, int y_pos)
+ : DCPTimelineView(timeline)
+ , _y_pos(y_pos)
+{
+
+}
+
+
+int
+DCPTimelineReelMarkerView::x_pos() const
+{
+ /* Nudge it over slightly so that the full line width is drawn on the left hand side */
+ return time_x(_time) + 2;
+}
+
+
+void
+DCPTimelineReelMarkerView::do_paint(wxGraphicsContext* gc)
+{
+ wxColour const outline = _active ? wxColour(0, 0, 0) : wxColour(128, 128, 128);
+ wxColour const fill = _active ? wxColour(255, 0, 0) : wxColour(192, 192, 192);
+ gc->SetPen(*wxThePenList->FindOrCreatePen(outline, 2, wxPENSTYLE_SOLID));
+ gc->SetBrush(*wxTheBrushList->FindOrCreateBrush(fill, wxBRUSHSTYLE_SOLID));
+
+ gc->DrawRectangle(x_pos(), _y_pos, HEAD_SIZE, HEAD_SIZE);
+
+ auto path = gc->CreatePath();
+ path.MoveToPoint(x_pos(), _y_pos + HEAD_SIZE + TAIL_LENGTH);
+ path.AddLineToPoint(x_pos(), _y_pos);
+ gc->StrokePath(path);
+ gc->FillPath(path);
+}
+
+
+dcpomatic::Rect<int>
+DCPTimelineReelMarkerView::bbox() const
+{
+ return { x_pos(), _y_pos, HEAD_SIZE, HEAD_SIZE + TAIL_LENGTH };
+}
+
diff --git a/src/wx/dcp_timeline_reel_marker_view.h b/src/wx/dcp_timeline_reel_marker_view.h
new file mode 100644
index 000000000..273d98259
--- /dev/null
+++ b/src/wx/dcp_timeline_reel_marker_view.h
@@ -0,0 +1,59 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_timeline_view.h"
+
+
+class DCPTimeline;
+
+
+class DCPTimelineReelMarkerView : public DCPTimelineView
+{
+public:
+ DCPTimelineReelMarkerView(DCPTimeline& timeline, int y_pos);
+
+ dcpomatic::Rect<int> bbox() const override;
+
+ dcpomatic::DCPTime time() const {
+ return _time;
+ }
+
+ void set_time(dcpomatic::DCPTime time) {
+ _time = time;
+ }
+
+ void set_active(bool active) {
+ _active = active;
+ }
+
+ static auto constexpr HEAD_SIZE = 16;
+ static auto constexpr TAIL_LENGTH = 28;
+ static auto constexpr HEIGHT = HEAD_SIZE + TAIL_LENGTH;
+
+private:
+ void do_paint(wxGraphicsContext* gc) override;
+ int x_pos() const;
+
+ dcpomatic::DCPTime _time;
+ int _y_pos;
+ bool _active = false;
+};
+
diff --git a/src/wx/dcp_timeline_view.h b/src/wx/dcp_timeline_view.h
new file mode 100644
index 000000000..24a75cad2
--- /dev/null
+++ b/src/wx/dcp_timeline_view.h
@@ -0,0 +1,44 @@
+/*
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_timeline.h"
+#include "timeline_view.h"
+
+
+class DCPTimelineView : public TimelineView<DCPTimeline>
+{
+public:
+ explicit DCPTimelineView(DCPTimeline& timeline)
+ : TimelineView(timeline)
+ {}
+
+ void paint(wxGraphicsContext* gc)
+ {
+ _last_paint_bbox = bbox();
+ do_paint(gc);
+ }
+
+protected:
+ virtual void do_paint(wxGraphicsContext* context) = 0;
+};
+
+
+
diff --git a/src/wx/dcpomatic_choice.cc b/src/wx/dcpomatic_choice.cc
index 2f1a6f0e9..f2e215439 100644
--- a/src/wx/dcpomatic_choice.cc
+++ b/src/wx/dcpomatic_choice.cc
@@ -40,14 +40,14 @@ Choice::Choice(wxWindow* parent)
void
-Choice::add(string const& entry)
+Choice::add_entry(string const& entry)
{
- add(std_to_wx(entry));
+ add_entry(std_to_wx(entry));
}
void
-Choice::add(wxString const& entry)
+Choice::add_entry(wxString const& entry)
{
if (_needs_clearing) {
Clear();
@@ -59,7 +59,7 @@ Choice::add(wxString const& entry)
void
-Choice::add(wxString const& entry, wxClientData* data)
+Choice::add_entry(wxString const& entry, wxClientData* data)
{
if (_needs_clearing) {
Clear();
@@ -71,7 +71,7 @@ Choice::add(wxString const& entry, wxClientData* data)
void
-Choice::add(wxString const& entry, wxString const& data)
+Choice::add_entry(wxString const& entry, wxString const& data)
{
if (_needs_clearing) {
Clear();
@@ -83,6 +83,18 @@ Choice::add(wxString const& entry, wxString const& data)
void
+Choice::set_entries(wxArrayString const& entries)
+{
+ if (GetStrings() == entries) {
+ return;
+ }
+
+ Clear();
+ Set(entries);
+}
+
+
+void
Choice::set(int index)
{
SetSelection(index);
diff --git a/src/wx/dcpomatic_choice.h b/src/wx/dcpomatic_choice.h
index dec0a3701..cc8115d20 100644
--- a/src/wx/dcpomatic_choice.h
+++ b/src/wx/dcpomatic_choice.h
@@ -32,10 +32,12 @@ class Choice : public wxChoice
public:
Choice(wxWindow* parent);
- void add(wxString const& entry);
- void add(wxString const& entry, wxClientData* data);
- void add(wxString const& entry, wxString const& data);
- void add(std::string const& entry);
+ void add_entry(wxString const& entry);
+ void add_entry(wxString const& entry, wxClientData* data);
+ void add_entry(wxString const& entry, wxString const& data);
+ void add_entry(std::string const& entry);
+ void set_entries(wxArrayString const& entries);
+
void set(int index);
void set_by_data(wxString const& data);
boost::optional<int> get() const;
diff --git a/src/wx/dir_picker_ctrl.cc b/src/wx/dir_picker_ctrl.cc
index 4c19da42a..a8c09e334 100644
--- a/src/wx/dir_picker_ctrl.cc
+++ b/src/wx/dir_picker_ctrl.cc
@@ -36,12 +36,13 @@ using namespace std;
using namespace boost;
-DirPickerCtrl::DirPickerCtrl (wxWindow* parent)
+DirPickerCtrl::DirPickerCtrl(wxWindow* parent, bool leaf)
: wxPanel (parent)
+ , _leaf(leaf)
{
_sizer = new wxBoxSizer (wxHORIZONTAL);
- _folder = new StaticText (this, wxT(""));
+ _folder = new StaticText(this, wxT(""), wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
wxFont font = _folder->GetFont ();
font.SetStyle (wxFONTSTYLE_ITALIC);
_folder->SetFont (font);
@@ -62,7 +63,11 @@ DirPickerCtrl::SetPath (wxString p)
if (_path == wxStandardPaths::Get().GetDocumentsDir()) {
_folder->SetLabel (_("My Documents"));
} else {
- _folder->SetLabel (_path);
+ if (_leaf) {
+ _folder->SetLabel(std_to_wx(boost::filesystem::path(wx_to_std(_path)).filename().string()));
+ } else {
+ _folder->SetLabel(_path);
+ }
}
wxCommandEvent ev (wxEVT_DIRPICKER_CHANGED, wxID_ANY);
diff --git a/src/wx/dir_picker_ctrl.h b/src/wx/dir_picker_ctrl.h
index f70168db4..31df9518c 100644
--- a/src/wx/dir_picker_ctrl.h
+++ b/src/wx/dir_picker_ctrl.h
@@ -33,7 +33,7 @@ LIBDCP_ENABLE_WARNINGS
class DirPickerCtrl : public wxPanel
{
public:
- explicit DirPickerCtrl (wxWindow *);
+ DirPickerCtrl(wxWindow *, bool leaf = false);
wxString GetPath () const;
void SetPath (wxString);
@@ -47,6 +47,7 @@ private:
wxButton* _browse;
wxString _path;
wxSizer* _sizer;
+ bool _leaf = false;
};
#endif
diff --git a/src/wx/disk_warning_dialog.cc b/src/wx/disk_warning_dialog.cc
index 531b7f4f9..9af4a2aca 100644
--- a/src/wx/disk_warning_dialog.cc
+++ b/src/wx/disk_warning_dialog.cc
@@ -18,9 +18,12 @@
*/
+
#include "disk_warning_dialog.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
+
DiskWarningDialog::DiskWarningDialog ()
: wxDialog(nullptr, wxID_ANY, _("Important notice"))
@@ -45,10 +48,10 @@ DiskWarningDialog::DiskWarningDialog ()
auto const confirmation = _("I am sure");
text->SetLabelMarkup(wxString::Format(
- _("The <b>DCP-o-matic Disk Writer</b> is\n\n<span weight=\"bold\" size=\"20480\" foreground=\"red\">BETA-GRADE TEST SOFTWARE</span>\n\n"
+ _("The <b>%s</b> is\n\n<span weight=\"bold\" size=\"20480\" foreground=\"red\">BETA-GRADE TEST SOFTWARE</span>\n\n"
"and may\n\n<span weight=\"bold\" size=\"20480\" foreground=\"red\">DESTROY DATA!</span>\n\n"
"If you are sure you want to continue please type\n\n<tt>%s</tt>\n\ninto the box below, then click OK."),
- confirmation));
+ variant::wx::dcpomatic_disk_writer(), confirmation));
}
bool
diff --git a/src/wx/dkdm_dialog.cc b/src/wx/dkdm_dialog.cc
index 34a8d6284..a36a9ceae 100644
--- a/src/wx/dkdm_dialog.cc
+++ b/src/wx/dkdm_dialog.cc
@@ -28,6 +28,7 @@
#include "recipients_panel.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/config.h"
#include "lib/film.h"
#include "lib/job_manager.h"
@@ -161,7 +162,7 @@ DKDMDialog::make_clicked ()
list<KDMWithMetadataPtr> kdms;
try {
for (auto i: _recipients->recipients()) {
- auto p = kdm_for_dkdm_recipient (film, _cpl->cpl(), i, _timing->from(), _timing->until());
+ auto p = kdm_for_dkdm_recipient(film, _cpl->cpl(), i, _timing->from(), _timing->until());
if (p) {
kdms.push_back (p);
}
@@ -170,7 +171,13 @@ DKDMDialog::make_clicked ()
if (e.starts_too_early()) {
error_dialog (this, _("The KDM start period is before (or close to) the start of the signing certificate's validity period. Use a later start time for this KDM."));
} else {
- error_dialog (this, _("The KDM end period is after (or close to) the end of the signing certificates' validity period. Either use an earlier end time for this KDM or re-create your signing certificates in the DCP-o-matic preferences window."));
+ error_dialog(
+ this,
+ variant::wx::insert_dcpomatic(
+ _("The KDM end period is after (or close to) the end of the signing certificates' validity period. "
+ "Either use an earlier end time for this KDM or re-create your signing certificates in the %s preferences window.")
+ )
+ );
}
return;
} catch (runtime_error& e) {
diff --git a/src/wx/export_video_file_dialog.cc b/src/wx/export_video_file_dialog.cc
index 611985602..6e32a8514 100644
--- a/src/wx/export_video_file_dialog.cc
+++ b/src/wx/export_video_file_dialog.cc
@@ -22,6 +22,7 @@
#include "check_box.h"
#include "export_video_file_dialog.h"
#include "file_picker_ctrl.h"
+#include "lib/ffmpeg_file_encoder.h"
#include "wx_util.h"
#include "lib/config.h"
#include <dcp/warnings.h>
@@ -35,30 +36,34 @@ using std::string;
using boost::bind;
-int constexpr FORMATS = 3;
+int constexpr FORMATS = 4;
wxString format_names[] = {
_("MOV / ProRes 4444"),
_("MOV / ProRes HQ"),
+ _("MOV / ProRes LT"),
_("MP4 / H.264"),
};
wxString format_filters[] = {
_("MOV files (*.mov)|*.mov"),
_("MOV files (*.mov)|*.mov"),
+ _("MOV files (*.mov)|*.mov"),
_("MP4 files (*.mp4)|*.mp4"),
};
wxString format_extensions[] = {
"mov",
"mov",
+ "mov",
"mp4",
};
ExportFormat formats[] = {
ExportFormat::PRORES_4444,
ExportFormat::PRORES_HQ,
+ ExportFormat::PRORES_LT,
ExportFormat::H264_AAC,
};
diff --git a/src/wx/export_video_file_dialog.h b/src/wx/export_video_file_dialog.h
index beb33610b..4e626be6b 100644
--- a/src/wx/export_video_file_dialog.h
+++ b/src/wx/export_video_file_dialog.h
@@ -20,7 +20,7 @@
#include "table_dialog.h"
-#include "lib/ffmpeg_encoder.h"
+#include "lib/ffmpeg_file_encoder.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
diff --git a/src/wx/file_picker_ctrl.cc b/src/wx/file_picker_ctrl.cc
index 82978dad6..39e7669d5 100644
--- a/src/wx/file_picker_ctrl.cc
+++ b/src/wx/file_picker_ctrl.cc
@@ -32,8 +32,8 @@ LIBDCP_ENABLE_WARNINGS
#include <boost/filesystem.hpp>
-using namespace std;
-using namespace boost;
+using std::string;
+using boost::optional;
FilePickerCtrl::FilePickerCtrl(
diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc
index 9f54db58e..fae02787a 100644
--- a/src/wx/film_editor.cc
+++ b/src/wx/film_editor.cc
@@ -54,13 +54,15 @@ FilmEditor::FilmEditor(wxWindow* parent, FilmViewer& viewer)
{
auto s = new wxBoxSizer (wxVERTICAL);
- auto notebook = new wxNotebook(this, wxID_ANY);
- s->Add(notebook, 1, wxEXPAND);
+ _notebook = new wxNotebook(this, wxID_ANY);
+ s->Add(_notebook, 1, wxEXPAND);
- _content_panel = new ContentPanel(notebook, _film, viewer);
- notebook->AddPage(_content_panel->window(), _("Content"), true);
- _dcp_panel = new DCPPanel(notebook, _film, viewer);
- notebook->AddPage(_dcp_panel->panel (), _("DCP"), false);
+ _content_panel = new ContentPanel(_notebook, _film, viewer);
+ _notebook->AddPage(_content_panel->window(), _("Content"), true);
+ _dcp_panel = new DCPPanel(_notebook, _film, viewer);
+ _notebook->AddPage(_dcp_panel->panel (), _("DCP"), false);
+
+ _notebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, boost::bind(&FilmEditor::page_changed, this, _1));
JobManager::instance()->ActiveJobsChanged.connect (
bind(&FilmEditor::active_jobs_changed, this, _2)
@@ -71,6 +73,18 @@ FilmEditor::FilmEditor(wxWindow* parent, FilmViewer& viewer)
}
+void
+FilmEditor::page_changed(wxBookCtrlEvent& ev)
+{
+ /* One of these events arrives early on with GetOldSelection() being a non-existent tab,
+ * and we want to ignore that.
+ */
+ if (_film && ev.GetOldSelection() < 2) {
+ _film->set_ui_state("FilmEditorTab", ev.GetSelection() == 0 ? "content" : "dcp");
+ }
+}
+
+
/** Called when the metadata stored in the Film object has changed;
* so that we can update the GUI.
* @param p Property of the Film that has changed.
@@ -144,6 +158,13 @@ FilmEditor::set_film (shared_ptr<Film> film)
if (!_film->content().empty()) {
_content_panel->set_selection (_film->content().front());
}
+
+ auto tab = _film->ui_state("FilmEditorTab").get_value_or("content");
+ if (tab == "content") {
+ _notebook->SetSelection(0);
+ } else if (tab == "dcp") {
+ _notebook->SetSelection(1);
+ }
}
diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h
index a3df266ad..54d639ef5 100644
--- a/src/wx/film_editor.h
+++ b/src/wx/film_editor.h
@@ -32,11 +32,12 @@ LIBDCP_ENABLE_WARNINGS
#include <boost/signals2.hpp>
-class wxNotebook;
-class Film;
class ContentPanel;
class DCPPanel;
+class Film;
class FilmViewer;
+class wxBookCtrlEvent;
+class wxNotebook;
/** @class FilmEditor
@@ -71,6 +72,9 @@ private:
void set_general_sensitivity (bool);
void active_jobs_changed (boost::optional<std::string>);
+ void page_changed(wxBookCtrlEvent& ev);
+
+ wxNotebook* _notebook;
ContentPanel* _content_panel;
DCPPanel* _dcp_panel;
diff --git a/src/wx/film_name_location_dialog.cc b/src/wx/film_name_location_dialog.cc
index 6c54f1848..b988d6251 100644
--- a/src/wx/film_name_location_dialog.cc
+++ b/src/wx/film_name_location_dialog.cc
@@ -35,8 +35,9 @@ LIBDCP_ENABLE_WARNINGS
#include <boost/filesystem.hpp>
-using namespace std;
-using namespace boost;
+using std::string;
+using boost::bind;
+using boost::optional;
boost::optional<boost::filesystem::path> FilmNameLocationDialog::_directory;
diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc
index b6f8096e8..50c3010d0 100644
--- a/src/wx/film_viewer.cc
+++ b/src/wx/film_viewer.cc
@@ -35,6 +35,7 @@
#include "lib/butler.h"
#include "lib/compose.hpp"
#include "lib/config.h"
+#include "lib/dcp_content.h"
#include "lib/dcpomatic_log.h"
#include "lib/examine_content_job.h"
#include "lib/exceptions.h"
@@ -174,7 +175,7 @@ FilmViewer::set_film (shared_ptr<Film> film)
}
try {
- _player.emplace(_film, _optimise_for_j2k ? Image::Alignment::COMPACT : Image::Alignment::PADDED);
+ _player.emplace(_film, _optimisation == Optimisation::NONE ? Image::Alignment::PADDED : Image::Alignment::COMPACT);
_player->set_fast ();
if (_dcp_decode_reduction) {
_player->set_dcp_decode_reduction (_dcp_decode_reduction);
@@ -235,9 +236,9 @@ void
FilmViewer::create_butler()
{
#if wxCHECK_VERSION(3, 1, 0)
- auto const j2k_gl_optimised = dynamic_pointer_cast<GLVideoView>(_video_view) && _optimise_for_j2k;
+ auto const opengl = dynamic_pointer_cast<GLVideoView>(_video_view);
#else
- auto const j2k_gl_optimised = false;
+ auto const opengl = false;
#endif
DCPOMATIC_ASSERT(_player);
@@ -249,9 +250,9 @@ FilmViewer::create_butler()
_audio_channels,
boost::bind(&PlayerVideo::force, AV_PIX_FMT_RGB24),
VideoRange::FULL,
- j2k_gl_optimised ? Image::Alignment::COMPACT : Image::Alignment::PADDED,
+ (opengl && _optimisation != Optimisation::NONE) ? Image::Alignment::COMPACT : Image::Alignment::PADDED,
true,
- j2k_gl_optimised,
+ opengl && _optimisation == Optimisation::JPEG2000,
(Config::instance()->sound() && _audio.isStreamOpen()) ? Butler::Audio::ENABLED : Butler::Audio::DISABLED
);
@@ -874,10 +875,11 @@ FilmViewer::image_changed (shared_ptr<PlayerVideo> pv)
void
-FilmViewer::set_optimise_for_j2k (bool o)
+FilmViewer::set_optimisation(Optimisation o)
{
- _optimise_for_j2k = o;
- _video_view->set_optimise_for_j2k (o);
+ _optimisation = o;
+ _video_view->set_optimisation(o);
+ destroy_and_maybe_create_butler();
}
@@ -899,6 +901,20 @@ FilmViewer::unset_crop_guess ()
}
+shared_ptr<DCPContent>
+FilmViewer::dcp() const
+{
+ if (_film) {
+ auto content = _film->content();
+ if (content.size() == 1) {
+ return dynamic_pointer_cast<DCPContent>(content.front());
+ }
+ }
+
+ return {};
+}
+
+
#if (RTAUDIO_VERSION_MAJOR >= 6)
void
FilmViewer::rtaudio_error_callback(string const& error)
diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h
index 5824f8baa..3ade364fe 100644
--- a/src/wx/film_viewer.h
+++ b/src/wx/film_viewer.h
@@ -24,9 +24,12 @@
*/
+#include "optimisation.h"
#include "video_view.h"
+#include "lib/change_signaller.h"
#include "lib/config.h"
#include "lib/film_property.h"
+#include "lib/player.h"
#include "lib/player_text.h"
#include "lib/signaller.h"
#include "lib/timer.h"
@@ -40,6 +43,7 @@ LIBDCP_ENABLE_WARNINGS
class Butler;
class ClosedCaptionsDialog;
+class DCPContent;
class FFmpegPlayer;
class Image;
class Player;
@@ -73,6 +77,11 @@ public:
return _film;
}
+ /** @return The DCP that we are playing back, if that's the only content in
+ * our Film.
+ */
+ std::shared_ptr<DCPContent> dcp() const;
+
void seek (dcpomatic::DCPTime t, bool accurate);
void seek (std::shared_ptr<Content> content, dcpomatic::ContentTime p, bool accurate);
void seek_by (dcpomatic::DCPTime by, bool accurate);
@@ -99,7 +108,7 @@ public:
void set_outline_subtitles (boost::optional<dcpomatic::Rect<double>>);
void set_eyes (Eyes e);
void set_pad_black (bool p);
- void set_optimise_for_j2k (bool o);
+ void set_optimisation(Optimisation o);
void set_crop_guess (dcpomatic::Rect<float> crop);
void unset_crop_guess ();
@@ -204,10 +213,7 @@ private:
boost::optional<int> _dcp_decode_reduction;
- /** true to assume that this viewer is only being used for JPEG2000 sources
- * so it can optimise accordingly.
- */
- bool _optimise_for_j2k = false;
+ Optimisation _optimisation = Optimisation::NONE;
ClosedCaptionsDialog* _closed_captions_dialog = nullptr;
diff --git a/src/wx/full_config_dialog.cc b/src/wx/full_config_dialog.cc
index c1c36c4a4..955ea6493 100644
--- a/src/wx/full_config_dialog.cc
+++ b/src/wx/full_config_dialog.cc
@@ -45,10 +45,13 @@
#include "send_test_email_dialog.h"
#include "server_dialog.h"
#include "static_text.h"
+#ifdef DCPOMATIC_GROK
+#include "grok/gpu_config_panel.h"
+#endif
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/config.h"
#include "lib/cross.h"
-#include "lib/dcp_content_type.h"
#include "lib/email.h"
#include "lib/exceptions.h"
#include "lib/filter.h"
@@ -104,12 +107,12 @@ private:
int r = 0;
add_language_controls (table, r);
- add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic should use"), true, wxGBPosition (r, 0));
+ add_label_to_sizer(table, _panel, variant::wx::insert_dcpomatic(_("Number of threads %s should use")), true, wxGBPosition(r, 0));
_master_encoding_threads = new wxSpinCtrl (_panel);
table->Add (_master_encoding_threads, wxGBPosition (r, 1));
++r;
- add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic encode server should use"), true, wxGBPosition (r, 0));
+ add_label_to_sizer(table, _panel, variant::wx::insert_dcpomatic_encode_server(_("Number of threads %s should use")), true, wxGBPosition(r, 0));
_server_encoding_threads = new wxSpinCtrl (_panel);
table->Add (_server_encoding_threads, wxGBPosition (r, 1));
++r;
@@ -143,8 +146,8 @@ private:
add_update_controls (table, r);
- _default_add_file_location->add(_("Same place as last time"));
- _default_add_file_location->add(_("Same place as project"));
+ _default_add_file_location->add_entry(_("Same place as last time"));
+ _default_add_file_location->add_entry(_("Same place as project"));
_default_add_file_location->bind(&FullGeneralPage::default_add_file_location_changed, this);
_config_file->Bind (wxEVT_FILEPICKER_CHANGED, boost::bind(&FullGeneralPage::config_file_changed, this));
@@ -307,23 +310,6 @@ private:
#endif
table->Add (_directory, 1, wxEXPAND);
- add_label_to_sizer (table, _panel, _("Default content type"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
- _dcp_content_type = new wxChoice (_panel, wxID_ANY);
- table->Add (_dcp_content_type);
-
- add_label_to_sizer (table, _panel, _("Default DCP audio channels"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
- _dcp_audio_channels = new wxChoice (_panel, wxID_ANY);
- table->Add (_dcp_audio_channels);
-
- {
- add_label_to_sizer (table, _panel, _("Default JPEG2000 bandwidth"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
- auto s = new wxBoxSizer (wxHORIZONTAL);
- _j2k_bandwidth = new wxSpinCtrl (_panel);
- s->Add (_j2k_bandwidth);
- add_label_to_sizer (s, _panel, _("Mbit/s"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
- table->Add (s, 1);
- }
-
{
add_label_to_sizer (table, _panel, _("Default audio delay"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
auto s = new wxBoxSizer (wxHORIZONTAL);
@@ -333,32 +319,11 @@ private:
table->Add (s, 1);
}
- add_label_to_sizer (table, _panel, _("Default standard"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
- _standard = new wxChoice (_panel, wxID_ANY);
- table->Add (_standard);
-
_enable_audio_language = new CheckBox(_panel, _("Default audio language"));
table->Add(_enable_audio_language, 1, wxEXPAND | wxALIGN_CENTRE_VERTICAL);
_audio_language = new LanguageTagWidget(_panel, _("Default audio language to use for new DCPs"), Config::instance()->default_audio_language(), wxString("cmnr-Hant-"));
table->Add(_audio_language->sizer());
- _enable_territory = new CheckBox(_panel, _("Default territory"));
- table->Add(_enable_territory, 1, wxEXPAND | wxALIGN_CENTRE_VERTICAL);
- _territory = new RegionSubtagWidget(_panel, _("Default territory to use for new DCPs"), Config::instance()->default_territory(), wxString("cmnr-Hant-"));
- table->Add(_territory->sizer());
-
- table->Add (_enable_metadata["facility"] = new CheckBox (_panel, _("Default facility")), 0, wxALIGN_CENTRE_VERTICAL);
- table->Add (_metadata["facility"] = new wxTextCtrl (_panel, wxID_ANY, wxT("")), 0, wxEXPAND);
-
- table->Add (_enable_metadata["studio"] = new CheckBox (_panel, _("Default studio")), 0, wxALIGN_CENTRE_VERTICAL);
- table->Add (_metadata["studio"] = new wxTextCtrl (_panel, wxID_ANY, wxT("")), 0, wxEXPAND);
-
- table->Add (_enable_metadata["chain"] = new CheckBox (_panel, _("Default chain")), 0, wxALIGN_CENTRE_VERTICAL);
- table->Add (_metadata["chain"] = new wxTextCtrl (_panel, wxID_ANY, wxT("")), 0, wxEXPAND);
-
- table->Add (_enable_metadata["distributor"] = new CheckBox (_panel, _("Default distributor")), 0, wxALIGN_CENTRE_VERTICAL);
- table->Add (_metadata["distributor"] = new wxTextCtrl (_panel, wxID_ANY, wxT("")), 0, wxEXPAND);
-
add_label_to_sizer (table, _panel, _("Default KDM directory"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
#ifdef DCPOMATIC_USE_OWN_PICKER
_kdm_directory = new DirPickerCtrl (_panel);
@@ -397,86 +362,25 @@ private:
_use_isdcf_name_by_default->bind(&DefaultsPage::use_isdcf_name_by_default_changed, this);
- for (auto i: DCPContentType::all()) {
- _dcp_content_type->Append (std_to_wx (i->pretty_name ()));
- }
-
- setup_audio_channels_choice (_dcp_audio_channels, 2);
-
- _dcp_content_type->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
- _dcp_audio_channels->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_audio_channels_changed, this));
-
- _j2k_bandwidth->SetRange (50, 250);
- _j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
-
_audio_delay->SetRange (-1000, 1000);
_audio_delay->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::audio_delay_changed, this));
- _standard->Append (_("SMPTE"));
- _standard->Append (_("Interop"));
- _standard->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::standard_changed, this));
-
- for (auto const& i: _enable_metadata) {
- i.second->bind(&DefaultsPage::metadata_changed, this);
- }
-
- for (auto const& i: _metadata) {
- i.second->Bind (wxEVT_TEXT, boost::bind(&DefaultsPage::metadata_changed, this));
- }
-
_enable_audio_language->bind(&DefaultsPage::enable_audio_language_toggled, this);
_audio_language->Changed.connect(boost::bind(&DefaultsPage::audio_language_changed, this));
-
- _enable_territory->bind(&DefaultsPage::enable_territory_toggled, this);
- _territory->Changed.connect(boost::bind(&DefaultsPage::territory_changed, this));
}
void config_changed () override
{
auto config = Config::instance ();
-
- auto const ct = DCPContentType::all ();
- for (size_t i = 0; i < ct.size(); ++i) {
- if (ct[i] == config->default_dcp_content_type()) {
- _dcp_content_type->SetSelection (i);
- }
- }
-
checked_set (_still_length, config->default_still_length ());
_directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
_kdm_directory->SetPath (std_to_wx (config->default_kdm_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
_kdm_type->set (config->default_kdm_type());
checked_set (_use_isdcf_name_by_default, config->use_isdcf_name_by_default());
- checked_set (_j2k_bandwidth, config->default_j2k_bandwidth() / 1000000);
- _j2k_bandwidth->SetRange (50, config->maximum_j2k_bandwidth() / 1000000);
- checked_set (_dcp_audio_channels, locale_convert<string> (config->default_dcp_audio_channels()));
checked_set (_audio_delay, config->default_audio_delay ());
- checked_set (_standard, config->default_interop() ? 1 : 0);
auto dal = config->default_audio_language();
checked_set(_enable_audio_language, static_cast<bool>(dal));
checked_set(_audio_language, dal ? dal : boost::none);
- auto dt = config->default_territory();
- checked_set(_enable_territory, static_cast<bool>(dt));
- checked_set(_territory, dt ? dt : boost::none);
-
- auto metadata = config->default_metadata();
-
- for (auto const& i: metadata) {
- _enable_metadata[i.first]->SetValue(true);
- checked_set (_metadata[i.first], i.second);
- }
-
- for (auto const& i: _enable_metadata) {
- if (metadata.find(i.first) == metadata.end()) {
- checked_set (i.second, false);
- }
- }
-
- for (auto const& i: _metadata) {
- if (metadata.find(i.first) == metadata.end()) {
- checked_set (i.second, wxT(""));
- }
- }
checked_set (_kdm_duration, config->default_kdm_duration().duration);
switch (config->default_kdm_duration().unit) {
@@ -523,26 +427,11 @@ private:
config->set_default_kdm_duration (RoughDuration(duration, unit));
}
- void j2k_bandwidth_changed ()
- {
- Config::instance()->set_default_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
- }
-
void audio_delay_changed ()
{
Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
}
- void dcp_audio_channels_changed ()
- {
- int const s = _dcp_audio_channels->GetSelection ();
- if (s != wxNOT_FOUND) {
- Config::instance()->set_default_dcp_audio_channels (
- locale_convert<int>(string_client_data(_dcp_audio_channels->GetClientObject(s)))
- );
- }
- }
-
void directory_changed ()
{
Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
@@ -568,29 +457,6 @@ private:
Config::instance()->set_default_still_length (_still_length->GetValue ());
}
- void dcp_content_type_changed ()
- {
- auto ct = DCPContentType::all ();
- Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
- }
-
- void standard_changed ()
- {
- Config::instance()->set_default_interop (_standard->GetSelection() == 1);
- }
-
- void metadata_changed ()
- {
- map<string, string> metadata;
- for (auto const& i: _enable_metadata) {
- if (i.second->GetValue()) {
- metadata[i.first] = wx_to_std(_metadata[i.first]->GetValue());
- }
- }
- Config::instance()->set_default_metadata (metadata);
- setup_sensitivity ();
- }
-
void enable_audio_language_toggled()
{
setup_sensitivity();
@@ -606,31 +472,11 @@ private:
}
}
- void enable_territory_toggled()
- {
- setup_sensitivity();
- territory_changed();
- }
-
- void territory_changed()
- {
- if (_enable_territory->get()) {
- Config::instance()->set_default_territory(_territory->get().get_value_or(dcp::LanguageTag::RegionSubtag("US")));
- } else {
- Config::instance()->unset_default_territory();
- }
- }
-
void setup_sensitivity ()
{
_audio_language->enable(_enable_audio_language->get());
- _territory->enable(_enable_territory->get());
- for (auto const& i: _enable_metadata) {
- _metadata[i.first]->Enable(i.second->GetValue());
- }
}
- wxSpinCtrl* _j2k_bandwidth;
wxSpinCtrl* _audio_delay;
wxSpinCtrl* _still_length;
#ifdef DCPOMATIC_USE_OWN_PICKER
@@ -644,15 +490,8 @@ private:
wxSpinCtrl* _kdm_duration;
wxChoice* _kdm_duration_unit;
CheckBox* _use_isdcf_name_by_default;
- wxChoice* _dcp_content_type;
- wxChoice* _dcp_audio_channels;
- wxChoice* _standard;
CheckBox* _enable_audio_language;
LanguageTagWidget* _audio_language;
- CheckBox* _enable_territory;
- RegionSubtagWidget* _territory;
- map<string, CheckBox*> _enable_metadata;
- map<string, wxTextCtrl*> _metadata;
};
@@ -983,8 +822,8 @@ private:
Email email(
wx_to_std(dialog.from()),
{ wx_to_std(dialog.to()) },
- wx_to_std(_("DCP-o-matic test email")),
- wx_to_std(_("This is a test email from DCP-o-matic."))
+ wx_to_std(variant::wx::insert_dcpomatic(_("%s test email"))),
+ wx_to_std(variant::wx::insert_dcpomatic(_("This is a test email from %s.")))
);
auto config = Config::instance();
try {
@@ -1404,32 +1243,50 @@ private:
add_label_to_sizer (table, _panel, _("Issuer"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_issuer = new wxTextCtrl (_panel, wxID_ANY);
- _issuer->SetToolTip (_("This will be written to the DCP's XML files as the <Issuer>. If it is blank, a default value mentioning DCP-o-matic will be used."));
+ _issuer->SetToolTip(
+ variant::wx::insert_dcpomatic(
+ _("This will be written to the DCP's XML files as the <Issuer>. If it is blank, a default value mentioning %s will be used.")
+ ));
table->Add (_issuer, 1, wxALL | wxEXPAND);
add_label_to_sizer (table, _panel, _("Creator"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_creator = new wxTextCtrl (_panel, wxID_ANY);
- _creator->SetToolTip (_("This will be written to the DCP's XML files as the <Creator>. If it is blank, a default value mentioning DCP-o-matic will be used."));
+ _creator->SetToolTip(
+ variant::wx::insert_dcpomatic(
+ _("This will be written to the DCP's XML files as the <Creator>. If it is blank, a default value mentioning %s will be used.")
+ ));
table->Add (_creator, 1, wxALL | wxEXPAND);
add_label_to_sizer (table, _panel, _("Company name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_company_name = new wxTextCtrl (_panel, wxID_ANY);
- _company_name->SetToolTip (_("This will be written to the DCP's MXF files as the 'company name'. If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
+ _company_name->SetToolTip(
+ variant::wx::insert_dcpomatic(
+ _("This will be written to the DCP's MXF files as the 'company name'. If it is blank, a default value mentioning libdcp (an internal %s library) will be used.")
+ ));
table->Add (_company_name, 1, wxALL | wxEXPAND);
add_label_to_sizer (table, _panel, _("Product name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_product_name = new wxTextCtrl (_panel, wxID_ANY);
- _product_name->SetToolTip (_("This will be written to the DCP's MXF files as the 'product name'. If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
+ _product_name->SetToolTip(
+ variant::wx::insert_dcpomatic(
+ _("This will be written to the DCP's MXF files as the 'product name'. If it is blank, a default value mentioning libdcp (an internal %s library) will be used.")
+ ));
table->Add (_product_name, 1, wxALL | wxEXPAND);
add_label_to_sizer (table, _panel, _("Product version"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_product_version = new wxTextCtrl (_panel, wxID_ANY);
- _product_version->SetToolTip (_("This will be written to the DCP's MXF files as the 'product version'. If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
+ _product_version->SetToolTip(
+ variant::wx::insert_dcpomatic(
+ _("This will be written to the DCP's MXF files as the 'product version'. If it is blank, a default value mentioning libdcp (an internal %s library) will be used.")
+ ));
table->Add (_product_version, 1, wxALL | wxEXPAND);
add_label_to_sizer (table, _panel, _("JPEG2000 comment"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_j2k_comment = new wxTextCtrl (_panel, wxID_ANY);
- _j2k_comment->SetToolTip (_("This will be written to the DCP's JPEG2000 data as a comment. If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
+ _j2k_comment->SetToolTip(
+ variant::wx::insert_dcpomatic(
+ _("This will be written to the DCP's JPEG2000 data as a comment. If it is blank, a default value mentioning libdcp (an internal %s library) will be used.")
+ ));
table->Add (_j2k_comment, 1, wxALL | wxEXPAND);
_panel->GetSizer()->Add (table, 0, wxEXPAND | wxALL, _border);
@@ -1519,10 +1376,19 @@ private:
_panel->GetSizer()->Add(table, 1, wxALL | wxEXPAND, _border);
{
- add_label_to_sizer(table, _panel, _("Maximum JPEG2000 bandwidth"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ add_label_to_sizer(table, _panel, _("Maximum JPEG2000 bit rate"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ auto s = new wxBoxSizer(wxHORIZONTAL);
+ _maximum_j2k_video_bit_rate = new wxSpinCtrl(_panel);
+ s->Add(_maximum_j2k_video_bit_rate, 1);
+ add_label_to_sizer(s, _panel, _("Mbit/s"), false, 0, wxLEFT | wxALIGN_CENTRE_VERTICAL);
+ table->Add(s, 1);
+ }
+
+ {
+ add_label_to_sizer(table, _panel, _("Maximum MPEG2 bit rate"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
auto s = new wxBoxSizer(wxHORIZONTAL);
- _maximum_j2k_bandwidth = new wxSpinCtrl(_panel);
- s->Add(_maximum_j2k_bandwidth, 1);
+ _maximum_mpeg2_video_bit_rate = new wxSpinCtrl(_panel);
+ s->Add(_maximum_mpeg2_video_bit_rate, 1);
add_label_to_sizer(s, _panel, _("Mbit/s"), false, 0, wxLEFT | wxALIGN_CENTRE_VERTICAL);
table->Add(s, 1);
}
@@ -1537,13 +1403,7 @@ private:
_allow_any_container = new CheckBox(_panel, _("Allow full-frame and non-standard container ratios"));
table->Add(_allow_any_container, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
- auto restart = new StaticText(_panel, _("(restart DCP-o-matic to see all ratios)"));
- auto font = restart->GetFont();
- font.SetStyle(wxFONTSTYLE_ITALIC);
- font.SetPointSize(font.GetPointSize() - 1);
- restart->SetFont(font);
- table->Add(restart, 1, wxALIGN_CENTRE_VERTICAL | wxBOTTOM, DCPOMATIC_CHECKBOX_BOTTOM_PAD);
- restart->SetFont(font);
+ table->AddSpacer(0);
checkbox(_("Allow creation of DCPs with 96kHz audio"), _allow_96khz_audio);
checkbox(_("Allow mapping to all audio channels"), _use_all_audio_channels);
@@ -1557,8 +1417,10 @@ private:
table->Add(s, 1);
}
- _maximum_j2k_bandwidth->SetRange(1, 1000);
- _maximum_j2k_bandwidth->Bind(wxEVT_SPINCTRL, boost::bind(&NonStandardPage::maximum_j2k_bandwidth_changed, this));
+ _maximum_j2k_video_bit_rate->SetRange(1, 1000);
+ _maximum_j2k_video_bit_rate->Bind(wxEVT_SPINCTRL, boost::bind(&NonStandardPage::maximum_j2k_video_bit_rate_changed, this));
+ _maximum_mpeg2_video_bit_rate->SetRange(1, 100);
+ _maximum_mpeg2_video_bit_rate->Bind(wxEVT_SPINCTRL, boost::bind(&NonStandardPage::maximum_mpeg2_video_bit_rate_changed, this));
_allow_any_dcp_frame_rate->bind(&NonStandardPage::allow_any_dcp_frame_rate_changed, this);
_allow_any_container->bind(&NonStandardPage::allow_any_container_changed, this);
_allow_96khz_audio->bind(&NonStandardPage::allow_96khz_audio_changed, this);
@@ -1572,7 +1434,8 @@ private:
{
auto config = Config::instance();
- checked_set(_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
+ checked_set(_maximum_j2k_video_bit_rate, config->maximum_video_bit_rate(VideoEncoding::JPEG2000) / 1000000);
+ checked_set(_maximum_mpeg2_video_bit_rate, config->maximum_video_bit_rate(VideoEncoding::MPEG2) / 1000000);
checked_set(_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate());
checked_set(_allow_any_container, config->allow_any_container());
checked_set(_allow_96khz_audio, config->allow_96khz_audio());
@@ -1581,9 +1444,14 @@ private:
checked_set(_isdcf_name_part_length, config->isdcf_name_part_length());
}
- void maximum_j2k_bandwidth_changed()
+ void maximum_j2k_video_bit_rate_changed()
{
- Config::instance()->set_maximum_j2k_bandwidth(_maximum_j2k_bandwidth->GetValue() * 1000000);
+ Config::instance()->set_maximum_video_bit_rate(VideoEncoding::JPEG2000, _maximum_j2k_video_bit_rate->GetValue() * 1000000);
+ }
+
+ void maximum_mpeg2_video_bit_rate_changed()
+ {
+ Config::instance()->set_maximum_video_bit_rate(VideoEncoding::MPEG2, _maximum_mpeg2_video_bit_rate->GetValue() * 1000000);
}
void allow_any_dcp_frame_rate_changed()
@@ -1616,7 +1484,8 @@ private:
Config::instance()->set_isdcf_name_part_length(_isdcf_name_part_length->GetValue());
}
- wxSpinCtrl* _maximum_j2k_bandwidth = nullptr;
+ wxSpinCtrl* _maximum_j2k_video_bit_rate = nullptr;
+ wxSpinCtrl* _maximum_mpeg2_video_bit_rate = nullptr;
CheckBox* _allow_any_dcp_frame_rate = nullptr;
CheckBox* _allow_any_container = nullptr;
CheckBox* _allow_96khz_audio = nullptr;
@@ -1671,7 +1540,7 @@ private:
_video_display_mode = new wxChoice (_panel, wxID_ANY);
table->Add (_video_display_mode);
- auto restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to change display mode)"), false);
+ auto restart = add_label_to_sizer(table, _panel, variant::wx::insert_dcpomatic(_("(restart %s to change display mode)")), false);
auto font = restart->GetFont();
font.SetStyle (wxFONTSTYLE_ITALIC);
font.SetPointSize (font.GetPointSize() - 1);
@@ -1946,6 +1815,9 @@ create_full_config_dialog ()
e->AddPage (new SoundPage (ps, border));
e->AddPage (new DefaultsPage (ps, border));
e->AddPage (new EncodingServersPage(ps, border));
+#ifdef DCPOMATIC_GROK
+ e->AddPage (new GPUPage (ps, border));
+#endif
e->AddPage (new KeysPage (ps, border));
e->AddPage (new TMSPage (ps, border));
e->AddPage (new EmailPage (ps, border));
diff --git a/src/wx/gl_video_view.cc b/src/wx/gl_video_view.cc
index 06c9f268b..c96fd02a0 100644
--- a/src/wx/gl_video_view.cc
+++ b/src/wx/gl_video_view.cc
@@ -194,12 +194,15 @@ static constexpr char fragment_source[] =
"\n"
"in vec2 TexCoord;\n"
"\n"
-"uniform sampler2D texture_sampler;\n"
+"uniform sampler2D texture_sampler_0;\n"
+"uniform sampler2D texture_sampler_1;\n"
+"uniform sampler2D texture_sampler_2;\n"
/* type = 0: draw outline content rectangle
* type = 1: draw crop guess rectangle
* type = 2: draw XYZ image
* type = 3: draw RGB image (with sRGB/Rec709 primaries)
* type = 4: draw RGB image (converting from Rec2020 primaries)
+ * type = 5; draw YUV image (Y in texture_sampler_0, U in texture_sampler_1, V in texture_sampler_2)
* See FragmentType enum below.
*/
"uniform int type = 0;\n"
@@ -270,7 +273,7 @@ static constexpr char fragment_source[] =
" FragColor = crop_guess_colour;\n"
" break;\n"
" case 2:\n"
-" FragColor = texture_bicubic(texture_sampler, TexCoord);\n"
+" FragColor = texture_bicubic(texture_sampler_0, TexCoord);\n"
" FragColor.x = pow(FragColor.x, IN_GAMMA) / DCI_COEFFICIENT;\n"
" FragColor.y = pow(FragColor.y, IN_GAMMA) / DCI_COEFFICIENT;\n"
" FragColor.z = pow(FragColor.z, IN_GAMMA) / DCI_COEFFICIENT;\n"
@@ -280,12 +283,22 @@ static constexpr char fragment_source[] =
" FragColor.z = pow(FragColor.z, OUT_GAMMA);\n"
" break;\n"
" case 3:\n"
-" FragColor = texture_bicubic(texture_sampler, TexCoord);\n"
+" FragColor = texture_bicubic(texture_sampler_0, TexCoord);\n"
" break;\n"
" case 4:\n"
-" FragColor = texture_bicubic(texture_sampler, TexCoord);\n"
+" FragColor = texture_bicubic(texture_sampler_0, TexCoord);\n"
" FragColor = rec2020_rec709_colour_conversion * FragColor;\n"
" break;\n"
+" case 5:\n"
+" float y = texture_bicubic(texture_sampler_0, TexCoord).x;\n"
+" float u = texture_bicubic(texture_sampler_1, TexCoord).x - 0.5;\n"
+" float v = texture_bicubic(texture_sampler_2, TexCoord).x - 0.5;\n"
+ // From https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.709_conversion
+" FragColor.x = y + 1.5748 * v;\n"
+" FragColor.y = y - 0.1873 * u - 0.4681 * v;\n"
+" FragColor.z = y + 1.8556 * u;\n"
+" FragColor.a = 1;\n"
+" break;\n"
" }\n"
"}\n";
@@ -297,6 +310,7 @@ enum class FragmentType
XYZ_IMAGE = 2,
REC709_IMAGE = 3,
REC2020_IMAGE = 4,
+ YUV420P_IMAGE = 5,
};
@@ -455,6 +469,18 @@ GLVideoView::setup_shaders ()
glDeleteShader (fragment_shader);
glUseProgram (program);
+ auto texture_0 = glGetUniformLocation(program, "texture_sampler_0");
+ check_gl_error("glGetUniformLocation");
+ glUniform1i(texture_0, 0);
+ check_gl_error("glUniform1i");
+ auto texture_1 = glGetUniformLocation(program, "texture_sampler_1");
+ check_gl_error("glGetUniformLocation");
+ glUniform1i(texture_1, 1);
+ check_gl_error("glUniform1i");
+ auto texture_2 = glGetUniformLocation(program, "texture_sampler_2");
+ check_gl_error("glGetUniformLocation");
+ glUniform1i(texture_2, 2);
+ check_gl_error("glUniform1i");
_fragment_type = glGetUniformLocation (program, "type");
check_gl_error ("glGetUniformLocation");
@@ -560,14 +586,18 @@ GLVideoView::draw ()
glBindVertexArray(_vao);
check_gl_error ("glBindVertexArray");
- if (_optimise_for_j2k) {
+ if (_optimisation == Optimisation::MPEG2) {
+ glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::YUV420P_IMAGE));
+ } else if (_optimisation == Optimisation::JPEG2000) {
glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::XYZ_IMAGE));
} else if (_rec2020) {
glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::REC2020_IMAGE));
} else {
glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::REC709_IMAGE));
}
- _video_texture->bind();
+ for (auto& texture: _video_textures) {
+ texture->bind();
+ }
glDrawElements (GL_TRIANGLES, indices_video_texture_number, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_video_texture_offset * sizeof(int)));
if (_have_subtitle_to_render) {
glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::REC709_IMAGE));
@@ -595,22 +625,39 @@ GLVideoView::draw ()
void
GLVideoView::set_image (shared_ptr<const PlayerVideo> pv)
{
- shared_ptr<const Image> video = _optimise_for_j2k ? pv->raw_image() : pv->image(boost::bind(&PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, true);
+ shared_ptr<const Image> video;
+
+ switch (_optimisation) {
+ case Optimisation::JPEG2000:
+ case Optimisation::MPEG2:
+ video = pv->raw_image();
+ break;
+ case Optimisation::NONE:
+ video = pv->image(boost::bind(&PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, true);
+ break;
+ }
/* Only the player's black frames should be aligned at this stage, so this should
* almost always have no work to do.
*/
video = Image::ensure_alignment (video, Image::Alignment::COMPACT);
- /** If _optimise_for_j2k is true we render a XYZ image, doing the colourspace
+ /** If _optimisation is J2K we render a XYZ image, doing the colourspace
* conversion, scaling and video range conversion in the GL shader.
+ * Similarly for MPEG2 we do YUV -> RGB and scaling in the shader.
* Otherwise we render a RGB image without any shader-side processing.
*/
- _video_texture->set (video);
+ if (_optimisation == Optimisation::MPEG2) {
+ for (int i = 0; i < 3; ++i) {
+ _video_textures[i]->set(video, i);
+ }
+ } else {
+ _video_textures[0]->set(video, 0);
+ }
auto const text = pv->text();
- _have_subtitle_to_render = static_cast<bool>(text) && _optimise_for_j2k;
+ _have_subtitle_to_render = static_cast<bool>(text) && _optimisation != Optimisation::NONE;
if (_have_subtitle_to_render) {
/* opt: only do this if it's a new subtitle? */
DCPOMATIC_ASSERT (text->image->alignment() == Image::Alignment::COMPACT);
@@ -707,9 +754,9 @@ GLVideoView::set_image (shared_ptr<const PlayerVideo> pv)
auto const sizing_changed = _last_canvas_size.changed() || _last_inter_position.changed() || _last_inter_size.changed() || _last_out_size.changed();
if (sizing_changed) {
- const auto video = _optimise_for_j2k ?
- Rectangle(canvas_size, inter_position.x + x_offset, inter_position.y + y_offset, inter_size)
- : Rectangle(canvas_size, x_offset, y_offset, out_size);
+ const auto video = _optimisation == Optimisation::NONE
+ ? Rectangle(canvas_size, x_offset, y_offset, out_size)
+ : Rectangle(canvas_size, inter_position.x + x_offset, inter_position.y + y_offset, inter_size);
glBufferSubData (GL_ARRAY_BUFFER, array_buffer_video_offset, video.size(), video.vertices());
check_gl_error ("glBufferSubData (video)");
@@ -739,14 +786,16 @@ GLVideoView::set_image (shared_ptr<const PlayerVideo> pv)
_rec2020 = pv->colour_conversion() && pv->colour_conversion()->about_equal(dcp::ColourConversion::rec2020_to_xyz(), 1e-6);
/* opt: where should these go? */
-
- glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- check_gl_error ("glTexParameteri");
-
- glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- check_gl_error ("glTexParameterf");
+ for (auto i = 0; i < 3; ++i) {
+ _video_textures[i]->bind();
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ check_gl_error ("glTexParameteri");
+
+ glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ check_gl_error ("glTexParameterf");
+ }
}
@@ -856,8 +905,11 @@ try
_vsync_enabled = true;
#endif
- _video_texture.reset(new Texture(_optimise_for_j2k ? 2 : 1));
- _subtitle_texture.reset(new Texture(1));
+ for (int i = 0; i < 3; ++i) {
+ std::unique_ptr<Texture> texture(new Texture(_optimisation == Optimisation::JPEG2000 ? 2 : 1, i));
+ _video_textures.push_back(std::move(texture));
+ }
+ _subtitle_texture.reset(new Texture(1, 4));
while (true) {
boost::mutex::scoped_lock lm (_playing_mutex);
@@ -905,8 +957,9 @@ GLVideoView::request_one_shot ()
}
-Texture::Texture (GLint unpack_alignment)
+Texture::Texture(GLint unpack_alignment, int unit)
: _unpack_alignment (unpack_alignment)
+ , _unit(unit)
{
glGenTextures (1, &_name);
check_gl_error ("glGenTextures");
@@ -922,13 +975,15 @@ Texture::~Texture ()
void
Texture::bind ()
{
+ glActiveTexture(GL_TEXTURE0 + _unit);
+ check_gl_error("glActiveTexture");
glBindTexture(GL_TEXTURE_2D, _name);
check_gl_error ("glBindTexture");
}
void
-Texture::set (shared_ptr<const Image> image)
+Texture::set(shared_ptr<const Image> image, int component)
{
auto const create = !_size || image->size() != _size;
_size = image->size();
@@ -941,8 +996,15 @@ Texture::set (shared_ptr<const Image> image)
GLint internal_format;
GLenum format;
GLenum type;
+ int subsample = 1;
switch (image->pixel_format()) {
+ case AV_PIX_FMT_YUV420P:
+ internal_format = GL_R8;
+ format = GL_RED;
+ type = GL_UNSIGNED_BYTE;
+ subsample = component > 0 ? 2 : 1;
+ break;
case AV_PIX_FMT_BGRA:
internal_format = GL_RGBA8;
format = GL_BGRA;
@@ -970,10 +1032,10 @@ Texture::set (shared_ptr<const Image> image)
bind ();
if (create) {
- glTexImage2D (GL_TEXTURE_2D, 0, internal_format, _size->width, _size->height, 0, format, type, image->data()[0]);
+ glTexImage2D (GL_TEXTURE_2D, 0, internal_format, _size->width / subsample, _size->height / subsample, 0, format, type, image->data()[component]);
check_gl_error ("glTexImage2D");
} else {
- glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, _size->width, _size->height, format, type, image->data()[0]);
+ glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, _size->width / subsample, _size->height / subsample, format, type, image->data()[component]);
check_gl_error ("glTexSubImage2D");
}
}
diff --git a/src/wx/gl_video_view.h b/src/wx/gl_video_view.h
index 69de7b76f..25750b64b 100644
--- a/src/wx/gl_video_view.h
+++ b/src/wx/gl_video_view.h
@@ -51,18 +51,19 @@ LIBDCP_ENABLE_WARNINGS
class Texture
{
public:
- Texture (GLint unpack_alignment);
+ Texture(GLint unpack_alignment, int unit = 0);
~Texture ();
Texture (Texture const&) = delete;
Texture& operator= (Texture const&) = delete;
void bind ();
- void set (std::shared_ptr<const Image> image);
+ void set(std::shared_ptr<const Image> image, int component = 0);
private:
GLuint _name;
GLint _unpack_alignment;
+ int _unit;
boost::optional<dcp::Size> _size;
};
@@ -137,7 +138,7 @@ private:
boost::atomic<wxSize> _canvas_size;
boost::atomic<bool> _rec2020;
- std::unique_ptr<Texture> _video_texture;
+ std::vector<std::unique_ptr<Texture>> _video_textures;
std::unique_ptr<Texture> _subtitle_texture;
bool _have_subtitle_to_render = false;
bool _vsync_enabled;
diff --git a/src/wx/grok/gpu_config_panel.h b/src/wx/grok/gpu_config_panel.h
new file mode 100644
index 000000000..cbf037592
--- /dev/null
+++ b/src/wx/grok/gpu_config_panel.h
@@ -0,0 +1,227 @@
+/*
+ Copyright (C) 2023 Grok Image Compression Inc.
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#pragma once
+
+static std::vector<std::string> get_gpu_names(boost::filesystem::path binary, boost::filesystem::path filename)
+{
+ // Execute the GPU listing program and redirect its output to a file
+ if (std::system((binary.string() + " > " + filename.string()).c_str()) < 0) {
+ return {};
+ }
+
+ std::vector<std::string> gpu_names;
+ std::ifstream file(filename.c_str());
+ if (file.is_open())
+ {
+ std::string line;
+ while (std::getline(file, line))
+ gpu_names.push_back(line);
+ file.close();
+ }
+
+ return gpu_names;
+}
+
+
+class GpuList : public wxPanel
+{
+public:
+ GpuList(wxPanel* parent)
+ : wxPanel(parent, wxID_ANY)
+ {
+ _combo_box = new wxComboBox(this, wxID_ANY, "", wxDefaultPosition, wxSize(400, -1));
+ _combo_box->Bind(wxEVT_COMBOBOX, &GpuList::OnComboBox, this);
+ update();
+
+ auto sizer = new wxBoxSizer(wxHORIZONTAL);
+ sizer->Add(_combo_box, 0, wxALIGN_CENTER_VERTICAL);
+ SetSizerAndFit(sizer);
+ }
+
+ void update()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ auto lister_binary = grok.binary_location / "gpu_lister";
+ auto lister_file = grok.binary_location / "gpus.txt";
+ if (boost::filesystem::exists(lister_binary)) {
+ auto gpu_names = get_gpu_names(lister_binary, lister_file);
+
+ _combo_box->Clear();
+ for (auto const& name: gpu_names) {
+ _combo_box->Append(name);
+ }
+ }
+ }
+
+ void set_selection(int sel)
+ {
+ if (sel < static_cast<int>(_combo_box->GetCount())) {
+ _combo_box->SetSelection(sel);
+ }
+ }
+
+private:
+ void OnComboBox(wxCommandEvent&)
+ {
+ auto selection = _combo_box->GetSelection();
+ if (selection != wxNOT_FOUND) {
+ auto grok = Config::instance()->grok().get_value_or({});
+ grok.selected = selection;
+ Config::instance()->set_grok(grok);
+ }
+ }
+
+ wxComboBox* _combo_box;
+ int _selection = 0;
+};
+
+
+class GPUPage : public Page
+{
+public:
+ GPUPage(wxSize panel_size, int border)
+ : Page(panel_size, border)
+ {}
+
+ wxString GetName() const override
+ {
+ return _("GPU");
+ }
+
+#ifdef DCPOMATIC_OSX
+ /* XXX: this icon does not exist */
+ wxBitmap GetLargeIcon() const override
+ {
+ return wxBitmap(icon_path("gpu"), wxBITMAP_TYPE_PNG);
+ }
+#endif
+
+private:
+ void setup() override
+ {
+ _enable_gpu = new CheckBox(_panel, _("Enable GPU acceleration"));
+ _panel->GetSizer()->Add(_enable_gpu, 0, wxALL | wxEXPAND, _border);
+
+ wxFlexGridSizer* table = new wxFlexGridSizer(2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ table->AddGrowableCol(1, 1);
+ _panel->GetSizer()->Add(table, 1, wxALL | wxEXPAND, _border);
+
+ add_label_to_sizer(table, _panel, _("Acceleration binary folder"), true, 0, wxLEFT | wxLEFT | wxALIGN_CENTRE_VERTICAL);
+ _binary_location = new wxDirPickerCtrl(_panel, wxDD_DIR_MUST_EXIST);
+ table->Add(_binary_location, 1, wxEXPAND);
+
+ add_label_to_sizer(table, _panel, _("GPU selection"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ _gpu_list_control = new GpuList(_panel);
+ table->Add(_gpu_list_control, 1, wxEXPAND);
+
+ add_label_to_sizer(table, _panel, _("License server"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ _server = new wxTextCtrl(_panel, wxID_ANY);
+ table->Add(_server, 1, wxEXPAND | wxALL);
+
+ add_label_to_sizer(table, _panel, _("Port"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ _port = new wxSpinCtrl(_panel, wxID_ANY);
+ _port->SetRange(0, 65535);
+ table->Add(_port);
+
+ add_label_to_sizer(table, _panel, _("License"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ _licence = new PasswordEntry(_panel);
+ table->Add(_licence->get_panel(), 1, wxEXPAND | wxALL);
+
+ _enable_gpu->bind(&GPUPage::enable_gpu_changed, this);
+ _binary_location->Bind(wxEVT_DIRPICKER_CHANGED, boost::bind (&GPUPage::binary_location_changed, this));
+ _server->Bind(wxEVT_TEXT, boost::bind(&GPUPage::server_changed, this));
+ _port->Bind(wxEVT_SPINCTRL, boost::bind(&GPUPage::port_changed, this));
+ _licence->Changed.connect(boost::bind(&GPUPage::licence_changed, this));
+
+ setup_sensitivity();
+ }
+
+ void setup_sensitivity()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+
+ _binary_location->Enable(grok.enable);
+ _gpu_list_control->Enable(grok.enable);
+ _server->Enable(grok.enable);
+ _port->Enable(grok.enable);
+ _licence->get_panel()->Enable(grok.enable);
+ }
+
+ void config_changed() override
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+
+ checked_set(_enable_gpu, grok.enable);
+ _binary_location->SetPath(std_to_wx(grok.binary_location.string()));
+ _gpu_list_control->update();
+ _gpu_list_control->set_selection(grok.selected);
+ checked_set(_server, grok.licence_server);
+ checked_set(_port, grok.licence_port);
+ checked_set(_licence, grok.licence);
+ }
+
+ void enable_gpu_changed()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ grok.enable = _enable_gpu->GetValue();
+ Config::instance()->set_grok(grok);
+
+ setup_sensitivity();
+ }
+
+ void binary_location_changed()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ grok.binary_location = wx_to_std(_binary_location->GetPath());
+ Config::instance()->set_grok(grok);
+
+ _gpu_list_control->update();
+ }
+
+ void server_changed()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ grok.licence_server = wx_to_std(_server->GetValue());
+ Config::instance()->set_grok(grok);
+ }
+
+ void port_changed()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ grok.licence_port = _port->GetValue();
+ Config::instance()->set_grok(grok);
+ }
+
+ void licence_changed()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ grok.licence = wx_to_std(_licence->get());
+ Config::instance()->set_grok(grok);
+ }
+
+ CheckBox* _enable_gpu = nullptr;
+ wxDirPickerCtrl* _binary_location = nullptr;
+ GpuList* _gpu_list_control = nullptr;
+ wxTextCtrl* _server = nullptr;
+ wxSpinCtrl* _port = nullptr;
+ PasswordEntry* _licence = nullptr;
+};
diff --git a/src/wx/id.h b/src/wx/id.h
index 99aa25855..4839a0868 100644
--- a/src/wx/id.h
+++ b/src/wx/id.h
@@ -24,4 +24,5 @@
#define DCPOMATIC_CPL_MENU (wxID_HIGHEST + 1000)
#define DCPOMATIC_CONTENT_MENU (wxID_HIGHEST + 1500)
#define DCPOMATIC_AUDIO_GAIN_MENU (wxID_HIGHEST + 2000)
+#define DCPOMATIC_DCP_TIMELINE_MENU (wxID_HIGHEST + 2500)
diff --git a/src/wx/job_view.cc b/src/wx/job_view.cc
index efe17f4de..d5b6c4632 100644
--- a/src/wx/job_view.cc
+++ b/src/wx/job_view.cc
@@ -25,6 +25,7 @@
#include "message_dialog.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/analyse_audio_job.h"
#include "lib/compose.hpp"
#include "lib/config.h"
@@ -165,7 +166,7 @@ JobView::finished ()
if (_job->enable_notify() && _notify->GetValue()) {
if (Config::instance()->notification(Config::MESSAGE_BOX)) {
- wxMessageBox (std_to_wx(_job->name() + ": " + _job->status()), _("DCP-o-matic"), wxICON_INFORMATION);
+ wxMessageBox(std_to_wx(_job->name() + ": " + _job->status()), variant::wx::dcpomatic(), wxICON_INFORMATION);
}
if (Config::instance()->notification(Config::EMAIL)) {
string body = Config::instance()->notification_email();
diff --git a/src/wx/kdm_cpl_panel.cc b/src/wx/kdm_cpl_panel.cc
index 4699582aa..523d0c369 100644
--- a/src/wx/kdm_cpl_panel.cc
+++ b/src/wx/kdm_cpl_panel.cc
@@ -23,6 +23,7 @@
#include "kdm_cpl_panel.h"
#include "static_text.h"
#include "wx_util.h"
+#include <dcp/filesystem.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <libxml++/libxml++.h>
@@ -41,11 +42,11 @@ KDMCPLPanel::KDMCPLPanel (wxWindow* parent, vector<CPLSummary> cpls)
/* CPL choice */
auto s = new wxBoxSizer (wxHORIZONTAL);
- add_label_to_sizer (s, this, _("CPL"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL);
+ add_label_to_sizer (s, this, _("CPL"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_cpl = new wxChoice (this, wxID_ANY);
- s->Add (_cpl, 1, wxEXPAND);
+ s->Add (_cpl, 1, wxTOP | wxEXPAND, DCPOMATIC_CHOICE_TOP_PAD);
_cpl_browse = new Button (this, _("Browse..."));
- s->Add (_cpl_browse, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
+ s->Add (_cpl_browse, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
vertical->Add (s, 0, wxEXPAND | wxTOP, DCPOMATIC_SIZER_GAP + 2);
/* CPL details */
diff --git a/src/wx/kdm_dialog.cc b/src/wx/kdm_dialog.cc
index 5ab13b4ce..1a9c564d4 100644
--- a/src/wx/kdm_dialog.cc
+++ b/src/wx/kdm_dialog.cc
@@ -29,6 +29,7 @@
#include "screens_panel.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/cinema.h"
#include "lib/config.h"
#include "lib/film.h"
@@ -125,7 +126,7 @@ KDMDialog::KDMDialog (wxWindow* parent, shared_ptr<const Film> film)
/* Bind */
- _screens->ScreensChanged.connect (boost::bind (&KDMDialog::setup_sensitivity, this));
+ _screens->ScreensChanged.connect(boost::bind(&KDMDialog::screens_changed, this));
_timing->TimingChanged.connect (boost::bind (&KDMDialog::setup_sensitivity, this));
_make->Bind (wxEVT_BUTTON, boost::bind (&KDMDialog::make_clicked, this));
_cpl->Changed.connect(boost::bind(&KDMDialog::cpl_changed, this));
@@ -140,6 +141,14 @@ KDMDialog::KDMDialog (wxWindow* parent, shared_ptr<const Film> film)
void
+KDMDialog::screens_changed()
+{
+ _timing->suggest_utc_offset(_screens->best_utc_offset());
+ setup_sensitivity();
+}
+
+
+void
KDMDialog::cpl_changed()
{
try {
@@ -196,8 +205,21 @@ KDMDialog::make_clicked ()
return film->make_kdm(_cpl->cpl(), begin, end);
};
- for (auto i: _screens->screens()) {
- auto p = kdm_for_screen(make_kdm, i, _timing->from(), _timing->until(), _output->formulation(), !_output->forensic_mark_video(), for_audio, period_checks);
+ CinemaList cinemas;
+
+ for (auto screen: _screens->screens()) {
+ auto p = kdm_for_screen(
+ make_kdm,
+ screen.first,
+ *cinemas.cinema(screen.first),
+ *cinemas.screen(screen.second),
+ _timing->from(),
+ _timing->until(),
+ _output->formulation(),
+ !_output->forensic_mark_video(),
+ for_audio,
+ period_checks
+ );
if (p) {
kdms.push_back (p);
}
@@ -219,7 +241,13 @@ KDMDialog::make_clicked ()
if (e.starts_too_early()) {
error_dialog (this, _("The KDM start period is before (or close to) the start of the signing certificate's validity period. Use a later start time for this KDM."));
} else {
- error_dialog (this, _("The KDM end period is after (or close to) the end of the signing certificates' validity period. Either use an earlier end time for this KDM or re-create your signing certificates in the DCP-o-matic preferences window."));
+ error_dialog(
+ this,
+ variant::wx::insert_dcpomatic(
+ _("The KDM end period is after (or close to) the end of the signing certificates' validity "
+ "period. Either use an earlier end time for this KDM or re-create your signing certificates "
+ "in the %s preferences window."))
+ );
}
return;
} catch (runtime_error& e) {
diff --git a/src/wx/kdm_dialog.h b/src/wx/kdm_dialog.h
index c1e9588fe..ec92db616 100644
--- a/src/wx/kdm_dialog.h
+++ b/src/wx/kdm_dialog.h
@@ -52,6 +52,7 @@ private:
void make_clicked ();
bool confirm_overwrite (boost::filesystem::path path);
void cpl_changed();
+ void screens_changed();
std::weak_ptr<const Film> _film;
ScreensPanel* _screens;
diff --git a/src/wx/kdm_timing_panel.cc b/src/wx/kdm_timing_panel.cc
index 0fd00de93..c892c9d48 100644
--- a/src/wx/kdm_timing_panel.cc
+++ b/src/wx/kdm_timing_panel.cc
@@ -19,11 +19,13 @@
*/
+#include "dcpomatic_choice.h"
#include "kdm_timing_panel.h"
#include "static_text.h"
#include "time_picker.h"
#include "wx_util.h"
#include "lib/config.h"
+#include <dcp/utc_offset.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/datectrl.h>
@@ -105,6 +107,10 @@ KDMTimingPanel::KDMTimingPanel (wxWindow* parent)
table->Add (_until_time, 0, wxALIGN_CENTER_VERTICAL);
+ add_label_to_sizer(table, this, _("UTC offset (time zone)"), true, 1, wxALIGN_CENTRE_VERTICAL);
+ _utc_offset = new Choice(this);
+ table->Add(_utc_offset, 0, wxALIGN_CENTRE_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
+
overall_sizer->Add (table, 0, wxTOP, DCPOMATIC_SIZER_GAP);
_warning = new StaticText (this, wxT(""));
@@ -115,6 +121,13 @@ KDMTimingPanel::KDMTimingPanel (wxWindow* parent)
_warning->SetForegroundColour (wxColour (255, 0, 0));
_warning->SetFont(font);
+ /* Default to UTC */
+ auto const sel = get_offsets(_offsets);
+ for (auto const& offset: _offsets) {
+ _utc_offset->add_entry(offset.name);
+ }
+ _utc_offset->set(sel);
+
/* I said I've been to the year 3000. Not much has changed but they live underwater. And your In-in-in-interop DCP
is pretty fine.
*/
@@ -125,33 +138,38 @@ KDMTimingPanel::KDMTimingPanel (wxWindow* parent)
_until_date->Bind (wxEVT_DATE_CHANGED, bind (&KDMTimingPanel::changed, this));
_from_time->Changed.connect (bind (&KDMTimingPanel::changed, this));
_until_time->Changed.connect (bind (&KDMTimingPanel::changed, this));
+ _utc_offset->bind(&KDMTimingPanel::utc_offset_changed, this);
SetSizer (overall_sizer);
}
-boost::posix_time::ptime
+dcp::LocalTime
KDMTimingPanel::from () const
{
- return posix_time (_from_date, _from_time);
+ return local_time(_from_date, _from_time, utc_offset());
}
-boost::posix_time::ptime
-KDMTimingPanel::posix_time (wxDatePickerCtrl* date_picker, TimePicker* time_picker)
+dcp::LocalTime
+KDMTimingPanel::local_time(wxDatePickerCtrl* date_picker, TimePicker* time_picker, dcp::UTCOffset offset)
{
auto const date = date_picker->GetValue ();
- return boost::posix_time::ptime (
- boost::gregorian::date (date.GetYear(), date.GetMonth() + 1, date.GetDay()),
- boost::posix_time::time_duration (time_picker->hours(), time_picker->minutes(), 0)
+ return dcp::LocalTime(
+ date.GetYear(),
+ date.GetMonth() + 1,
+ date.GetDay(),
+ time_picker->hours(),
+ time_picker->minutes(),
+ offset
);
}
-boost::posix_time::ptime
+dcp::LocalTime
KDMTimingPanel::until () const
{
- return posix_time (_until_date, _until_time);
+ return local_time(_until_date, _until_time, utc_offset());
}
@@ -173,3 +191,38 @@ KDMTimingPanel::changed () const
TimingChanged ();
}
+
+
+dcp::UTCOffset
+KDMTimingPanel::utc_offset() const
+{
+ auto const sel = _utc_offset->get();
+ if (!sel || *sel >= int(_offsets.size())) {
+ return {};
+ }
+
+ return _offsets[*sel].offset;
+}
+
+
+void
+KDMTimingPanel::utc_offset_changed()
+{
+ _utc_offset_changed_once = true;
+ changed();
+}
+
+
+void
+KDMTimingPanel::suggest_utc_offset(dcp::UTCOffset offset)
+{
+ if (!_utc_offset_changed_once) {
+ for (size_t i = 0; i < _offsets.size(); ++i) {
+ if (_offsets[i].offset == offset) {
+ _utc_offset->set(i);
+ break;
+ }
+ }
+ }
+}
+
diff --git a/src/wx/kdm_timing_panel.h b/src/wx/kdm_timing_panel.h
index 7221ba722..a6199534a 100644
--- a/src/wx/kdm_timing_panel.h
+++ b/src/wx/kdm_timing_panel.h
@@ -18,6 +18,9 @@
*/
+
+#include "wx_util.h"
+#include <dcp/utc_offset.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
@@ -25,30 +28,42 @@ LIBDCP_ENABLE_WARNINGS
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/signals2.hpp>
-class wxDatePickerCtrl;
+
+class Choice;
class TimePicker;
+class wxDatePickerCtrl;
+
class KDMTimingPanel : public wxPanel
{
public:
explicit KDMTimingPanel (wxWindow* parent);
- /** @return KDM from time in local time */
- boost::posix_time::ptime from () const;
- /** @return KDM until time in local time */
- boost::posix_time::ptime until () const;
+ dcp::LocalTime from() const;
+ dcp::LocalTime until() const;
bool valid () const;
+ /** Give a UTC offset from a cinema that the user just selected. If the user
+ * never changed the UTC offset in the panel, the suggested UTC will be set.
+ */
+ void suggest_utc_offset(dcp::UTCOffset offset);
+
boost::signals2::signal<void ()> TimingChanged;
private:
void changed () const;
- static boost::posix_time::ptime posix_time (wxDatePickerCtrl *, TimePicker *);
+ void utc_offset_changed();
+ dcp::UTCOffset utc_offset() const;
+
+ static dcp::LocalTime local_time(wxDatePickerCtrl *, TimePicker *, dcp::UTCOffset offset);
wxDatePickerCtrl* _from_date;
wxDatePickerCtrl* _until_date;
TimePicker* _from_time;
TimePicker* _until_time;
+ Choice* _utc_offset;
+ bool _utc_offset_changed_once = false;
wxStaticText* _warning;
+ std::vector<Offset> _offsets;
};
diff --git a/src/wx/load_config_from_zip_dialog.cc b/src/wx/load_config_from_zip_dialog.cc
new file mode 100644
index 000000000..a7d573ded
--- /dev/null
+++ b/src/wx/load_config_from_zip_dialog.cc
@@ -0,0 +1,59 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "load_config_from_zip_dialog.h"
+#include "wx_util.h"
+#include "lib/config.h"
+#include "lib/unzipper.h"
+
+
+LoadConfigFromZIPDialog::LoadConfigFromZIPDialog(wxWindow* parent, boost::filesystem::path zip_file)
+ : TableDialog(parent, _("Load configuration from ZIP file"), 1, 0, true)
+{
+ _use_current = add(new wxRadioButton(this, wxID_ANY, _("Copy the cinemas in the ZIP file over the current list at")));
+ auto current_path = add(new wxStaticText(this, wxID_ANY, std_to_wx(Config::instance()->cinemas_file().string())));
+ auto current_path_font = current_path->GetFont();
+ current_path_font.SetFamily(wxFONTFAMILY_TELETYPE);
+ current_path->SetFont(current_path_font);
+
+ _use_zip = add(new wxRadioButton(this, wxID_ANY, _("Copy the cinemas in the ZIP file to the original location at")));
+ auto zip_path = add(new wxStaticText(this, wxID_ANY, std_to_wx(Config::cinemas_file_from_zip(zip_file).string())));
+ auto zip_path_font = zip_path->GetFont();
+ zip_path_font.SetFamily(wxFONTFAMILY_TELETYPE);
+ zip_path->SetFont(zip_path_font);
+
+ _ignore = add(new wxRadioButton(this, wxID_ANY, _("Do not use the cinemas in the ZIP file")));
+
+ layout();
+}
+
+
+Config::CinemasAction
+LoadConfigFromZIPDialog::action() const
+{
+ if (_use_current->GetValue()) {
+ return Config::CinemasAction::WRITE_TO_CURRENT_PATH;
+ } else if (_use_zip->GetValue()) {
+ return Config::CinemasAction::WRITE_TO_PATH_IN_ZIPPED_CONFIG;
+ }
+
+ return Config::CinemasAction::IGNORE;
+}
diff --git a/src/wx/load_config_from_zip_dialog.h b/src/wx/load_config_from_zip_dialog.h
new file mode 100644
index 000000000..f5f4ec6ea
--- /dev/null
+++ b/src/wx/load_config_from_zip_dialog.h
@@ -0,0 +1,41 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "table_dialog.h"
+#include "lib/config.h"
+#include <boost/filesystem.hpp>
+
+
+class LoadConfigFromZIPDialog : public TableDialog
+{
+public:
+ LoadConfigFromZIPDialog(wxWindow* parent, boost::filesystem::path zip_file);
+
+ Config::CinemasAction action() const;
+
+private:
+ wxRadioButton* _use_current;
+ wxRadioButton* _use_zip;
+ wxRadioButton* _ignore;
+};
+
+
+
diff --git a/src/wx/metadata_dialog.cc b/src/wx/metadata_dialog.cc
index 347f2fffd..1d0758175 100644
--- a/src/wx/metadata_dialog.cc
+++ b/src/wx/metadata_dialog.cc
@@ -204,9 +204,9 @@ MetadataDialog::setup_standard (wxPanel* panel, wxSizer* sizer)
{
add_label_to_sizer(sizer, panel, _("Territory type"), true, 0, wxALIGN_CENTER_VERTICAL);
_territory_type = new Choice(panel);
- _territory_type->add(_("Specific"), wx_to_std(territory_type_to_string(TerritoryType::SPECIFIC)));
- _territory_type->add(_("International texted"), wx_to_std(territory_type_to_string(TerritoryType::INTERNATIONAL_TEXTED)));
- _territory_type->add(_("International textless"), wx_to_std(territory_type_to_string(TerritoryType::INTERNATIONAL_TEXTLESS)));
+ _territory_type->add_entry(_("Specific"), wx_to_std(territory_type_to_string(TerritoryType::SPECIFIC)));
+ _territory_type->add_entry(_("International texted"), wx_to_std(territory_type_to_string(TerritoryType::INTERNATIONAL_TEXTED)));
+ _territory_type->add_entry(_("International textless"), wx_to_std(territory_type_to_string(TerritoryType::INTERNATIONAL_TEXTLESS)));
sizer->Add(_territory_type);
_enable_release_territory = new CheckBox(panel, _("Release territory"));
@@ -330,8 +330,8 @@ MetadataDialog::setup_advanced (wxPanel* panel, wxSizer* sizer)
sizer->Add (s, 1, wxEXPAND);
}
- _luminance_unit->add(_("candela per m²"));
- _luminance_unit->add(_("foot lambert"));
+ _luminance_unit->add_entry(_("candela per m²"));
+ _luminance_unit->add_entry(_("foot lambert"));
}
diff --git a/src/wx/optimisation.h b/src/wx/optimisation.h
new file mode 100644
index 000000000..5d5a0197f
--- /dev/null
+++ b/src/wx/optimisation.h
@@ -0,0 +1,37 @@
+/*
+ Copyright (C) 2019-2021 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_OPTIMISATION_H
+#define DCPOMATIC_OPTIMISATION_H
+
+
+/** An optimisation that can be requested of the FilmViewer if it is known that
+ * all content will be the same type.
+ */
+enum class Optimisation
+{
+ JPEG2000, ///< all content is JPEG2000
+ MPEG2, ///< all content is Interop MPEG2
+ NONE, ///< viewer must be prepared for anything
+};
+
+
+#endif
diff --git a/src/wx/player_config_dialog.cc b/src/wx/player_config_dialog.cc
index 58874e50b..b2a02be24 100644
--- a/src/wx/player_config_dialog.cc
+++ b/src/wx/player_config_dialog.cc
@@ -37,6 +37,7 @@
#include "server_dialog.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/config.h"
#include "lib/cross.h"
#include "lib/dcp_content_type.h"
@@ -85,13 +86,19 @@ public:
private:
void setup () override
{
- wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
_panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
int r = 0;
add_language_controls (table, r);
add_update_controls (table, r);
+ _enable_http_server = new CheckBox(_panel, _("Enable HTTP control interface on port"));
+ table->Add(_enable_http_server, wxGBPosition(r, 0));
+ _http_server_port = new wxSpinCtrl(_panel);
+ table->Add(_http_server_port, wxGBPosition(r, 1));
+ ++r;
+
add_label_to_sizer (table, _panel, _("Start player as"), true, wxGBPosition(r, 0));
_player_mode = new wxChoice (_panel, wxID_ANY);
_player_mode->Append (_("window"));
@@ -114,7 +121,7 @@ private:
table->Add (_video_display_mode, wxGBPosition(r, 1));
++r;
- wxStaticText* restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to change display mode)"), false, wxGBPosition(r, 0));
+ auto restart = add_label_to_sizer(table, _panel, variant::wx::insert_dcpomatic_player(_("(restart %s to change display mode)")), false, wxGBPosition(r, 0));
wxFont font = restart->GetFont();
font.SetStyle (wxFONTSTYLE_ITALIC);
font.SetPointSize (font.GetPointSize() - 1);
@@ -135,6 +142,11 @@ private:
_video_display_mode->Bind (wxEVT_CHOICE, bind(&PlayerGeneralPage::video_display_mode_changed, this));
_respect_kdm->bind(&PlayerGeneralPage::respect_kdm_changed, this);
_debug_log_file->Bind (wxEVT_FILEPICKER_CHANGED, bind(&PlayerGeneralPage::debug_log_file_changed, this));
+ _enable_http_server->bind(&PlayerGeneralPage::enable_http_server_changed, this);
+ _http_server_port->SetRange(1, 32767);
+ _http_server_port->Bind(wxEVT_SPINCTRL, boost::bind(&PlayerGeneralPage::http_server_port_changed, this));
+
+ setup_sensitivity();
}
void config_changed () override
@@ -169,6 +181,11 @@ private:
if (config->player_debug_log_file()) {
checked_set (_debug_log_file, *config->player_debug_log_file());
}
+
+ checked_set(_enable_http_server, config->enable_player_http_server());
+ checked_set(_http_server_port, config->player_http_server_port());
+
+ setup_sensitivity();
}
private:
@@ -213,11 +230,28 @@ private:
}
}
+ void enable_http_server_changed()
+ {
+ Config::instance()->set_enable_player_http_server(_enable_http_server->get());
+ }
+
+ void http_server_port_changed()
+ {
+ Config::instance()->set_player_http_server_port(_http_server_port->GetValue());
+ }
+
+ void setup_sensitivity()
+ {
+ _http_server_port->Enable(_enable_http_server->get());
+ }
+
wxChoice* _player_mode;
wxChoice* _image_display;
wxChoice* _video_display_mode;
CheckBox* _respect_kdm;
FilePickerCtrl* _debug_log_file;
+ CheckBox* _enable_http_server;
+ wxSpinCtrl* _http_server_port;
};
@@ -353,7 +387,7 @@ private:
wxPreferencesEditor*
create_player_config_dialog ()
{
- auto e = new wxPreferencesEditor (_("DCP-o-matic Player Preferences"));
+ auto e = new wxPreferencesEditor(variant::wx::insert_dcpomatic_player(_("%s Preferences")));
#ifdef DCPOMATIC_OSX
/* Width that we force some of the config panels to be on OSX so that
diff --git a/src/wx/player_information.cc b/src/wx/player_information.cc
index 057d26740..395dbc367 100644
--- a/src/wx/player_information.cc
+++ b/src/wx/player_information.cc
@@ -120,13 +120,7 @@ PlayerInformation::periodic_update ()
void
PlayerInformation::triggered_update ()
{
- shared_ptr<DCPContent> dcp;
- if (_viewer.film()) {
- auto content = _viewer.film()->content();
- if (content.size() == 1) {
- dcp = dynamic_pointer_cast<DCPContent>(content.front());
- }
- }
+ auto dcp = _viewer.dcp();
if (!dcp) {
checked_set (_dcp[0], _("No DCP loaded."));
diff --git a/src/wx/playlist_controls.cc b/src/wx/playlist_controls.cc
index 3a1bba362..f51bcc4e2 100644
--- a/src/wx/playlist_controls.cc
+++ b/src/wx/playlist_controls.cc
@@ -25,6 +25,7 @@
#include "playlist_controls.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/compose.hpp"
#include "lib/constants.h"
#include "lib/cross.h"
@@ -331,7 +332,7 @@ PlaylistControls::spl_selection_changed ()
void
PlaylistControls::select_playlist (int selected, int position)
{
- wxProgressDialog dialog (_("DCP-o-matic"), "Loading playlist and KDMs");
+ wxProgressDialog dialog(variant::wx::dcpomatic(), "Loading playlist and KDMs");
for (auto const& i: _playlists[selected].get()) {
dialog.Pulse ();
@@ -403,7 +404,7 @@ PlaylistControls::update_current_content ()
{
DCPOMATIC_ASSERT (_selected_playlist);
- wxProgressDialog dialog (_("DCP-o-matic"), "Loading content");
+ wxProgressDialog dialog(variant::wx::dcpomatic(), "Loading content");
setup_sensitivity ();
dialog.Pulse ();
diff --git a/src/wx/playlist_editor_config_dialog.cc b/src/wx/playlist_editor_config_dialog.cc
index d5c4106ea..67bedc05e 100644
--- a/src/wx/playlist_editor_config_dialog.cc
+++ b/src/wx/playlist_editor_config_dialog.cc
@@ -18,8 +18,11 @@
*/
-#include "playlist_editor_config_dialog.h"
+
#include "config_dialog.h"
+#include "playlist_editor_config_dialog.h"
+#include "wx_variant.h"
+
/** @file src/playlist_editor_config_dialog.cc
* @brief A dialogue to edit DCP-o-matic Playlist Editor configuration.
@@ -28,7 +31,7 @@
wxPreferencesEditor*
create_playlist_editor_config_dialog ()
{
- auto e = new wxPreferencesEditor (_("DCP-o-matic Playlist Editor Preferences"));
+ auto e = new wxPreferencesEditor(variant::wx::insert_dcpomatic_playlist_editor(_("%s Preferences")));
#ifdef DCPOMATIC_OSX
/* Width that we force some of the config panels to be on OSX so that
diff --git a/src/wx/recipient_dialog.cc b/src/wx/recipient_dialog.cc
index a69adb169..992c88362 100644
--- a/src/wx/recipient_dialog.cc
+++ b/src/wx/recipient_dialog.cc
@@ -55,7 +55,7 @@ column (string s)
RecipientDialog::RecipientDialog (
- wxWindow* parent, wxString title, string name, string notes, vector<string> emails, int utc_offset_hour, int utc_offset_minute, optional<dcp::Certificate> recipient
+ wxWindow* parent, wxString title, string name, string notes, vector<string> emails, optional<dcp::Certificate> recipient
)
: wxDialog (parent, wxID_ANY, title)
, _recipient (recipient)
@@ -76,11 +76,6 @@ RecipientDialog::RecipientDialog (
_sizer->Add (_notes, wxGBPosition (r, 1));
++r;
- add_label_to_sizer (_sizer, this, _("UTC offset (time zone)"), true, wxGBPosition (r, 0));
- _utc_offset = new wxChoice (this, wxID_ANY);
- _sizer->Add (_utc_offset, wxGBPosition (r, 1));
- ++r;
-
add_label_to_sizer (_sizer, this, _("Email addresses for KDM delivery"), false, wxGBPosition (r, 0), wxGBSpan (1, 2));
++r;
@@ -125,17 +120,6 @@ RecipientDialog::RecipientDialog (
overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
}
- /* Default to UTC */
- size_t sel = get_offsets (_offsets);
- for (size_t i = 0; i < _offsets.size(); ++i) {
- _utc_offset->Append (_offsets[i].name);
- if (_offsets[i].hour == utc_offset_hour && _offsets[i].minute == utc_offset_minute) {
- sel = i;
- }
- }
-
- _utc_offset->SetSelection (sel);
-
overall_sizer->Layout ();
overall_sizer->SetSizeHints (this);
@@ -234,28 +218,3 @@ RecipientDialog::emails () const
{
return _emails;
}
-
-
-int
-RecipientDialog::utc_offset_hour () const
-{
- int const sel = _utc_offset->GetSelection();
- if (sel < 0 || sel > int (_offsets.size())) {
- return 0;
- }
-
- return _offsets[sel].hour;
-}
-
-int
-RecipientDialog::utc_offset_minute () const
-{
- int const sel = _utc_offset->GetSelection();
- if (sel < 0 || sel > int (_offsets.size())) {
- return 0;
- }
-
- return _offsets[sel].minute;
-}
-
-
diff --git a/src/wx/recipient_dialog.h b/src/wx/recipient_dialog.h
index a46e67af5..61f6b3031 100644
--- a/src/wx/recipient_dialog.h
+++ b/src/wx/recipient_dialog.h
@@ -44,8 +44,6 @@ public:
std::string name = "",
std::string notes = "",
std::vector<std::string> emails = std::vector<std::string>(),
- int utc_offset_hour = 0,
- int utc_offset_minute = 0,
boost::optional<dcp::Certificate> c = boost::optional<dcp::Certificate>()
);
@@ -53,8 +51,6 @@ public:
std::string notes () const;
boost::optional<dcp::Certificate> recipient () const;
std::vector<std::string> emails () const;
- int utc_offset_hour () const;
- int utc_offset_minute () const;
private:
void get_recipient_from_file ();
@@ -71,8 +67,6 @@ private:
wxButton* _get_recipient_from_file;
EditableList<std::string, EmailDialog>* _email_list;
std::vector<std::string> _emails;
- wxChoice* _utc_offset;
- std::vector<Offset> _offsets;
boost::optional<dcp::Certificate> _recipient;
};
diff --git a/src/wx/recipients_panel.cc b/src/wx/recipients_panel.cc
index 485a0f94e..9596c3cfd 100644
--- a/src/wx/recipients_panel.cc
+++ b/src/wx/recipients_panel.cc
@@ -23,7 +23,7 @@
#include "wx_util.h"
#include "recipient_dialog.h"
#include "dcpomatic_button.h"
-#include "lib/config.h"
+#include "lib/dkdm_recipient_list.h"
#include <list>
#include <iostream>
@@ -103,15 +103,15 @@ RecipientsPanel::setup_sensitivity ()
void
-RecipientsPanel::add_recipient (shared_ptr<DKDMRecipient> r)
+RecipientsPanel::add_recipient(DKDMRecipientID id, DKDMRecipient const& recipient)
{
string const search = wx_to_std(_search->GetValue());
- if (!search.empty() && !_collator.find(search, r->name)) {
+ if (!search.empty() && !_collator.find(search, recipient.name)) {
return;
}
- _recipients[_targets->AppendItem(_root, std_to_wx(r->name))] = r;
+ _recipients.emplace(make_pair(_targets->AppendItem(_root, std_to_wx(recipient.name)), id));
_targets->SortChildren (_root);
}
@@ -122,9 +122,10 @@ RecipientsPanel::add_recipient_clicked ()
{
RecipientDialog dialog(GetParent(), _("Add recipient"));
if (dialog.ShowModal() == wxID_OK) {
- auto r = std::make_shared<DKDMRecipient>(dialog.name(), dialog.notes(), dialog.recipient(), dialog.emails(), dialog.utc_offset_hour(), dialog.utc_offset_minute());
- Config::instance()->add_dkdm_recipient (r);
- add_recipient (r);
+ auto recipient = DKDMRecipient(dialog.name(), dialog.notes(), dialog.recipient(), dialog.emails());
+ DKDMRecipientList recipient_list;
+ auto const id = recipient_list.add_dkdm_recipient(recipient);
+ add_recipient(id, recipient);
}
}
@@ -136,20 +137,28 @@ RecipientsPanel::edit_recipient_clicked ()
return;
}
- auto c = *_selected.begin();
+ DKDMRecipientList recipients;
+ auto selection = *_selected.begin();
+ auto const recipient_id = selection.second;
+ auto recipient = recipients.dkdm_recipient(recipient_id);
+ DCPOMATIC_ASSERT(recipient);
RecipientDialog dialog(
- GetParent(), _("Edit recipient"), c.second->name, c.second->notes, c.second->emails, c.second->utc_offset_hour, c.second->utc_offset_minute, c.second->recipient
+ GetParent(),
+ _("Edit recipient"),
+ recipient->name,
+ recipient->notes,
+ recipient->emails,
+ recipient->recipient
);
if (dialog.ShowModal() == wxID_OK) {
- c.second->name = dialog.name();
- c.second->emails = dialog.emails();
- c.second->notes = dialog.notes();
- c.second->utc_offset_hour = dialog.utc_offset_hour();
- c.second->utc_offset_minute = dialog.utc_offset_minute();
- _targets->SetItemText(c.first, std_to_wx(dialog.name()));
- Config::instance()->changed (Config::DKDM_RECIPIENTS);
+ recipient->name = dialog.name();
+ recipient->emails = dialog.emails();
+ recipient->notes = dialog.notes();
+ recipient->recipient = dialog.recipient();
+ recipients.update_dkdm_recipient(recipient_id, *recipient);
+ _targets->SetItemText(selection.first, std_to_wx(dialog.name()));
}
}
@@ -158,7 +167,8 @@ void
RecipientsPanel::remove_recipient_clicked ()
{
for (auto const& i: _selected) {
- Config::instance()->remove_dkdm_recipient (i.second);
+ DKDMRecipientList recipient_list;
+ recipient_list.remove_dkdm_recipient(i.second);
_targets->Delete (i.first);
}
@@ -166,19 +176,15 @@ RecipientsPanel::remove_recipient_clicked ()
}
-list<shared_ptr<DKDMRecipient>>
+list<DKDMRecipient>
RecipientsPanel::recipients () const
{
- list<shared_ptr<DKDMRecipient>> r;
-
- for (auto const& i: _selected) {
- r.push_back (i.second);
+ list<DKDMRecipient> all;
+ DKDMRecipientList recipients;
+ for (auto const& recipient: recipients.dkdm_recipients()) {
+ all.push_back(recipient.second);
}
-
- r.sort ();
- r.unique ();
-
- return r;
+ return all;
}
@@ -204,7 +210,7 @@ RecipientsPanel::selection_changed ()
for (size_t i = 0; i < s.GetCount(); ++i) {
RecipientMap::const_iterator j = _recipients.find (s[i]);
if (j != _recipients.end ()) {
- _selected[j->first] = j->second;
+ _selected.emplace(*j);
}
}
@@ -218,8 +224,9 @@ RecipientsPanel::add_recipients ()
{
_root = _targets->AddRoot ("Foo");
- for (auto i: Config::instance()->dkdm_recipients()) {
- add_recipient (i);
+ DKDMRecipientList recipients;
+ for (auto const& recipient: recipients.dkdm_recipients()) {
+ add_recipient(recipient.first, recipient.second);
}
}
diff --git a/src/wx/recipients_panel.h b/src/wx/recipients_panel.h
index 6e1f1408f..d252b8d06 100644
--- a/src/wx/recipients_panel.h
+++ b/src/wx/recipients_panel.h
@@ -21,6 +21,7 @@
#include "lib/collator.h"
#include "lib/dkdm_recipient.h"
+#include "lib/dkdm_recipient_list.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/srchctrl.h>
@@ -43,12 +44,12 @@ public:
void setup_sensitivity ();
- std::list<std::shared_ptr<DKDMRecipient>> recipients () const;
+ std::list<DKDMRecipient> recipients() const;
boost::signals2::signal<void ()> RecipientsChanged;
private:
void add_recipients ();
- void add_recipient (std::shared_ptr<DKDMRecipient>);
+ void add_recipient(DKDMRecipientID id, DKDMRecipient const& recipient);
void add_recipient_clicked ();
void edit_recipient_clicked ();
void remove_recipient_clicked ();
@@ -63,7 +64,7 @@ private:
wxButton* _remove_recipient;
wxTreeItemId _root;
- typedef std::map<wxTreeItemId, std::shared_ptr<DKDMRecipient>> RecipientMap;
+ typedef std::map<wxTreeItemId, DKDMRecipientID> RecipientMap;
RecipientMap _recipients;
RecipientMap _selected;
diff --git a/src/wx/save_template_dialog.cc b/src/wx/save_template_dialog.cc
index 6a6644efe..0917387c9 100644
--- a/src/wx/save_template_dialog.cc
+++ b/src/wx/save_template_dialog.cc
@@ -18,54 +18,90 @@
*/
+
+#include "dcpomatic_choice.h"
#include "save_template_dialog.h"
#include "wx_util.h"
#include "lib/config.h"
+#include "lib/constants.h"
+
using std::string;
#if BOOST_VERSION >= 106100
using namespace boost::placeholders;
#endif
+
+using boost::optional;
+
+
SaveTemplateDialog::SaveTemplateDialog (wxWindow* parent)
: TableDialog (parent, _("Save template"), 2, 1, true)
{
- add (_("Template name"), true);
- _name = add (new wxTextCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (300, -1)));
- _name->SetFocus ();
+ _default = add(new wxRadioButton(this, wxID_ANY, _("Save as default")));
+ add_spacer();
+ _existing = add(new wxRadioButton(this, wxID_ANY, _("Save over existing template")));
+ _existing_name = add(new Choice(this));
+ _new = add(new wxRadioButton(this, wxID_ANY, _("Save as new with name")));
+ _new_name = add(new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(300, -1)));
+
+ _default->SetFocus ();
layout ();
- wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK, this));
+ auto ok = dynamic_cast<wxButton *>(FindWindowById(wxID_OK, this));
ok->Bind (wxEVT_BUTTON, boost::bind(&SaveTemplateDialog::check, this, _1));
- _name->Bind (wxEVT_TEXT, boost::bind(&SaveTemplateDialog::setup_sensitivity, this));
+ _default->Bind(wxEVT_RADIOBUTTON, boost::bind(&SaveTemplateDialog::setup_sensitivity, this));
+ _existing->Bind(wxEVT_RADIOBUTTON, boost::bind(&SaveTemplateDialog::setup_sensitivity, this));
+ _new->Bind(wxEVT_RADIOBUTTON, boost::bind(&SaveTemplateDialog::setup_sensitivity, this));
+ _existing_name->Bind(wxEVT_TEXT, boost::bind(&SaveTemplateDialog::setup_sensitivity, this));
setup_sensitivity ();
+
+ for (auto name: Config::instance()->templates()) {
+ _existing_name->add_entry(name);
+ }
}
void
SaveTemplateDialog::setup_sensitivity ()
{
- wxButton* ok = dynamic_cast<wxButton *>(FindWindowById(wxID_OK, this));
+ auto const have_templates = !Config::instance()->templates().empty();
+ _existing->Enable(have_templates);
+ _existing_name->Enable(have_templates && _existing->GetValue());
+ _new_name->Enable(_new->GetValue());
+
+ auto ok = dynamic_cast<wxButton *>(FindWindowById(wxID_OK, this));
if (ok) {
- ok->Enable (!_name->GetValue().IsEmpty());
+ ok->Enable(_default->GetValue() || (_existing->GetValue() && have_templates) || (_new->GetValue() && !_new_name->GetValue().IsEmpty()));
}
}
-string
+optional<string>
SaveTemplateDialog::name () const
{
- return wx_to_std (_name->GetValue ());
+ if (_default->GetValue()) {
+ return {};
+ } else if (_existing->GetValue()) {
+ DCPOMATIC_ASSERT(_existing_name->get());
+ auto index = *_existing_name->get();
+ auto templates = Config::instance()->templates();
+ DCPOMATIC_ASSERT(index < int(templates.size()));
+ return templates[index];
+ } else {
+ return wx_to_std(_new_name->GetValue());
+ }
}
+
void
SaveTemplateDialog::check (wxCommandEvent& ev)
{
bool ok = true;
- if (Config::instance()->existing_template (wx_to_std (_name->GetValue ()))) {
+ if (_new->GetValue() && Config::instance()->existing_template(wx_to_std(_new_name->GetValue()))) {
ok = confirm_dialog (this, _("There is already a template with this name. Do you want to overwrite it?"));
}
diff --git a/src/wx/save_template_dialog.h b/src/wx/save_template_dialog.h
index 7e4808ca2..101d35ddf 100644
--- a/src/wx/save_template_dialog.h
+++ b/src/wx/save_template_dialog.h
@@ -18,18 +18,26 @@
*/
+
#include "table_dialog.h"
+class Choice;
+
+
class SaveTemplateDialog : public TableDialog
{
public:
explicit SaveTemplateDialog (wxWindow* parent);
- std::string name () const;
+ boost::optional<std::string> name() const;
private:
void setup_sensitivity ();
void check (wxCommandEvent& ev);
- wxTextCtrl* _name;
+ wxRadioButton* _default;
+ wxRadioButton* _existing;
+ Choice* _existing_name;
+ wxRadioButton* _new;
+ wxTextCtrl* _new_name;
};
diff --git a/src/wx/screens_panel.cc b/src/wx/screens_panel.cc
index d3b1db77d..fdc4dacc3 100644
--- a/src/wx/screens_panel.cc
+++ b/src/wx/screens_panel.cc
@@ -25,6 +25,7 @@
#include "screen_dialog.h"
#include "screens_panel.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/cinema.h"
#include "lib/config.h"
#include "lib/screen.h"
@@ -125,8 +126,6 @@ ScreensPanel::ScreensPanel (wxWindow* parent)
_uncheck_all->Bind (wxEVT_BUTTON, boost::bind(&ScreensPanel::uncheck_all, this));
SetSizer(_overall_sizer);
-
- _config_connection = Config::instance()->Changed.connect(boost::bind(&ScreensPanel::config_changed, this, _1));
}
@@ -188,28 +187,37 @@ ScreensPanel::convert_to_lower(string& s)
bool
-ScreensPanel::matches_search(shared_ptr<const Cinema> cinema, string search)
+ScreensPanel::matches_search(Cinema const& cinema, string search)
{
if (search.empty()) {
return true;
}
- return _collator.find(search, cinema->name);
+ return _collator.find(search, cinema.name);
}
+/** Add an existing cinema to the GUI */
optional<wxTreeListItem>
-ScreensPanel::add_cinema (shared_ptr<Cinema> cinema, wxTreeListItem previous)
+ScreensPanel::add_cinema(CinemaID cinema_id, wxTreeListItem previous)
{
+ CinemaList cinemas;
+ auto cinema = cinemas.cinema(cinema_id);
+ DCPOMATIC_ASSERT(cinema);
+
auto const search = wx_to_std(_search->GetValue());
- if (!matches_search(cinema, search)) {
+ if (!matches_search(*cinema, search)) {
return {};
}
+ auto screens = cinemas.screens(cinema_id);
+
if (_show_only_checked->get()) {
- auto screens = cinema->screens();
- auto iter = std::find_if(screens.begin(), screens.end(), [this](shared_ptr<dcpomatic::Screen> screen) {
- return _checked_screens.find(screen) != _checked_screens.end();
+ auto iter = std::find_if(screens.begin(), screens.end(), [this](pair<ScreenID, dcpomatic::Screen> const& screen) {
+ auto iter2 = std::find_if(_checked_screens.begin(), _checked_screens.end(), [screen](pair<CinemaID, ScreenID> const& checked) {
+ return checked.second == screen.first;
+ });
+ return iter2 != _checked_screens.end();
});
if (iter == screens.end()) {
return {};
@@ -218,29 +226,34 @@ ScreensPanel::add_cinema (shared_ptr<Cinema> cinema, wxTreeListItem previous)
auto id = _targets->InsertItem(_targets->GetRootItem(), previous, std_to_wx(cinema->name));
- _item_to_cinema[id] = cinema;
- _cinema_to_item[cinema] = id;
+ _item_to_cinema.emplace(make_pair(id, cinema_id));
+ _cinema_to_item[cinema_id] = id;
- for (auto screen: cinema->screens()) {
- add_screen (cinema, screen);
+ for (auto screen: screens) {
+ add_screen(cinema_id, screen.first);
}
return id;
}
+/** Add an existing screen to the GUI */
optional<wxTreeListItem>
-ScreensPanel::add_screen (shared_ptr<Cinema> cinema, shared_ptr<Screen> screen)
+ScreensPanel::add_screen(CinemaID cinema_id, ScreenID screen_id)
{
- auto item = cinema_to_item(cinema);
+ auto item = cinema_to_item(cinema_id);
if (!item) {
return {};
}
+ CinemaList cinemas;
+ auto screen = cinemas.screen(screen_id);
+ DCPOMATIC_ASSERT(screen);
+
auto id = _targets->AppendItem(*item, std_to_wx(screen->name));
- _item_to_screen[id] = screen;
- _screen_to_item[screen] = id;
+ _item_to_screen.emplace(make_pair(id, make_pair(cinema_id, screen_id)));
+ _screen_to_item[screen_id] = id;
return item;
}
@@ -252,37 +265,31 @@ ScreensPanel::add_cinema_clicked ()
CinemaDialog dialog(GetParent(), _("Add Cinema"));
if (dialog.ShowModal() == wxID_OK) {
- auto cinema = make_shared<Cinema>(dialog.name(), dialog.emails(), dialog.notes(), dialog.utc_offset_hour(), dialog.utc_offset_minute());
+ auto cinema = Cinema(dialog.name(), dialog.emails(), dialog.notes(), dialog.utc_offset());
- auto cinemas = sorted_cinemas();
+ CinemaList cinemas;
+ auto existing_cinemas = cinemas.cinemas();
- try {
- _ignore_cinemas_changed = true;
- dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
- Config::instance()->add_cinema(cinema);
- } catch (FileError& e) {
- error_dialog(GetParent(), _("Could not write cinema details to the cinemas.xml file. Check that the location of cinemas.xml is valid in DCP-o-matic's preferences."), std_to_wx(e.what()));
- return;
- }
+ auto const cinema_id = cinemas.add_cinema(cinema);
wxTreeListItem previous = wxTLI_FIRST;
bool found = false;
auto const search = wx_to_std(_search->GetValue());
- for (auto existing_cinema: cinemas) {
- if (!matches_search(existing_cinema, search)) {
+ for (auto existing_cinema: existing_cinemas) {
+ if (!matches_search(existing_cinema.second, search)) {
continue;
}
- if (_collator.compare(dialog.name(), existing_cinema->name) < 0) {
+ if (_collator.compare(dialog.name(), existing_cinema.second.name) < 0) {
/* existing_cinema should be after the one we're inserting */
found = true;
break;
}
- auto item = cinema_to_item(existing_cinema);
+ auto item = cinema_to_item(existing_cinema.first);
DCPOMATIC_ASSERT(item);
previous = *item;
}
- auto item = add_cinema(cinema, found ? previous : wxTLI_LAST);
+ auto item = add_cinema(cinema_id, found ? previous : wxTLI_LAST);
if (item) {
_targets->UnselectAll ();
@@ -294,13 +301,13 @@ ScreensPanel::add_cinema_clicked ()
}
-shared_ptr<Cinema>
+optional<CinemaID>
ScreensPanel::cinema_for_operation () const
{
if (_selected_cinemas.size() == 1) {
return _selected_cinemas[0];
} else if (_selected_screens.size() == 1) {
- return _selected_screens[0]->cinema;
+ return _selected_screens[0].first;
}
return {};
@@ -310,28 +317,29 @@ ScreensPanel::cinema_for_operation () const
void
ScreensPanel::edit_cinema_clicked ()
{
- auto cinema = cinema_for_operation ();
- if (cinema) {
- edit_cinema(cinema);
+ auto cinema_id = cinema_for_operation();
+ if (cinema_id) {
+ edit_cinema(*cinema_id);
}
}
void
-ScreensPanel::edit_cinema(shared_ptr<Cinema> cinema)
+ScreensPanel::edit_cinema(CinemaID cinema_id)
{
- CinemaDialog dialog(
- GetParent(), _("Edit cinema"), cinema->name, cinema->emails, cinema->notes, cinema->utc_offset_hour(), cinema->utc_offset_minute()
- );
+ CinemaList cinemas;
+ auto cinema = cinemas.cinema(cinema_id);
+ DCPOMATIC_ASSERT(cinema);
+
+ CinemaDialog dialog(GetParent(), _("Edit cinema"), cinema->name, cinema->emails, cinema->notes, cinema->utc_offset);
if (dialog.ShowModal() == wxID_OK) {
- cinema->name = dialog.name();
cinema->emails = dialog.emails();
+ cinema->name = dialog.name();
cinema->notes = dialog.notes();
- cinema->set_utc_offset_hour(dialog.utc_offset_hour());
- cinema->set_utc_offset_minute(dialog.utc_offset_minute());
- notify_cinemas_changed();
- auto item = cinema_to_item(cinema);
+ cinema->utc_offset = dialog.utc_offset();
+ cinemas.update_cinema(cinema_id, *cinema);
+ auto item = cinema_to_item(cinema_id);
DCPOMATIC_ASSERT(item);
_targets->SetItemText (*item, std_to_wx(dialog.name()));
}
@@ -341,8 +349,11 @@ ScreensPanel::edit_cinema(shared_ptr<Cinema> cinema)
void
ScreensPanel::remove_cinema_clicked ()
{
+ CinemaList cinemas;
+
if (_selected_cinemas.size() == 1) {
- if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the cinema '%s'?"), std_to_wx(_selected_cinemas[0]->name)))) {
+ auto cinema = cinemas.cinema(_selected_cinemas[0]);
+ if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the cinema '%s'?"), std_to_wx(cinema->name)))) {
return;
}
} else {
@@ -353,14 +364,12 @@ ScreensPanel::remove_cinema_clicked ()
auto cinemas_to_remove = _selected_cinemas;
- for (auto const& cinema: cinemas_to_remove) {
- _ignore_cinemas_changed = true;
- dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
- for (auto screen: cinema->screens()) {
- _checked_screens.erase(screen);
+ for (auto const& cinema_id: cinemas_to_remove) {
+ for (auto screen: cinemas.screens(cinema_id)) {
+ _checked_screens.erase({cinema_id, screen.first});
}
- Config::instance()->remove_cinema(cinema);
- auto item = cinema_to_item(cinema);
+ cinemas.remove_cinema(cinema_id);
+ auto item = cinema_to_item(cinema_id);
DCPOMATIC_ASSERT(item);
_targets->DeleteItem(*item);
}
@@ -373,8 +382,8 @@ ScreensPanel::remove_cinema_clicked ()
void
ScreensPanel::add_screen_clicked ()
{
- auto cinema = cinema_for_operation ();
- if (!cinema) {
+ auto cinema_id = cinema_for_operation();
+ if (!cinema_id) {
return;
}
@@ -384,8 +393,10 @@ ScreensPanel::add_screen_clicked ()
return;
}
- for (auto screen: cinema->screens()) {
- if (screen->name == dialog.name()) {
+ CinemaList cinemas;
+
+ for (auto screen: cinemas.screens(*cinema_id)) {
+ if (screen.second.name == dialog.name()) {
error_dialog (
GetParent(),
wxString::Format (
@@ -397,11 +408,10 @@ ScreensPanel::add_screen_clicked ()
}
}
- auto screen = std::make_shared<Screen>(dialog.name(), dialog.notes(), dialog.recipient(), dialog.recipient_file(), dialog.trusted_devices());
- cinema->add_screen (screen);
- notify_cinemas_changed();
+ auto screen = Screen(dialog.name(), dialog.notes(), dialog.recipient(), dialog.recipient_file(), dialog.trusted_devices());
+ auto const screen_id = cinemas.add_screen(*cinema_id, screen);
- auto id = add_screen (cinema, screen);
+ auto id = add_screen(*cinema_id, screen_id);
if (id) {
_targets->Expand (id.get ());
}
@@ -412,30 +422,33 @@ void
ScreensPanel::edit_screen_clicked ()
{
if (_selected_screens.size() == 1) {
- edit_screen(_selected_screens[0]);
+ edit_screen(_selected_screens[0].first, _selected_screens[0].second);
}
}
void
-ScreensPanel::edit_screen(shared_ptr<Screen> edit_screen)
+ScreensPanel::edit_screen(CinemaID cinema_id, ScreenID screen_id)
{
+ CinemaList cinemas;
+ auto screen = cinemas.screen(screen_id);
+ DCPOMATIC_ASSERT(screen);
+
ScreenDialog dialog(
GetParent(), _("Edit screen"),
- edit_screen->name,
- edit_screen->notes,
- edit_screen->recipient,
- edit_screen->recipient_file,
- edit_screen->trusted_devices
+ screen->name,
+ screen->notes,
+ screen->recipient,
+ screen->recipient_file,
+ screen->trusted_devices
);
if (dialog.ShowModal() != wxID_OK) {
return;
}
- auto cinema = edit_screen->cinema;
- for (auto screen: cinema->screens()) {
- if (screen != edit_screen && screen->name == dialog.name()) {
+ for (auto screen: cinemas.screens(cinema_id)) {
+ if (screen.first != screen_id && screen.second.name == dialog.name()) {
error_dialog (
GetParent(),
wxString::Format (
@@ -447,14 +460,14 @@ ScreensPanel::edit_screen(shared_ptr<Screen> edit_screen)
}
}
- edit_screen->name = dialog.name();
- edit_screen->notes = dialog.notes();
- edit_screen->recipient = dialog.recipient();
- edit_screen->recipient_file = dialog.recipient_file();
- edit_screen->trusted_devices = dialog.trusted_devices();
- notify_cinemas_changed();
+ screen->name = dialog.name();
+ screen->notes = dialog.notes();
+ screen->recipient = dialog.recipient();
+ screen->recipient_file = dialog.recipient_file();
+ screen->trusted_devices = dialog.trusted_devices();
+ cinemas.update_screen(screen_id, *screen);
- auto item = screen_to_item(edit_screen);
+ auto item = screen_to_item(screen_id);
DCPOMATIC_ASSERT (item);
_targets->SetItemText(*item, std_to_wx(dialog.name()));
}
@@ -463,8 +476,12 @@ ScreensPanel::edit_screen(shared_ptr<Screen> edit_screen)
void
ScreensPanel::remove_screen_clicked ()
{
+ CinemaList cinemas;
+
if (_selected_screens.size() == 1) {
- if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the screen '%s'?"), std_to_wx(_selected_screens[0]->name)))) {
+ auto screen = cinemas.screen(_selected_screens[0].second);
+ DCPOMATIC_ASSERT(screen);
+ if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the screen '%s'?"), std_to_wx(screen->name)))) {
return;
}
} else {
@@ -473,10 +490,10 @@ ScreensPanel::remove_screen_clicked ()
}
}
- for (auto screen: _selected_screens) {
- _checked_screens.erase(screen);
- screen->cinema->remove_screen(screen);
- auto item = screen_to_item(screen);
+ for (auto screen_id: _selected_screens) {
+ _checked_screens.erase(screen_id);
+ cinemas.remove_screen(screen_id.second);
+ auto item = screen_to_item(screen_id.second);
DCPOMATIC_ASSERT(item);
_targets->DeleteItem(*item);
}
@@ -485,17 +502,14 @@ ScreensPanel::remove_screen_clicked ()
* as well.
*/
selection_changed();
- notify_cinemas_changed();
setup_show_only_checked();
}
-vector<shared_ptr<Screen>>
+std::set<pair<CinemaID, ScreenID>>
ScreensPanel::screens () const
{
- vector<shared_ptr<Screen>> output;
- std::copy (_checked_screens.begin(), _checked_screens.end(), std::back_inserter(output));
- return output;
+ return _checked_screens;
}
@@ -521,10 +535,10 @@ ScreensPanel::selection_changed ()
for (size_t i = 0; i < selection.size(); ++i) {
if (auto cinema = item_to_cinema(selection[i])) {
- _selected_cinemas.push_back(cinema);
+ _selected_cinemas.push_back(*cinema);
}
if (auto screen = item_to_screen(selection[i])) {
- _selected_screens.push_back(screen);
+ _selected_screens.push_back(*screen);
}
}
@@ -532,24 +546,12 @@ ScreensPanel::selection_changed ()
}
-list<shared_ptr<Cinema>>
-ScreensPanel::sorted_cinemas() const
-{
- auto cinemas = Config::instance()->cinemas();
-
- cinemas.sort(
- [this](shared_ptr<Cinema> a, shared_ptr<Cinema> b) { return _collator.compare(a->name, b->name) < 0; }
- );
-
- return cinemas;
-}
-
-
void
ScreensPanel::add_cinemas ()
{
- for (auto cinema: sorted_cinemas()) {
- add_cinema (cinema, wxTLI_LAST);
+ CinemaList cinemas;
+ for (auto cinema: cinemas.cinemas()) {
+ add_cinema (cinema.first, wxTLI_LAST);
}
}
@@ -583,7 +585,7 @@ ScreensPanel::display_filter_changed()
}
for (auto const& selection: _selected_screens) {
- if (auto item = screen_to_item(selection)) {
+ if (auto item = screen_to_item(selection.second)) {
_targets->Select (*item);
}
}
@@ -593,7 +595,7 @@ ScreensPanel::display_filter_changed()
_ignore_check_change = true;
for (auto const& checked: _checked_screens) {
- if (auto item = screen_to_item(checked)) {
+ if (auto item = screen_to_item(checked.second)) {
_targets->CheckItem(*item, wxCHK_CHECKED);
setup_cinema_checked_state(*item);
}
@@ -609,9 +611,9 @@ ScreensPanel::set_screen_checked (wxTreeListItem item, bool checked)
auto screen = item_to_screen(item);
DCPOMATIC_ASSERT(screen);
if (checked) {
- _checked_screens.insert(screen);
+ _checked_screens.insert({screen->first, screen->second});
} else {
- _checked_screens.erase(screen);
+ _checked_screens.erase({screen->first, screen->second});
}
setup_show_only_checked();
@@ -665,7 +667,7 @@ ScreensPanel::checkbox_changed (wxTreeListEvent& ev)
}
-shared_ptr<Cinema>
+optional<CinemaID>
ScreensPanel::item_to_cinema (wxTreeListItem item) const
{
auto iter = _item_to_cinema.find (item);
@@ -677,7 +679,7 @@ ScreensPanel::item_to_cinema (wxTreeListItem item) const
}
-shared_ptr<Screen>
+optional<pair<CinemaID, ScreenID>>
ScreensPanel::item_to_screen (wxTreeListItem item) const
{
auto iter = _item_to_screen.find (item);
@@ -690,7 +692,7 @@ ScreensPanel::item_to_screen (wxTreeListItem item) const
optional<wxTreeListItem>
-ScreensPanel::cinema_to_item (shared_ptr<Cinema> cinema) const
+ScreensPanel::cinema_to_item(CinemaID cinema) const
{
auto iter = _cinema_to_item.find (cinema);
if (iter == _cinema_to_item.end()) {
@@ -702,7 +704,7 @@ ScreensPanel::cinema_to_item (shared_ptr<Cinema> cinema) const
optional<wxTreeListItem>
-ScreensPanel::screen_to_item (shared_ptr<Screen> screen) const
+ScreensPanel::screen_to_item(ScreenID screen) const
{
auto iter = _screen_to_item.find (screen);
if (iter == _screen_to_item.end()) {
@@ -713,32 +715,6 @@ ScreensPanel::screen_to_item (shared_ptr<Screen> screen) const
}
-bool
-ScreensPanel::notify_cinemas_changed()
-{
- _ignore_cinemas_changed = true;
- dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
-
- try {
- Config::instance()->changed(Config::CINEMAS);
- } catch (FileError& e) {
- error_dialog(GetParent(), _("Could not write cinema details to the cinemas.xml file. Check that the location of cinemas.xml is valid in DCP-o-matic's preferences."), std_to_wx(e.what()));
- return false;
- }
-
- return true;
-}
-
-
-void
-ScreensPanel::config_changed(Config::Property property)
-{
- if (property == Config::Property::CINEMAS && !_ignore_cinemas_changed) {
- clear_and_re_add();
- }
-}
-
-
void
ScreensPanel::item_activated(wxTreeListEvent& ev)
{
@@ -748,7 +724,7 @@ ScreensPanel::item_activated(wxTreeListEvent& ev)
} else {
auto iter = _item_to_screen.find(ev.GetItem());
if (iter != _item_to_screen.end()) {
- edit_screen(iter->second);
+ edit_screen(iter->second.first, iter->second.second);
}
}
}
@@ -767,3 +743,16 @@ ScreensPanel::setup_show_only_checked()
setup_sensitivity();
}
+
+dcp::UTCOffset
+ScreensPanel::best_utc_offset() const
+{
+ std::set<CinemaID> unique_cinema_ids;
+ for (auto const& screen: screens()) {
+ unique_cinema_ids.insert(screen.first);
+ }
+
+ CinemaList cinema_list;
+ return cinema_list.unique_utc_offset(unique_cinema_ids).get_value_or(dcp::UTCOffset());
+}
+
diff --git a/src/wx/screens_panel.h b/src/wx/screens_panel.h
index 80a7b3843..98ec2c631 100644
--- a/src/wx/screens_panel.h
+++ b/src/wx/screens_panel.h
@@ -19,6 +19,7 @@
*/
+#include "lib/cinema_list.h"
#include "lib/collator.h"
#include "lib/config.h"
#include <dcp/warnings.h>
@@ -38,7 +39,6 @@ namespace dcpomatic {
}
-class Cinema;
class CheckBox;
@@ -48,45 +48,44 @@ public:
explicit ScreensPanel (wxWindow* parent);
~ScreensPanel ();
- std::vector<std::shared_ptr<dcpomatic::Screen>> screens () const;
+ std::set<std::pair<CinemaID, ScreenID>> screens() const;
void setup_sensitivity ();
+ dcp::UTCOffset best_utc_offset() const;
+
boost::signals2::signal<void ()> ScreensChanged;
private:
void add_cinemas ();
- boost::optional<wxTreeListItem> add_cinema (std::shared_ptr<Cinema>, wxTreeListItem previous);
- boost::optional<wxTreeListItem> add_screen (std::shared_ptr<Cinema>, std::shared_ptr<dcpomatic::Screen>);
+ boost::optional<wxTreeListItem> add_cinema(CinemaID cinema, wxTreeListItem previous);
+ boost::optional<wxTreeListItem> add_screen(CinemaID cinema, ScreenID screen);
void add_cinema_clicked ();
void edit_cinema_clicked ();
- void edit_cinema(std::shared_ptr<Cinema> cinema);
+ void edit_cinema(CinemaID cinema_id);
void remove_cinema_clicked ();
void add_screen_clicked ();
void edit_screen_clicked ();
- void edit_screen(std::shared_ptr<dcpomatic::Screen> screen);
+ void edit_screen(CinemaID cinema_id, ScreenID screen_id);
void remove_screen_clicked ();
void selection_changed_shim (wxTreeListEvent &);
void selection_changed ();
void display_filter_changed();
void checkbox_changed (wxTreeListEvent& ev);
void item_activated(wxTreeListEvent& ev);
- std::shared_ptr<Cinema> cinema_for_operation () const;
+ boost::optional<CinemaID> cinema_for_operation() const;
void set_screen_checked (wxTreeListItem item, bool checked);
void setup_cinema_checked_state (wxTreeListItem screen);
void check_all ();
void uncheck_all ();
- bool notify_cinemas_changed();
void clear_and_re_add();
- void config_changed(Config::Property);
void convert_to_lower(std::string& s);
- bool matches_search(std::shared_ptr<const Cinema> cinema, std::string search);
- std::list<std::shared_ptr<Cinema>> sorted_cinemas() const;
+ bool matches_search(Cinema const& cinema, std::string search);
void setup_show_only_checked();
- std::shared_ptr<Cinema> item_to_cinema (wxTreeListItem item) const;
- std::shared_ptr<dcpomatic::Screen> item_to_screen (wxTreeListItem item) const;
- boost::optional<wxTreeListItem> cinema_to_item (std::shared_ptr<Cinema> cinema) const;
- boost::optional<wxTreeListItem> screen_to_item (std::shared_ptr<dcpomatic::Screen> screen) const;
+ boost::optional<CinemaID> item_to_cinema(wxTreeListItem item) const;
+ boost::optional<std::pair<CinemaID, ScreenID>> item_to_screen(wxTreeListItem item) const;
+ boost::optional<wxTreeListItem> cinema_to_item(CinemaID cinema) const;
+ boost::optional<wxTreeListItem> screen_to_item(ScreenID screen) const;
wxBoxSizer* _overall_sizer;
wxSearchCtrl* _search;
@@ -104,24 +103,19 @@ private:
/* We want to be able to search (and so remove selected things from the view)
* but not deselect them, so we maintain lists of selected cinemas and screens.
*/
- std::vector<std::shared_ptr<Cinema>> _selected_cinemas;
- std::vector<std::shared_ptr<dcpomatic::Screen>> _selected_screens;
- /* Likewise with checked screens, except that we can work out which cinemas
- * are checked from which screens are checked, so we don't need to store the
- * cinemas.
- */
- std::set<std::shared_ptr<dcpomatic::Screen>> _checked_screens;
+ std::vector<CinemaID> _selected_cinemas;
+ /* List of cinema_id, screen_id */
+ std::vector<std::pair<CinemaID, ScreenID>> _selected_screens;
+ /* Likewise with checked screens */
+ std::set<std::pair<CinemaID, ScreenID>> _checked_screens;
- std::map<wxTreeListItem, std::shared_ptr<Cinema>> _item_to_cinema;
- std::map<wxTreeListItem, std::shared_ptr<dcpomatic::Screen>> _item_to_screen;
- std::map<std::shared_ptr<Cinema>, wxTreeListItem> _cinema_to_item;
- std::map<std::shared_ptr<dcpomatic::Screen>, wxTreeListItem> _screen_to_item;
+ std::map<wxTreeListItem, CinemaID> _item_to_cinema;
+ std::map<wxTreeListItem, std::pair<CinemaID, ScreenID>> _item_to_screen;
+ std::map<CinemaID, wxTreeListItem> _cinema_to_item;
+ std::map<ScreenID, wxTreeListItem> _screen_to_item;
bool _ignore_selection_change = false;
bool _ignore_check_change = false;
Collator _collator;
-
- boost::signals2::scoped_connection _config_connection;
- bool _ignore_cinemas_changed = false;
};
diff --git a/src/wx/system_information_dialog.cc b/src/wx/system_information_dialog.cc
index fbae2e010..26138900a 100644
--- a/src/wx/system_information_dialog.cc
+++ b/src/wx/system_information_dialog.cc
@@ -23,6 +23,7 @@
#include "gl_video_view.h"
#include "system_information_dialog.h"
#include "wx_util.h"
+#include "wx_variant.h"
#ifdef DCPOMATIC_OSX
@@ -48,7 +49,7 @@ SystemInformationDialog::SystemInformationDialog(wxWindow* parent, FilmViewer co
if (!gl) {
add (_("OpenGL version"), true);
- add (_("unknown (OpenGL not enabled in DCP-o-matic)"), false);
+ add(variant::wx::insert_dcpomatic(_("unknown (OpenGL not enabled in %s)")), false);
} else {
auto information = gl->information();
auto add_string = [this, &information](GLenum name, wxString label) {
@@ -79,7 +80,7 @@ SystemInformationDialog::SystemInformationDialog(wxWindow* parent, FilmViewer co
: TableDialog (parent, _("System information"), 2, 1, false)
{
add (_("OpenGL version"), true);
- add (_("OpenGL renderer not supported by this DCP-o-matic version"), false);
+ add(variant::wx::insert_dcpomatic(_("OpenGL renderer not supported by this %s version")), false);
}
#endif
diff --git a/src/wx/text_panel.cc b/src/wx/text_panel.cc
index 78c024565..4b6e900db 100644
--- a/src/wx/text_panel.cc
+++ b/src/wx/text_panel.cc
@@ -84,14 +84,6 @@ TextPanel::create ()
refer = _("Use this DCP's closed caption as OV and make VF");
}
- _reference = new CheckBox (this, refer);
- _reference_note = new StaticText (this, wxT(""));
- _reference_note->Wrap (200);
- auto font = _reference_note->GetFont();
- font.SetStyle(wxFONTSTYLE_ITALIC);
- font.SetPointSize(font.GetPointSize() - 1);
- _reference_note->SetFont(font);
-
_use = new CheckBox (this, _("Use as"));
_type = new wxChoice (this, wxID_ANY);
_type->Append (_("open subtitles"));
@@ -132,7 +124,6 @@ TextPanel::create ()
_y_scale->SetRange (0, 1000);
_line_spacing->SetRange (0, 1000);
- _reference->bind(&TextPanel::reference_clicked, this);
_use->bind(&TextPanel::use_toggled, this);
_type->Bind (wxEVT_CHOICE, boost::bind (&TextPanel::type_changed, this));
_burn->bind(&TextPanel::burn_toggled, this);
@@ -232,12 +223,6 @@ TextPanel::add_to_grid ()
{
int r = 0;
- auto reference_sizer = new wxBoxSizer (wxVERTICAL);
- reference_sizer->Add (_reference, 0);
- reference_sizer->Add (_reference_note, 0);
- _grid->Add (reference_sizer, wxGBPosition(r, 0), wxGBSpan(1, 4));
- ++r;
-
auto use = new wxBoxSizer (wxHORIZONTAL);
use->Add (_use, 0, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_GAP);
use->Add (_type, 1, wxEXPAND, 0);
@@ -496,15 +481,6 @@ TextPanel::film_content_changed (int property)
if (_language_type) {
_language_type->SetSelection (text ? (text->language_is_additional() ? 1 : 0) : 0);
}
- } else if (property == DCPContentProperty::REFERENCE_TEXT) {
- if (scs) {
- auto dcp = dynamic_pointer_cast<DCPContent> (scs);
- checked_set (_reference, dcp ? dcp->reference_text(_original_type) : false);
- } else {
- checked_set (_reference, false);
- }
-
- setup_sensitivity ();
} else if (property == DCPContentProperty::TEXTS) {
setup_sensitivity ();
} else if (property == ContentProperty::TRIM_START) {
@@ -593,17 +569,7 @@ TextPanel::setup_sensitivity ()
dcp = dynamic_pointer_cast<DCPContent>(sel.front());
}
- string why_not;
- bool const can_reference = dcp && dcp->can_reference_text (_parent->film(), _original_type, why_not);
- wxString cannot;
- if (why_not.empty()) {
- cannot = _("Cannot reference this DCP's subtitles or captions.");
- } else {
- cannot = _("Cannot reference this DCP's subtitles or captions: ") + std_to_wx(why_not);
- }
- setup_refer_button (_reference, _reference_note, dcp, can_reference, cannot);
-
- bool const reference = _reference->GetValue ();
+ auto const reference = dcp && dcp->reference_text(_original_type);
auto const type = current_type ();
@@ -746,7 +712,7 @@ TextPanel::text_view_clicked ()
if (decoder) {
_text_view.reset(this, _parent->film(), c.front(), c.front()->text_of_original_type(_original_type), decoder, _parent->film_viewer());
- _text_view->Show ();
+ _text_view->show();
}
}
@@ -763,23 +729,6 @@ TextPanel::fonts_dialog_clicked ()
void
-TextPanel::reference_clicked ()
-{
- auto c = _parent->selected ();
- if (c.size() != 1) {
- return;
- }
-
- auto d = dynamic_pointer_cast<DCPContent> (c.front ());
- if (!d) {
- return;
- }
-
- d->set_reference_text (_original_type, _reference->GetValue ());
-}
-
-
-void
TextPanel::appearance_dialog_clicked ()
{
auto c = _parent->selected_text ();
diff --git a/src/wx/text_panel.h b/src/wx/text_panel.h
index 5adad5a3e..a2afba439 100644
--- a/src/wx/text_panel.h
+++ b/src/wx/text_panel.h
@@ -56,7 +56,6 @@ private:
void stream_changed ();
void text_view_clicked ();
void fonts_dialog_clicked ();
- void reference_clicked ();
void appearance_dialog_clicked ();
void outline_subtitles_changed ();
TextType current_type () const;
@@ -74,8 +73,6 @@ private:
void update_outline_subtitles_in_viewer ();
void clear_outline_subtitles ();
- CheckBox* _reference;
- wxStaticText* _reference_note;
CheckBox* _outline_subtitles = nullptr;
CheckBox* _use;
wxChoice* _type;
diff --git a/src/wx/text_view.cc b/src/wx/text_view.cc
index 7e5267886..bde7b09e9 100644
--- a/src/wx/text_view.cc
+++ b/src/wx/text_view.cc
@@ -42,14 +42,17 @@ using namespace boost::placeholders;
#endif
+WindowMetrics TextView::_metrics;
+
+
TextView::TextView (
wxWindow* parent, shared_ptr<Film> film, shared_ptr<Content> content, shared_ptr<TextContent> text, shared_ptr<Decoder> decoder, FilmViewer& viewer
)
- : wxDialog (parent, wxID_ANY, _("Captions"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+ : wxDialog(parent, wxID_ANY, _("Captions"), _metrics.position, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, _content (content)
, _film_viewer (viewer)
{
- _list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL);
+ _list = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, _metrics.size, wxLC_REPORT | wxLC_SINGLE_SEL);
{
wxListItem ip;
@@ -104,10 +107,35 @@ TextView::TextView (
}
while (!decoder->pass ()) {}
SetSizerAndFit (sizer);
+
+ _list->Bind(wxEVT_SIZE, boost::bind(&TextView::list_sized, this, _1));
+ Bind(wxEVT_MOVE, boost::bind(&TextView::moved, this, _1));
}
void
+TextView::list_sized(wxSizeEvent& ev)
+{
+ _metrics.size = ev.GetSize();
+ ev.Skip();
+}
+
+
+void
+TextView::moved(wxMoveEvent& ev)
+{
+ _metrics.position = ClientToScreen({0, 0});
+ ev.Skip();
+}
+
+
+void
+TextView::show()
+{
+ _metrics.show(this);
+}
+
+void
TextView::data_start (ContentStringText cts)
{
for (auto const& i: cts.subs) {
@@ -147,6 +175,6 @@ TextView::subtitle_selected (wxListEvent& ev)
DCPOMATIC_ASSERT (ev.GetIndex() < int(_start_times.size()));
auto lc = _content.lock ();
DCPOMATIC_ASSERT (lc);
- /* Add on a frame here to work around any rounding errors and make sure land in the subtitle */
+ /* Add on a frame here to work around any rounding errors and make sure we land in the subtitle */
_film_viewer.seek(lc, _start_times[ev.GetIndex()] + ContentTime::from_frames(1, _frc->source), true);
}
diff --git a/src/wx/text_view.h b/src/wx/text_view.h
index 8cf3c78bb..d1271ef26 100644
--- a/src/wx/text_view.h
+++ b/src/wx/text_view.h
@@ -18,6 +18,8 @@
*/
+
+#include "window_metrics.h"
#include "lib/content_text.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
@@ -25,10 +27,12 @@ LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
LIBDCP_ENABLE_WARNINGS
+
class Decoder;
class FilmViewer;
class Film;
+
class TextView : public wxDialog
{
public:
@@ -41,10 +45,14 @@ public:
FilmViewer& viewer
);
+ void show();
+
private:
void data_start (ContentStringText cts);
void data_stop (dcpomatic::ContentTime time);
void subtitle_selected (wxListEvent &);
+ void moved(wxMoveEvent& ev);
+ void list_sized(wxSizeEvent& ev);
wxListCtrl* _list;
int _subs;
@@ -53,4 +61,6 @@ private:
std::vector<dcpomatic::ContentTime> _start_times;
std::weak_ptr<Content> _content;
FilmViewer& _film_viewer;
+
+ static WindowMetrics _metrics;
};
diff --git a/src/wx/timecode.cc b/src/wx/timecode.cc
index 1e6a1930d..64fe87190 100644
--- a/src/wx/timecode.cc
+++ b/src/wx/timecode.cc
@@ -114,7 +114,7 @@ void
TimecodeBase::changed ()
{
if (_set_button && !_ignore_changed) {
- _set_button->Enable (true);
+ _set_button->Enable(valid());
}
}
diff --git a/src/wx/timecode.h b/src/wx/timecode.h
index 6c5d8ae23..22899ddc9 100644
--- a/src/wx/timecode.h
+++ b/src/wx/timecode.h
@@ -50,6 +50,7 @@ public:
protected:
void changed ();
void set_clicked ();
+ virtual bool valid() const = 0;
wxSizer* _sizer;
wxPanel* _editable;
@@ -96,6 +97,11 @@ public:
_frames->SetHint (std_to_wx(dcp::raw_convert<std::string>(hmsf.f)));
}
+ void set_maximum(dcpomatic::HMSF maximum)
+ {
+ _maximum = std::move(maximum);
+ }
+
dcpomatic::HMSF get () const
{
auto value_or_hint = [](wxTextCtrl const * t) {
@@ -116,6 +122,13 @@ public:
{
return T(get(), fps);
}
+
+private:
+ bool valid() const override {
+ return !_maximum || get() <= *_maximum;
+ }
+
+ boost::optional<dcpomatic::HMSF> _maximum;
};
#endif
diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc
index 2789d2a54..329f4ef00 100644
--- a/src/wx/timeline.cc
+++ b/src/wx/timeline.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
@@ -18,1012 +18,25 @@
*/
-#include "content_panel.h"
-#include "film_editor.h"
-#include "film_viewer.h"
-#include "timeline.h"
-#include "timeline_atmos_content_view.h"
-#include "timeline_audio_content_view.h"
-#include "timeline_labels_view.h"
-#include "timeline_reels_view.h"
-#include "timeline_text_content_view.h"
-#include "timeline_time_axis_view.h"
-#include "timeline_video_content_view.h"
-#include "wx_util.h"
-#include "lib/atmos_mxf_content.h"
-#include "lib/audio_content.h"
-#include "lib/film.h"
-#include "lib/image_content.h"
-#include "lib/playlist.h"
-#include "lib/text_content.h"
-#include "lib/timer.h"
-#include "lib/video_content.h"
-#include <dcp/scope_guard.h>
-#include <dcp/warnings.h>
-LIBDCP_DISABLE_WARNINGS
-#include <wx/graphics.h>
-LIBDCP_ENABLE_WARNINGS
-#include <iterator>
-#include <list>
-
-using std::abs;
-using std::dynamic_pointer_cast;
-using std::list;
-using std::make_shared;
-using std::max;
-using std::min;
-using std::shared_ptr;
-using std::weak_ptr;
-using boost::bind;
-using boost::optional;
-using namespace dcpomatic;
-#if BOOST_VERSION >= 106100
-using namespace boost::placeholders;
-#endif
+#include "timeline.h"
/* 3 hours in 640 pixels */
-double const Timeline::_minimum_pixels_per_second = 640.0 / (60 * 60 * 3);
-int const Timeline::_minimum_pixels_per_track = 16;
-
-
-Timeline::Timeline(wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, FilmViewer& viewer)
- : wxPanel (parent, wxID_ANY)
- , _labels_canvas (new wxScrolledCanvas (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE))
- , _main_canvas (new wxScrolledCanvas (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE))
- , _content_panel (cp)
- , _film (film)
- , _viewer (viewer)
- , _time_axis_view (new TimelineTimeAxisView (*this, 64))
- , _reels_view (new TimelineReelsView (*this, 32))
- , _labels_view (new TimelineLabelsView (*this))
- , _tracks (0)
- , _left_down (false)
- , _down_view_position (0)
- , _first_move (false)
- , _menu (this, viewer)
- , _snap (true)
- , _tool (SELECT)
- , _x_scroll_rate (16)
- , _y_scroll_rate (16)
- , _pixels_per_track (48)
- , _first_resize (true)
- , _timer (this)
-{
-#ifndef __WXOSX__
- _labels_canvas->SetDoubleBuffered (true);
- _main_canvas->SetDoubleBuffered (true);
-#endif
-
- auto sizer = new wxBoxSizer (wxHORIZONTAL);
- sizer->Add (_labels_canvas, 0, wxEXPAND);
- _labels_canvas->SetMinSize (wxSize (_labels_view->bbox().width, -1));
- sizer->Add (_main_canvas, 1, wxEXPAND);
- SetSizer (sizer);
-
- _labels_canvas->Bind (wxEVT_PAINT, boost::bind (&Timeline::paint_labels, this));
- _main_canvas->Bind (wxEVT_PAINT, boost::bind (&Timeline::paint_main, this));
- _main_canvas->Bind (wxEVT_LEFT_DOWN, boost::bind (&Timeline::left_down, this, _1));
- _main_canvas->Bind (wxEVT_LEFT_UP, boost::bind (&Timeline::left_up, this, _1));
- _main_canvas->Bind (wxEVT_RIGHT_DOWN, boost::bind (&Timeline::right_down, this, _1));
- _main_canvas->Bind (wxEVT_MOTION, boost::bind (&Timeline::mouse_moved, this, _1));
- _main_canvas->Bind (wxEVT_SIZE, boost::bind (&Timeline::resized, this));
- _main_canvas->Bind (wxEVT_MOUSEWHEEL, boost::bind(&Timeline::mouse_wheel_turned, this, _1));
- _main_canvas->Bind (wxEVT_SCROLLWIN_TOP, boost::bind (&Timeline::scrolled, this, _1));
- _main_canvas->Bind (wxEVT_SCROLLWIN_BOTTOM, boost::bind (&Timeline::scrolled, this, _1));
- _main_canvas->Bind (wxEVT_SCROLLWIN_LINEUP, boost::bind (&Timeline::scrolled, this, _1));
- _main_canvas->Bind (wxEVT_SCROLLWIN_LINEDOWN, boost::bind (&Timeline::scrolled, this, _1));
- _main_canvas->Bind (wxEVT_SCROLLWIN_PAGEUP, boost::bind (&Timeline::scrolled, this, _1));
- _main_canvas->Bind (wxEVT_SCROLLWIN_PAGEDOWN, boost::bind (&Timeline::scrolled, this, _1));
- _main_canvas->Bind (wxEVT_SCROLLWIN_THUMBTRACK, boost::bind (&Timeline::scrolled, this, _1));
-
- film_change(ChangeType::DONE, FilmProperty::CONTENT);
-
- SetMinSize (wxSize (640, 4 * pixels_per_track() + 96));
-
- _film_changed_connection = film->Change.connect (bind (&Timeline::film_change, this, _1, _2));
- _film_content_change_connection = film->ContentChange.connect (bind (&Timeline::film_content_change, this, _1, _3, _4));
-
- Bind (wxEVT_TIMER, boost::bind(&Timeline::update_playhead, this));
- _timer.Start (200, wxTIMER_CONTINUOUS);
-
- setup_scrollbars ();
- _labels_canvas->ShowScrollbars (wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
-}
-
-
-void
-Timeline::mouse_wheel_turned(wxMouseEvent& event)
-{
- auto const rotation = event.GetWheelRotation();
-
- if (event.ControlDown()) {
- /* On my mouse one click of the scroll wheel is 120, and it's -ve when
- * scrolling the wheel towards me.
- */
- auto const scale = rotation > 0 ?
- (1.0 / (rotation / 90.0)) :
- (-rotation / 90.0);
-
- int before_start_x;
- int before_start_y;
- _main_canvas->GetViewStart(&before_start_x, &before_start_y);
-
- auto const before_pps = _pixels_per_second.get_value_or(1);
- auto const before_pos = _last_mouse_wheel_x && *_last_mouse_wheel_x == event.GetX() ?
- *_last_mouse_wheel_time :
- (before_start_x * _x_scroll_rate + event.GetX()) / before_pps;
-
- set_pixels_per_second(before_pps * scale);
- setup_scrollbars();
-
- auto after_left = std::max(0.0, before_pos * _pixels_per_second.get_value_or(1) - event.GetX());
- _main_canvas->Scroll(after_left / _x_scroll_rate, before_start_y);
- _labels_canvas->Scroll(0, before_start_y);
- Refresh();
-
- if (!_last_mouse_wheel_x || *_last_mouse_wheel_x != event.GetX()) {
- _last_mouse_wheel_x = event.GetX();
- _last_mouse_wheel_time = before_pos;
- }
- } else if (event.ShiftDown()) {
- int before_start_x;
- int before_start_y;
- _main_canvas->GetViewStart(&before_start_x, &before_start_y);
- auto const width = _main_canvas->GetSize().GetWidth();
- _main_canvas->Scroll(std::max(0.0, before_start_x - rotation * 100.0 / width), before_start_y);
- }
-}
-
-
-void
-Timeline::update_playhead ()
-{
- Refresh ();
-}
-
-
-void
-Timeline::set_pixels_per_second (double pps)
-{
- _pixels_per_second = max (_minimum_pixels_per_second, pps);
-}
-
-
-void
-Timeline::paint_labels ()
-{
- wxPaintDC dc (_labels_canvas);
-
- auto film = _film.lock();
- if (film->content().empty()) {
- return;
- }
-
- auto gc = wxGraphicsContext::Create (dc);
- if (!gc) {
- return;
- }
-
- dcp::ScopeGuard sg = [gc]() { delete gc; };
-
- int vsx, vsy;
- _labels_canvas->GetViewStart (&vsx, &vsy);
- gc->Translate (-vsx * _x_scroll_rate, -vsy * _y_scroll_rate + tracks_y_offset());
-
- _labels_view->paint (gc, {});
-}
-
-
-void
-Timeline::paint_main ()
-{
- wxPaintDC dc (_main_canvas);
- dc.Clear();
-
- auto film = _film.lock();
- if (film->content().empty()) {
- return;
- }
-
- _main_canvas->DoPrepareDC (dc);
-
- auto gc = wxGraphicsContext::Create (dc);
- if (!gc) {
- return;
- }
-
- dcp::ScopeGuard sg = [gc]() { delete gc; };
-
- gc->SetAntialiasMode (wxANTIALIAS_DEFAULT);
-
- for (auto i: _views) {
-
- auto ic = dynamic_pointer_cast<TimelineContentView> (i);
-
- /* Find areas of overlap with other content views, so that we can plot them */
- list<dcpomatic::Rect<int>> overlaps;
- for (auto j: _views) {
- auto jc = dynamic_pointer_cast<TimelineContentView> (j);
- /* No overlap with non-content views, views on different tracks, audio views or non-active views */
- if (!ic || !jc || i == j || ic->track() != jc->track() || ic->track().get_value_or(2) >= 2 || !ic->active() || !jc->active()) {
- continue;
- }
-
- auto r = j->bbox().intersection(i->bbox());
- if (r) {
- overlaps.push_back (r.get ());
- }
- }
-
- i->paint (gc, overlaps);
- }
-
- if (_zoom_point) {
- gc->SetPen(gui_is_dark() ? *wxWHITE_PEN : *wxBLACK_PEN);
- gc->SetBrush (*wxTRANSPARENT_BRUSH);
- gc->DrawRectangle (
- min (_down_point.x, _zoom_point->x),
- min (_down_point.y, _zoom_point->y),
- abs (_down_point.x - _zoom_point->x),
- abs (_down_point.y - _zoom_point->y)
- );
- }
-
- /* Playhead */
-
- gc->SetPen (*wxRED_PEN);
- auto path = gc->CreatePath ();
- double const ph = _viewer.position().seconds() * pixels_per_second().get_value_or(0);
- path.MoveToPoint (ph, 0);
- path.AddLineToPoint (ph, pixels_per_track() * _tracks + 32);
- gc->StrokePath (path);
-}
-
-
-void
-Timeline::film_change(ChangeType type, FilmProperty p)
-{
- if (type != ChangeType::DONE) {
- return;
- }
-
- if (p == FilmProperty::CONTENT || p == FilmProperty::REEL_TYPE || p == FilmProperty::REEL_LENGTH) {
- ensure_ui_thread ();
- recreate_views ();
- } else if (p == FilmProperty::CONTENT_ORDER) {
- Refresh ();
- }
-}
-
-
-void
-Timeline::recreate_views ()
-{
- auto film = _film.lock ();
- if (!film) {
- return;
- }
-
- _views.clear ();
- _views.push_back (_time_axis_view);
- _views.push_back (_reels_view);
-
- for (auto i: film->content ()) {
- if (i->video) {
- _views.push_back (make_shared<TimelineVideoContentView>(*this, i));
- }
-
- if (i->has_mapped_audio()) {
- _views.push_back (make_shared<TimelineAudioContentView>(*this, i));
- }
-
- for (auto j: i->text) {
- _views.push_back (make_shared<TimelineTextContentView>(*this, i, j));
- }
-
- if (i->atmos) {
- _views.push_back (make_shared<TimelineAtmosContentView>(*this, i));
- }
- }
-
- assign_tracks ();
- setup_scrollbars ();
- Refresh ();
-}
-
-
-void
-Timeline::film_content_change (ChangeType type, int property, bool frequent)
-{
- if (type != ChangeType::DONE) {
- return;
- }
-
- ensure_ui_thread ();
-
- if (property == AudioContentProperty::STREAMS || property == VideoContentProperty::FRAME_TYPE) {
- recreate_views ();
- } else if (property == ContentProperty::POSITION || property == ContentProperty::LENGTH) {
- _reels_view->force_redraw ();
- } else if (!frequent) {
- setup_scrollbars ();
- Refresh ();
- }
-}
-
-
-template <class T>
-int
-place (shared_ptr<const Film> film, TimelineViewList& views, int& tracks)
-{
- int const base = tracks;
-
- for (auto i: views) {
- if (!dynamic_pointer_cast<T>(i)) {
- continue;
- }
-
- auto cv = dynamic_pointer_cast<TimelineContentView> (i);
- DCPOMATIC_ASSERT(cv);
-
- int t = base;
-
- auto content = cv->content();
- DCPTimePeriod const content_period = content->period(film);
-
- while (true) {
- auto j = views.begin();
- while (j != views.end()) {
- auto test = dynamic_pointer_cast<T> (*j);
- if (!test) {
- ++j;
- continue;
- }
-
- auto test_content = test->content();
- if (
- test->track() && test->track().get() == t &&
- content_period.overlap(test_content->period(film))
- ) {
- /* we have an overlap on track `t' */
- ++t;
- break;
- }
-
- ++j;
- }
-
- if (j == views.end ()) {
- /* no overlap on `t' */
- break;
- }
- }
-
- cv->set_track (t);
- tracks = max (tracks, t + 1);
- }
-
- return tracks - base;
-}
-
-
-/** Compare the mapped output channels of two TimelineViews, so we can into
- * order of first mapped DCP channel.
- */
-struct AudioMappingComparator {
- bool operator()(shared_ptr<TimelineView> a, shared_ptr<TimelineView> b) {
- int la = -1;
- auto cva = dynamic_pointer_cast<TimelineAudioContentView>(a);
- if (cva) {
- auto oc = cva->content()->audio->mapping().mapped_output_channels();
- la = *min_element(boost::begin(oc), boost::end(oc));
- }
- int lb = -1;
- auto cvb = dynamic_pointer_cast<TimelineAudioContentView>(b);
- if (cvb) {
- auto oc = cvb->content()->audio->mapping().mapped_output_channels();
- lb = *min_element(boost::begin(oc), boost::end(oc));
- }
- return la < lb;
- }
-};
-
-
-void
-Timeline::assign_tracks ()
-{
- /* Tracks are:
- Video 1
- Video 2
- Video N
- Text 1
- Text 2
- Text N
- Atmos
- Audio 1
- Audio 2
- Audio N
- */
-
- auto film = _film.lock ();
- DCPOMATIC_ASSERT (film);
-
- _tracks = 0;
-
- for (auto i: _views) {
- auto c = dynamic_pointer_cast<TimelineContentView>(i);
- if (c) {
- c->unset_track ();
- }
- }
-
- int const video_tracks = place<TimelineVideoContentView> (film, _views, _tracks);
- int const text_tracks = place<TimelineTextContentView> (film, _views, _tracks);
-
- /* Atmos */
-
- bool have_atmos = false;
- for (auto i: _views) {
- auto cv = dynamic_pointer_cast<TimelineAtmosContentView>(i);
- if (cv) {
- cv->set_track (_tracks);
- have_atmos = true;
- }
- }
-
- if (have_atmos) {
- ++_tracks;
- }
-
- /* Audio. We're sorting the views so that we get the audio views in order of increasing
- DCP channel index.
- */
-
- auto views = _views;
- sort(views.begin(), views.end(), AudioMappingComparator());
- int const audio_tracks = place<TimelineAudioContentView> (film, views, _tracks);
-
- _labels_view->set_video_tracks (video_tracks);
- _labels_view->set_audio_tracks (audio_tracks);
- _labels_view->set_text_tracks (text_tracks);
- _labels_view->set_atmos (have_atmos);
-
- _time_axis_view->set_y (tracks());
- _reels_view->set_y (8);
-}
-
-
-int
-Timeline::tracks () const
-{
- return _tracks;
-}
-
-
-void
-Timeline::setup_scrollbars ()
-{
- auto film = _film.lock ();
- if (!film || !_pixels_per_second) {
- return;
- }
-
- int const h = tracks() * pixels_per_track() + tracks_y_offset() + _time_axis_view->bbox().height;
-
- _labels_canvas->SetVirtualSize (_labels_view->bbox().width, h);
- _labels_canvas->SetScrollRate (_x_scroll_rate, _y_scroll_rate);
- _main_canvas->SetVirtualSize (*_pixels_per_second * film->length().seconds(), h);
- _main_canvas->SetScrollRate (_x_scroll_rate, _y_scroll_rate);
-}
-
-
-shared_ptr<TimelineView>
-Timeline::event_to_view (wxMouseEvent& ev)
-{
- /* Search backwards through views so that we find the uppermost one first */
- auto i = _views.rbegin();
-
- int vsx, vsy;
- _main_canvas->GetViewStart (&vsx, &vsy);
- Position<int> const p (ev.GetX() + vsx * _x_scroll_rate, ev.GetY() + vsy * _y_scroll_rate);
-
- while (i != _views.rend() && !(*i)->bbox().contains (p)) {
- ++i;
- }
-
- if (i == _views.rend ()) {
- return {};
- }
-
- return *i;
-}
-
-
-void
-Timeline::left_down (wxMouseEvent& ev)
-{
- _left_down = true;
- _down_point = ev.GetPosition ();
-
- switch (_tool) {
- case SELECT:
- left_down_select (ev);
- break;
- case ZOOM:
- case ZOOM_ALL:
- case SNAP:
- case SEQUENCE:
- /* Nothing to do */
- break;
- }
-}
-
-
-void
-Timeline::left_down_select (wxMouseEvent& ev)
-{
- auto view = event_to_view (ev);
- auto content_view = dynamic_pointer_cast<TimelineContentView>(view);
-
- _down_view.reset ();
+double constexpr minimum_pixels_per_second = 640.0 / (60 * 60 * 3);
- if (content_view) {
- _down_view = content_view;
- _down_view_position = content_view->content()->position ();
- }
- if (dynamic_pointer_cast<TimelineTimeAxisView>(view)) {
- int vsx, vsy;
- _main_canvas->GetViewStart(&vsx, &vsy);
- _viewer.seek(DCPTime::from_seconds((ev.GetPosition().x + vsx * _x_scroll_rate) / _pixels_per_second.get_value_or(1)), true);
- }
-
- for (auto i: _views) {
- auto cv = dynamic_pointer_cast<TimelineContentView>(i);
- if (!cv) {
- continue;
- }
-
- if (!ev.ShiftDown ()) {
- cv->set_selected (view == i);
- }
- }
-
- if (content_view && ev.ShiftDown ()) {
- content_view->set_selected (!content_view->selected ());
- }
-
- _first_move = false;
-
- if (_down_view) {
- /* Pre-compute the points that we might snap to */
- for (auto i: _views) {
- auto cv = dynamic_pointer_cast<TimelineContentView>(i);
- if (!cv || cv == _down_view || cv->content() == _down_view->content()) {
- continue;
- }
-
- auto film = _film.lock ();
- DCPOMATIC_ASSERT (film);
-
- _start_snaps.push_back (cv->content()->position());
- _end_snaps.push_back (cv->content()->position());
- _start_snaps.push_back (cv->content()->end(film));
- _end_snaps.push_back (cv->content()->end(film));
-
- for (auto i: cv->content()->reel_split_points(film)) {
- _start_snaps.push_back (i);
- }
- }
-
- /* Tell everyone that things might change frequently during the drag */
- _down_view->content()->set_change_signals_frequent (true);
- }
-}
-
-
-void
-Timeline::left_up (wxMouseEvent& ev)
-{
- _left_down = false;
-
- switch (_tool) {
- case SELECT:
- left_up_select (ev);
- break;
- case ZOOM:
- left_up_zoom (ev);
- break;
- case ZOOM_ALL:
- case SNAP:
- case SEQUENCE:
- break;
- }
-}
-
-
-void
-Timeline::left_up_select (wxMouseEvent& ev)
+Timeline::Timeline(wxWindow* parent)
+ : wxPanel(parent, wxID_ANY)
{
- if (_down_view) {
- _down_view->content()->set_change_signals_frequent (false);
- }
- _content_panel->set_selection (selected_content ());
- /* Since we may have just set change signals back to `not-frequent', we have to
- make sure this position change is signalled, even if the position value has
- not changed since the last time it was set (with frequent=true). This is
- a bit of a hack.
- */
- set_position_from_event (ev, true);
-
- /* Clear up up the stuff we don't do during drag */
- assign_tracks ();
- setup_scrollbars ();
- Refresh ();
-
- _start_snaps.clear ();
- _end_snaps.clear ();
-}
-
-
-void
-Timeline::left_up_zoom (wxMouseEvent& ev)
-{
- _zoom_point = ev.GetPosition ();
-
- int vsx, vsy;
- _main_canvas->GetViewStart (&vsx, &vsy);
-
- wxPoint top_left(min(_down_point.x, _zoom_point->x), min(_down_point.y, _zoom_point->y));
- wxPoint bottom_right(max(_down_point.x, _zoom_point->x), max(_down_point.y, _zoom_point->y));
-
- if ((bottom_right.x - top_left.x) < 8 || (bottom_right.y - top_left.y) < 8) {
- /* Very small zoom rectangle: we assume it wasn't intentional */
- _zoom_point = optional<wxPoint> ();
- Refresh ();
- return;
- }
-
- auto const time_left = DCPTime::from_seconds((top_left.x + vsx) / *_pixels_per_second);
- auto const time_right = DCPTime::from_seconds((bottom_right.x + vsx) / *_pixels_per_second);
- set_pixels_per_second (double(GetSize().GetWidth()) / (time_right.seconds() - time_left.seconds()));
-
- double const tracks_top = double(top_left.y - tracks_y_offset()) / _pixels_per_track;
- double const tracks_bottom = double(bottom_right.y - tracks_y_offset()) / _pixels_per_track;
- set_pixels_per_track (lrint(GetSize().GetHeight() / (tracks_bottom - tracks_top)));
-
- setup_scrollbars ();
- int const y = (tracks_top * _pixels_per_track + tracks_y_offset()) / _y_scroll_rate;
- _main_canvas->Scroll (time_left.seconds() * *_pixels_per_second / _x_scroll_rate, y);
- _labels_canvas->Scroll (0, y);
-
- _zoom_point = optional<wxPoint> ();
- Refresh ();
-}
-
-
-void
-Timeline::set_pixels_per_track (int h)
-{
- _pixels_per_track = max(_minimum_pixels_per_track, h);
}
void
-Timeline::mouse_moved (wxMouseEvent& ev)
+Timeline::set_pixels_per_second(double pps)
{
- switch (_tool) {
- case SELECT:
- mouse_moved_select (ev);
- break;
- case ZOOM:
- mouse_moved_zoom (ev);
- break;
- case ZOOM_ALL:
- case SNAP:
- case SEQUENCE:
- break;
- }
+ _pixels_per_second = std::max(minimum_pixels_per_second, pps);
}
-void
-Timeline::mouse_moved_select (wxMouseEvent& ev)
-{
- if (!_left_down) {
- return;
- }
-
- set_position_from_event (ev);
-}
-
-
-void
-Timeline::mouse_moved_zoom (wxMouseEvent& ev)
-{
- if (!_left_down) {
- return;
- }
-
- _zoom_point = ev.GetPosition ();
- setup_scrollbars();
- Refresh ();
-}
-
-
-void
-Timeline::right_down (wxMouseEvent& ev)
-{
- switch (_tool) {
- case SELECT:
- right_down_select (ev);
- break;
- case ZOOM:
- /* Zoom out */
- set_pixels_per_second (*_pixels_per_second / 2);
- set_pixels_per_track (_pixels_per_track / 2);
- setup_scrollbars ();
- Refresh ();
- break;
- case ZOOM_ALL:
- case SNAP:
- case SEQUENCE:
- break;
- }
-}
-
-
-void
-Timeline::right_down_select (wxMouseEvent& ev)
-{
- auto view = event_to_view (ev);
- auto cv = dynamic_pointer_cast<TimelineContentView> (view);
- if (!cv) {
- return;
- }
-
- if (!cv->selected ()) {
- clear_selection ();
- cv->set_selected (true);
- }
-
- _menu.popup (_film, selected_content (), selected_views (), ev.GetPosition ());
-}
-
-
-void
-Timeline::maybe_snap (DCPTime a, DCPTime b, optional<DCPTime>& nearest_distance) const
-{
- auto const d = a - b;
- if (!nearest_distance || d.abs() < nearest_distance.get().abs()) {
- nearest_distance = d;
- }
-}
-
-
-void
-Timeline::set_position_from_event (wxMouseEvent& ev, bool force_emit)
-{
- if (!_pixels_per_second) {
- return;
- }
-
- double const pps = _pixels_per_second.get ();
-
- auto const p = ev.GetPosition();
-
- if (!_first_move) {
- /* We haven't moved yet; in that case, we must move the mouse some reasonable distance
- before the drag is considered to have started.
- */
- int const dist = sqrt (pow (p.x - _down_point.x, 2) + pow (p.y - _down_point.y, 2));
- if (dist < 8) {
- return;
- }
- _first_move = true;
- }
-
- if (!_down_view) {
- return;
- }
-
- auto new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / pps);
-
- auto film = _film.lock ();
- DCPOMATIC_ASSERT (film);
-
- if (_snap) {
- auto const new_end = new_position + _down_view->content()->length_after_trim(film);
- /* Signed `distance' to nearest thing (i.e. negative is left on the timeline,
- positive is right).
- */
- optional<DCPTime> nearest_distance;
-
- /* Find the nearest snap point */
-
- for (auto i: _start_snaps) {
- maybe_snap (i, new_position, nearest_distance);
- }
-
- for (auto i: _end_snaps) {
- maybe_snap (i, new_end, nearest_distance);
- }
-
- if (nearest_distance) {
- /* Snap if it's close; `close' means within a proportion of the time on the timeline */
- if (nearest_distance.get().abs() < DCPTime::from_seconds ((width() / pps) / 64)) {
- new_position += nearest_distance.get ();
- }
- }
- }
-
- if (new_position < DCPTime ()) {
- new_position = DCPTime ();
- }
-
- _down_view->content()->set_position (film, new_position, force_emit);
-
- film->set_sequence (false);
-}
-
-
-void
-Timeline::force_redraw (dcpomatic::Rect<int> const & r)
-{
- _main_canvas->RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
-}
-
-
-shared_ptr<const Film>
-Timeline::film () const
-{
- return _film.lock ();
-}
-
-
-void
-Timeline::resized ()
-{
- if (_main_canvas->GetSize().GetWidth() > 0 && _first_resize) {
- zoom_all ();
- _first_resize = false;
- }
- setup_scrollbars ();
-}
-
-
-void
-Timeline::clear_selection ()
-{
- for (auto i: _views) {
- shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView>(i);
- if (cv) {
- cv->set_selected (false);
- }
- }
-}
-
-
-TimelineContentViewList
-Timeline::selected_views () const
-{
- TimelineContentViewList sel;
-
- for (auto i: _views) {
- auto cv = dynamic_pointer_cast<TimelineContentView>(i);
- if (cv && cv->selected()) {
- sel.push_back (cv);
- }
- }
-
- return sel;
-}
-
-
-ContentList
-Timeline::selected_content () const
-{
- ContentList sel;
-
- for (auto i: selected_views()) {
- sel.push_back(i->content());
- }
-
- return sel;
-}
-
-
-void
-Timeline::set_selection (ContentList selection)
-{
- for (auto i: _views) {
- auto cv = dynamic_pointer_cast<TimelineContentView> (i);
- if (cv) {
- cv->set_selected (find (selection.begin(), selection.end(), cv->content ()) != selection.end ());
- }
- }
-}
-
-
-int
-Timeline::tracks_y_offset () const
-{
- return _reels_view->bbox().height + 4;
-}
-
-
-int
-Timeline::width () const
-{
- return _main_canvas->GetVirtualSize().GetWidth();
-}
-
-
-void
-Timeline::scrolled (wxScrollWinEvent& ev)
-{
- if (ev.GetOrientation() == wxVERTICAL) {
- int x, y;
- _main_canvas->GetViewStart (&x, &y);
- _labels_canvas->Scroll (0, y);
- }
- ev.Skip ();
-}
-
-
-void
-Timeline::tool_clicked (Tool t)
-{
- switch (t) {
- case ZOOM:
- case SELECT:
- _tool = t;
- break;
- case ZOOM_ALL:
- zoom_all ();
- break;
- case SNAP:
- case SEQUENCE:
- break;
- }
-}
-
-
-void
-Timeline::zoom_all ()
-{
- auto film = _film.lock ();
- DCPOMATIC_ASSERT (film);
- set_pixels_per_second((_main_canvas->GetSize().GetWidth() - 32) / std::max(1.0, film->length().seconds()));
- set_pixels_per_track((_main_canvas->GetSize().GetHeight() - tracks_y_offset() - _time_axis_view->bbox().height - 32) / std::max(1, _tracks));
- setup_scrollbars ();
- _main_canvas->Scroll (0, 0);
- _labels_canvas->Scroll (0, 0);
- Refresh ();
-}
-
-
-void
-Timeline::keypress(wxKeyEvent const& event)
-{
- if (event.GetKeyCode() == WXK_DELETE) {
- auto film = _film.lock();
- DCPOMATIC_ASSERT(film);
- film->remove_content(selected_content());
- } else {
- switch (event.GetRawKeyCode()) {
- case '+':
- set_pixels_per_second(_pixels_per_second.get_value_or(1) * 2);
- setup_scrollbars();
- break;
- case '-':
- set_pixels_per_second(_pixels_per_second.get_value_or(1) / 2);
- setup_scrollbars();
- break;
- }
- }
-}
-
diff --git a/src/wx/timeline.h b/src/wx/timeline.h
index 621609fa7..cc35913b9 100644
--- a/src/wx/timeline.h
+++ b/src/wx/timeline.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2013-2019 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
@@ -19,137 +19,32 @@
*/
-#include "content_menu.h"
-#include "timeline_content_view.h"
-#include "lib/film_property.h"
-#include "lib/rect.h"
+#ifndef DCPOMATIC_TIMELINE_H
+#define DCPOMATIC_TIMELINE_H
+
+
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
LIBDCP_ENABLE_WARNINGS
-#include <boost/signals2.hpp>
-
-
-class ContentPanel;
-class Film;
-class FilmViewer;
-class TimelineLabelsView;
-class TimelineReelsView;
-class TimelineTimeAxisView;
-class TimelineView;
+#include <boost/optional.hpp>
class Timeline : public wxPanel
{
public:
- Timeline (wxWindow *, ContentPanel *, std::shared_ptr<Film>, FilmViewer& viewer);
-
- std::shared_ptr<const Film> film () const;
-
- void force_redraw (dcpomatic::Rect<int> const &);
-
- int width () const;
+ explicit Timeline(wxWindow* parent);
- int pixels_per_track () const {
- return _pixels_per_track;
- }
-
- boost::optional<double> pixels_per_second () const {
+ boost::optional<double> pixels_per_second() const {
return _pixels_per_second;
}
- int tracks () const;
- void set_snap (bool s) {
- _snap = s;
- }
+protected:
+ void set_pixels_per_second(double pps);
- bool snap () const {
- return _snap;
- }
-
- void set_selection (ContentList selection);
-
- enum Tool {
- SELECT,
- ZOOM,
- ZOOM_ALL,
- SNAP,
- SEQUENCE
- };
-
- void tool_clicked (Tool t);
-
- int tracks_y_offset () const;
-
- void keypress(wxKeyEvent const &);
-
-private:
- void paint_labels ();
- void paint_main ();
- void left_down (wxMouseEvent &);
- void left_down_select (wxMouseEvent &);
- void left_up (wxMouseEvent &);
- void left_up_select (wxMouseEvent &);
- void left_up_zoom (wxMouseEvent &);
- void right_down (wxMouseEvent &);
- void right_down_select (wxMouseEvent &);
- void mouse_moved (wxMouseEvent &);
- void mouse_moved_select (wxMouseEvent &);
- void mouse_moved_zoom (wxMouseEvent &);
- void film_change(ChangeType type, FilmProperty);
- void film_content_change (ChangeType type, int, bool frequent);
- void resized ();
- void assign_tracks ();
- void set_position_from_event (wxMouseEvent& ev, bool force_emit = false);
- void clear_selection ();
- void recreate_views ();
- void setup_scrollbars ();
- void scrolled (wxScrollWinEvent& ev);
- void set_pixels_per_second (double pps);
- void set_pixels_per_track (int h);
- void zoom_all ();
- void update_playhead ();
- void mouse_wheel_turned(wxMouseEvent& event);
-
- std::shared_ptr<TimelineView> event_to_view (wxMouseEvent &);
- TimelineContentViewList selected_views () const;
- ContentList selected_content () const;
- void maybe_snap (dcpomatic::DCPTime a, dcpomatic::DCPTime b, boost::optional<dcpomatic::DCPTime>& nearest_distance) const;
-
- wxScrolledCanvas* _labels_canvas;
- wxScrolledCanvas* _main_canvas;
- ContentPanel* _content_panel;
- std::weak_ptr<Film> _film;
- FilmViewer& _viewer;
- TimelineViewList _views;
- std::shared_ptr<TimelineTimeAxisView> _time_axis_view;
- std::shared_ptr<TimelineReelsView> _reels_view;
- std::shared_ptr<TimelineLabelsView> _labels_view;
- int _tracks;
boost::optional<double> _pixels_per_second;
- bool _left_down;
- wxPoint _down_point;
- boost::optional<wxPoint> _zoom_point;
- std::shared_ptr<TimelineContentView> _down_view;
- dcpomatic::DCPTime _down_view_position;
- bool _first_move;
- ContentMenu _menu;
- bool _snap;
- std::list<dcpomatic::DCPTime> _start_snaps;
- std::list<dcpomatic::DCPTime> _end_snaps;
- Tool _tool;
- int _x_scroll_rate;
- int _y_scroll_rate;
- int _pixels_per_track;
- bool _first_resize;
- wxTimer _timer;
- boost::optional<int> _last_mouse_wheel_x;
- boost::optional<double> _last_mouse_wheel_time;
-
- static double const _minimum_pixels_per_second;
- static int const _minimum_pixels_per_track;
-
- boost::signals2::scoped_connection _film_changed_connection;
- boost::signals2::scoped_connection _film_content_change_connection;
};
+
+
+#endif
diff --git a/src/wx/timeline_content_view.cc b/src/wx/timeline_content_view.cc
index 5d039d0d3..69a675c42 100644
--- a/src/wx/timeline_content_view.cc
+++ b/src/wx/timeline_content_view.cc
@@ -19,10 +19,11 @@
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include "timeline_content_view.h"
#include "wx_util.h"
#include "lib/content.h"
+#include "lib/util.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/graphics.h>
@@ -37,8 +38,8 @@ using namespace boost::placeholders;
#endif
-TimelineContentView::TimelineContentView (Timeline& tl, shared_ptr<Content> c)
- : TimelineView (tl)
+TimelineContentView::TimelineContentView(ContentTimeline& tl, shared_ptr<Content> c)
+ : ContentTimelineView(tl)
, _content (c)
{
_content_connection = c->Change.connect (bind (&TimelineContentView::content_change, this, _1, _3));
diff --git a/src/wx/timeline_content_view.h b/src/wx/timeline_content_view.h
index 7794120cd..7b206d30f 100644
--- a/src/wx/timeline_content_view.h
+++ b/src/wx/timeline_content_view.h
@@ -23,7 +23,7 @@
#define DCPOMATIC_TIMELINE_CONTENT_VIEW_H
-#include "timeline_view.h"
+#include "content_timeline_view.h"
#include "lib/change_signaller.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
@@ -37,10 +37,10 @@ class Content;
/** @class TimelineContentView
* @brief Parent class for views of pieces of content.
*/
-class TimelineContentView : public TimelineView
+class TimelineContentView : public ContentTimelineView
{
public:
- TimelineContentView (Timeline& tl, std::shared_ptr<Content> c);
+ TimelineContentView(ContentTimeline& tl, std::shared_ptr<Content> c);
dcpomatic::Rect<int> bbox () const override;
diff --git a/src/wx/timeline_labels_view.cc b/src/wx/timeline_labels_view.cc
index 181adc5ca..c869d7ec5 100644
--- a/src/wx/timeline_labels_view.cc
+++ b/src/wx/timeline_labels_view.cc
@@ -19,7 +19,7 @@
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include "timeline_labels_view.h"
#include "wx_util.h"
#include <dcp/warnings.h>
@@ -34,8 +34,8 @@ using std::max;
using std::min;
-TimelineLabelsView::TimelineLabelsView (Timeline& tl)
- : TimelineView (tl)
+TimelineLabelsView::TimelineLabelsView(ContentTimeline& tl)
+ : ContentTimelineView(tl)
{
wxString labels[] = {
_("Video"),
diff --git a/src/wx/timeline_labels_view.h b/src/wx/timeline_labels_view.h
index fb80b1bf3..324531c39 100644
--- a/src/wx/timeline_labels_view.h
+++ b/src/wx/timeline_labels_view.h
@@ -19,16 +19,16 @@
*/
-#include "timeline_view.h"
+#include "content_timeline_view.h"
class wxWindow;
-class TimelineLabelsView : public TimelineView
+class TimelineLabelsView : public ContentTimelineView
{
public:
- explicit TimelineLabelsView (Timeline& tl);
+ explicit TimelineLabelsView(ContentTimeline& tl);
dcpomatic::Rect<int> bbox () const override;
diff --git a/src/wx/timeline_reels_view.cc b/src/wx/timeline_reels_view.cc
index 0601a1196..5f2a78079 100644
--- a/src/wx/timeline_reels_view.cc
+++ b/src/wx/timeline_reels_view.cc
@@ -19,7 +19,7 @@
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include "timeline_reels_view.h"
#include "wx_util.h"
#include "lib/film.h"
@@ -35,8 +35,8 @@ using std::list;
using namespace dcpomatic;
-TimelineReelsView::TimelineReelsView (Timeline& tl, int y)
- : TimelineView (tl)
+TimelineReelsView::TimelineReelsView(ContentTimeline& tl, int y)
+ : ContentTimelineView(tl)
, _y (y)
{
diff --git a/src/wx/timeline_reels_view.h b/src/wx/timeline_reels_view.h
index 357fe2ce4..7dbc34308 100644
--- a/src/wx/timeline_reels_view.h
+++ b/src/wx/timeline_reels_view.h
@@ -19,13 +19,13 @@
*/
-#include "timeline_view.h"
+#include "content_timeline_view.h"
-class TimelineReelsView : public TimelineView
+class TimelineReelsView : public ContentTimelineView
{
public:
- TimelineReelsView (Timeline& tl, int y);
+ TimelineReelsView(ContentTimeline& tl, int y);
dcpomatic::Rect<int> bbox () const override;
void set_y (int y);
diff --git a/src/wx/timeline_time_axis_view.cc b/src/wx/timeline_time_axis_view.cc
index d055bda7d..d9b7710c6 100644
--- a/src/wx/timeline_time_axis_view.cc
+++ b/src/wx/timeline_time_axis_view.cc
@@ -19,7 +19,7 @@
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include "timeline_time_axis_view.h"
#include "wx_util.h"
#include <dcp/warnings.h>
@@ -34,8 +34,8 @@ using std::list;
using namespace dcpomatic;
-TimelineTimeAxisView::TimelineTimeAxisView (Timeline& tl, int y)
- : TimelineView (tl)
+TimelineTimeAxisView::TimelineTimeAxisView(ContentTimeline& tl, int y)
+ : ContentTimelineView(tl)
, _y (y)
{
diff --git a/src/wx/timeline_time_axis_view.h b/src/wx/timeline_time_axis_view.h
index 4c8e090fe..f89121776 100644
--- a/src/wx/timeline_time_axis_view.h
+++ b/src/wx/timeline_time_axis_view.h
@@ -18,12 +18,14 @@
*/
-#include "timeline_view.h"
-class TimelineTimeAxisView : public TimelineView
+#include "content_timeline_view.h"
+
+
+class TimelineTimeAxisView : public ContentTimelineView
{
public:
- TimelineTimeAxisView (Timeline& tl, int y);
+ TimelineTimeAxisView(ContentTimeline& tl, int y);
dcpomatic::Rect<int> bbox () const override;
void set_y (int y);
diff --git a/src/wx/timeline_view.cc b/src/wx/timeline_view.cc
deleted file mode 100644
index 2897c98e3..000000000
--- a/src/wx/timeline_view.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
-
- 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 <http://www.gnu.org/licenses/>.
-
-*/
-
-
-#include "timeline_view.h"
-#include "timeline.h"
-
-
-using std::list;
-using namespace dcpomatic;
-
-
-/** @class TimelineView
- * @brief Parent class for components of the timeline (e.g. a piece of content or an axis).
- */
-TimelineView::TimelineView (Timeline& t)
- : _timeline (t)
-{
-
-}
-
-
-void
-TimelineView::paint (wxGraphicsContext* g, list<dcpomatic::Rect<int>> overlaps)
-{
- _last_paint_bbox = bbox ();
- do_paint (g, overlaps);
-}
-
-
-void
-TimelineView::force_redraw ()
-{
- _timeline.force_redraw (_last_paint_bbox.extended(4));
- _timeline.force_redraw (bbox().extended(4));
-}
-
-
-int
-TimelineView::time_x (DCPTime t) const
-{
- return t.seconds() * _timeline.pixels_per_second().get_value_or(0);
-}
-
-
-int
-TimelineView::y_pos(int t) const
-{
- return t * _timeline.pixels_per_track() + _timeline.tracks_y_offset();
-}
-
-
diff --git a/src/wx/timeline_view.h b/src/wx/timeline_view.h
index 166a1121a..32eedde09 100644
--- a/src/wx/timeline_view.h
+++ b/src/wx/timeline_view.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
@@ -28,40 +28,42 @@
class wxGraphicsContext;
-class Timeline;
-/** @class TimelineView
- * @brief Parent class for components of the timeline (e.g. a piece of content or an axis).
+/** @class ContentTimelineView
+ * @brief Parent class for components of the content timeline (e.g. a piece of content or an axis).
*/
+template <class Timeline>
class TimelineView
{
public:
- explicit TimelineView (Timeline& t);
- virtual ~TimelineView () {}
+ explicit TimelineView(Timeline& timeline)
+ : _timeline(timeline)
+ {}
- TimelineView (TimelineView const&) = delete;
- TimelineView& operator= (TimelineView const&) = delete;
+ virtual ~TimelineView () = default;
- void paint (wxGraphicsContext* g, std::list<dcpomatic::Rect<int>> overlaps);
- void force_redraw ();
+ TimelineView(TimelineView const&) = delete;
+ TimelineView& operator=(TimelineView const&) = delete;
- virtual dcpomatic::Rect<int> bbox () const = 0;
+ void force_redraw()
+ {
+ _timeline.force_redraw(_last_paint_bbox.extended(4));
+ _timeline.force_redraw(bbox().extended(4));
+ }
-protected:
- virtual void do_paint (wxGraphicsContext *, std::list<dcpomatic::Rect<int>> overlaps) = 0;
+ virtual dcpomatic::Rect<int> bbox() const = 0;
- int time_x (dcpomatic::DCPTime t) const;
- int y_pos(int t) const;
+protected:
+ int time_x(dcpomatic::DCPTime t) const
+ {
+ return t.seconds() * _timeline.pixels_per_second().get_value_or(0);
+ }
Timeline& _timeline;
-
-private:
dcpomatic::Rect<int> _last_paint_bbox;
};
-typedef std::vector<std::shared_ptr<TimelineView>> TimelineViewList;
-
-
#endif
+
diff --git a/src/wx/try_unmount_dialog.cc b/src/wx/try_unmount_dialog.cc
index ddfaec5c0..37d6e75fa 100644
--- a/src/wx/try_unmount_dialog.cc
+++ b/src/wx/try_unmount_dialog.cc
@@ -22,6 +22,7 @@
#include "static_text.h"
#include "try_unmount_dialog.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
@@ -32,14 +33,17 @@ static int constexpr width = 300;
TryUnmountDialog::TryUnmountDialog (wxWindow* parent, wxString description)
- : wxDialog (parent, wxID_ANY, _("DCP-o-matic Disk Writer"))
+ : wxDialog(parent, wxID_ANY, variant::wx::dcpomatic_disk_writer())
{
auto sizer = new wxBoxSizer (wxVERTICAL);
auto text = new StaticText (this, wxEmptyString, wxDefaultPosition, wxSize(width, -1));
sizer->Add (text, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
text->SetLabelMarkup (
- wxString::Format(_("The drive <b>%s</b> is mounted.\n\nIt must be unmounted before DCP-o-matic can write to it.\n\nShould DCP-o-matic try to unmount it now?"), description)
+ wxString::Format(
+ _("The drive <b>%s</b> is mounted.\n\nIt must be unmounted before %s can write to it.\n\nShould DCP-o-matic try to unmount it now?"),
+ description, variant::wx::dcpomatic()
+ )
);
text->Wrap(width);
diff --git a/src/wx/update_dialog.cc b/src/wx/update_dialog.cc
index 0a7319704..7c687aa60 100644
--- a/src/wx/update_dialog.cc
+++ b/src/wx/update_dialog.cc
@@ -22,6 +22,7 @@
#include "static_text.h"
#include "update_dialog.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/hyperlink.h>
@@ -40,9 +41,9 @@ UpdateDialog::UpdateDialog (wxWindow* parent, optional<string> stable, optional<
wxStaticText* message;
if ((stable || test) && !(stable && test)) {
- message = new StaticText (this, _("A new version of DCP-o-matic is available."));
+ message = new StaticText(this, variant::wx::insert_dcpomatic(_("A new version of %s is available.")));
} else {
- message = new StaticText (this, _("New versions of DCP-o-matic are available."));
+ message = new StaticText(this, variant::wx::insert_dcpomatic(_("New versions of %s are available.")));
}
overall_sizer->Add (message, 0, wxTOP | wxLEFT | wxRIGHT, DCPOMATIC_DIALOG_BORDER);
diff --git a/src/wx/verify_dcp_progress_dialog.cc b/src/wx/verify_dcp_progress_dialog.cc
index 6c941c98d..5e3a7be62 100644
--- a/src/wx/verify_dcp_progress_dialog.cc
+++ b/src/wx/verify_dcp_progress_dialog.cc
@@ -20,6 +20,7 @@
#include "verify_dcp_progress_dialog.h"
+#include "verify_dcp_progress_panel.h"
#include "wx_util.h"
#include "lib/cross.h"
#include "lib/job.h"
@@ -36,36 +37,17 @@ using std::shared_ptr;
using boost::optional;
-static int const max_file_name_length = 80;
-
-
VerifyDCPProgressDialog::VerifyDCPProgressDialog (wxWindow* parent, wxString title)
: wxDialog (parent, wxID_ANY, title)
+ , _panel(new VerifyDCPProgressPanel(this))
, _cancel (false)
{
- wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
-
- _job_name = new wxStaticText (this, wxID_ANY, wxT(""));
- overall_sizer->Add (_job_name, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, DCPOMATIC_SIZER_GAP);
-
- _file_name = new wxStaticText (this, wxID_ANY, wxT(""));
- wxFont file_name_font (*wxNORMAL_FONT);
- file_name_font.SetFamily (wxFONTFAMILY_MODERN);
- file_name_font.SetPointSize (file_name_font.GetPointSize() - 2);
- _file_name->SetFont (file_name_font);
-
- int w;
- int h;
- _file_name->GetTextExtent (std_to_wx(string(max_file_name_length, 'X')), &w, &h);
- _file_name->SetMinSize (wxSize(w, -1));
+ auto overall_sizer = new wxBoxSizer (wxVERTICAL);
- overall_sizer->Add (_file_name, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, DCPOMATIC_SIZER_GAP);
+ overall_sizer->Add(_panel, 0, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
- _progress = new wxGauge (this, wxID_ANY, 100);
- overall_sizer->Add (_progress, 0, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
-
- wxButton* cancel = new wxButton (this, wxID_ANY, _("Cancel"));
- wxSizer* buttons = new wxBoxSizer (wxHORIZONTAL);
+ auto cancel = new wxButton(this, wxID_ANY, _("Cancel"));
+ auto buttons = new wxBoxSizer(wxHORIZONTAL);
buttons->AddStretchSpacer ();
buttons->Add (cancel, 0);
overall_sizer->Add (buttons, 0, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
@@ -94,26 +76,8 @@ VerifyDCPProgressDialog::run (shared_ptr<Job> job)
while (jm->work_to_do()) {
wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
dcpomatic_sleep_seconds (1);
- optional<float> const progress = job->progress ();
- if (progress) {
- _progress->SetValue (*progress * 100);
- } else {
- _progress->Pulse ();
- }
- string const sub = job->sub_name ();
- size_t colon = sub.find (":");
- if (colon != string::npos) {
- _job_name->SetLabel (std_to_wx(sub.substr(0, colon)));
- string file_name;
- if ((sub.length() - colon - 1) > max_file_name_length) {
- file_name = "..." + sub.substr(sub.length() - max_file_name_length + 3);
- } else {
- file_name = sub.substr(colon + 1);
- }
- _file_name->SetLabel (std_to_wx(file_name));
- } else {
- _job_name->SetLabel (std_to_wx(sub));
- }
+
+ _panel->update(job);
if (_cancel) {
break;
diff --git a/src/wx/verify_dcp_progress_dialog.h b/src/wx/verify_dcp_progress_dialog.h
index 190c302ea..1ae3336e8 100644
--- a/src/wx/verify_dcp_progress_dialog.h
+++ b/src/wx/verify_dcp_progress_dialog.h
@@ -25,10 +25,9 @@ LIBDCP_DISABLE_WARNINGS
LIBDCP_ENABLE_WARNINGS
#include <memory>
-class Job;
-class wxGauge;
-class wxStaticText;
+class Job;
+class VerifyDCPProgressPanel;
class VerifyDCPProgressDialog : public wxDialog
@@ -41,9 +40,7 @@ public:
private:
void cancel ();
- wxStaticText* _job_name;
- wxStaticText* _file_name;
- wxGauge* _progress;
+ VerifyDCPProgressPanel* _panel;
bool _cancel;
};
diff --git a/src/wx/verify_dcp_progress_panel.cc b/src/wx/verify_dcp_progress_panel.cc
new file mode 100644
index 000000000..4c4ce6883
--- /dev/null
+++ b/src/wx/verify_dcp_progress_panel.cc
@@ -0,0 +1,87 @@
+/*
+ Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "lib/job.h"
+#include "verify_dcp_progress_panel.h"
+#include "wx_util.h"
+
+
+using std::shared_ptr;
+using std::string;
+
+
+auto constexpr max_file_name_length = 80;
+
+
+
+VerifyDCPProgressPanel::VerifyDCPProgressPanel(wxWindow* parent)
+ : wxPanel(parent, wxID_ANY)
+{
+ auto overall_sizer = new wxBoxSizer(wxVERTICAL);
+
+ _job_name = new wxStaticText(this, wxID_ANY, wxT(""));
+ overall_sizer->Add(_job_name, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, DCPOMATIC_SIZER_GAP);
+
+ _file_name = new wxStaticText(this, wxID_ANY, wxT(""));
+ wxFont file_name_font(*wxNORMAL_FONT);
+ file_name_font.SetFamily(wxFONTFAMILY_MODERN);
+ file_name_font.SetPointSize(file_name_font.GetPointSize() - 2);
+ _file_name->SetFont(file_name_font);
+
+ int w;
+ int h;
+ _file_name->GetTextExtent(std_to_wx(string(max_file_name_length, 'X')), &w, &h);
+ _file_name->SetMinSize(wxSize(w, -1));
+
+ overall_sizer->Add(_file_name, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, DCPOMATIC_SIZER_GAP);
+
+ _progress = new wxGauge(this, wxID_ANY, 100);
+ overall_sizer->Add(_progress, 0, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
+
+ SetSizerAndFit (overall_sizer);
+}
+
+
+void
+VerifyDCPProgressPanel::update(shared_ptr<Job> job)
+{
+ auto const progress = job->progress();
+ if (progress) {
+ _progress->SetValue(*progress * 100);
+ } else {
+ _progress->Pulse();
+ }
+ string const sub = job->sub_name();
+ size_t colon = sub.find(":");
+ if (colon != string::npos) {
+ _job_name->SetLabel(std_to_wx(sub.substr(0, colon)));
+ string file_name;
+ if ((sub.length() - colon - 1) > max_file_name_length) {
+ file_name = "..." + sub.substr(sub.length() - max_file_name_length + 3);
+ } else {
+ file_name = sub.substr(colon + 1);
+ }
+ _file_name->SetLabel(std_to_wx(file_name));
+ } else {
+ _job_name->SetLabel(std_to_wx(sub));
+ }
+}
+
diff --git a/src/wx/verify_dcp_progress_panel.h b/src/wx/verify_dcp_progress_panel.h
new file mode 100644
index 000000000..3fde50827
--- /dev/null
+++ b/src/wx/verify_dcp_progress_panel.h
@@ -0,0 +1,44 @@
+/*
+ Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+#include <memory>
+
+
+class Job;
+
+
+class VerifyDCPProgressPanel : public wxPanel
+{
+public:
+ VerifyDCPProgressPanel(wxWindow* parent);
+
+ void update(std::shared_ptr<Job> job);
+
+private:
+ wxStaticText* _job_name;
+ wxStaticText* _file_name;
+ wxGauge* _progress;
+};
+
diff --git a/src/wx/verify_dcp_result_dialog.cc b/src/wx/verify_dcp_result_dialog.cc
new file mode 100644
index 000000000..806eac85a
--- /dev/null
+++ b/src/wx/verify_dcp_result_dialog.cc
@@ -0,0 +1,47 @@
+/*
+ Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "verify_dcp_result_dialog.h"
+#include "verify_dcp_result_panel.h"
+#include "wx_util.h"
+
+
+using std::shared_ptr;
+
+
+VerifyDCPResultDialog::VerifyDCPResultDialog(wxWindow* parent, shared_ptr<VerifyDCPJob> job)
+ : wxDialog (parent, wxID_ANY, _("DCP verification"), wxDefaultPosition, {600, 400})
+{
+ auto sizer = new wxBoxSizer (wxVERTICAL);
+
+ auto panel = new VerifyDCPResultPanel(this);
+ panel->fill(job);
+ sizer->Add(panel, 1, wxEXPAND);
+
+ auto buttons = CreateStdDialogButtonSizer(0);
+ sizer->Add (CreateSeparatedSizer(buttons), wxSizerFlags().Expand().DoubleBorder());
+ buttons->SetAffirmativeButton (new wxButton (this, wxID_OK));
+ buttons->Realize ();
+
+ SetSizer (sizer);
+ sizer->Layout ();
+ sizer->SetSizeHints (this);
+}
diff --git a/src/wx/verify_dcp_dialog.h b/src/wx/verify_dcp_result_dialog.h
index 076217b1f..8953ff8c5 100644
--- a/src/wx/verify_dcp_dialog.h
+++ b/src/wx/verify_dcp_result_dialog.h
@@ -30,8 +30,8 @@ class wxRichTextCtrl;
class VerifyDCPJob;
-class VerifyDCPDialog : public wxDialog
+class VerifyDCPResultDialog : public wxDialog
{
public:
- VerifyDCPDialog (wxWindow* parent, std::shared_ptr<VerifyDCPJob> job);
+ VerifyDCPResultDialog(wxWindow* parent, std::shared_ptr<VerifyDCPJob> job);
};
diff --git a/src/wx/verify_dcp_dialog.cc b/src/wx/verify_dcp_result_panel.cc
index c7a32e5dd..d10c94350 100644
--- a/src/wx/verify_dcp_dialog.cc
+++ b/src/wx/verify_dcp_result_panel.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2018 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
@@ -19,11 +19,14 @@
*/
-#include "verify_dcp_dialog.h"
+#include "dcpomatic_button.h"
+#include "file_dialog.h"
+#include "verify_dcp_result_panel.h"
#include "wx_util.h"
#include "lib/verify_dcp_job.h"
#include <dcp/raw_convert.h>
#include <dcp/verify.h>
+#include <dcp/verify_report.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/richtext/richtextctrl.h>
@@ -39,55 +42,66 @@ using std::string;
using std::vector;
-VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job)
- : wxDialog (parent, wxID_ANY, _("DCP verification"), wxDefaultPosition, {600, 400})
+VerifyDCPResultPanel::VerifyDCPResultPanel(wxWindow* parent)
+ : wxPanel(parent, wxID_ANY)
{
- auto sizer = new wxBoxSizer (wxVERTICAL);
- auto notebook = new wxNotebook (this, wxID_ANY);
- sizer->Add (notebook, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
-
- map<dcp::VerificationNote::Type, wxRichTextCtrl*> pages;
- pages[dcp::VerificationNote::Type::ERROR] = new wxRichTextCtrl (notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
- notebook->AddPage (pages[dcp::VerificationNote::Type::ERROR], _("Errors"));
- pages[dcp::VerificationNote::Type::BV21_ERROR] = new wxRichTextCtrl (notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
- notebook->AddPage (pages[dcp::VerificationNote::Type::BV21_ERROR], _("SMPTE Bv2.1 errors"));
- pages[dcp::VerificationNote::Type::WARNING] = new wxRichTextCtrl (notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
- notebook->AddPage (pages[dcp::VerificationNote::Type::WARNING], _("Warnings"));
-
- auto summary = new wxStaticText (this, wxID_ANY, wxT(""));
- sizer->Add (summary, 0, wxALL, DCPOMATIC_DIALOG_BORDER);
-
- auto buttons = CreateStdDialogButtonSizer (0);
- sizer->Add (CreateSeparatedSizer(buttons), wxSizerFlags().Expand().DoubleBorder());
- buttons->SetAffirmativeButton (new wxButton (this, wxID_OK));
- buttons->Realize ();
-
- SetSizer (sizer);
- sizer->Layout ();
- sizer->SetSizeHints (this);
-
- for (auto const& i: pages) {
+ auto sizer = new wxBoxSizer(wxVERTICAL);
+ auto notebook = new wxNotebook(this, wxID_ANY);
+ sizer->Add(notebook, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+
+ _pages[dcp::VerificationNote::Type::ERROR] = new wxRichTextCtrl(notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
+ notebook->AddPage(_pages[dcp::VerificationNote::Type::ERROR], _("Errors"));
+ _pages[dcp::VerificationNote::Type::BV21_ERROR] = new wxRichTextCtrl(notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
+ notebook->AddPage(_pages[dcp::VerificationNote::Type::BV21_ERROR], _("SMPTE Bv2.1 errors"));
+ _pages[dcp::VerificationNote::Type::WARNING] = new wxRichTextCtrl(notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
+ notebook->AddPage(_pages[dcp::VerificationNote::Type::WARNING], _("Warnings"));
+
+ _summary = new wxStaticText(this, wxID_ANY, wxT(""));
+ sizer->Add(_summary, 0, wxALL, DCPOMATIC_DIALOG_BORDER);
+
+ auto save_sizer = new wxBoxSizer(wxHORIZONTAL);
+ _save_text_report = new Button(this, _("Save report as text..."));
+ save_sizer->Add(_save_text_report, 0, wxALL, DCPOMATIC_SIZER_GAP);
+ _save_html_report = new Button(this, _("Save report as HTML..."));
+ save_sizer->Add(_save_html_report, 0, wxALL, DCPOMATIC_SIZER_GAP);
+ sizer->Add(save_sizer);
+
+ SetSizer(sizer);
+ sizer->Layout();
+ sizer->SetSizeHints(this);
+
+ for (auto const& i: _pages) {
i.second->GetCaret()->Hide();
}
- if (job->finished_ok() && job->notes().empty()) {
- summary->SetLabel (_("DCP validates OK."));
+ _save_text_report->bind(&VerifyDCPResultPanel::save_text_report, this);
+ _save_html_report->bind(&VerifyDCPResultPanel::save_html_report, this);
+
+ _save_text_report->Enable(false);
+ _save_html_report->Enable(false);
+}
+
+
+void
+VerifyDCPResultPanel::fill(shared_ptr<VerifyDCPJob> job)
+{
+ if (job->finished_ok() && job->result().notes.empty()) {
+ _summary->SetLabel(_("DCP validates OK."));
return;
}
- map<dcp::VerificationNote::Type, int> counts;
- counts[dcp::VerificationNote::Type::WARNING] = 0;
- counts[dcp::VerificationNote::Type::BV21_ERROR] = 0;
- counts[dcp::VerificationNote::Type::ERROR] = 0;
-
- auto add_bullet = [&pages](dcp::VerificationNote::Type type, wxString message) {
- pages[type]->BeginStandardBullet(N_("standard/diamond"), 1, 50);
- pages[type]->WriteText (message);
- pages[type]->Newline ();
- pages[type]->EndStandardBullet ();
+ vector<dcp::VerificationNote::Type> types = {
+ dcp::VerificationNote::Type::WARNING,
+ dcp::VerificationNote::Type::BV21_ERROR,
+ dcp::VerificationNote::Type::ERROR
};
- auto add = [&counts, &add_bullet](dcp::VerificationNote note, wxString message) {
+ map<dcp::VerificationNote::Type, std::vector<wxString>> notes;
+ for (auto type: types) {
+ notes[type] = {};
+ }
+
+ auto add = [&notes](dcp::VerificationNote note, wxString message) {
if (note.reference_hash()) {
message.Replace("%reference_hash", std_to_wx(note.reference_hash().get()));
}
@@ -123,23 +137,25 @@ VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job
if (note.other_id()) {
message.Replace("%other_id", std_to_wx(note.other_id().get()));
}
- add_bullet (note.type(), message);
- counts[note.type()]++;
+ if (note.cpl_id()) {
+ message.Replace("%cpl", std_to_wx(note.cpl_id().get()));
+ }
+
+ notes[note.type()].push_back(message);
};
if (job->finished_in_error() && job->error_summary() != "") {
/* We have an error that did not come from dcp::verify */
- add_bullet (dcp::VerificationNote::Type::ERROR, std_to_wx(job->error_summary()));
- ++counts[dcp::VerificationNote::Type::ERROR];
+ notes[dcp::VerificationNote::Type::ERROR].push_back(std_to_wx(job->error_summary()));
}
- for (auto i: job->notes()) {
+ for (auto i: job->result().notes) {
switch (i.code()) {
case dcp::VerificationNote::Code::FAILED_READ:
- add (i, _("Could not read DCP (%n)"));
+ add(i, _("Could not read DCP (%n)"));
break;
case dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES:
- add(i, _("The hash (%reference_hash) of the CPL %n in the PKL does not agree with the CPL file (%calculated_hash). This probably means that the CPL file is corrupt."));
+ add(i, _("The hash (%reference_hash) of the CPL %cpl in the PKL does not agree with the CPL file (%calculated_hash). This probably means that the CPL file is corrupt."));
break;
case dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE:
add(i, _("The picture in a reel has a frame rate of %n, which is not valid."));
@@ -259,11 +275,14 @@ VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job
case dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE:
add(i, _("The sound asset %f has an invalid frame rate of %n."));
break;
+ case dcp::VerificationNote::Code::INVALID_SOUND_BIT_DEPTH:
+ add(i, _("The sound asset %f has an invalid bit depth of %n."));
+ break;
case dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT:
- add(i, _("The CPL %n has no <AnnotationText> tag."));
+ add(i, _("The CPL %cpl has no <AnnotationText> tag."));
break;
case dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT:
- add(i, _("The CPL %n has an <AnnotationText> which is not the same as its <ContentTitleText>."));
+ add(i, _("The CPL %cpcpl has an <AnnotationText> which is not the same as its <ContentTitleText>."));
break;
case dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION:
add(i, _("At least one asset in a reel does not have the same duration as the others."));
@@ -308,19 +327,19 @@ VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job
add(i, _("The DCP has a LFOC of %n instead of the reel duration minus one."));
break;
case dcp::VerificationNote::Code::MISSING_CPL_METADATA:
- add(i, _("The CPL %n has no CPL metadata tag."));
+ add(i, _("The CPL %cpl has no CPL metadata tag."));
break;
case dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER:
- add(i, _("The CPL %n has no CPL metadata version number tag."));
+ add(i, _("The CPL %cpl has no CPL metadata version number tag."));
break;
case dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA:
- add(i, _("The CPL %n has no CPL extension metadata tag."));
+ add(i, _("The CPL %cpl has no CPL extension metadata tag."));
break;
case dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA:
add(i, _("The CPL %f has an invalid CPL extension metadata tag (%n)"));
break;
case dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT:
- add(i, _("The CPL %n has encrypted content but is not signed."));
+ add(i, _("The CPL %cpl has encrypted content but is not signed."));
break;
case dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT:
add(i, _("The PKL %n has encrypted content but is not signed."));
@@ -382,7 +401,7 @@ VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job
case dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION:
{
vector<string> parts;
- boost::split (parts, i.note().get(), boost::is_any_of(" "));
+ boost::split(parts, i.note().get(), boost::is_any_of(" "));
add(i, wxString::Format(_("The reel duration (%s) of some timed text is not the same as the ContainerDuration (%s) of its MXF."), std_to_wx(parts[0]), std_to_wx(parts[1])));
break;
}
@@ -447,54 +466,127 @@ VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job
add(i, _("The asset with ID %id in the asset map actually has an id of %other_id"));
break;
case dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT:
- add(i, _("The <LabelText> in a <ContentVersion> in CPL %id is empty"));
+ add(i, _("The <LabelText> in a <ContentVersion> in CPL %cpl is empty"));
break;
case dcp::VerificationNote::Code::INVALID_CPL_NAMESPACE:
- add(i, _("The CPL %f has an invalid namespace %n"));
+ add(i, _("The CPL %cpl has an invalid namespace %n"));
break;
case dcp::VerificationNote::Code::MISSING_CPL_CONTENT_VERSION:
- add(i, _("The CPL %n has no <ContentVersion> tag"));
+ add(i, _("The CPL %cpl has no <ContentVersion> tag"));
+ break;
+ case dcp::VerificationNote::Code::MATCHING_CPL_HASHES:
+ case dcp::VerificationNote::Code::CORRECT_PICTURE_HASH:
+ case dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES:
+ case dcp::VerificationNote::Code::VALID_RELEASE_TERRITORY:
+ case dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT:
+ case dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL:
+ case dcp::VerificationNote::Code::ALL_ENCRYPTED:
+ case dcp::VerificationNote::Code::NONE_ENCRYPTED:
+ case dcp::VerificationNote::Code::VALID_CONTENT_KIND:
+ case dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA:
+ case dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT:
+ /* These are all "OK" messages which we don't report here */
break;
}
}
wxString summary_text;
- if (counts[dcp::VerificationNote::Type::ERROR] == 1) {
+ if (notes[dcp::VerificationNote::Type::ERROR].size() == 1) {
/// TRANSLATORS: this will be used at the start of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
summary_text = _("1 error, ");
} else {
/// TRANSLATORS: this will be used at the start of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
- summary_text = wxString::Format("%d errors, ", counts[dcp::VerificationNote::Type::ERROR]);
+ summary_text = wxString::Format("%d errors, ", static_cast<int>(notes[dcp::VerificationNote::Type::ERROR].size()));
}
- if (counts[dcp::VerificationNote::Type::BV21_ERROR] == 1) {
+ if (notes[dcp::VerificationNote::Type::BV21_ERROR].size() == 1) {
/// TRANSLATORS: this will be used in the middle of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
summary_text += _("1 Bv2.1 error, ");
} else {
/// TRANSLATORS: this will be used in the middle of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
- summary_text += wxString::Format("%d Bv2.1 errors, ", counts[dcp::VerificationNote::Type::BV21_ERROR]);
+ summary_text += wxString::Format("%d Bv2.1 errors, ", static_cast<int>(notes[dcp::VerificationNote::Type::BV21_ERROR].size()));
}
- if (counts[dcp::VerificationNote::Type::WARNING] == 1) {
+ if (notes[dcp::VerificationNote::Type::WARNING].size() == 1) {
/// TRANSLATORS: this will be used at the end of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
summary_text += _("and 1 warning.");
} else {
/// TRANSLATORS: this will be used at the end of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
- summary_text += wxString::Format("and %d warnings.", counts[dcp::VerificationNote::Type::WARNING]);
+ summary_text += wxString::Format("and %d warnings.", static_cast<int>(notes[dcp::VerificationNote::Type::WARNING].size()));
+ }
+
+ _summary->SetLabel(summary_text);
+
+ auto add_bullet = [this](dcp::VerificationNote::Type type, wxString message) {
+ _pages[type]->BeginStandardBullet(N_("standard/diamond"), 1, 50);
+ _pages[type]->WriteText(message);
+ _pages[type]->Newline();
+ _pages[type]->EndStandardBullet();
+ };
+
+ if (notes[dcp::VerificationNote::Type::ERROR].empty()) {
+ add_bullet(dcp::VerificationNote::Type::ERROR, _("No errors found."));
}
- summary->SetLabel(summary_text);
+ if (notes[dcp::VerificationNote::Type::BV21_ERROR].empty()) {
+ add_bullet(dcp::VerificationNote::Type::BV21_ERROR, _("No SMPTE Bv2.1 errors found."));
+ }
- if (counts[dcp::VerificationNote::Type::ERROR] == 0) {
- add_bullet (dcp::VerificationNote::Type::ERROR, _("No errors found."));
+ if (notes[dcp::VerificationNote::Type::WARNING].empty()) {
+ add_bullet(dcp::VerificationNote::Type::WARNING, _("No warnings found."));
}
- if (counts[dcp::VerificationNote::Type::BV21_ERROR] == 0) {
- add_bullet (dcp::VerificationNote::Type::BV21_ERROR, _("No SMPTE Bv2.1 errors found."));
+ for (auto type: types) {
+ std::sort(notes[type].begin(), notes[type].end());
+ for (auto i = notes[type].begin(); i != notes[type].end(); ++i) {
+ int extra = 0;
+ while (std::next(i) != notes[type].end() && *std::next(i) == *i) {
+ ++i;
+ ++extra;
+ }
+ if (extra == 1) {
+ add_bullet(type, wxString::Format(_("%s (repeated twice)."), i->SubString(0, i->Length() - 2)));
+ } else if (extra > 1) {
+ add_bullet(type, wxString::Format(_("%s (repeated %d times)."), i->SubString(0, i->Length() - 2), extra + 1));
+ } else {
+ add_bullet(type, *i);
+ }
+ }
}
- if (counts[dcp::VerificationNote::Type::WARNING] == 0) {
- add_bullet (dcp::VerificationNote::Type::WARNING, _("No warnings found."));
+ _job = job;
+ _save_text_report->Enable(true);
+ _save_html_report->Enable(true);
+}
+
+
+template <class T>
+void save(wxWindow* parent, wxString filter, dcp::VerificationResult const& result)
+{
+ FileDialog dialog(parent, _("Verification report"), filter, wxFD_SAVE | wxFD_OVERWRITE_PROMPT, "SaveVerificationReport");
+ if (!dialog.show()) {
+ return;
+ }
+
+ T formatter(dialog.path());
+ dcp::verify_report(result, formatter);
+}
+
+
+void
+VerifyDCPResultPanel::save_text_report()
+{
+ if (_job) {
+ save<dcp::TextFormatter>(this, wxT("Text files (*.txt)|*.txt"), _job->result());
+ }
+}
+
+
+void
+VerifyDCPResultPanel::save_html_report()
+{
+ if (_job) {
+ save<dcp::HTMLFormatter>(this, wxT("HTML files (*.htm;*html)|*.htm;*.html"), _job->result());
}
}
diff --git a/src/wx/verify_dcp_result_panel.h b/src/wx/verify_dcp_result_panel.h
new file mode 100644
index 000000000..8cf92118b
--- /dev/null
+++ b/src/wx/verify_dcp_result_panel.h
@@ -0,0 +1,50 @@
+/*
+ Copyright (C) 2018 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <dcp/verify.h>
+#include <wx/wx.h>
+#include <map>
+#include <memory>
+
+
+class Button;
+class VerifyDCPJob;
+class wxRichTextCtrl;
+
+
+class VerifyDCPResultPanel : public wxPanel
+{
+public:
+ VerifyDCPResultPanel(wxWindow* parent);
+
+ void fill(std::shared_ptr<VerifyDCPJob> job);
+
+private:
+ void save_text_report();
+ void save_html_report();
+
+ wxStaticText* _summary;
+ std::map<dcp::VerificationNote::Type, wxRichTextCtrl*> _pages;
+ Button* _save_text_report;
+ Button* _save_html_report;
+
+ std::shared_ptr<VerifyDCPJob> _job;
+};
diff --git a/src/wx/video_panel.cc b/src/wx/video_panel.cc
index e065f9bb4..65f381fe3 100644
--- a/src/wx/video_panel.cc
+++ b/src/wx/video_panel.cc
@@ -76,14 +76,6 @@ VideoPanel::VideoPanel (ContentPanel* p)
void
VideoPanel::create ()
{
- _reference = new CheckBox (this, _("Use this DCP's video as OV and make VF"));
- _reference_note = new StaticText (this, wxT(""));
- _reference_note->Wrap (200);
- auto font = _reference_note->GetFont();
- font.SetStyle(wxFONTSTYLE_ITALIC);
- font.SetPointSize(font.GetPointSize() - 1);
- _reference_note->SetFont(font);
-
_type_label = create_label (this, _("Type"), true);
_frame_type = new ContentChoice<VideoContent, VideoFrameType> (
this,
@@ -207,6 +199,9 @@ VideoPanel::create ()
_range->Append (_("Video (MPEG, 16-235)"));
_description = new StaticText (this, wxT ("\n \n \n \n \n"), wxDefaultPosition, wxDefaultSize);
+ auto font = _description->GetFont();
+ font.SetStyle(wxFONTSTYLE_ITALIC);
+ font.SetPointSize(font.GetPointSize() - 1);
_description->SetFont(font);
_left_crop->wrapped()->SetRange (0, 4096);
@@ -227,7 +222,6 @@ VideoPanel::create ()
_fade_in->Changed.connect (boost::bind (&VideoPanel::fade_in_changed, this));
_fade_out->Changed.connect (boost::bind (&VideoPanel::fade_out_changed, this));
- _reference->bind(&VideoPanel::reference_clicked, this);
_scale_fit->Bind (wxEVT_RADIOBUTTON, boost::bind (&VideoPanel::scale_fit_clicked, this));
_scale_custom->Bind (wxEVT_RADIOBUTTON, boost::bind (&VideoPanel::scale_custom_clicked, this));
_scale_custom_edit->Bind (wxEVT_BUTTON, boost::bind (&VideoPanel::scale_custom_edit_clicked, this));
@@ -248,12 +242,6 @@ VideoPanel::add_to_grid ()
{
int r = 0;
- auto reference_sizer = new wxBoxSizer (wxVERTICAL);
- reference_sizer->Add (_reference, 0);
- reference_sizer->Add (_reference_note, 0);
- _grid->Add (reference_sizer, wxGBPosition(r, 0), wxGBSpan(1, 3));
- ++r;
-
add_label_to_sizer (_grid, _type_label, true, wxGBPosition(r, 0));
_frame_type->add (_grid, wxGBPosition(r, 1), wxGBSpan(1, 2));
++r;
@@ -462,15 +450,6 @@ VideoPanel::film_content_changed (int property)
} else {
_fade_out->clear ();
}
- } else if (property == DCPContentProperty::REFERENCE_VIDEO) {
- if (vc.size() == 1) {
- shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (vc.front ());
- checked_set (_reference, dcp ? dcp->reference_video () : false);
- } else {
- checked_set (_reference, false);
- }
-
- setup_sensitivity ();
} else if (property == VideoContentProperty::RANGE) {
if (vcs) {
checked_set (_range, vcs->video->range() == VideoRange::FULL ? 0 : 1);
@@ -598,15 +577,7 @@ VideoPanel::setup_sensitivity ()
dcp = dynamic_pointer_cast<DCPContent> (sel.front ());
}
- string why_not;
- bool const can_reference = dcp && dcp->can_reference_video (_parent->film(), why_not);
- wxString cannot;
- if (why_not.empty()) {
- cannot = _("Cannot reference this DCP's video.");
- } else {
- cannot = _("Cannot reference this DCP's video: ") + std_to_wx(why_not);
- }
- setup_refer_button (_reference, _reference_note, dcp, can_reference, cannot);
+ bool const reference = dcp && dcp->reference_video();
bool any_use = false;
for (auto i: _parent->selected_video()) {
@@ -615,7 +586,7 @@ VideoPanel::setup_sensitivity ()
}
}
- bool const enable = !_reference->GetValue() && any_use;
+ bool const enable = !reference && any_use;
if (!enable) {
_frame_type->wrapped()->Enable (false);
@@ -688,23 +659,6 @@ VideoPanel::fade_out_changed ()
void
-VideoPanel::reference_clicked ()
-{
- auto c = _parent->selected ();
- if (c.size() != 1) {
- return;
- }
-
- auto d = dynamic_pointer_cast<DCPContent> (c.front ());
- if (!d) {
- return;
- }
-
- d->set_reference_video (_reference->GetValue ());
-}
-
-
-void
VideoPanel::scale_fit_clicked ()
{
for (auto i: _parent->selected_video()) {
diff --git a/src/wx/video_panel.h b/src/wx/video_panel.h
index 686d1b99b..e6b689b03 100644
--- a/src/wx/video_panel.h
+++ b/src/wx/video_panel.h
@@ -53,7 +53,6 @@ public:
void content_selection_changed () override;
private:
- void reference_clicked ();
void colour_conversion_changed ();
void edit_colour_conversion_clicked ();
void range_changed ();
@@ -73,8 +72,6 @@ private:
void setup_description ();
void setup_sensitivity ();
- CheckBox* _reference;
- wxStaticText* _reference_note;
wxStaticText* _type_label;
ContentChoice<VideoContent, VideoFrameType>* _frame_type;
wxStaticText* _crop_label;
diff --git a/src/wx/video_view.h b/src/wx/video_view.h
index 387cca9f6..3ea03a5fd 100644
--- a/src/wx/video_view.h
+++ b/src/wx/video_view.h
@@ -23,6 +23,7 @@
#define DCPOMATIC_VIDEO_VIEW_H
+#include "optimisation.h"
#include "lib/dcpomatic_time.h"
#include "lib/exception_store.h"
#include "lib/signaller.h"
@@ -131,8 +132,8 @@ public:
_three_d = t;
}
- void set_optimise_for_j2k (bool o) {
- _optimise_for_j2k = o;
+ void set_optimisation(Optimisation o) {
+ _optimisation = o;
}
protected:
@@ -180,7 +181,7 @@ protected:
StateTimer _state_timer;
- bool _optimise_for_j2k = false;
+ Optimisation _optimisation = Optimisation::NONE;
private:
/** Mutex protecting all the state in this class */
diff --git a/src/wx/window_metrics.cc b/src/wx/window_metrics.cc
new file mode 100644
index 000000000..4b0c6ce66
--- /dev/null
+++ b/src/wx/window_metrics.cc
@@ -0,0 +1,45 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "window_metrics.h"
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/app.h>
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+
+
+void
+WindowMetrics::show(wxWindow* window) const
+{
+#ifdef DCPOMATIC_LINUX
+ auto const position_before = position;
+#endif
+
+ window->Show();
+
+#ifdef DCPOMATIC_LINUX
+ wxTheApp->CallAfter([window, position_before] {
+ window->SetPosition(position_before);
+ });
+#endif
+}
+
diff --git a/src/wx/window_metrics.h b/src/wx/window_metrics.h
new file mode 100644
index 000000000..30a921134
--- /dev/null
+++ b/src/wx/window_metrics.h
@@ -0,0 +1,39 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/gdicmn.h>
+LIBDCP_ENABLE_WARNINGS
+
+
+class wxWindow;
+
+
+class WindowMetrics
+{
+public:
+ void show(wxWindow* window) const;
+
+ wxPoint position = wxDefaultPosition;
+ wxSize size = wxDefaultSize;
+};
+
diff --git a/src/wx/wscript b/src/wx/wscript
index a6eefa69f..0255149ed 100644
--- a/src/wx/wscript
+++ b/src/wx/wscript
@@ -48,12 +48,23 @@ sources = """
content_panel.cc
content_properties_dialog.cc
content_sub_panel.cc
+ content_timeline.cc
+ content_timeline_atmos_view.cc
+ content_timeline_audio_view.cc
+ content_timeline_dialog.cc
+ content_timeline_text_view.cc
+ content_timeline_video_view.cc
+ content_timeline_view.cc
content_version_dialog.cc
content_view.cc
controls.cc
credentials_download_certificate_panel.cc
custom_scale_dialog.cc
+ dcp_referencing_dialog.cc
dcp_panel.cc
+ dcp_timeline.cc
+ dcp_timeline_dialog.cc
+ dcp_timeline_reel_marker_view.cc
dcp_text_track_dialog.cc
dcpomatic_button.cc
dcpomatic_choice.cc
@@ -102,6 +113,7 @@ sources = """
language_subtag_panel.cc
language_tag_dialog.cc
language_tag_widget.cc
+ load_config_from_zip_dialog.cc
kdm_choice.cc
make_chain_dialog.cc
markers.cc
@@ -159,27 +171,25 @@ sources = """
timer_display.cc
timecode.cc
timeline.cc
- timeline_atmos_content_view.cc
timeline_content_view.cc
- timeline_dialog.cc
- timeline_audio_content_view.cc
timeline_labels_view.cc
- timeline_text_content_view.cc
timeline_reels_view.cc
timeline_time_axis_view.cc
- timeline_video_content_view.cc
- timeline_view.cc
timing_panel.cc
try_unmount_dialog.cc
update_dialog.cc
- verify_dcp_dialog.cc
+ verify_dcp_result_dialog.cc
+ verify_dcp_result_panel.cc
verify_dcp_progress_dialog.cc
+ verify_dcp_progress_panel.cc
video_panel.cc
video_view.cc
video_waveform_dialog.cc
video_waveform_plot.cc
- wx_util.cc
+ window_metrics.cc
wx_signal_manager.cc
+ wx_util.cc
+ wx_variant.cc
"""
def configure(conf):
diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc
index 28ac6ab5c..1c04a4755 100644
--- a/src/wx/wx_util.cc
+++ b/src/wx/wx_util.cc
@@ -31,20 +31,22 @@
#include "static_text.h"
#include "wx_ptr.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/config.h"
#include "lib/cross.h"
#include "lib/job.h"
#include "lib/job_manager.h"
#include "lib/util.h"
+#include "lib/variant.h"
#include "lib/version.h"
#include <dcp/locale_convert.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
-#include <wx/spinctrl.h>
-#include <wx/splash.h>
-#include <wx/progdlg.h>
#include <wx/filepicker.h>
+#include <wx/progdlg.h>
#include <wx/sizer.h>
+#include <wx/spinctrl.h>
+#include <wx/splash.h>
LIBDCP_ENABLE_WARNINGS
#include <boost/thread.hpp>
@@ -161,7 +163,7 @@ add_label_to_sizer (wxGridBagSizer* s, wxStaticText* t, bool, wxGBPosition pos,
void
error_dialog (wxWindow* parent, wxString m, optional<wxString> e)
{
- auto d = make_wx<wxMessageDialog>(parent, m, _("DCP-o-matic"), wxOK | wxICON_ERROR);
+ auto d = make_wx<wxMessageDialog>(parent, m, variant::wx::dcpomatic(), wxOK | wxICON_ERROR);
if (e) {
wxString em = *e;
em[0] = wxToupper (em[0]);
@@ -178,7 +180,7 @@ error_dialog (wxWindow* parent, wxString m, optional<wxString> e)
void
message_dialog (wxWindow* parent, wxString m)
{
- auto d = make_wx<wxMessageDialog>(parent, m, _("DCP-o-matic"), wxOK | wxICON_INFORMATION);
+ auto d = make_wx<wxMessageDialog>(parent, m, variant::wx::dcpomatic(), wxOK | wxICON_INFORMATION);
d->ShowModal ();
}
@@ -187,7 +189,7 @@ message_dialog (wxWindow* parent, wxString m)
bool
confirm_dialog (wxWindow* parent, wxString m)
{
- auto d = make_wx<wxMessageDialog>(parent, m, _("DCP-o-matic"), wxYES_NO | wxICON_QUESTION);
+ auto d = make_wx<wxMessageDialog>(parent, m, variant::wx::dcpomatic(), wxYES_NO | wxICON_QUESTION);
return d->ShowModal() == wxID_YES;
}
@@ -409,6 +411,65 @@ checked_set(RegionSubtagWidget* widget, optional<dcp::LanguageTag::RegionSubtag>
}
+#ifdef DCPOMATIC_OSX
+
+void
+dcpomatic_setup_i18n()
+{
+ wxLog::EnableLogging();
+
+ auto get_locale_value = [](CFLocaleKey key) {
+ CFLocaleRef cflocale = CFLocaleCopyCurrent();
+ auto value = (CFStringRef) CFLocaleGetValue(cflocale, key);
+ char buffer[64];
+ CFStringGetCString(value, buffer, sizeof(buffer), kCFStringEncodingUTF8);
+ CFRelease(cflocale);
+ return string(buffer);
+ };
+
+ auto translations = new wxTranslations();
+
+ auto config_lang = Config::instance()->language();
+ if (config_lang && !config_lang->empty()) {
+ translations->SetLanguage(std_to_wx(*config_lang));
+ } else {
+ /* We want to use the user's preferred language. It seems that if we use the wxWidgets default we will get the
+ * language for the locale, which may not be what we want (e.g. for a machine in Germany, configured for DE locale,
+ * but with the preferred language set to English).
+ *
+ * Instead, the the language code from macOS then get the corresponding canonical language string with region,
+ * which wxTranslations::SetLanguage will accept.
+ */
+ auto const language_code = get_locale_value(kCFLocaleLanguageCode);
+ /* Ideally this would be wxUILocale (as wxLocale is deprecated) but we want to keep this building
+ * with the old wxWidgets we use for the older macOS builds.
+ */
+ auto const info = wxLocale::FindLanguageInfo(std_to_wx(language_code));
+ if (info) {
+#if wxCHECK_VERSION(3, 1, 6)
+ translations->SetLanguage(info->GetCanonicalWithRegion());
+#else
+ translations->SetLanguage(info->CanonicalName);
+#endif
+ }
+ }
+
+#ifdef DCPOMATIC_DEBUG
+ wxFileTranslationsLoader::AddCatalogLookupPathPrefix(wxT("build/src/wx/mo"));
+ wxFileTranslationsLoader::AddCatalogLookupPathPrefix(wxT("build/src/tools/mo"));
+#endif
+
+ translations->AddStdCatalog();
+ translations->AddCatalog(wxT("libdcpomatic2-wx"));
+ translations->AddCatalog(wxT("dcpomatic2"));
+
+ wxTranslations::Set(translations);
+
+ dcpomatic_setup_gettext_i18n(config_lang.get_value_or(""));
+}
+
+#else
+
void
dcpomatic_setup_i18n ()
{
@@ -458,6 +519,8 @@ dcpomatic_setup_i18n ()
}
}
+#endif
+
int
wx_get (wxSpinCtrl* w)
@@ -623,35 +686,35 @@ display_progress (wxString title, wxString task)
int
get_offsets (vector<Offset>& offsets)
{
- offsets.push_back (Offset(_("UTC-11"), -11, 0));
- offsets.push_back (Offset(_("UTC-10"), -10, 0));
- offsets.push_back (Offset(_("UTC-9"), -9, 0));
- offsets.push_back (Offset(_("UTC-8"), -8, 0));
- offsets.push_back (Offset(_("UTC-7"), -7, 0));
- offsets.push_back (Offset(_("UTC-6"), -6, 0));
- offsets.push_back (Offset(_("UTC-5"), -5, 0));
- offsets.push_back (Offset(_("UTC-4:30"), -4, 30));
- offsets.push_back (Offset(_("UTC-4"), -4, 0));
- offsets.push_back (Offset(_("UTC-3:30"), -3, 30));
- offsets.push_back (Offset(_("UTC-3"), -3, 0));
- offsets.push_back (Offset(_("UTC-2"), -2, 0));
- offsets.push_back (Offset(_("UTC-1"), -1, 0));
+ offsets.push_back({_("UTC-11"), dcp::UTCOffset(-11, 0)});
+ offsets.push_back({_("UTC-10"), dcp::UTCOffset(-10, 0)});
+ offsets.push_back({_("UTC-9"), dcp::UTCOffset( -9, 0)});
+ offsets.push_back({_("UTC-8"), dcp::UTCOffset( -8, 0)});
+ offsets.push_back({_("UTC-7"), dcp::UTCOffset( -7, 0)});
+ offsets.push_back({_("UTC-6"), dcp::UTCOffset( -6, 0)});
+ offsets.push_back({_("UTC-5"), dcp::UTCOffset( -5, 0)});
+ offsets.push_back({_("UTC-4:30"),dcp::UTCOffset( -4, -30)});
+ offsets.push_back({_("UTC-4"), dcp::UTCOffset( -4, 0)});
+ offsets.push_back({_("UTC-3:30"),dcp::UTCOffset( -3, -30)});
+ offsets.push_back({_("UTC-3"), dcp::UTCOffset( -3, 0)});
+ offsets.push_back({_("UTC-2"), dcp::UTCOffset( -2, 0)});
+ offsets.push_back({_("UTC-1"), dcp::UTCOffset( -1, 0)});
int utc = offsets.size();
- offsets.push_back (Offset(_("UTC") , 0, 0));
- offsets.push_back (Offset(_("UTC+1"), 1, 0));
- offsets.push_back (Offset(_("UTC+2"), 2, 0));
- offsets.push_back (Offset(_("UTC+3"), 3, 0));
- offsets.push_back (Offset(_("UTC+4"), 4, 0));
- offsets.push_back (Offset(_("UTC+5"), 5, 0));
- offsets.push_back (Offset(_("UTC+5:30"), 5, 30));
- offsets.push_back (Offset(_("UTC+6"), 6, 0));
- offsets.push_back (Offset(_("UTC+7"), 7, 0));
- offsets.push_back (Offset(_("UTC+8"), 8, 0));
- offsets.push_back (Offset(_("UTC+9"), 9, 0));
- offsets.push_back (Offset(_("UTC+9:30"), 9, 30));
- offsets.push_back (Offset(_("UTC+10"), 10, 0));
- offsets.push_back (Offset(_("UTC+11"), 11, 0));
- offsets.push_back (Offset(_("UTC+12"), 12, 0));
+ offsets.push_back({_("UTC") , dcp::UTCOffset( 0, 0)});
+ offsets.push_back({_("UTC+1"), dcp::UTCOffset( 1, 0)});
+ offsets.push_back({_("UTC+2"), dcp::UTCOffset( 2, 0)});
+ offsets.push_back({_("UTC+3"), dcp::UTCOffset( 3, 0)});
+ offsets.push_back({_("UTC+4"), dcp::UTCOffset( 4, 0)});
+ offsets.push_back({_("UTC+5"), dcp::UTCOffset( 5, 0)});
+ offsets.push_back({_("UTC+5:30"),dcp::UTCOffset( 5, 30)});
+ offsets.push_back({_("UTC+6"), dcp::UTCOffset( 6, 0)});
+ offsets.push_back({_("UTC+7"), dcp::UTCOffset( 7, 0)});
+ offsets.push_back({_("UTC+8"), dcp::UTCOffset( 8, 0)});
+ offsets.push_back({_("UTC+9"), dcp::UTCOffset( 9, 0)});
+ offsets.push_back({_("UTC+9:30"),dcp::UTCOffset( 9, 30)});
+ offsets.push_back({_("UTC+10"), dcp::UTCOffset( 10, 0)});
+ offsets.push_back({_("UTC+11"), dcp::UTCOffset( 11, 0)});
+ offsets.push_back({_("UTC+12"), dcp::UTCOffset( 12, 0)});
return utc;
}
@@ -747,20 +810,12 @@ report_config_load_failure(wxWindow* parent, Config::LoadFailure what)
case Config::LoadFailure::CONFIG:
message_dialog(parent, _("The existing configuration failed to load. Default values will be used instead. These may take a short time to create."));
break;
- case Config::LoadFailure::CINEMAS:
- message_dialog(
- parent,
- _(wxString::Format("The cinemas list for creating KDMs (cinemas.xml) failed to load. Please check the numbered backup files in %s",
- std_to_wx(Config::instance()->cinemas_file().parent_path().string())))
- );
- break;
- case Config::LoadFailure::DKDM_RECIPIENTS:
- message_dialog(
- parent,
- _(wxString::Format("The recipients list for creating DKDMs (dkdm_recipients.xml) failed to load. Please check the numbered backup files in %s",
- std_to_wx(Config::instance()->dkdm_recipients_file().parent_path().string())))
- );
- break;
}
}
+
+wxString
+wx::report_problem()
+{
+ return std_to_wx(::report_problem());
+}
diff --git a/src/wx/wx_util.h b/src/wx/wx_util.h
index 66b01640c..db1b3ae0c 100644
--- a/src/wx/wx_util.h
+++ b/src/wx/wx_util.h
@@ -31,6 +31,7 @@
#include "wx_ptr.h"
#include "lib/config.h"
#include "lib/dcpomatic_time.h"
+#include <dcp/utc_offset.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/gbsizer.h>
@@ -56,7 +57,7 @@ class PasswordEntry;
#define DCPOMATIC_SIZER_GAP 8
#define DCPOMATIC_DIALOG_BORDER 12
#ifdef __WXGTK3__
-#define DCPOMATIC_SPIN_CTRL_WIDTH 118
+#define DCPOMATIC_SPIN_CTRL_WIDTH 124
#else
#define DCPOMATIC_SPIN_CTRL_WIDTH 56
#endif
@@ -74,7 +75,7 @@ class PasswordEntry;
/** Amount by which you need to top-pad a choice to make it line up, in some cases */
#ifdef DCPOMATIC_OSX
-#define DCPOMATIC_CHOICE_TOP_PAD 1
+#define DCPOMATIC_CHOICE_TOP_PAD 2
#else
#define DCPOMATIC_CHOICE_TOP_PAD 0
#endif
@@ -131,21 +132,24 @@ extern double dpi_scale_factor (wxWindow* window);
extern int search_ctrl_height ();
extern void report_config_load_failure(wxWindow* parent, Config::LoadFailure what);
+
struct Offset
{
- Offset (wxString n, int h, int m)
- : name (n)
- , hour (h)
- , minute (m)
+ Offset(wxString name_, dcp::UTCOffset offset_)
+ : name(name_)
+ , offset(offset_)
{}
wxString name;
- int hour;
- int minute;
+ dcp::UTCOffset offset;
};
extern int get_offsets (std::vector<Offset>& offsets);
+namespace wx {
+ extern wxString report_problem();
+}
+
extern void checked_set (FilePickerCtrl* widget, boost::filesystem::path value);
extern void checked_set (wxDirPickerCtrl* widget, boost::filesystem::path value);
diff --git a/src/wx/wx_variant.cc b/src/wx/wx_variant.cc
new file mode 100644
index 000000000..96b00bba4
--- /dev/null
+++ b/src/wx/wx_variant.cc
@@ -0,0 +1,147 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "wx_util.h"
+#include "wx_variant.h"
+#include "lib/variant.h"
+
+
+
+wxString
+variant::wx::dcpomatic()
+{
+ return std_to_wx(variant::dcpomatic());
+}
+
+wxString
+variant::wx::dcpomatic_batch_converter()
+{
+ return std_to_wx(variant::dcpomatic_batch_converter());
+}
+
+wxString
+variant::wx::dcpomatic_combiner()
+{
+ return std_to_wx(variant::dcpomatic_combiner());
+}
+
+wxString
+variant::wx::dcpomatic_disk_writer()
+{
+ return std_to_wx(variant::dcpomatic_disk_writer());
+}
+
+wxString
+variant::wx::dcpomatic_editor()
+{
+ return std_to_wx(variant::dcpomatic_editor());
+}
+
+wxString
+variant::wx::dcpomatic_encode_server()
+{
+ return std_to_wx(variant::dcpomatic_encode_server());
+}
+
+wxString
+variant::wx::dcpomatic_kdm_creator()
+{
+ return std_to_wx(variant::dcpomatic_kdm_creator());
+}
+
+wxString
+variant::wx::dcpomatic_player()
+{
+ return std_to_wx(variant::dcpomatic_player());
+}
+
+wxString
+variant::wx::dcpomatic_playlist_editor()
+{
+ return std_to_wx(variant::dcpomatic_playlist_editor());
+}
+
+wxString
+variant::wx::dcpomatic_verifier()
+{
+ return std_to_wx(variant::dcpomatic_verifier());
+}
+
+wxString
+variant::wx::insert_dcpomatic(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic());
+}
+
+wxString
+variant::wx::insert_dcpomatic_batch_converter(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_batch_converter());
+}
+
+wxString
+variant::wx::insert_dcpomatic_disk_writer(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_disk_writer());
+}
+
+wxString
+variant::wx::insert_dcpomatic_editor(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_editor());
+}
+
+wxString
+variant::wx::insert_dcpomatic_encode_server(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_encode_server());
+}
+
+wxString
+variant::wx::insert_dcpomatic_kdm_creator(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_kdm_creator());
+}
+
+wxString
+variant::wx::insert_dcpomatic_player(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_player());
+}
+
+wxString
+variant::wx::insert_dcpomatic_playlist_editor(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_playlist_editor());
+}
+
+wxString
+variant::wx::insert_dcpomatic_verifier(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_verifier());
+}
+
+wxString
+variant::wx::report_problem_email()
+{
+ return std_to_wx(variant::report_problem_email());
+}
+
diff --git a/src/wx/wx_variant.h b/src/wx/wx_variant.h
new file mode 100644
index 000000000..bf2bcaee0
--- /dev/null
+++ b/src/wx/wx_variant.h
@@ -0,0 +1,54 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <wx/wx.h>
+
+
+namespace variant {
+namespace wx {
+
+
+wxString dcpomatic();
+wxString dcpomatic_batch_converter();
+wxString dcpomatic_combiner();
+wxString dcpomatic_disk_writer();
+wxString dcpomatic_editor();
+wxString dcpomatic_encode_server();
+wxString dcpomatic_kdm_creator();
+wxString dcpomatic_player();
+wxString dcpomatic_playlist_editor();
+wxString dcpomatic_verifier();
+
+wxString insert_dcpomatic(wxString const& s);
+wxString insert_dcpomatic_batch_converter(wxString const& s);
+wxString insert_dcpomatic_disk_writer(wxString const& s);
+wxString insert_dcpomatic_editor(wxString const& s);
+wxString insert_dcpomatic_encode_server(wxString const& s);
+wxString insert_dcpomatic_kdm_creator(wxString const& s);
+wxString insert_dcpomatic_player(wxString const& s);
+wxString insert_dcpomatic_playlist_editor(wxString const& s);
+wxString insert_dcpomatic_verifier(wxString const& s);
+
+wxString report_problem_email();
+
+
+}
+}
diff --git a/test/2536_regression_test.cc b/test/2536_regression_test.cc
index 7233839ae..9f74ffd09 100644
--- a/test/2536_regression_test.cc
+++ b/test/2536_regression_test.cc
@@ -37,7 +37,7 @@ BOOST_AUTO_TEST_CASE(crash_rendering_vf_interop_subs_test)
auto prefix = std::string("crash_rendering_vf_interop_subs_test");
auto video = content_factory("test/data/flat_red.png");
- auto ov = new_test_film2(prefix + "_ov", video);
+ auto ov = new_test_film(prefix + "_ov", video);
ov->set_interop(true);
make_and_verify_dcp(
@@ -48,7 +48,7 @@ BOOST_AUTO_TEST_CASE(crash_rendering_vf_interop_subs_test)
auto ov_dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
auto subtitles = content_factory("test/data/short.srt");
- auto vf = new_test_film2(prefix + "_vf", { ov_dcp, subtitles.front() });
+ auto vf = new_test_film(prefix + "_vf", { ov_dcp, subtitles.front() });
vf->set_interop(true);
vf->set_reel_type(ReelType::BY_VIDEO_CONTENT);
ov_dcp->set_reference_video(true);
@@ -66,7 +66,7 @@ BOOST_AUTO_TEST_CASE(crash_rendering_vf_interop_subs_test)
auto vf_dcp = make_shared<DCPContent>(vf->dir(vf->dcp_name()));
vf_dcp->add_ov(ov->dir(ov->dcp_name()));
- auto test = new_test_film2(prefix + "_test", { vf_dcp });
+ auto test = new_test_film(prefix + "_test", { vf_dcp });
vf_dcp->text[0]->set_use(true);
auto player = make_shared<Player>(test, Image::Alignment::COMPACT);
diff --git a/test/4k_test.cc b/test/4k_test.cc
index 9e80a6e27..a1b9477b1 100644
--- a/test/4k_test.cc
+++ b/test/4k_test.cc
@@ -44,15 +44,11 @@ using std::make_shared;
BOOST_AUTO_TEST_CASE (fourk_test)
{
- auto film = new_test_film ("4k_test");
- LogSwitcher ls (film->log());
- film->set_name ("4k_test");
auto c = make_shared<FFmpegContent>("test/data/test.mp4");
+ auto film = new_test_film("4k_test", { c });
+ LogSwitcher ls (film->log());
film->set_resolution (Resolution::FOUR_K);
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
- film->set_container (Ratio::from_id ("185"));
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
+ film->set_dcp_content_type(DCPContentType::from_isdcf_name("FTR"));
make_and_verify_dcp (
film,
diff --git a/test/atmos_test.cc b/test/atmos_test.cc
index 9fdddc979..ef7449f20 100644
--- a/test/atmos_test.cc
+++ b/test/atmos_test.cc
@@ -45,7 +45,7 @@ BOOST_AUTO_TEST_CASE (atmos_passthrough_test)
{
Cleanup cl;
- auto film = new_test_film2 (
+ auto film = new_test_film(
"atmos_passthrough_test",
content_factory(TestPaths::private_data() / "atmos_asset.mxf"),
&cl
@@ -66,7 +66,7 @@ BOOST_AUTO_TEST_CASE (atmos_encrypted_passthrough_test)
auto ref = TestPaths::private_data() / "atmos_asset.mxf";
auto content = content_factory(TestPaths::private_data() / "atmos_asset.mxf");
- auto film = new_test_film2 ("atmos_encrypted_passthrough_test", content, &cl);
+ auto film = new_test_film("atmos_encrypted_passthrough_test", content, &cl);
film->set_encrypted (true);
film->_key = dcp::Key ("4fac12927eb122af1c2781aa91f3a4cc");
@@ -82,7 +82,7 @@ BOOST_AUTO_TEST_CASE (atmos_encrypted_passthrough_test)
auto content2 = make_shared<DCPContent>(film->dir(film->dcp_name()));
content2->add_kdm (kdm);
- auto film2 = new_test_film2 ("atmos_encrypted_passthrough_test2", {content2}, &cl);
+ auto film2 = new_test_film("atmos_encrypted_passthrough_test2", {content2}, &cl);
make_and_verify_dcp (film2, { dcp::VerificationNote::Code::MISSING_CPL_METADATA });
BOOST_CHECK (mxf_atmos_files_same(ref, dcp_file(film2, "atmos"), true));
@@ -97,12 +97,14 @@ BOOST_AUTO_TEST_CASE (atmos_trim_test)
auto ref = TestPaths::private_data() / "atmos_asset.mxf";
auto content = content_factory(TestPaths::private_data() / "atmos_asset.mxf");
- auto film = new_test_film2 ("atmos_trim_test", content, &cl);
+ auto film = new_test_film("atmos_trim_test", content, &cl);
content[0]->set_trim_start(film, dcpomatic::ContentTime::from_seconds(1));
/* Just check that the encode runs; I'm not sure how to test the MXF */
make_and_verify_dcp (film, { dcp::VerificationNote::Code::MISSING_CPL_METADATA });
+
+ cl.run();
}
@@ -129,14 +131,14 @@ BOOST_AUTO_TEST_CASE(atmos_replace_test)
};
auto atmos_0 = content_factory("test/data/atmos_0.mxf");
- auto ov = new_test_film2("atmos_merge_test_ov", atmos_0);
+ auto ov = new_test_film("atmos_merge_test_ov", atmos_0);
make_and_verify_dcp(ov, { dcp::VerificationNote::Code::MISSING_CPL_METADATA });
// atmos_0.mxf should contain all zeros for its data
check(ov, 0);
auto atmos_1 = content_factory("test/data/atmos_1.mxf");
auto ov_content = std::make_shared<DCPContent>(boost::filesystem::path("build/test/atmos_merge_test_ov") / ov->dcp_name());
- auto vf = new_test_film2("atmos_merge_test_vf", { ov_content, atmos_1.front() });
+ auto vf = new_test_film("atmos_merge_test_vf", { ov_content, atmos_1.front() });
ov_content->set_reference_video(true);
atmos_1.front()->set_position(vf, dcpomatic::DCPTime());
make_and_verify_dcp(vf, { dcp::VerificationNote::Code::MISSING_CPL_METADATA, dcp::VerificationNote::Code::EXTERNAL_ASSET }, false);
diff --git a/test/audio_analysis_test.cc b/test/audio_analysis_test.cc
index 8ded9eda9..41f0abead 100644
--- a/test/audio_analysis_test.cc
+++ b/test/audio_analysis_test.cc
@@ -102,15 +102,8 @@ BOOST_AUTO_TEST_CASE (audio_analysis_serialisation_test)
BOOST_AUTO_TEST_CASE (audio_analysis_test)
{
- auto film = new_test_film ("audio_analysis_test");
- film->set_dcp_content_type (DCPContentType::from_isdcf_name("FTR"));
- film->set_container (Ratio::from_id("185"));
- film->set_name ("audio_analysis_test");
- boost::filesystem::path p = TestPaths::private_data() / "betty_L.wav";
-
- auto c = make_shared<FFmpegContent>(p);
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto c = make_shared<FFmpegContent>(TestPaths::private_data() / "betty_L.wav");
+ auto film = new_test_film("audio_analysis_test", { c });
auto job = make_shared<AnalyseAudioJob>(film, film->playlist(), false);
JobManager::instance()->add (job);
@@ -121,12 +114,8 @@ BOOST_AUTO_TEST_CASE (audio_analysis_test)
/** Check that audio analysis works (i.e. runs without error) with a -ve delay */
BOOST_AUTO_TEST_CASE (audio_analysis_negative_delay_test)
{
- auto film = new_test_film ("audio_analysis_negative_delay_test");
- film->set_name ("audio_analysis_negative_delay_test");
auto c = make_shared<FFmpegContent>(TestPaths::private_data() / "boon_telly.mkv");
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
-
+ auto film = new_test_film("audio_analysis_negative_delay_test", { c });
c->audio->set_delay (-250);
auto job = make_shared<AnalyseAudioJob>(film, film->playlist(), false);
@@ -138,11 +127,8 @@ BOOST_AUTO_TEST_CASE (audio_analysis_negative_delay_test)
/** Check audio analysis that is incorrect in 2e98263 */
BOOST_AUTO_TEST_CASE (audio_analysis_test2)
{
- auto film = new_test_film ("audio_analysis_test2");
- film->set_name ("audio_analysis_test2");
auto c = make_shared<FFmpegContent>(TestPaths::private_data() / "3d_thx_broadway_2010_lossless.m2ts");
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("audio_analysis_test2", { c });
auto job = make_shared<AnalyseAudioJob>(film, film->playlist(), false);
JobManager::instance()->add (job);
@@ -155,16 +141,10 @@ BOOST_AUTO_TEST_CASE (audio_analysis_test2)
*/
BOOST_AUTO_TEST_CASE (audio_analysis_test3)
{
- auto film = new_test_film ("analyse_audio_test");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name("TLR"));
- film->set_name ("frobozz");
-
auto content = make_shared<FFmpegContent>("test/data/white.wav");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
-
+ auto film = new_test_film("analyse_audio_test", { content });
film->set_audio_channels (12);
+
boost::signals2::connection connection;
bool done = false;
JobManager::instance()->analyse_audio(film, film->playlist(), false, connection, [&done](Job::Result) { done = true; });
@@ -176,13 +156,8 @@ BOOST_AUTO_TEST_CASE (audio_analysis_test3)
/** Run an audio analysis that triggered an exception in the audio decoder at one point */
BOOST_AUTO_TEST_CASE (analyse_audio_test4)
{
- auto film = new_test_film ("analyse_audio_test");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name("TLR"));
- film->set_name ("frobozz");
auto content = content_factory(TestPaths::private_data() / "20 The Wedding Convoy Song.m4a")[0];
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("analyse_audio_test", { content });
auto playlist = make_shared<Playlist>();
playlist->add (film, content);
@@ -194,7 +169,7 @@ BOOST_AUTO_TEST_CASE (analyse_audio_test4)
BOOST_AUTO_TEST_CASE (analyse_audio_leqm_test)
{
- auto film = new_test_film2 ("analyse_audio_leqm_test");
+ auto film = new_test_film("analyse_audio_leqm_test");
film->set_audio_channels (2);
auto content = content_factory(TestPaths::private_data() / "betty_stereo_48k.wav")[0];
film->examine_and_add_content (content);
@@ -216,7 +191,7 @@ BOOST_AUTO_TEST_CASE (analyse_audio_leqm_test)
BOOST_AUTO_TEST_CASE(analyse_audio_leqm_same_with_empty_channels)
{
auto dcp = make_shared<DCPContent>(TestPaths::private_data() / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV");
- auto film = new_test_film2("analyse_audio_leqm_test2", { dcp });
+ auto film = new_test_film("analyse_audio_leqm_test2", { dcp });
film->set_audio_channels(8);
auto analyse = [film, dcp](int channels) {
@@ -242,7 +217,7 @@ BOOST_AUTO_TEST_CASE(analyse_audio_leqm_same_with_empty_channels)
BOOST_AUTO_TEST_CASE(analyse_audio_with_long_silent_end)
{
auto content = content_factory(TestPaths::private_data() / "2364.mkv")[0];
- auto film = new_test_film2("analyse_audio_with_long_silent_end", { content });
+ auto film = new_test_film("analyse_audio_with_long_silent_end", { content });
auto playlist = make_shared<Playlist>();
playlist->add(film, content);
@@ -255,7 +230,7 @@ BOOST_AUTO_TEST_CASE(analyse_audio_with_long_silent_end)
BOOST_AUTO_TEST_CASE(analyse_audio_with_strange_channel_count)
{
auto content = content_factory(TestPaths::private_data() / "mali.mkv")[0];
- auto film = new_test_film2("analyse_audio_with_strange_channel_count", { content });
+ auto film = new_test_film("analyse_audio_with_strange_channel_count", { content });
auto playlist = make_shared<Playlist>();
playlist->add(film, content);
@@ -268,14 +243,14 @@ BOOST_AUTO_TEST_CASE(analyse_audio_with_strange_channel_count)
BOOST_AUTO_TEST_CASE(analyse_audio_with_more_channels_than_film)
{
auto picture = content_factory("test/data/flat_red.png");
- auto film_16ch = new_test_film2("analyse_audio_with_more_channels_than_film_16ch", picture);
+ auto film_16ch = new_test_film("analyse_audio_with_more_channels_than_film_16ch", picture);
film_16ch->set_audio_channels(16);
make_and_verify_dcp(film_16ch);
auto pcm_16ch = find_file(film_16ch->dir(film_16ch->dcp_name()), "pcm_");
auto sound = content_factory(pcm_16ch)[0];
- auto film_6ch = new_test_film2("analyse_audio_with_more_channels_than_film_6ch", { sound });
+ auto film_6ch = new_test_film("analyse_audio_with_more_channels_than_film_6ch", { sound });
auto playlist = make_shared<Playlist>();
playlist->add(film_6ch, sound);
@@ -288,7 +263,7 @@ BOOST_AUTO_TEST_CASE(analyse_audio_with_more_channels_than_film)
BOOST_AUTO_TEST_CASE(analyse_audio_uses_processor_when_analysing_whole_film)
{
auto sound = content_factory(TestPaths::private_data() / "betty_stereo.wav")[0];
- auto film = new_test_film2("analyse_audio_uses_processor_when_analysing_whole_film", { sound });
+ auto film = new_test_film("analyse_audio_uses_processor_when_analysing_whole_film", { sound });
auto job = make_shared<AnalyseAudioJob>(film, film->playlist(), true);
JobManager::instance()->add(job);
@@ -308,3 +283,32 @@ BOOST_AUTO_TEST_CASE(analyse_audio_uses_processor_when_analysing_whole_film)
BOOST_CHECK(centre_non_zero);
}
+
+BOOST_AUTO_TEST_CASE(ebur128_test)
+{
+ auto dcp = make_shared<DCPContent>(TestPaths::private_data() / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV");
+ auto film = new_test_film("ebur128_test", { dcp });
+ film->set_audio_channels(8);
+
+ auto analyse = [film, dcp](int channels) {
+ film->set_audio_channels(channels);
+ auto playlist = make_shared<Playlist>();
+ playlist->add(film, dcp);
+ boost::signals2::connection c;
+ JobManager::instance()->analyse_audio(film, playlist, false, c, [](Job::Result) {});
+ BOOST_CHECK(!wait_for_jobs());
+ return AudioAnalysis(film->audio_analysis_path(playlist));
+ };
+
+ auto six = analyse(6);
+ BOOST_CHECK_CLOSE(six.true_peak()[0], 0.520668, 1);
+ BOOST_CHECK_CLOSE(six.true_peak()[1], 0.519579, 1);
+ BOOST_CHECK_CLOSE(six.true_peak()[2], 0.533980, 1);
+ BOOST_CHECK_CLOSE(six.true_peak()[3], 0.326270, 1);
+ BOOST_CHECK_CLOSE(six.true_peak()[4], 0.363581, 1);
+ BOOST_CHECK_CLOSE(six.true_peak()[5], 0.317751, 1);
+ BOOST_CHECK_CLOSE(six.overall_true_peak().get(), 0.53398, 1);
+ BOOST_CHECK_CLOSE(six.overall_true_peak().get(), 0.53398, 1);
+ BOOST_CHECK_CLOSE(six.integrated_loudness().get(), -18.1432, 1);
+ BOOST_CHECK_CLOSE(six.loudness_range().get(), 6.92, 1);
+}
diff --git a/test/audio_content_test.cc b/test/audio_content_test.cc
index 97f55d53a..cc9fad712 100644
--- a/test/audio_content_test.cc
+++ b/test/audio_content_test.cc
@@ -34,7 +34,7 @@
BOOST_AUTO_TEST_CASE (audio_content_fade_empty_region)
{
auto content = content_factory("test/data/impulse_train.wav");
- auto film = new_test_film2("audio_content_fade_empty_region", content);
+ auto film = new_test_film("audio_content_fade_empty_region", content);
BOOST_CHECK(content[0]->audio->fade(content[0]->audio->stream(), 0, 0, 48000).empty());
}
@@ -43,7 +43,7 @@ BOOST_AUTO_TEST_CASE (audio_content_fade_empty_region)
BOOST_AUTO_TEST_CASE (audio_content_fade_no_fade)
{
auto content = content_factory("test/data/impulse_train.wav");
- auto film = new_test_film2("audio_content_fade_no_fade", content);
+ auto film = new_test_film("audio_content_fade_no_fade", content);
auto const stream = content[0]->audio->stream();
@@ -56,7 +56,7 @@ BOOST_AUTO_TEST_CASE (audio_content_fade_no_fade)
BOOST_AUTO_TEST_CASE (audio_content_fade_unfaded_part)
{
auto content = content_factory("test/data/impulse_train.wav")[0];
- auto film = new_test_film2("audio_content_fade_unfaded_part", { content });
+ auto film = new_test_film("audio_content_fade_unfaded_part", { content });
auto const stream = content->audio->stream();
@@ -72,7 +72,7 @@ BOOST_AUTO_TEST_CASE (audio_content_fade_unfaded_part)
BOOST_AUTO_TEST_CASE (audio_content_within_the_fade_in)
{
auto content = content_factory("test/data/impulse_train.wav")[0];
- auto film = new_test_film2("audio_content_within_the_fade_in", { content });
+ auto film = new_test_film("audio_content_within_the_fade_in", { content });
content->audio->set_fade_in(dcpomatic::ContentTime::from_frames(2000, 48000));
@@ -87,7 +87,7 @@ BOOST_AUTO_TEST_CASE (audio_content_within_the_fade_in)
BOOST_AUTO_TEST_CASE (audio_content_within_the_fade_out)
{
auto content = content_factory("test/data/impulse_train.wav")[0];
- auto film = new_test_film2("audio_content_within_the_fade_out", { content });
+ auto film = new_test_film("audio_content_within_the_fade_out", { content });
auto const stream = content->audio->stream();
@@ -105,7 +105,7 @@ BOOST_AUTO_TEST_CASE (audio_content_within_the_fade_out)
BOOST_AUTO_TEST_CASE (audio_content_overlapping_the_fade_in)
{
auto content = content_factory("test/data/impulse_train.wav")[0];
- auto film = new_test_film2("audio_content_overlapping_the_fade_in", { content });
+ auto film = new_test_film("audio_content_overlapping_the_fade_in", { content });
content->audio->set_fade_in(dcpomatic::ContentTime::from_frames(2000, 48000));
content->audio->set_fade_out(dcpomatic::ContentTime::from_frames(2000, 48000));
@@ -124,7 +124,7 @@ BOOST_AUTO_TEST_CASE (audio_content_overlapping_the_fade_in)
BOOST_AUTO_TEST_CASE (audio_content_overlapping_the_fade_out)
{
auto content = content_factory("test/data/impulse_train.wav")[0];
- auto film = new_test_film2("audio_content_overlapping_the_fade_out", { content });
+ auto film = new_test_film("audio_content_overlapping_the_fade_out", { content });
auto const stream = content->audio->stream();
@@ -145,7 +145,7 @@ BOOST_AUTO_TEST_CASE (audio_content_overlapping_the_fade_out)
BOOST_AUTO_TEST_CASE (audio_content_fade_in_and_out)
{
auto content = content_factory("test/data/impulse_train.wav")[0];
- auto film = new_test_film2("audio_content_fade_in_and_out", { content });
+ auto film = new_test_film("audio_content_fade_in_and_out", { content });
auto const stream = content->audio->stream();
auto const length = stream->length();
@@ -164,7 +164,7 @@ BOOST_AUTO_TEST_CASE (audio_content_fade_in_and_out)
BOOST_AUTO_TEST_CASE (audio_content_fade_in_with_trim)
{
auto content = content_factory("test/data/impulse_train.wav")[0];
- auto film = new_test_film2("audio_content_fade_in_with_trim", { content });
+ auto film = new_test_film("audio_content_fade_in_with_trim", { content });
auto const stream = content->audio->stream();
@@ -191,7 +191,7 @@ BOOST_AUTO_TEST_CASE (audio_content_fade_in_with_trim)
BOOST_AUTO_TEST_CASE (audio_content_fade_out_with_trim)
{
auto content = content_factory("test/data/impulse_train.wav")[0];
- auto film = new_test_film2("audio_content_fade_out_with_trim", { content });
+ auto film = new_test_film("audio_content_fade_out_with_trim", { content });
auto const stream = content->audio->stream();
auto const length = stream->length();
@@ -221,7 +221,7 @@ BOOST_AUTO_TEST_CASE (audio_content_fade_out_with_trim_at_44k1)
{
/* 5s at 44.1kHz */
auto content = content_factory("test/data/white.wav")[0];
- auto film = new_test_film2("audio_content_fade_out_with_trim_at_44k1", { content });
+ auto film = new_test_film("audio_content_fade_out_with_trim_at_44k1", { content });
auto const stream = content->audio->stream();
@@ -253,7 +253,7 @@ BOOST_AUTO_TEST_CASE (audio_content_fade_out_with_trim_at_44k1)
BOOST_AUTO_TEST_CASE (audio_content_fades_same_as_video)
{
auto content = content_factory("test/data/staircase.mov")[0];
- auto film = new_test_film2("audio_content_fades_same_as_video", { content });
+ auto film = new_test_film("audio_content_fades_same_as_video", { content });
content->audio->set_use_same_fades_as_video(true);
content->video->set_fade_in(9);
@@ -268,7 +268,7 @@ BOOST_AUTO_TEST_CASE (audio_content_fades_same_as_video)
BOOST_AUTO_TEST_CASE(fade_out_works_with_dcp_content)
{
auto dcp = std::make_shared<DCPContent>(TestPaths::private_data() / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV");
- auto film = new_test_film2("fade_out_works_with_dcp_content", { dcp });
+ auto film = new_test_film("fade_out_works_with_dcp_content", { dcp });
dcp->audio->set_fade_out(dcpomatic::ContentTime::from_seconds(15));
make_and_verify_dcp(film);
diff --git a/test/audio_delay_test.cc b/test/audio_delay_test.cc
index bafdea4e1..369881d71 100644
--- a/test/audio_delay_test.cc
+++ b/test/audio_delay_test.cc
@@ -56,7 +56,7 @@ void test_audio_delay (int delay_in_ms)
{
string const film_name = "audio_delay_test_" + lexical_cast<string> (delay_in_ms);
auto content = make_shared<FFmpegContent>("test/data/staircase.wav");
- auto film = new_test_film2 (film_name, { content });
+ auto film = new_test_film(film_name, { content });
content->audio->set_delay (delay_in_ms);
diff --git a/test/audio_processor_test.cc b/test/audio_processor_test.cc
index 4fe5eeb10..8ec829bf1 100644
--- a/test/audio_processor_test.cc
+++ b/test/audio_processor_test.cc
@@ -41,11 +41,8 @@ using std::make_shared;
/** Test the mid-side decoder for analysis and DCP-making */
BOOST_AUTO_TEST_CASE (audio_processor_test)
{
- auto film = new_test_film ("audio_processor_test");
- film->set_name ("audio_processor_test");
auto c = make_shared<FFmpegContent>("test/data/white.wav");
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("audio_processor_test", { c });
film->set_audio_channels(16);
film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
diff --git a/test/burnt_subtitle_test.cc b/test/burnt_subtitle_test.cc
index 8d7dcd143..2be712262 100644
--- a/test/burnt_subtitle_test.cc
+++ b/test/burnt_subtitle_test.cc
@@ -30,7 +30,7 @@
#include "lib/dcp_content.h"
#include "lib/dcp_content_type.h"
#include "lib/film.h"
-#include "lib/ffmpeg_encoder.h"
+#include "lib/ffmpeg_film_encoder.h"
#include "lib/log_entry.h"
#include "lib/ratio.h"
#include "lib/text_content.h"
@@ -39,9 +39,9 @@
#include <dcp/cpl.h>
#include <dcp/reel.h>
#include <dcp/j2k_transcode.h>
-#include <dcp/mono_picture_asset.h>
-#include <dcp/mono_picture_asset_reader.h>
-#include <dcp/mono_picture_frame.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/mono_j2k_picture_asset_reader.h>
+#include <dcp/mono_j2k_picture_frame.h>
#include <dcp/openjpeg_image.h>
#include <dcp/reel_picture_asset.h>
#include <dcp/reel_mono_picture_asset.h>
@@ -58,22 +58,20 @@ using namespace dcpomatic;
/** Build a small DCP with no picture and a single subtitle overlaid onto it from a SubRip file */
BOOST_AUTO_TEST_CASE (burnt_subtitle_test_subrip)
{
- auto film = new_test_film ("burnt_subtitle_test_subrip");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
auto content = content_factory("test/data/subrip2.srt")[0];
+ auto film = new_test_film("burnt_subtitle_test_subrip", { content });
+ film->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
content->text[0]->set_use(true);
content->text[0]->set_burn(true);
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
make_and_verify_dcp(
film,
{ dcp::VerificationNote::Code::MISSING_CPL_METADATA }
);
-#ifdef DCPOMATIC_WINDOWS
+#if defined(DCPOMATIC_WINDOWS)
check_dcp("test/data/windows/burnt_subtitle_test_subrip", film);
+#elif defined(DCPOMATIC_OSX)
+ check_dcp("test/data/mac/burnt_subtitle_test_subrip", film);
#else
check_dcp("test/data/burnt_subtitle_test_subrip", film);
#endif
@@ -82,14 +80,11 @@ BOOST_AUTO_TEST_CASE (burnt_subtitle_test_subrip)
/** Build a small DCP with no picture and a single subtitle overlaid onto it from a DCP XML file */
BOOST_AUTO_TEST_CASE (burnt_subtitle_test_dcp)
{
- auto film = new_test_film ("burnt_subtitle_test_dcp");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
auto content = content_factory("test/data/dcp_sub.xml")[0];
+ auto film = new_test_film("burnt_subtitle_test_dcp", { content });
+ film->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
+ film->set_name("frobozz");
content->text[0]->set_use(true);
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
make_and_verify_dcp(
film,
{
@@ -104,26 +99,18 @@ BOOST_AUTO_TEST_CASE (burnt_subtitle_test_dcp)
/** Burn some subtitles into an existing DCP to check the colour conversion */
BOOST_AUTO_TEST_CASE (burnt_subtitle_test_onto_dcp)
{
- auto film = new_test_film ("burnt_subtitle_test_onto_dcp");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
- film->examine_and_add_content(content_factory("test/data/flat_black.png")[0]);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("burnt_subtitle_test_onto_dcp", { content_factory("test/data/flat_black.png")[0] });
+ film->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
make_and_verify_dcp (film);
Config::instance()->set_log_types (Config::instance()->log_types() | LogEntry::TYPE_DEBUG_ENCODE);
- auto film2 = new_test_film ("burnt_subtitle_test_onto_dcp2");
- film2->set_container (Ratio::from_id ("185"));
- film2->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film2->set_name ("frobozz");
auto background_dcp = make_shared<DCPContent>(film->dir(film->dcp_name()));
- film2->examine_and_add_content (background_dcp);
auto sub = content_factory("test/data/subrip2.srt")[0];
+ auto film2 = new_test_film("burnt_subtitle_test_onto_dcp2", { background_dcp, sub });
+ film2->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
+ film2->set_name("frobozz");
sub->text[0]->set_burn(true);
sub->text[0]->set_effect(dcp::Effect::BORDER);
- film2->examine_and_add_content (sub);
- BOOST_REQUIRE (!wait_for_jobs());
make_and_verify_dcp (film2);
BOOST_CHECK (background_dcp->position() == DCPTime());
@@ -137,15 +124,17 @@ BOOST_AUTO_TEST_CASE (burnt_subtitle_test_onto_dcp)
BOOST_REQUIRE (dcp.cpls().front()->reels().front()->main_picture()->asset());
auto pic = dynamic_pointer_cast<dcp::ReelMonoPictureAsset> (
dcp.cpls().front()->reels().front()->main_picture()
- )->mono_asset();
+ )->mono_j2k_asset();
BOOST_REQUIRE (pic);
auto frame = pic->start_read()->get_frame(12);
auto xyz = frame->xyz_image ();
BOOST_CHECK_EQUAL (xyz->size().width, 1998);
BOOST_CHECK_EQUAL (xyz->size().height, 1080);
-#ifdef DCPOMATIC_WINDOWS
+#if defined(DCPOMATIC_WINDOWS)
check_dcp("test/data/windows/burnt_subtitle_test_onto_dcp2", film2);
+#elif defined(DCPOMATIC_OSX)
+ check_dcp("test/data/mac/burnt_subtitle_test_onto_dcp2", film2);
#else
check_dcp("test/data/burnt_subtitle_test_onto_dcp2", film2);
#endif
@@ -160,7 +149,7 @@ BOOST_AUTO_TEST_CASE(burnt_subtitle_test_position)
{
auto const name = String::compose("burnt_subtitle_test_position_%1", alignment);
auto subs = content_factory(String::compose("test/data/burn_%1.xml", alignment));
- auto film = new_test_film2(name, subs);
+ auto film = new_test_film(name, subs);
subs[0]->text[0]->set_use(true);
subs[0]->text[0]->set_burn(true);
make_and_verify_dcp(
@@ -171,8 +160,10 @@ BOOST_AUTO_TEST_CASE(burnt_subtitle_test_position)
dcp::VerificationNote::Code::MISSING_CPL_METADATA
});
-#ifdef DCPOMATIC_WINDOWS
+#if defined(DCPOMATIC_WINDOWS)
check_dcp(String::compose("test/data/windows/%1", name), film);
+#elif defined(DCPOMATIC_OSX)
+ check_dcp(String::compose("test/data/mac/%1", name), film);
#else
check_dcp(String::compose("test/data/%1", name), film);
#endif
@@ -193,13 +184,13 @@ BOOST_AUTO_TEST_CASE(burn_empty_subtitle_test)
Cleanup cl;
auto content = content_factory("test/data/empty_sub.xml")[0];
- auto film = new_test_film2("burnt_empty_subtitle_test", { content });
+ auto film = new_test_film("burnt_empty_subtitle_test", { content });
content->text[0]->set_use(true);
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
auto file = boost::filesystem::path("build") / "test" / "burnt_empty_subtitle_test.mov";
cl.add(file);
- FFmpegEncoder encoder(film, job, file, ExportFormat::PRORES_4444, false, false, false, 23);
+ FFmpegFilmEncoder encoder(film, job, file, ExportFormat::PRORES_4444, false, false, false, 23);
encoder.go();
cl.run();
diff --git a/test/butler_test.cc b/test/butler_test.cc
index 1c59de798..f7c307e3c 100644
--- a/test/butler_test.cc
+++ b/test/butler_test.cc
@@ -40,17 +40,9 @@ using namespace dcpomatic;
BOOST_AUTO_TEST_CASE (butler_test1)
{
- auto film = new_test_film ("butler_test1");
- film->set_dcp_content_type (DCPContentType::from_isdcf_name("FTR"));
- film->set_name ("butler_test1");
- film->set_container (Ratio::from_id ("185"));
-
auto video = content_factory("test/data/flat_red.png")[0];
- film->examine_and_add_content (video);
auto audio = content_factory("test/data/staircase.wav")[0];
- film->examine_and_add_content (audio);
- BOOST_REQUIRE (!wait_for_jobs ());
-
+ auto film = new_test_film("butler_test1", { video, audio });
film->set_audio_channels (6);
/* This is the map of the player output (5.1) to the butler output (also 5.1) */
@@ -96,7 +88,7 @@ BOOST_AUTO_TEST_CASE (butler_test2)
{
auto content = content_factory(TestPaths::private_data() / "arrietty_JP-EN.mkv");
BOOST_REQUIRE (!content.empty());
- auto film = new_test_film2 ("butler_test2", { content.front() });
+ auto film = new_test_film("butler_test2", { content.front() });
BOOST_REQUIRE (content.front()->audio);
content.front()->audio->set_delay(100);
diff --git a/test/bv20_test.cc b/test/bv20_test.cc
index 5530a05d0..d41868601 100644
--- a/test/bv20_test.cc
+++ b/test/bv20_test.cc
@@ -49,7 +49,8 @@ has_mxf_mca_subdescriptors(shared_ptr<const Film> film)
* whether they exist.
*/
- ASDCP::PCM::MXFReader reader;
+ Kumu::FileReaderFactory factory;
+ ASDCP::PCM::MXFReader reader(factory);
auto r = reader.OpenRead(find_file(film->dir(film->dcp_name()), "pcm_").string());
BOOST_REQUIRE(!ASDCP_FAILURE(r));
@@ -71,7 +72,7 @@ BOOST_AUTO_TEST_CASE(bv21_extensions_used_when_not_limited)
{
auto picture = content_factory("test/data/flat_red.png");
auto sound = content_factory("test/data/sine_440.wav");
- auto film = new_test_film2("bv21_extensions_used_when_not_limited", { picture.front(), sound.front() });
+ auto film = new_test_film("bv21_extensions_used_when_not_limited", { picture.front(), sound.front() });
make_and_verify_dcp(film);
@@ -84,7 +85,7 @@ BOOST_AUTO_TEST_CASE(bv21_extensions_not_used_when_limited)
{
auto picture = content_factory("test/data/flat_red.png");
auto sound = content_factory("test/data/sine_440.wav");
- auto film = new_test_film2("bv21_extensions_not_used_when_limited", { picture.front(), sound.front () });
+ auto film = new_test_film("bv21_extensions_not_used_when_limited", { picture.front(), sound.front () });
film->set_limit_to_smpte_bv20(true);
make_and_verify_dcp(film);
diff --git a/test/cinema_list_test.cc b/test/cinema_list_test.cc
new file mode 100644
index 000000000..46c9d5195
--- /dev/null
+++ b/test/cinema_list_test.cc
@@ -0,0 +1,226 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "lib/cinema.h"
+#include "lib/cinema_list.h"
+#include "lib/config.h"
+#include "lib/screen.h"
+#include "test.h"
+#include <dcp/certificate.h>
+#include <dcp/filesystem.h>
+#include <dcp/util.h>
+#include <boost/filesystem.hpp>
+#include <boost/test/unit_test.hpp>
+#include <list>
+#include <string>
+
+
+using std::pair;
+using std::string;
+using std::vector;
+
+
+static
+boost::filesystem::path
+setup(string name)
+{
+ boost::filesystem::path db = boost::filesystem::path("build") / "test" / (name + ".db");
+ boost::system::error_code ec;
+ boost::filesystem::remove(db, ec);
+ return db;
+}
+
+
+BOOST_AUTO_TEST_CASE(add_cinema_test)
+{
+ auto const db = setup("add_cinema_test");
+
+ auto const name = "Bob's Zero-G Cinema";
+ auto const emails = vector<string>{"zerogbob@hotmail.com"};
+ auto const notes = "Nice enough place but the popcorn keeps floating away";
+ auto const utc_offset = dcp::UTCOffset{5, 0};
+
+ CinemaList cinemas(db);
+ cinemas.add_cinema({name, emails, notes, utc_offset});
+
+ CinemaList cinemas2(db);
+ auto const check = cinemas2.cinemas();
+ BOOST_REQUIRE_EQUAL(check.size(), 1U);
+ BOOST_CHECK(check[0].second.name == name);
+ BOOST_CHECK(check[0].second.emails == emails);
+ BOOST_CHECK_EQUAL(check[0].second.notes, notes);
+ BOOST_CHECK(check[0].second.utc_offset == utc_offset);
+}
+
+
+BOOST_AUTO_TEST_CASE(remove_cinema_test)
+{
+ auto const db = setup("remove_cinema_test");
+
+ auto const name1 = "Bob's Zero-G Cinema";
+ auto const emails1 = vector<string>{"zerogbob@hotmail.com"};
+ auto const notes1 = "Nice enough place but the popcorn keeps floating away";
+ auto const utc_offset1 = dcp::UTCOffset{-4, -30};
+
+ auto const name2 = "Angie's Infinite-Screen Cinema";
+ auto const emails2 = vector<string>{"angie@infinitium.com", "projection-screen912341235@infinitium.com"};
+ auto const notes2 = "Nice enough place but it's very hard to find the right screen";
+ auto const utc_offset2 = dcp::UTCOffset{9, 0};
+
+ CinemaList cinemas(db);
+ auto const id1 = cinemas.add_cinema({name1, emails1, notes1, utc_offset1});
+ cinemas.add_cinema({name2, emails2, notes2, utc_offset2});
+
+ auto const check = cinemas.cinemas();
+ BOOST_REQUIRE_EQUAL(check.size(), 2U);
+ BOOST_CHECK(check[0].second.name == name2);
+ BOOST_CHECK(check[0].second.emails == emails2);
+ BOOST_CHECK_EQUAL(check[0].second.notes, notes2);
+ BOOST_CHECK(check[0].second.utc_offset == utc_offset2);
+ BOOST_CHECK(check[1].second.name == name1);
+ BOOST_CHECK(check[1].second.emails == emails1);
+ BOOST_CHECK_EQUAL(check[1].second.notes, notes1);
+ BOOST_CHECK(check[1].second.utc_offset == utc_offset1);
+
+ cinemas.remove_cinema(id1);
+
+ auto const check2 = cinemas.cinemas();
+ BOOST_REQUIRE_EQUAL(check2.size(), 1U);
+ BOOST_CHECK(check2[0].second.name == name2);
+ BOOST_CHECK(check2[0].second.emails == emails2);
+ BOOST_CHECK_EQUAL(check2[0].second.notes, notes2);
+}
+
+
+BOOST_AUTO_TEST_CASE(update_cinema_test)
+{
+ auto const db = setup("update_cinema_test");
+
+ auto const name1 = "Bob's Zero-G Cinema";
+ auto const emails1 = vector<string>{"zerogbob@hotmail.com"};
+ auto const notes1 = "Nice enough place but the popcorn keeps floating away";
+ auto const utc_offset1 = dcp::UTCOffset{-4, -30};
+
+ auto const name2 = "Angie's Infinite-Screen Cinema";
+ auto const emails2 = vector<string>{"angie@infinitium.com", "projection-screen912341235@infinitium.com"};
+ auto const notes2 = "Nice enough place but it's very hard to find the right screen";
+ auto const utc_offset2 = dcp::UTCOffset{9, 0};
+
+ CinemaList cinemas(db);
+ auto const id = cinemas.add_cinema({name1, emails1, notes1, utc_offset1});
+ cinemas.add_cinema({name2, emails2, notes2, utc_offset2});
+
+ auto check = cinemas.cinemas();
+ BOOST_REQUIRE_EQUAL(check.size(), 2U);
+ /* Alphabetically ordered so first is 2 */
+ BOOST_CHECK_EQUAL(check[0].second.name, name2);
+ BOOST_CHECK(check[0].second.emails == emails2);
+ BOOST_CHECK_EQUAL(check[0].second.notes, notes2);
+ BOOST_CHECK(check[0].second.utc_offset == utc_offset2);
+ /* Then 1 */
+ BOOST_CHECK_EQUAL(check[1].second.name, name1);
+ BOOST_CHECK(check[1].second.emails == emails1);
+ BOOST_CHECK_EQUAL(check[1].second.notes, notes1);
+ BOOST_CHECK(check[1].second.utc_offset == utc_offset1);
+
+ cinemas.update_cinema(id, Cinema{name1, vector<string>{"bob@zerogkino.com"}, notes1, utc_offset1});
+
+ check = cinemas.cinemas();
+ BOOST_REQUIRE_EQUAL(check.size(), 2U);
+ BOOST_CHECK_EQUAL(check[0].second.name, name2);
+ BOOST_CHECK(check[0].second.emails == emails2);
+ BOOST_CHECK_EQUAL(check[0].second.notes, notes2);
+ BOOST_CHECK(check[0].second.utc_offset == utc_offset2);
+ BOOST_CHECK_EQUAL(check[1].second.name, name1);
+ BOOST_CHECK(check[1].second.emails == vector<string>{"bob@zerogkino.com"});
+ BOOST_CHECK_EQUAL(check[1].second.notes, notes1);
+ BOOST_CHECK(check[1].second.utc_offset == utc_offset1);
+}
+
+
+BOOST_AUTO_TEST_CASE(add_screen_test)
+{
+ auto const db = setup("add_screen_test");
+
+ CinemaList cinemas(db);
+ auto const cinema_id = cinemas.add_cinema({"Name", { "foo@bar.com" }, "", dcp::UTCOffset()});
+ auto const screen_id = cinemas.add_screen(
+ cinema_id,
+ dcpomatic::Screen(
+ "Screen 1",
+ "Smells of popcorn",
+ dcp::Certificate(dcp::file_to_string("test/data/cert.pem")),
+ string("test/data/cert.pem"),
+ vector<TrustedDevice>{}
+ ));
+
+ auto check = cinemas.screens(cinema_id);
+ BOOST_REQUIRE_EQUAL(check.size(), 1U);
+ BOOST_CHECK(check[0].first == screen_id);
+ BOOST_CHECK_EQUAL(check[0].second.name, "Screen 1");
+ BOOST_CHECK_EQUAL(check[0].second.notes, "Smells of popcorn");
+ BOOST_CHECK(check[0].second.recipient == dcp::Certificate(dcp::file_to_string("test/data/cert.pem")));
+ BOOST_CHECK(check[0].second.recipient_file == string("test/data/cert.pem"));
+}
+
+
+BOOST_AUTO_TEST_CASE(cinemas_list_copy_from_xml_test)
+{
+ ConfigRestorer cr("build/test/cinemas_list_copy_config");
+
+ dcp::filesystem::remove_all(*Config::override_path);
+ dcp::filesystem::create_directories(*Config::override_path);
+ dcp::filesystem::copy_file("test/data/cinemas2.xml", *Config::override_path / "cinemas2.xml");
+
+ CinemaList cinema_list;
+ cinema_list.read_legacy_file(Config::instance()->read_path("cinemas2.xml"));
+ auto cinemas = cinema_list.cinemas();
+ BOOST_REQUIRE_EQUAL(cinemas.size(), 3U);
+
+ auto cinema_iter = cinemas.begin();
+ BOOST_CHECK_EQUAL(cinema_iter->second.name, "Great");
+ BOOST_CHECK_EQUAL(cinema_iter->second.emails.size(), 1U);
+ BOOST_CHECK_EQUAL(cinema_iter->second.emails[0], "julie@tinyscreen.com");
+ BOOST_CHECK(cinema_iter->second.utc_offset == dcp::UTCOffset(1, 0));
+ ++cinema_iter;
+
+ BOOST_CHECK_EQUAL(cinema_iter->second.name, "classy joint");
+ BOOST_CHECK_EQUAL(cinema_iter->second.notes, "Can't stand this place");
+ ++cinema_iter;
+
+ BOOST_CHECK_EQUAL(cinema_iter->second.name, "stinking dump");
+ BOOST_CHECK_EQUAL(cinema_iter->second.emails.size(), 2U);
+ BOOST_CHECK_EQUAL(cinema_iter->second.emails[0], "bob@odourscreen.com");
+ BOOST_CHECK_EQUAL(cinema_iter->second.emails[1], "alice@whiff.com");
+ BOOST_CHECK_EQUAL(cinema_iter->second.notes, "Great cinema, smells of roses");
+ BOOST_CHECK(cinema_iter->second.utc_offset == dcp::UTCOffset(-7, 0));
+ auto screens = cinema_list.screens(cinema_iter->first);
+ BOOST_CHECK_EQUAL(screens.size(), 2U);
+ auto screen_iter = screens.begin();
+ BOOST_CHECK_EQUAL(screen_iter->second.name, "1");
+ BOOST_CHECK(screen_iter->second.recipient);
+ BOOST_CHECK_EQUAL(screen_iter->second.recipient->subject_dn_qualifier(), "CVsuuv9eYsQZSl8U4fDpvOmzZhI=");
+ ++screen_iter;
+ BOOST_CHECK_EQUAL(screen_iter->second.name, "2");
+ BOOST_CHECK(screen_iter->second.recipient);
+ BOOST_CHECK_EQUAL(screen_iter->second.recipient->subject_dn_qualifier(), "CVsuuv9eYsQZSl8U4fDpvOmzZhI=");
+}
+
diff --git a/test/client_server_test.cc b/test/client_server_test.cc
index 596668aae..43084bf31 100644
--- a/test/client_server_test.cc
+++ b/test/client_server_test.cc
@@ -20,21 +20,20 @@
/** @file test/client_server_test.cc
- * @brief Test the server class.
+ * @brief Test the remote encoding code.
* @ingroup feature
- *
- * Create a test image and then encode it using the standard mechanism
- * and also using a EncodeServer object running on localhost. Compare the resulting
- * encoded data to check that they are the same.
*/
+#include "lib/content_factory.h"
#include "lib/cross.h"
#include "lib/dcp_video.h"
#include "lib/dcpomatic_log.h"
#include "lib/encode_server.h"
#include "lib/encode_server_description.h"
+#include "lib/encode_server_finder.h"
#include "lib/file_log.h"
+#include "lib/film.h"
#include "lib/image.h"
#include "lib/j2k_image_proxy.h"
#include "lib/player_video.h"
@@ -51,6 +50,7 @@ using std::weak_ptr;
using boost::thread;
using boost::optional;
using dcp::ArrayData;
+using namespace dcpomatic;
void
@@ -105,7 +105,7 @@ BOOST_AUTO_TEST_CASE (client_server_test_rgb)
ColourConversion(),
VideoRange::FULL,
weak_ptr<Content>(),
- optional<Frame>(),
+ optional<ContentTime>(),
false
);
@@ -184,7 +184,7 @@ BOOST_AUTO_TEST_CASE (client_server_test_yuv)
ColourConversion(),
VideoRange::FULL,
weak_ptr<Content>(),
- optional<Frame>(),
+ optional<ContentTime>(),
false
);
@@ -250,7 +250,7 @@ BOOST_AUTO_TEST_CASE (client_server_test_j2k)
ColourConversion(),
VideoRange::FULL,
weak_ptr<Content>(),
- optional<Frame>(),
+ optional<ContentTime>(),
false
);
@@ -275,7 +275,7 @@ BOOST_AUTO_TEST_CASE (client_server_test_j2k)
PresetColourConversion::all().front().conversion,
VideoRange::FULL,
weak_ptr<Content>(),
- optional<Frame>(),
+ optional<ContentTime>(),
false
);
@@ -312,6 +312,32 @@ BOOST_AUTO_TEST_CASE (client_server_test_j2k)
server->stop ();
server_thread.join();
+
+ EncodeServerFinder::drop();
}
+BOOST_AUTO_TEST_CASE(real_encode_with_server)
+{
+ Cleanup cl;
+
+ auto content = content_factory(TestPaths::private_data() / "dolby_aurora.vob");
+ auto film = new_test_film("real_encode_with_server", content, &cl);
+ film->set_interop(false);
+
+ EncodeServerFinder::instance();
+
+ EncodeServer server(true, 4);
+ thread server_thread(boost::bind(&EncodeServer::run, &server));
+
+ make_and_verify_dcp(film);
+
+ server.stop();
+ server_thread.join();
+
+ BOOST_CHECK(server.frames_encoded() > 0);
+ EncodeServerFinder::drop();
+
+ cl.run();
+}
+
diff --git a/test/closed_caption_test.cc b/test/closed_caption_test.cc
index 313e2a8d3..1a9531f4a 100644
--- a/test/closed_caption_test.cc
+++ b/test/closed_caption_test.cc
@@ -40,7 +40,7 @@ BOOST_AUTO_TEST_CASE (closed_caption_test1)
Cleanup cl;
auto content = make_shared<StringTextFileContent>("test/data/subrip.srt");
- auto film = new_test_film2 ("closed_caption_test1", { content }, &cl);
+ auto film = new_test_film("closed_caption_test1", { content }, &cl);
content->only_text()->set_type (TextType::CLOSED_CAPTION);
@@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE (closed_caption_test2)
auto content1 = make_shared<StringTextFileContent>("test/data/subrip.srt");
auto content2 = make_shared<StringTextFileContent>("test/data/subrip2.srt");
auto content3 = make_shared<StringTextFileContent>("test/data/subrip3.srt");
- auto film = new_test_film2 ("closed_caption_test2", { content1, content2, content3 }, &cl);
+ auto film = new_test_film("closed_caption_test2", { content1, content2, content3 }, &cl);
content1->only_text()->set_type (TextType::CLOSED_CAPTION);
content1->only_text()->set_dcp_track (DCPTextTrack("First track", dcp::LanguageTag("fr-FR")));
diff --git a/test/config_test.cc b/test/config_test.cc
index 103a6a585..8278f55a9 100644
--- a/test/config_test.cc
+++ b/test/config_test.cc
@@ -20,7 +20,12 @@
#include "lib/cinema.h"
+#include "lib/cinema_list.h"
#include "lib/config.h"
+#include "lib/dkdm_recipient.h"
+#include "lib/dkdm_recipient_list.h"
+#include "lib/unzipper.h"
+#include "lib/zipper.h"
#include "test.h"
#include <boost/test/unit_test.hpp>
#include <fstream>
@@ -38,7 +43,7 @@ rewrite_bad_config (string filename, string extra_line)
{
using namespace boost::filesystem;
- auto base = path("build/test/bad_config/2.16");
+ auto base = path("build/test/bad_config/2.18");
auto file = base / filename;
boost::system::error_code ec;
@@ -59,10 +64,7 @@ rewrite_bad_config (string filename, string extra_line)
BOOST_AUTO_TEST_CASE (config_backup_test)
{
- ConfigRestorer cr;
-
- Config::override_path = "build/test/bad_config";
- Config::drop();
+ ConfigRestorer cr("build/test/bad_config");
boost::filesystem::remove_all ("build/test/bad_config");
/* Write an invalid config file to config.xml */
@@ -73,47 +75,49 @@ BOOST_AUTO_TEST_CASE (config_backup_test)
*/
Config::instance();
- BOOST_CHECK ( boost::filesystem::exists("build/test/bad_config/2.16/config.xml.1"));
- BOOST_CHECK (dcp::file_to_string("build/test/bad_config/2.16/config.xml.1") == first_write_xml);
- BOOST_CHECK (!boost::filesystem::exists("build/test/bad_config/2.16/config.xml.2"));
- BOOST_CHECK (!boost::filesystem::exists("build/test/bad_config/2.16/config.xml.3"));
- BOOST_CHECK (!boost::filesystem::exists("build/test/bad_config/2.16/config.xml.4"));
+ boost::filesystem::path const prefix = "build/test/bad_config/2.18";
+
+ BOOST_CHECK(boost::filesystem::exists(prefix / "config.xml.1"));
+ BOOST_CHECK(dcp::file_to_string(prefix / "config.xml.1") == first_write_xml);
+ BOOST_CHECK(!boost::filesystem::exists(prefix / "config.xml.2"));
+ BOOST_CHECK(!boost::filesystem::exists(prefix / "config.xml.3"));
+ BOOST_CHECK(!boost::filesystem::exists(prefix / "config.xml.4"));
Config::drop();
auto const second_write_xml = rewrite_bad_config("config.xml", "second write");
Config::instance();
- BOOST_CHECK ( boost::filesystem::exists("build/test/bad_config/2.16/config.xml.1"));
- BOOST_CHECK (dcp::file_to_string("build/test/bad_config/2.16/config.xml.1") == first_write_xml);
- BOOST_CHECK ( boost::filesystem::exists("build/test/bad_config/2.16/config.xml.2"));
- BOOST_CHECK (dcp::file_to_string("build/test/bad_config/2.16/config.xml.2") == second_write_xml);
- BOOST_CHECK (!boost::filesystem::exists("build/test/bad_config/2.16/config.xml.3"));
- BOOST_CHECK (!boost::filesystem::exists("build/test/bad_config/2.16/config.xml.4"));
+ BOOST_CHECK(boost::filesystem::exists(prefix / "config.xml.1"));
+ BOOST_CHECK(dcp::file_to_string(prefix / "config.xml.1") == first_write_xml);
+ BOOST_CHECK(boost::filesystem::exists(prefix / "config.xml.2"));
+ BOOST_CHECK(dcp::file_to_string(prefix / "config.xml.2") == second_write_xml);
+ BOOST_CHECK(!boost::filesystem::exists(prefix / "config.xml.3"));
+ BOOST_CHECK(!boost::filesystem::exists(prefix / "config.xml.4"));
Config::drop();
auto const third_write_xml = rewrite_bad_config("config.xml", "third write");
Config::instance();
- BOOST_CHECK ( boost::filesystem::exists("build/test/bad_config/2.16/config.xml.1"));
- BOOST_CHECK (dcp::file_to_string("build/test/bad_config/2.16/config.xml.1") == first_write_xml);
- BOOST_CHECK ( boost::filesystem::exists("build/test/bad_config/2.16/config.xml.2"));
- BOOST_CHECK (dcp::file_to_string("build/test/bad_config/2.16/config.xml.2") == second_write_xml);
- BOOST_CHECK ( boost::filesystem::exists("build/test/bad_config/2.16/config.xml.3"));
- BOOST_CHECK (dcp::file_to_string("build/test/bad_config/2.16/config.xml.3") == third_write_xml);
- BOOST_CHECK (!boost::filesystem::exists("build/test/bad_config/2.16/config.xml.4"));
+ BOOST_CHECK(boost::filesystem::exists(prefix / "config.xml.1"));
+ BOOST_CHECK(dcp::file_to_string(prefix / "config.xml.1") == first_write_xml);
+ BOOST_CHECK(boost::filesystem::exists(prefix / "config.xml.2"));
+ BOOST_CHECK(dcp::file_to_string(prefix / "config.xml.2") == second_write_xml);
+ BOOST_CHECK(boost::filesystem::exists(prefix / "config.xml.3"));
+ BOOST_CHECK(dcp::file_to_string(prefix / "config.xml.3") == third_write_xml);
+ BOOST_CHECK(!boost::filesystem::exists(prefix / "config.xml.4"));
Config::drop();
auto const fourth_write_xml = rewrite_bad_config("config.xml", "fourth write");
Config::instance();
- BOOST_CHECK (boost::filesystem::exists("build/test/bad_config/2.16/config.xml.1"));
- BOOST_CHECK (dcp::file_to_string("build/test/bad_config/2.16/config.xml.1") == first_write_xml);
- BOOST_CHECK (boost::filesystem::exists("build/test/bad_config/2.16/config.xml.2"));
- BOOST_CHECK (dcp::file_to_string("build/test/bad_config/2.16/config.xml.2") == second_write_xml);
- BOOST_CHECK (boost::filesystem::exists("build/test/bad_config/2.16/config.xml.3"));
- BOOST_CHECK (dcp::file_to_string("build/test/bad_config/2.16/config.xml.3") == third_write_xml);
- BOOST_CHECK (boost::filesystem::exists("build/test/bad_config/2.16/config.xml.4"));
- BOOST_CHECK (dcp::file_to_string("build/test/bad_config/2.16/config.xml.4") == fourth_write_xml);
+ BOOST_CHECK(boost::filesystem::exists(prefix / "config.xml.1"));
+ BOOST_CHECK(dcp::file_to_string(prefix / "config.xml.1") == first_write_xml);
+ BOOST_CHECK(boost::filesystem::exists(prefix / "config.xml.2"));
+ BOOST_CHECK(dcp::file_to_string(prefix / "config.xml.2") == second_write_xml);
+ BOOST_CHECK(boost::filesystem::exists(prefix / "config.xml.3"));
+ BOOST_CHECK(dcp::file_to_string(prefix / "config.xml.3") == third_write_xml);
+ BOOST_CHECK(boost::filesystem::exists(prefix / "config.xml.4"));
+ BOOST_CHECK(dcp::file_to_string(prefix / "config.xml.4") == fourth_write_xml);
}
@@ -121,13 +125,10 @@ BOOST_AUTO_TEST_CASE (config_backup_with_link_test)
{
using namespace boost::filesystem;
- ConfigRestorer cr;
-
auto base = path("build/test/bad_config");
- auto version = base / "2.16";
+ auto version = base / "2.18";
- Config::override_path = base;
- Config::drop();
+ ConfigRestorer cr(base);
boost::filesystem::remove_all (base);
@@ -151,91 +152,121 @@ BOOST_AUTO_TEST_CASE (config_backup_with_link_test)
BOOST_AUTO_TEST_CASE (config_write_utf8_test)
{
- ConfigRestorer cr;
+ ConfigRestorer cr("build/test");
boost::filesystem::remove_all ("build/test/config.xml");
boost::filesystem::copy_file ("test/data/utf8_config.xml", "build/test/config.xml");
- Config::override_path = "build/test";
- Config::drop ();
Config::instance()->write();
check_text_file ("test/data/utf8_config.xml", "build/test/config.xml");
}
-BOOST_AUTO_TEST_CASE (config_upgrade_test)
+/* 2.14 -> 2.18 */
+BOOST_AUTO_TEST_CASE (config_upgrade_test1)
{
- ConfigRestorer cr;
+ boost::filesystem::path dir = "build/test/config_upgrade_test1";
+ ConfigRestorer cr(dir);
- boost::filesystem::path dir = "build/test/config_upgrade_test";
- Config::override_path = dir;
- Config::drop ();
boost::filesystem::remove_all (dir);
boost::filesystem::create_directories (dir);
boost::filesystem::copy_file ("test/data/2.14.config.xml", dir / "config.xml");
boost::filesystem::copy_file ("test/data/2.14.cinemas.xml", dir / "cinemas.xml");
- Config::instance();
try {
- /* This will fail to write cinemas.xml since the link is to a non-existent directory */
- Config::instance()->write();
+ /* This will fail to read cinemas.xml since the link is to a non-existent directory */
+ Config::instance();
} catch (...) {}
+ Config::instance()->write();
+
check_xml (dir / "config.xml", "test/data/2.14.config.xml", {});
check_xml (dir / "cinemas.xml", "test/data/2.14.cinemas.xml", {});
#ifdef DCPOMATIC_WINDOWS
/* This file has the windows path for dkdm_recipients.xml (with backslashes) */
- check_xml (dir / "2.16" / "config.xml", "test/data/2.16.config.windows.xml", {});
+ check_xml(dir / "2.18" / "config.xml", "test/data/2.18.config.windows.sqlite.xml", {});
#else
- check_xml (dir / "2.16" / "config.xml", "test/data/2.16.config.xml", {});
+ check_xml(dir / "2.18" / "config.xml", "test/data/2.18.config.sqlite.xml", {});
#endif
- /* cinemas.xml is not copied into 2.16 as its format has not changed */
- BOOST_REQUIRE (!boost::filesystem::exists(dir / "2.16" / "cinemas.xml"));
+ /* cinemas.xml is not copied into 2.18 as its format has not changed */
+ BOOST_REQUIRE (!boost::filesystem::exists(dir / "2.18" / "cinemas.xml"));
}
-BOOST_AUTO_TEST_CASE (config_keep_cinemas_if_making_new_config)
+/* 2.16 -> 2.18 */
+BOOST_AUTO_TEST_CASE (config_upgrade_test2)
{
- ConfigRestorer cr;
+ boost::filesystem::path dir = "build/test/config_upgrade_test2";
+ ConfigRestorer cr(dir);
+ boost::filesystem::remove_all (dir);
+ boost::filesystem::create_directories (dir);
+
+#ifdef DCPOMATIC_WINDOWS
+ boost::filesystem::copy_file("test/data/2.16.config.windows.xml", dir / "config.xml");
+#else
+ boost::filesystem::copy_file("test/data/2.16.config.xml", dir / "config.xml");
+#endif
+ boost::filesystem::copy_file("test/data/2.14.cinemas.xml", dir / "cinemas.xml");
+ try {
+ /* This will fail to read cinemas.xml since the link is to a non-existent directory */
+ Config::instance();
+ } catch (...) {}
+
+ Config::instance()->write();
+
+ check_xml(dir / "cinemas.xml", "test/data/2.14.cinemas.xml", {});
+#ifdef DCPOMATIC_WINDOWS
+ /* This file has the windows path for dkdm_recipients.xml (with backslashes) */
+ check_xml(dir / "2.18" / "config.xml", "test/data/2.18.config.windows.xml", {});
+ check_xml(dir / "config.xml", "test/data/2.16.config.windows.xml", {});
+#else
+ check_xml(dir / "2.18" / "config.xml", "test/data/2.18.config.xml", {});
+ check_xml(dir / "config.xml", "test/data/2.16.config.xml", {});
+#endif
+ /* cinemas.xml is not copied into 2.18 as its format has not changed */
+ BOOST_REQUIRE (!boost::filesystem::exists(dir / "2.18" / "cinemas.xml"));
+}
+
+BOOST_AUTO_TEST_CASE (config_keep_cinemas_if_making_new_config)
+{
boost::filesystem::path dir = "build/test/config_keep_cinemas_if_making_new_config";
- Config::override_path = dir;
- Config::drop ();
+ ConfigRestorer cr(dir);
boost::filesystem::remove_all (dir);
boost::filesystem::create_directories (dir);
Config::instance()->write();
- Config::instance()->add_cinema(make_shared<Cinema>("My Great Cinema", vector<string>(), "", 0, 0));
- Config::instance()->write();
+ CinemaList cinemas;
+ cinemas.add_cinema({"My Great Cinema", {}, "", dcp::UTCOffset()});
- boost::filesystem::copy_file (dir / "cinemas.xml", dir / "backup_for_test.xml");
+ boost::filesystem::copy_file(dir / "cinemas.sqlite3", dir / "backup_for_test.sqlite3");
Config::drop ();
- boost::filesystem::remove (dir / "2.16" / "config.xml");
+ boost::filesystem::remove (dir / "2.18" / "config.xml");
Config::instance();
- check_text_file(dir / "backup_for_test.xml", dir / "cinemas.xml");
+ check_file(dir / "backup_for_test.sqlite3", dir / "cinemas.sqlite3");
}
BOOST_AUTO_TEST_CASE(keep_config_if_cinemas_fail_to_load)
{
- ConfigRestorer cr;
-
/* Make a new config */
boost::filesystem::path dir = "build/test/keep_config_if_cinemas_fail_to_load";
- Config::override_path = dir;
- Config::drop();
+ ConfigRestorer cr(dir);
boost::filesystem::remove_all(dir);
boost::filesystem::create_directories(dir);
Config::instance()->write();
- auto const cinemas = dir / "cinemas.xml";
+ CinemaList cinema_list;
+ cinema_list.add_cinema(Cinema("Foo", {}, "Bar", dcp::UTCOffset()));
+
+ auto const cinemas = dir / "cinemas.sqlite3";
/* Back things up */
- boost::filesystem::copy_file(dir / "2.16" / "config.xml", dir / "config_backup_for_test.xml");
- boost::filesystem::copy_file(cinemas, dir / "cinemas_backup_for_test.xml");
+ boost::filesystem::copy_file(dir / "2.18" / "config.xml", dir / "config_backup_for_test.xml");
+ boost::filesystem::copy_file(cinemas, dir / "cinemas_backup_for_test.sqlite3");
/* Corrupt the cinemas */
Config::drop();
@@ -244,8 +275,282 @@ BOOST_AUTO_TEST_CASE(keep_config_if_cinemas_fail_to_load)
corrupt.close();
Config::instance();
- /* We should have a new cinemas.xml and the old config.xml */
- check_text_file(dir / "2.16" / "config.xml", dir / "config_backup_for_test.xml");
- check_text_file(cinemas, dir / "cinemas_backup_for_test.xml");
+ /* We should have the old config.xml */
+ check_text_file(dir / "2.18" / "config.xml", dir / "config_backup_for_test.xml");
+}
+
+
+BOOST_AUTO_TEST_CASE(read_cinemas_xml_and_write_sqlite)
+{
+ /* Set up a config with an XML cinemas file */
+ boost::filesystem::path dir = "build/test/read_cinemas_xml_and_write_sqlite";
+ boost::filesystem::remove_all(dir);
+ boost::filesystem::create_directories(dir);
+ boost::filesystem::create_directories(dir / "2.18");
+
+ boost::filesystem::copy_file("test/data/cinemas.xml", dir / "cinemas.xml");
+ boost::filesystem::copy_file("test/data/2.18.config.xml", dir / "2.18" / "config.xml");
+ {
+ Editor editor(dir / "2.18" / "config.xml");
+ editor.replace(
+ "/home/realldoesnt/exist/this/path/is/nonsense.sqlite3",
+ boost::filesystem::canonical(dir / "cinemas.xml").string()
+ );
+ }
+
+ ConfigRestorer cr(dir);
+
+ /* This should make a sqlite3 file containing the recipients from cinemas.xml */
+ Config::instance();
+
+ {
+ CinemaList test(dir / "cinemas.sqlite3");
+
+ /* The detailed creation of sqlite3 from XML is tested in cinema_list_test.cc */
+ auto cinemas = test.cinemas();
+ BOOST_REQUIRE_EQUAL(cinemas.size(), 3U);
+ BOOST_CHECK_EQUAL(cinemas[0].second.name, "Great");
+ BOOST_CHECK_EQUAL(cinemas[1].second.name, "classy joint");
+ BOOST_CHECK_EQUAL(cinemas[2].second.name, "stinking dump");
+
+ /* Add another recipient to the sqlite */
+ test.add_cinema({"The ol' 1-seater", {}, "Quiet but lonely", dcp::UTCOffset()});
+ }
+
+ /* Reload the config; the old XML should not clobber the new sqlite3 */
+ Config::drop();
+ Config::instance();
+
+ {
+ CinemaList test(dir / "cinemas.sqlite3");
+
+ auto cinemas = test.cinemas();
+ BOOST_REQUIRE_EQUAL(cinemas.size(), 4U);
+ BOOST_CHECK_EQUAL(cinemas[0].second.name, "Great");
+ BOOST_CHECK_EQUAL(cinemas[1].second.name, "The ol' 1-seater");
+ BOOST_CHECK_EQUAL(cinemas[2].second.name, "classy joint");
+ BOOST_CHECK_EQUAL(cinemas[3].second.name, "stinking dump");
+ }
+}
+
+
+BOOST_AUTO_TEST_CASE(read_dkdm_recipients_xml_and_write_sqlite)
+{
+ /* Set up a config with an XML cinemas file */
+ boost::filesystem::path dir = "build/test/read_dkdm_recipients_xml_and_write_sqlite";
+ boost::filesystem::remove_all(dir);
+ boost::filesystem::create_directories(dir);
+ boost::filesystem::create_directories(dir / "2.18");
+
+ boost::filesystem::copy_file("test/data/dkdm_recipients.xml", dir / "dkdm_recipients.xml");
+ boost::filesystem::copy_file("test/data/2.18.config.xml", dir / "2.18" / "config.xml");
+ {
+ Editor editor(dir / "2.18" / "config.xml");
+ editor.replace(
+ "build/test/config_upgrade_test/dkdm_recipients.xml",
+ boost::filesystem::canonical(dir / "dkdm_recipients.xml").string()
+ );
+ }
+
+ ConfigRestorer cr(dir);
+
+ /* This should make a sqlite3 file containing the recipients from dkdm_recipients.xml */
+ Config::instance();
+
+ {
+ DKDMRecipientList test(dir / "dkdm_recipients.sqlite3");
+
+ /* The detailed creation of sqlite3 from XML is tested in dkdm_recipient_list_test.cc */
+ auto recipients = test.dkdm_recipients();
+ BOOST_REQUIRE_EQUAL(recipients.size(), 2U);
+ BOOST_CHECK_EQUAL(recipients[0].second.name, "Bob's Epics");
+ BOOST_CHECK_EQUAL(recipients[1].second.name, "Sharon's Shorts");
+
+ /* Add another recipient to the sqlite */
+ test.add_dkdm_recipient({"Carl's Classics", "Oldies but goodies", {}, {}});
+ }
+
+ /* Reload the config; the old XML should not clobber the new sqlite3 */
+ Config::drop();
+ Config::instance();
+
+ {
+ DKDMRecipientList test(dir / "dkdm_recipients.sqlite3");
+
+ auto recipients = test.dkdm_recipients();
+ BOOST_REQUIRE_EQUAL(recipients.size(), 3U);
+ BOOST_CHECK_EQUAL(recipients[0].second.name, "Bob's Epics");
+ BOOST_CHECK_EQUAL(recipients[1].second.name, "Carl's Classics");
+ BOOST_CHECK_EQUAL(recipients[2].second.name, "Sharon's Shorts");
+ }
+}
+
+
+BOOST_AUTO_TEST_CASE(save_config_as_zip_test)
+{
+ boost::filesystem::path const dir = "build/test/save_config_as_zip_test";
+ ConfigRestorer cr(dir);
+ boost::system::error_code ec;
+ boost::filesystem::remove_all(dir, ec);
+ boost::filesystem::create_directories(dir);
+ boost::filesystem::copy_file("test/data/2.18.config.xml", dir / "config.xml");
+
+ Config::instance()->set_cinemas_file(dir / "cinemas.sqlite3");
+ Config::instance()->set_dkdm_recipients_file(dir / "dkdm_recipients.sqlite3");
+
+ CinemaList cinemas;
+ cinemas.add_cinema({"My Great Cinema", {}, "", dcp::UTCOffset()});
+ DKDMRecipientList recipients;
+ recipients.add_dkdm_recipient({"Carl's Classics", "Oldies but goodies", {}, {}});
+
+ boost::filesystem::path const zip = "build/test/save.zip";
+ boost::filesystem::remove(zip, ec);
+ save_all_config_as_zip(zip);
+ Unzipper unzipper(zip);
+
+ BOOST_CHECK(unzipper.contains("config.xml"));
+ BOOST_CHECK(unzipper.contains("cinemas.sqlite3"));
+ BOOST_CHECK(unzipper.contains("dkdm_recipients.sqlite3"));
+}
+
+
+/** Load a config ZIP file, which contains an XML cinemas file, and ask to overwrite
+ * the existing cinemas file that we had.
+ */
+BOOST_AUTO_TEST_CASE(load_config_from_zip_with_only_xml_current)
+{
+ ConfigRestorer cr;
+
+ auto cinemas_file = Config::instance()->cinemas_file();
+
+ boost::filesystem::path const zip = "build/test/load.zip";
+ boost::system::error_code ec;
+ boost::filesystem::remove(zip, ec);
+
+ Zipper zipper(zip);
+ zipper.add(
+ "config.xml",
+ boost::algorithm::replace_all_copy(
+ dcp::file_to_string("test/data/2.18.config.xml"),
+ "/home/realldoesnt/exist/this/path/is/nonsense.xml",
+ ""
+ )
+ );
+
+ zipper.add("cinemas.xml", dcp::file_to_string("test/data/cinemas.xml"));
+ zipper.close();
+
+ Config::instance()->load_from_zip(zip, Config::CinemasAction::WRITE_TO_CURRENT_PATH);
+
+ CinemaList cinema_list(cinemas_file);
+ auto cinemas = cinema_list.cinemas();
+ BOOST_REQUIRE_EQUAL(cinemas.size(), 3U);
+ BOOST_CHECK_EQUAL(cinemas[0].second.name, "Great");
+ BOOST_CHECK_EQUAL(cinemas[1].second.name, "classy joint");
+ BOOST_CHECK_EQUAL(cinemas[2].second.name, "stinking dump");
+}
+
+
+/** Load a config ZIP file, which contains an XML cinemas file, and ask to write it to
+ * the location specified by the zipped config.xml.
+ */
+BOOST_AUTO_TEST_CASE(load_config_from_zip_with_only_xml_zip)
+{
+ ConfigRestorer cr;
+
+ boost::filesystem::path const zip = "build/test/load.zip";
+ boost::system::error_code ec;
+ boost::filesystem::remove(zip, ec);
+
+ Zipper zipper(zip);
+ zipper.add(
+ "config.xml",
+ boost::algorithm::replace_all_copy(
+ dcp::file_to_string("test/data/2.18.config.xml"),
+ "/home/realldoesnt/exist/this/path/is/nonsense.sqlite3",
+ "build/test/hide/it/here/cinemas.sqlite3"
+ )
+ );
+
+ zipper.add("cinemas.xml", dcp::file_to_string("test/data/cinemas.xml"));
+ zipper.close();
+
+ Config::instance()->load_from_zip(zip, Config::CinemasAction::WRITE_TO_PATH_IN_ZIPPED_CONFIG);
+
+ CinemaList cinema_list("build/test/hide/it/here/cinemas.sqlite3");
+ auto cinemas = cinema_list.cinemas();
+ BOOST_REQUIRE_EQUAL(cinemas.size(), 3U);
+ BOOST_CHECK_EQUAL(cinemas[0].second.name, "Great");
+ BOOST_CHECK_EQUAL(cinemas[1].second.name, "classy joint");
+ BOOST_CHECK_EQUAL(cinemas[2].second.name, "stinking dump");
+}
+
+
+/** Load a config ZIP file, which contains an XML cinemas file, and ask to ignore it */
+BOOST_AUTO_TEST_CASE(load_config_from_zip_with_only_xml_ignore)
+{
+ ConfigRestorer cr;
+
+ CinemaList cinema_list("build/test/hide/it/here/cinemas.sqlite3");
+ cinema_list.add_cinema(Cinema("Foo", {}, "Bar", dcp::UTCOffset()));
+
+ boost::filesystem::path const zip = "build/test/load.zip";
+ boost::system::error_code ec;
+ boost::filesystem::remove(zip, ec);
+
+ Zipper zipper(zip);
+ zipper.add(
+ "config.xml",
+ boost::algorithm::replace_all_copy(
+ dcp::file_to_string("test/data/2.18.config.xml"),
+ "/home/realldoesnt/exist/this/path/is/nonsense.xml",
+ "build/test/hide/it/here/cinemas.sqlite3"
+ )
+ );
+
+ zipper.add("cinemas.xml", "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Cinemas/>");
+ zipper.close();
+
+ Config::instance()->load_from_zip(zip, Config::CinemasAction::IGNORE);
+
+ auto cinemas = cinema_list.cinemas();
+ BOOST_CHECK(!cinemas.empty());
+}
+
+
+BOOST_AUTO_TEST_CASE(use_sqlite_if_present)
+{
+ /* Set up a config with an XML cinemas file */
+ boost::filesystem::path dir = "build/test/read_cinemas_xml_and_write_sqlite";
+ boost::filesystem::remove_all(dir);
+ boost::filesystem::create_directories(dir);
+ boost::filesystem::create_directories(dir / "2.18");
+
+ boost::filesystem::copy_file("test/data/cinemas.xml", dir / "cinemas.xml");
+ boost::filesystem::copy_file("test/data/2.18.config.xml", dir / "2.18" / "config.xml");
+ {
+ Editor editor(dir / "2.18" / "config.xml");
+ editor.replace(
+ "/home/realldoesnt/exist/this/path/is/nonsense.sqlite3",
+ boost::filesystem::canonical(dir / "cinemas.xml").string()
+ );
+ }
+
+ ConfigRestorer cr(dir);
+
+ /* This should make a sqlite3 file containing the recipients from cinemas.xml.
+ * But it won't write config.xml, so config.xml will still point to cinemas.xml.
+ * This also happens in real life - but I'm not sure how (perhaps just when DoM is
+ * loaded but doesn't save the config, and then another tool is loaded).
+ */
+ Config::instance();
+
+ BOOST_CHECK(boost::filesystem::exists(dir / "cinemas.sqlite3"));
+
+ Config::drop();
+
+ BOOST_CHECK(Config::instance()->cinemas_file() == boost::filesystem::canonical(dir / "cinemas.sqlite3"));
}
+
+
diff --git a/test/content_test.cc b/test/content_test.cc
index 5ab714b47..b9a25a02b 100644
--- a/test/content_test.cc
+++ b/test/content_test.cc
@@ -41,15 +41,9 @@ using namespace dcpomatic;
/** There has been garbled audio with this piece of content */
BOOST_AUTO_TEST_CASE (content_test1)
{
- auto film = new_test_film ("content_test1");
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
- film->set_name ("content_test1");
- film->set_container (Ratio::from_id ("185"));
- film->set_audio_channels(16);
-
auto content = content_factory(TestPaths::private_data() / "demo_sound_bug.mkv")[0];
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs ());
+ auto film = new_test_film("content_test1", { content });
+ film->set_audio_channels(16);
make_and_verify_dcp (
film,
{ dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
@@ -65,7 +59,7 @@ BOOST_AUTO_TEST_CASE (content_test1)
BOOST_AUTO_TEST_CASE (content_test2)
{
auto content = content_factory("test/data/red_23976.mp4")[0];
- auto film = new_test_film2 ("content_test2", {content});
+ auto film = new_test_film("content_test2", {content});
content->set_trim_start(film, ContentTime::from_seconds(0.5));
make_and_verify_dcp (film);
}
@@ -75,7 +69,7 @@ BOOST_AUTO_TEST_CASE (content_test2)
BOOST_AUTO_TEST_CASE (content_test3)
{
auto content = content_factory("test/data/red_24.mp4")[0];
- auto film = new_test_film2 ("content_test3", {content});
+ auto film = new_test_film("content_test3", {content});
film->set_sequence (false);
/* Trim */
@@ -117,7 +111,7 @@ BOOST_AUTO_TEST_CASE (content_test3)
/** Content containing video will have its length rounded to the nearest video frame */
BOOST_AUTO_TEST_CASE (content_test4)
{
- auto film = new_test_film2 ("content_test4");
+ auto film = new_test_film("content_test4");
auto video = content_factory("test/data/count300bd24.m2ts")[0];
film->examine_and_add_content (video);
@@ -132,7 +126,7 @@ BOOST_AUTO_TEST_CASE (content_test4)
BOOST_AUTO_TEST_CASE (content_test5)
{
auto audio = content_factory("test/data/sine_16_48_220_10.wav");
- auto film = new_test_film2 ("content_test5", audio);
+ auto film = new_test_film("content_test5", audio);
audio[0]->set_trim_end(dcpomatic::ContentTime(3000));
@@ -145,16 +139,17 @@ BOOST_AUTO_TEST_CASE (content_test6)
{
Cleanup cl;
- auto film = new_test_film2 (
+ auto film = new_test_film(
"content_test6",
content_factory(TestPaths::private_data() / "fha.mkv"),
&cl
);
film->set_audio_channels(16);
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, 100000000);
make_and_verify_dcp (film);
- check_dcp (TestPaths::private_data() / "fha", film);
+ check_dcp (TestPaths::private_data() / "v2.18.x" / "fha", film);
cl.run ();
}
@@ -163,17 +158,25 @@ BOOST_AUTO_TEST_CASE (content_test6)
/** Reel length error when making the test for #1833 */
BOOST_AUTO_TEST_CASE (content_test7)
{
+ Cleanup cl;
+
auto content = content_factory(TestPaths::private_data() / "clapperboard.mp4");
- auto film = new_test_film2 ("content_test7", content);
+ auto film = new_test_film("content_test7", content, &cl);
content[0]->audio->set_delay(-1000);
make_and_verify_dcp (film, { dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K });
+
+ cl.run();
}
/** WAVs with markers (I think) can end up making audio packets with no channels and no frames (#2617) */
BOOST_AUTO_TEST_CASE(wav_with_markers_zero_channels_test)
{
+ Cleanup cl;
+
auto content = content_factory(TestPaths::private_data() / "wav_with_markers.wav");
- auto film = new_test_film2("wav_with_markers_zero_channels_test", content);
+ auto film = new_test_film("wav_with_markers_zero_channels_test", content, &cl);
make_and_verify_dcp(film, { dcp::VerificationNote::Code::MISSING_CPL_METADATA });
+
+ cl.run();
}
diff --git a/test/cpl_hash_test.cc b/test/cpl_hash_test.cc
index 3b430a6bd..603fa4b8d 100644
--- a/test/cpl_hash_test.cc
+++ b/test/cpl_hash_test.cc
@@ -43,7 +43,7 @@ BOOST_AUTO_TEST_CASE (hash_added_to_imported_dcp_test)
using namespace boost::filesystem;
string const ov_name = "hash_added_to_imported_dcp_test_ov";
- auto ov = new_test_film2(
+ auto ov = new_test_film(
ov_name,
content_factory("test/data/flat_red.png")
);
@@ -70,7 +70,7 @@ BOOST_AUTO_TEST_CASE (hash_added_to_imported_dcp_test)
string const vf_name = "hash_added_to_imported_dcp_test_vf";
auto ov_content = make_shared<DCPContent>(String::compose("build/test/%1/%2", ov_name, ov->dcp_name()));
- auto vf = new_test_film2 (
+ auto vf = new_test_film(
vf_name, { ov_content }
);
diff --git a/test/cpl_metadata_test.cc b/test/cpl_metadata_test.cc
index 544e78899..59ceed064 100644
--- a/test/cpl_metadata_test.cc
+++ b/test/cpl_metadata_test.cc
@@ -43,7 +43,7 @@ BOOST_AUTO_TEST_CASE(main_sound_configuration_test_51_vi)
auto Rs = content_factory("test/data/Rs.wav")[0];
auto VI = content_factory("test/data/sine_440.wav")[0];
- auto film = new_test_film2("main_sound_configuration_test_51_vi", { picture, L, R, C, Lfe, Ls, Rs, VI });
+ auto film = new_test_film("main_sound_configuration_test_51_vi", { picture, L, R, C, Lfe, Ls, Rs, VI });
film->set_audio_channels(8);
auto set_map = [](shared_ptr<Content> content, dcp::Channel channel) {
@@ -88,7 +88,7 @@ BOOST_AUTO_TEST_CASE(main_sound_configuration_test_71)
auto BsR = content_factory("test/data/Rs.wav")[0];
auto VI = content_factory("test/data/sine_440.wav")[0];
- auto film = new_test_film2("main_sound_configuration_test_51_vi", { picture, L, R, C, Lfe, Ls, Rs, BsL, BsR, VI });
+ auto film = new_test_film("main_sound_configuration_test_51_vi", { picture, L, R, C, Lfe, Ls, Rs, BsL, BsR, VI });
film->set_audio_channels(12);
auto set_map = [](shared_ptr<Content> content, dcp::Channel channel) {
diff --git a/test/create_cli_test.cc b/test/create_cli_test.cc
index aae5fb6de..7006fa6a0 100644
--- a/test/create_cli_test.cc
+++ b/test/create_cli_test.cc
@@ -179,11 +179,11 @@ BOOST_AUTO_TEST_CASE (create_cli_test)
BOOST_CHECK_EQUAL(cc._fourk, true);
BOOST_CHECK (!cc.error);
- cc = run ("dcpomatic2_create --j2k-bandwidth 120 foo.mp4");
+ cc = run ("dcpomatic2_create --video-bit-rate 120 foo.mp4");
BOOST_REQUIRE_EQUAL (cc.content.size(), 1U);
BOOST_CHECK_EQUAL (cc.content[0].path, "foo.mp4");
- BOOST_REQUIRE(cc._j2k_bandwidth);
- BOOST_CHECK_EQUAL(*cc._j2k_bandwidth, 120000000);
+ BOOST_REQUIRE(cc._video_bit_rate);
+ BOOST_CHECK_EQUAL(*cc._video_bit_rate, 120000000);
BOOST_CHECK (!cc.error);
cc = run ("dcpomatic2_create --channel L fred.wav --channel R jim.wav sheila.wav");
@@ -224,9 +224,7 @@ BOOST_AUTO_TEST_CASE (create_cli_test)
BOOST_AUTO_TEST_CASE(create_cli_template_test)
{
- ConfigRestorer cr;
-
- Config::override_path = "test/data";
+ ConfigRestorer cr("test/data");
auto cc = run("dcpomatic2_create test/data/flat_red.png");
auto film = cc.make_film();
@@ -288,25 +286,3 @@ BOOST_AUTO_TEST_CASE(create_cli_template_test)
film = cc.make_film();
BOOST_CHECK(film->interop());
}
-
-
-BOOST_AUTO_TEST_CASE(create_cli_defaults_test)
-{
- ConfigRestorer cr;
-
- /* I think on balance dcpomatic2_create should not use the defaults from Config;
- * it seems a bit surprising that settings from a GUI tool can change the behaviour of
- * a CLI tool, and at some point we're probably going to remove all the default config
- * options from the main DoM anyway (in favour of a default template).
- */
- Config::instance()->set_default_interop(true);
- auto cc = run("dcpomatic2_create test/data/flat_red.png");
- auto film = cc.make_film();
- BOOST_CHECK(!film->interop());
-
- Config::instance()->set_default_dcp_content_type(DCPContentType::from_isdcf_name("FT"));
- cc = run("dcpomatic2_create test/data/flat_red.png");
- film = cc.make_film();
- BOOST_CHECK_EQUAL(film->dcp_content_type()->isdcf_name(), "TST");
-}
-
diff --git a/test/data b/test/data
-Subproject 6a4fa8b7c13e4f09fcee995191a2c86e1eff9d6
+Subproject e789dcb8769e329e33b2eb94f283c69f91deb92
diff --git a/test/dcp_decoder_test.cc b/test/dcp_decoder_test.cc
index d0052f4a1..6e2bdcb6a 100644
--- a/test/dcp_decoder_test.cc
+++ b/test/dcp_decoder_test.cc
@@ -52,15 +52,15 @@ BOOST_AUTO_TEST_CASE (check_reuse_old_data_test)
{
/* Make some DCPs */
- auto ov = new_test_film2 ("check_reuse_old_data_ov", content_factory("test/data/flat_red.png"));
+ auto ov = new_test_film("check_reuse_old_data_ov", content_factory("test/data/flat_red.png"));
make_and_verify_dcp (ov);
auto ov_content = make_shared<DCPContent>(ov->dir(ov->dcp_name(false)));
- auto vf = new_test_film2 ("check_reuse_old_data_vf", {ov_content, content_factory("test/data/L.wav")[0]});
+ auto vf = new_test_film("check_reuse_old_data_vf", {ov_content, content_factory("test/data/L.wav")[0]});
ov_content->set_reference_video (true);
make_and_verify_dcp(vf, {dcp::VerificationNote::Code::EXTERNAL_ASSET}, false);
- auto encrypted = new_test_film2 ("check_reuse_old_data_decrypted");
+ auto encrypted = new_test_film("check_reuse_old_data_decrypted");
encrypted->examine_and_add_content (content_factory("test/data/flat_red.png")[0]);
BOOST_REQUIRE (!wait_for_jobs());
encrypted->set_encrypted (true);
@@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE (check_reuse_old_data_test)
/* Add just the OV to a new project, move it around a bit and check that
the _reels get reused.
*/
- auto test = new_test_film2 ("check_reuse_old_data_test1");
+ auto test = new_test_film("check_reuse_old_data_test1");
ov_content = make_shared<DCPContent>(ov->dir(ov->dcp_name(false)));
test->examine_and_add_content (ov_content);
BOOST_REQUIRE (!wait_for_jobs());
@@ -96,7 +96,7 @@ BOOST_AUTO_TEST_CASE (check_reuse_old_data_test)
/* Add the VF to a new project, then add the OV and check that the
_reels did not get reused.
*/
- test = new_test_film2 ("check_reuse_old_data_test2");
+ test = new_test_film("check_reuse_old_data_test2");
auto vf_content = make_shared<DCPContent>(vf->dir(vf->dcp_name(false)));
test->examine_and_add_content (vf_content);
BOOST_REQUIRE (!wait_for_jobs());
@@ -114,7 +114,7 @@ BOOST_AUTO_TEST_CASE (check_reuse_old_data_test)
BOOST_REQUIRE (reels != decoder->reels());
/* Add a KDM to an encrypted DCP and check that the _reels did not get reused */
- test = new_test_film2 ("check_reuse_old_data_test3");
+ test = new_test_film("check_reuse_old_data_test3");
auto encrypted_content = make_shared<DCPContent>(encrypted->dir(encrypted->dcp_name(false)));
test->examine_and_add_content (encrypted_content);
BOOST_REQUIRE (!wait_for_jobs());
diff --git a/test/dcp_digest_file_test.cc b/test/dcp_digest_file_test.cc
index f22aa6d67..5c12fbb33 100644
--- a/test/dcp_digest_file_test.cc
+++ b/test/dcp_digest_file_test.cc
@@ -66,7 +66,7 @@ BOOST_AUTO_TEST_CASE (dcp_digest_file_test2)
};
auto red = content_factory("test/data/flat_red.png");
- auto ov = new_test_film2 ("dcp_digest_file_test2_ov", red);
+ auto ov = new_test_film("dcp_digest_file_test2_ov", red);
ov->set_encrypted (true);
make_and_verify_dcp (ov);
@@ -90,7 +90,7 @@ BOOST_AUTO_TEST_CASE (dcp_digest_file_test2)
ov_dcp->add_kdm (kdm);
ov_dcp->set_reference_video (true);
ov_dcp->set_reference_audio (true);
- auto vf = new_test_film2 ("dcp_digest_file_test2_vf", { ov_dcp });
+ auto vf = new_test_film("dcp_digest_file_test2_vf", { ov_dcp });
vf->set_encrypted (true);
make_and_verify_dcp(vf, {dcp::VerificationNote::Code::EXTERNAL_ASSET}, false);
diff --git a/test/dcp_examiner_test.cc b/test/dcp_examiner_test.cc
index 7275a33c7..6efbd1bbc 100644
--- a/test/dcp_examiner_test.cc
+++ b/test/dcp_examiner_test.cc
@@ -34,13 +34,13 @@ using std::make_shared;
BOOST_AUTO_TEST_CASE(check_examine_vfs)
{
auto image = content_factory("test/data/scope_red.png")[0];
- auto ov = new_test_film2("check_examine_vfs_ov", { image });
+ auto ov = new_test_film("check_examine_vfs_ov", { image });
ov->set_container(Ratio::from_id("239"));
make_and_verify_dcp(ov);
auto ov_dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
auto second_reel = content_factory("test/data/scope_red.png")[0];
- auto vf = new_test_film2("check_examine_vfs_vf", { ov_dcp, second_reel });
+ auto vf = new_test_film("check_examine_vfs_vf", { ov_dcp, second_reel });
vf->set_container(Ratio::from_id("239"));
vf->set_reel_type(ReelType::BY_VIDEO_CONTENT);
ov_dcp->set_reference_video(true);
diff --git a/test/dcp_metadata_test.cc b/test/dcp_metadata_test.cc
index dd81a4161..f3d912631 100644
--- a/test/dcp_metadata_test.cc
+++ b/test/dcp_metadata_test.cc
@@ -31,7 +31,7 @@
BOOST_AUTO_TEST_CASE (dcp_metadata_test)
{
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2 ("dcp_metadata_test", content);
+ auto film = new_test_film("dcp_metadata_test", content);
Config::instance()->set_dcp_creator ("this is the creator");
Config::instance()->set_dcp_issuer ("this is the issuer");
@@ -54,7 +54,7 @@ BOOST_AUTO_TEST_CASE (dcp_metadata_test)
BOOST_AUTO_TEST_CASE(main_picture_active_area_test)
{
auto content = content_factory(TestPaths::private_data() / "bbc405.png");
- auto film = new_test_film2("main_picture_active_area_test", content);
+ auto film = new_test_film("main_picture_active_area_test", content);
film->set_resolution(Resolution::FOUR_K);
film->set_interop(false);
diff --git a/test/dcp_playback_test.cc b/test/dcp_playback_test.cc
index a5b69e181..3b08f998c 100644
--- a/test/dcp_playback_test.cc
+++ b/test/dcp_playback_test.cc
@@ -38,10 +38,8 @@ using namespace dcpomatic;
/** Simulate the work that the player does, for profiling */
BOOST_AUTO_TEST_CASE (dcp_playback_test)
{
- auto film = new_test_film ("dcp_playback_test");
auto content = make_shared<DCPContent>(TestPaths::private_data() / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("dcp_playback_test", { content });
Player player(film, Image::Alignment::PADDED);
diff --git a/test/dcp_subtitle_test.cc b/test/dcp_subtitle_test.cc
index 837362e84..22e4fd9ff 100644
--- a/test/dcp_subtitle_test.cc
+++ b/test/dcp_subtitle_test.cc
@@ -37,7 +37,7 @@
#include "lib/text_content.h"
#include "lib/text_decoder.h"
#include "test.h"
-#include <dcp/mono_picture_asset.h>
+#include <dcp/mono_j2k_picture_asset.h>
#include <dcp/openjpeg_image.h>
#include <dcp/smpte_subtitle_asset.h>
#include <boost/test/unit_test.hpp>
@@ -75,14 +75,10 @@ store (ContentStringText sub)
/** Test pass-through of a very simple DCP subtitle file */
BOOST_AUTO_TEST_CASE (dcp_subtitle_test)
{
- auto film = new_test_film ("dcp_subtitle_test");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
- film->set_interop (false);
auto content = make_shared<DCPSubtitleContent>("test/data/dcp_sub.xml");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs ());
+ auto film = new_test_film("dcp_subtitle_test", { content });
+ film->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
+ film->set_name("frobozz");
BOOST_CHECK_EQUAL (content->full_length(film).get(), DCPTime::from_seconds(2).get());
@@ -107,13 +103,8 @@ BOOST_AUTO_TEST_CASE (dcp_subtitle_test)
/** Test parsing of a subtitle within an existing DCP */
BOOST_AUTO_TEST_CASE (dcp_subtitle_within_dcp_test)
{
- auto film = new_test_film ("dcp_subtitle_within_dcp_test");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
auto content = make_shared<DCPContent>(TestPaths::private_data() / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs ());
+ auto film = new_test_film("dcp_subtitle_within_dcp_test", { content });
auto decoder = make_shared<DCPDecoder>(film, content, false, false, shared_ptr<DCPDecoder>());
decoder->only_text()->PlainStart.connect (bind (store, _1));
@@ -130,13 +121,8 @@ BOOST_AUTO_TEST_CASE (dcp_subtitle_within_dcp_test)
/** Test subtitles whose text includes things like &lt;b&gt; */
BOOST_AUTO_TEST_CASE (dcp_subtitle_test2)
{
- auto film = new_test_film ("dcp_subtitle_test2");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
auto content = make_shared<DCPSubtitleContent>("test/data/dcp_sub2.xml");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs ());
+ auto film = new_test_film("dcp_subtitle_test2", { content });
auto decoder = make_shared<DCPSubtitleDecoder>(film, content);
decoder->only_text()->PlainStart.connect (bind (store, _1));
@@ -154,15 +140,10 @@ BOOST_AUTO_TEST_CASE (dcp_subtitle_test2)
/** Test a failure case */
BOOST_AUTO_TEST_CASE (dcp_subtitle_test3)
{
- auto film = new_test_film ("dcp_subtitle_test3");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
- film->set_interop (true);
auto content = make_shared<DCPSubtitleContent>("test/data/dcp_sub3.xml");
- film->examine_and_add_content (content);
+ auto film = new_test_film("dcp_subtitle_test3", { content });
+ film->set_interop (true);
content->only_text()->set_language(dcp::LanguageTag("de"));
- BOOST_REQUIRE (!wait_for_jobs ());
make_and_verify_dcp (film, { dcp::VerificationNote::Code::INVALID_STANDARD });
@@ -192,7 +173,7 @@ BOOST_AUTO_TEST_CASE (dcp_subtitle_test4)
{
auto content = make_shared<DCPSubtitleContent>("test/data/dcp_sub3.xml");
auto content2 = make_shared<DCPSubtitleContent>("test/data/dcp_sub3.xml");
- auto film = new_test_film2 ("dcp_subtitle_test4", {content, content2});
+ auto film = new_test_film("dcp_subtitle_test4", {content, content2});
film->set_interop (true);
content->only_text()->add_font(make_shared<Font>("font1"));
@@ -227,7 +208,7 @@ check_font_tags (vector<cxml::NodePtr> nodes)
BOOST_AUTO_TEST_CASE (dcp_subtitle_test5)
{
auto content = make_shared<DCPSubtitleContent>("test/data/dcp_sub6.xml");
- auto film = new_test_film2 ("dcp_subtitle_test5", {content});
+ auto film = new_test_film("dcp_subtitle_test5", {content});
film->set_interop (true);
content->only_text()->set_language(dcp::LanguageTag("de"));
@@ -246,12 +227,12 @@ BOOST_AUTO_TEST_CASE (dcp_subtitle_test5)
BOOST_AUTO_TEST_CASE (test_font_override)
{
auto content = make_shared<DCPSubtitleContent>("test/data/dcp_sub4.xml");
- auto film = new_test_film2("test_font_override", {content});
+ auto film = new_test_film("test_font_override", {content});
film->set_interop(true);
content->only_text()->set_language(dcp::LanguageTag("de"));
BOOST_REQUIRE_EQUAL(content->text.size(), 1U);
- auto font = content->text.front()->get_font("0_theFontId");
+ auto font = content->text.front()->get_font("theFontId");
BOOST_REQUIRE(font);
font->set_file("test/data/Inconsolata-VF.ttf");
@@ -285,7 +266,7 @@ BOOST_AUTO_TEST_CASE(entity_from_dcp_source)
source_xml.close();
auto content = make_shared<DCPSubtitleContent>("build/test/entity_from_dcp_source.xml");
- auto film = new_test_film2("entity_from_dcp_source", { content });
+ auto film = new_test_film("entity_from_dcp_source", { content });
film->set_interop(false);
content->only_text()->set_use(true);
content->only_text()->set_burn(false);
@@ -327,7 +308,7 @@ BOOST_AUTO_TEST_CASE(entity_from_dcp_source)
dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING,
});
- dcp::MonoPictureAsset burnt(dcp_file(film, "j2c_"));
+ dcp::MonoJ2KPictureAsset burnt(dcp_file(film, "j2c_"));
auto frame = burnt.start_read()->get_frame(12)->xyz_image();
auto const size = frame->size();
int max_X = 0;
diff --git a/test/digest_test.cc b/test/digest_test.cc
index 2d49768f7..39737b7f5 100644
--- a/test/digest_test.cc
+++ b/test/digest_test.cc
@@ -65,17 +65,11 @@ openssl_hash (boost::filesystem::path file)
/** Test the digests made by the DCP writing code on a multi-reel DCP */
BOOST_AUTO_TEST_CASE (digest_test)
{
- auto film = new_test_film ("digest_test");
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
- film->set_name ("digest_test");
auto r = make_shared<ImageContent>("test/data/flat_red.png");
auto g = make_shared<ImageContent>("test/data/flat_green.png");
auto b = make_shared<ImageContent>("test/data/flat_blue.png");
- film->examine_and_add_content (r);
- film->examine_and_add_content (g);
- film->examine_and_add_content (b);
+ auto film = new_test_film("digest_test", { r, g, b });
film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
- BOOST_REQUIRE (!wait_for_jobs());
BOOST_CHECK (Config::instance()->master_encoding_threads() > 1);
make_and_verify_dcp (film);
diff --git a/test/disk_writer_test.cc b/test/disk_writer_test.cc
index 553adcae7..ff39eaaea 100644
--- a/test/disk_writer_test.cc
+++ b/test/disk_writer_test.cc
@@ -228,110 +228,3 @@ BOOST_AUTO_TEST_CASE (disk_writer_test3)
cl.run();
}
-
-
-BOOST_AUTO_TEST_CASE (osx_drive_identification_test)
-{
- vector<OSXDisk> disks;
-
- auto disk = [&disks](string mount_point, string media_path, bool whole, std::vector<boost::filesystem::path> mount_points)
- {
- auto mp = analyse_osx_media_path (media_path);
- if (mp) {
- disks.push_back({mount_point, {}, {}, *mp, whole, mount_points, 0});
- }
- };
-
- auto find_unmounted = [](vector<OSXDisk> disks) {
- auto drives = osx_disks_to_drives (disks);
- vector<Drive> unmounted;
- std::copy_if (drives.begin(), drives.end(), std::back_inserter(unmounted), [](Drive const& drive) { return !drive.mounted(); });
- return unmounted;
- };
-
- disk("/dev/disk4s1", "IODeviceTree:/arm-io@10F00000/apcie@90000000/pci-bridge1@1/pcie-xhci@0/@7:1", false, {});
- disk("/dev/disk4", "IODeviceTree:/arm-io@10F00000/apcie@90000000/pci-bridge1@1/pcie-xhci@0/@7:0", true, {});
- disk("/dev/disk0", "IODeviceTree:/arm-io@10F00000/ans@77400000/iop-ans-nub/AppleANS3NVMeController/@1:0", true, {});
- disk("/dev/disk0s1", "IODeviceTree:/arm-io@10F00000/ans@77400000/iop-ans-nub/AppleANS3NVMeController/@1:1", false, {});
- disk("/dev/disk0s2", "IODeviceTree:/arm-io@10F00000/ans@77400000/iop-ans-nub/AppleANS3NVMeController/@1:2", false, {});
- disk("/dev/disk0s3", "IODeviceTree:/arm-io@10F00000/ans@77400000/iop-ans-nub/AppleANS3NVMeController/@1:3", false, {});
- disk("/dev/disk1", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/iBootSystemContainer@1/AppleAPFSContainerScheme/AppleAPFSMedia", true, {});
- disk("/dev/disk2", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/RecoveryOSContainer@3/AppleAPFSContainerScheme/AppleAPFSMedia", true, {});
- disk("/dev/disk3", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia", false, {});
- disk("/dev/disk1s1", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/iBootSystemContainer@1/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/iSCPreboot@1", false, {});
- disk("/dev/disk1s2", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/iBootSystemContainer@1/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/xART@2", false, {});
- disk("/dev/disk1s3", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/iBootSystemContainer@1/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Hardware@3", false, {});
- disk("/dev/disk1s4", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/iBootSystemContainer@1/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Recovery@4", false, {});
- disk("/dev/disk2s1", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/RecoveryOSContainer@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Recovery@1", false, {});
- disk("/dev/disk2s2", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/RecoveryOSContainer@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Update@2", false, {});
- disk("/dev/disk3s1", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Macintosh HD@1", false, {});
- disk("/dev/disk3s4", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Update@4", false, {"/System/Volumes/Update"});
- disk("/dev/disk3s5", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Data@5", false, {"/System/Volumes/Data"});
- disk("/dev/disk3s2", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Preboot@2", false, {"/System/Volumes/Preboot"});
- disk("/dev/disk3s3", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Recovery@3", false, {});
- disk("/dev/disk3s6", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/VM@6", false, {"/System/Volumes/VM"});
- disk("/dev/disk3s1s1", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Macintosh HD@1/com.apple.os.update-EA882DCA7A28EBA0A6E94689836BB10D77D84D1AEE2468E17775A447AA815278@1", false, {"/"});
-
- vector<Drive> writeable = find_unmounted (disks);
- BOOST_REQUIRE_EQUAL (writeable.size(), 1U);
- BOOST_CHECK_EQUAL (writeable[0].device(), "/dev/disk4");
-
- disks.clear ();
- disk("/dev/disk4s1", "IOService:/IOResources/IOHDIXController/IOHDIXHDDriveOutKernel@0/IODiskImageBlockStorageDeviceOutKernel/IOBlockStorageDriver/Apple UDIF read-only compressed (zlib) Media/IOGUIDPartitionScheme/disk image@1", false, {});
- disk("/dev/disk4", "IOService:/IOResources/IOHDIXController/IOHDIXHDDriveOutKernel@0/IODiskImageBlockStorageDeviceOutKernel/IOBlockStorageDriver/Apple UDIF read-only compressed (zlib) Media", true, {});
- disk("/dev/disk3s1", "IODeviceTree:/PCI0@0/XHC1@14/@2:1", false, {});
- disk("/dev/disk3", "IODeviceTree:/PCI0@0/XHC1@14/@2:0", true, {});
- disk("/dev/disk0", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:0", true, {});
- disk("/dev/disk0s1", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:1", false, {});
- disk("/dev/disk0s2", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:2", false, {"/Volumes/Macintosh HD"});
- disk("/dev/disk0s3", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:3", false, {});
- disk("/dev/disk0s4", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:4", false, {});
- disk("/dev/disk0s5", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:5", false, {"/Volumes/High Sierra"});
- disk("/dev/disk0s6", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:6", false, {});
- disk("/dev/disk0s7", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:7", false, {"/Volumes/Recovery HD"});
- disk("/dev/disk1", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 3@3/AppleAPFSContainerScheme/AppleAPFSMedia", true, {});
- disk("/dev/disk", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 4@4/AppleAPFSContainerScheme/AppleAPFSMedia", true, {});
- disk("/dev/disk1s1", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 3@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Untitled - Data@1", false, {"/Volumes/Untitled - Data"});
- disk("/dev/disk1s2", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 3@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Preboot@2", false, {});
- disk("/dev/disk1s3", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 3@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Recovery@3", false, {});
- disk("/dev/disk1s4", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 3@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/VM@4", false, {});
- disk("/dev/disk1s5", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 3@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Untitled@5", false, {"/Volumes/Untitled"});
- disk("/dev/disk2s1", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 4@4/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Catalina - Data@1", false, {});
- disk("/dev/disk2s2", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 4@4/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Preboot@2", false, {});
- disk("/dev/disk2s3", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 4@4/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Recovery@3", false, {});
- disk("/dev/disk2s4", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 4@4/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/VM@4", false, {"/private/var/vm"});
- disk("/dev/disk2s5", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 4@4/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Catalina@5", false, {"/"});
-
- writeable = find_unmounted (disks);
- BOOST_REQUIRE_EQUAL (writeable.size(), 1U);
- BOOST_CHECK_EQUAL (writeable[0].device(), "/dev/disk3");
-
- disks.clear ();
- disk("/dev/disk7", "IOService:/IOResources/IOHDIXController/IOHDIXHDDriveOutKernel@3/IODiskImageBlockStorageDeviceOutKernel/IOBlockStorageDriver/Apple UDIF read-only compressed (zlib) Media", true, {});
- disk("/dev/disk7s1", "IOService:/IOResources/IOHDIXController/IOHDIXHDDriveOutKernel@3/IODiskImageBlockStorageDeviceOutKernel/IOBlockStorageDriver/Apple UDIF read-only compressed (zlib) Media/IOGUIDPartitionScheme/disk image@1", false, {});
- disk("/dev/disk6s1", "MediaPathKey is IOService:/IOResources/IOHDIXController/IOHDIXHDDriveOutKernel@2/IODiskImageBlockStorageDeviceOutKernel/IOBlockStorageDriver/Apple UDIF read-only compressed (zlib) Media/IOGUIDPartitionScheme/disk image@1", false, {});
- disk("/dev/disk6", "IOService:/IOResources/IOHDIXController/IOHDIXHDDriveOutKernel@2/IODiskImageBlockStorageDeviceOutKernel/IOBlockStorageDriver/Apple UDIF read-only compressed (zlib) Media", true, {});
- disk("/dev/disk5s1", "IOService:/IOResources/IOHDIXController/IOHDIXHDDriveOutKernel@1/IODiskImageBlockStorageDeviceOutKernel/IOBlockStorageDriver/Apple UDIF read-only compressed (zlib) Media/IOGUIDPartitionScheme/disk image@1", false, {});
- disk("/dev/disk5", "IOService:/IOResources/IOHDIXController/IOHDIXHDDriveOutKernel@1/IODiskImageBlockStorageDeviceOutKernel/IOBlockStorageDriver/Apple UDIF read-only compressed (zlib) Media", true, {});
- disk("/dev/disk4s1", "IOService:/IOResources/IOHDIXController/IOHDIXHDDriveOutKernel@0/IODiskImageBlockStorageDeviceOutKernel/IOBlockStorageDriver/Apple UDIF read-only compressed (zlib) Media/IOGUIDPartitionScheme/disk image@1", false, {});
- disk("/dev/disk4", "IOService:/IOResources/IOHDIXController/IOHDIXHDDriveOutKernel@0/IODiskImageBlockStorageDeviceOutKernel/IOBlockStorageDriver/Apple UDIF read-only compressed (zlib) Media", true, {});
- disk("/dev/disk0", "IODeviceTree:/PCI0@1e0000/pci8086,2829@1F,2/PRT3@3/PMP@0/@0:0", true, {});
- disk("/dev/disk2", "IODeviceTree:/PCI0@1e0000/pci8086,2829@1F,2/PRT1@1/PMP@0/@0:0", true, {});
- disk("/dev/disk1", "IODeviceTree:/PCI0@1e0000/pci8086,2829@1F,2/PRT0@0/PMP@0/@0:0", true, {});
- disk("/dev/disk1s1", "IODeviceTree:/PCI0@1e0000/pci8086,2829@1F,2/PRT0@0/PMP@0/@0:1", false, {"/Volumes/EFI"});
- disk("/dev/disk2s1", "IODeviceTree:/PCI0@1e0000/pci8086,2829@1F,2/PRT1@1/PMP@0/@0:1", false, {});
- disk("/dev/disk2s2", "IODeviceTree:/PCI0@1e0000/pci8086,2829@1F,2/PRT1@1/PMP@0/@0:2", false, {});
- disk("/dev/disk3", "IOService:/AppleACPIPlatformExpert/PCI0@1e0000/AppleACPIPCI/pci8086,2829@1F,2/AppleAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/VBOX HARDDISK Media/IOGUIDPartitionScheme/disk image@2/AppleAPFSContainerScheme/AppleAPFSMedia", false, {});
- disk("/dev/disk3s1", "IOService:/AppleACPIPlatformExpert/PCI0@1e0000/AppleACPIPCI/pci8086,2829@1F,2/AppleAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/VBOX HARDDISK Media/IOGUIDPartitionScheme/disk image@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/macOS - Data@1", false, {"/System/Volumes/Data"});
- disk("/dev/disk3s2", "IOService:/AppleACPIPlatformExpert/PCI0@1e0000/AppleACPIPCI/pci8086,2829@1F,2/AppleAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/VBOX HARDDISK Media/IOGUIDPartitionScheme/disk image@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Preboot@2", false, {"/System/Volumes/Preboot"});
- disk("/dev/disk3s3", "IOService:/AppleACPIPlatformExpert/PCI0@1e0000/AppleACPIPCI/pci8086,2829@1F,2/AppleAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/VBOX HARDDISK Media/IOGUIDPartitionScheme/disk image@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Recovery@3", false, {});
- disk("/dev/disk3s4", "IOService:/AppleACPIPlatformExpert/PCI0@1e0000/AppleACPIPCI/pci8086,2829@1F,2/AppleAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/VBOX HARDDISK Media/IOGUIDPartitionScheme/disk image@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/VM@4", false, {"/System/Volumes/VM"});
- disk("/dev/disk3s5", "IOService:/AppleACPIPlatformExpert/PCI0@1e0000/AppleACPIPCI/pci8086,2829@1F,2/AppleAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/VBOX HARDDISK Media/IOGUIDPartitionScheme/disk image@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/macOS@5", false, {});
- disk("/dev/disk3s6", "IOService:/AppleACPIPlatformExpert/PCI0@1e0000/AppleACPIPCI/pci8086,2829@1F,2/AppleAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/VBOX HARDDISK Media/IOGUIDPartitionScheme/disk image@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Update@6", false, {"/System/Volumes/Update"});
- disk("/dev/disk3s5s1", "IOService:/AppleACPIPlatformExpert/PCI0@1e0000/AppleACPIPCI/pci8086,2829@1F,2/AppleAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/VBOX HARDDISK Media/IOGUIDPartitionScheme/disk image@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/macOS@5/com.apple.os.update-5523D8E63431315F9F949CCDD0274BF797F5CEE4EAF616D4C66A01B8D6A83C7B@1", false, {"/"});
-
- writeable = find_unmounted (disks);
- BOOST_REQUIRE_EQUAL (writeable.size(), 1U);
- BOOST_CHECK_EQUAL (writeable[0].device(), "/dev/disk0");
-}
-
diff --git a/test/dkdm_recipient_list_test.cc b/test/dkdm_recipient_list_test.cc
new file mode 100644
index 000000000..406d181a1
--- /dev/null
+++ b/test/dkdm_recipient_list_test.cc
@@ -0,0 +1,57 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "lib/config.h"
+#include "lib/dkdm_recipient.h"
+#include "lib/dkdm_recipient_list.h"
+#include "test.h"
+#include <dcp/filesystem.h>
+#include <boost/test/unit_test.hpp>
+
+
+BOOST_AUTO_TEST_CASE(dkdm_receipient_list_copy_from_xml_test)
+{
+ ConfigRestorer cr("build/test/dkdm_recipient_list_copy_config");
+
+ dcp::filesystem::remove_all(*Config::override_path);
+ dcp::filesystem::create_directories(*Config::override_path);
+ dcp::filesystem::copy_file("test/data/dkdm_recipients.xml", *Config::override_path / "dkdm_recipients.xml");
+
+ DKDMRecipientList dkdm_recipient_list;
+ dkdm_recipient_list.read_legacy_file(Config::instance()->read_path("dkdm_recipients.xml"));
+ auto dkdm_recipients = dkdm_recipient_list.dkdm_recipients();
+ BOOST_REQUIRE_EQUAL(dkdm_recipients.size(), 2U);
+
+ auto dkdm_recipient_iter = dkdm_recipients.begin();
+ BOOST_CHECK_EQUAL(dkdm_recipient_iter->second.name, "Bob's Epics");
+ BOOST_CHECK_EQUAL(dkdm_recipient_iter->second.emails.size(), 2U);
+ BOOST_CHECK_EQUAL(dkdm_recipient_iter->second.emails[0], "epicbob@gmail.com");
+ BOOST_CHECK_EQUAL(dkdm_recipient_iter->second.emails[1], "boblikesemlong@cinema-bob.com");
+ BOOST_CHECK_EQUAL(dkdm_recipient_iter->second.recipient->subject_dn_qualifier(), "r5/Q5f3UTm7qzoF5QzNZP6aEuvI=");
+ ++dkdm_recipient_iter;
+
+ BOOST_CHECK_EQUAL(dkdm_recipient_iter->second.name, "Sharon's Shorts");
+ BOOST_CHECK_EQUAL(dkdm_recipient_iter->second.notes, "Even if it sucks, at least it's over quickly");
+ BOOST_CHECK_EQUAL(dkdm_recipient_iter->second.recipient->subject_dn_qualifier(), "FHerM3Us/DWuqD1MnztStSlFJO0=");
+ ++dkdm_recipient_iter;
+}
+
+
diff --git a/test/empty_caption_test.cc b/test/empty_caption_test.cc
index 3138c4af2..d9ae4a1c7 100644
--- a/test/empty_caption_test.cc
+++ b/test/empty_caption_test.cc
@@ -32,7 +32,7 @@ BOOST_AUTO_TEST_CASE (check_for_no_empty_text_nodes_in_failure_case)
Cleanup cl;
auto content = content_factory("test/data/empty.srt");
- auto film = new_test_film2("check_for_no_empty_text_nodes_in_failure_case", content, &cl);
+ auto film = new_test_film("check_for_no_empty_text_nodes_in_failure_case", content, &cl);
auto text = content[0]->text.front();
text->set_type (TextType::CLOSED_CAPTION);
text->set_dcp_track({"English", dcp::LanguageTag("en")});
diff --git a/test/empty_test.cc b/test/empty_test.cc
index b186954b3..922d6a5b1 100644
--- a/test/empty_test.cc
+++ b/test/empty_test.cc
@@ -55,7 +55,7 @@ has_video (shared_ptr<const Content> content)
BOOST_AUTO_TEST_CASE (empty_test1)
{
- auto film = new_test_film2 ("empty_test1");
+ auto film = new_test_film("empty_test1");
film->set_sequence (false);
auto contentA = make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
auto contentB = make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
@@ -88,7 +88,7 @@ BOOST_AUTO_TEST_CASE (empty_test1)
/** Some tests where the first empty period is not at time 0 */
BOOST_AUTO_TEST_CASE (empty_test2)
{
- auto film = new_test_film2 ("empty_test2");
+ auto film = new_test_film("empty_test2");
film->set_sequence (false);
auto contentA = make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
auto contentB = make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
@@ -127,7 +127,7 @@ BOOST_AUTO_TEST_CASE (empty_test2)
/** Test for when the film's playlist is not the same as the one passed into Empty */
BOOST_AUTO_TEST_CASE (empty_test3)
{
- auto film = new_test_film2 ("empty_test3");
+ auto film = new_test_film("empty_test3");
film->set_sequence (false);
auto contentA = make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
auto contentB = make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
@@ -160,7 +160,7 @@ BOOST_AUTO_TEST_CASE (empty_test3)
BOOST_AUTO_TEST_CASE (empty_test_with_overlapping_content)
{
- auto film = new_test_film2 ("empty_test_with_overlapping_content");
+ auto film = new_test_film("empty_test_with_overlapping_content");
film->set_sequence (false);
auto contentA = make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
auto contentB = make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
diff --git a/test/encryption_test.cc b/test/encryption_test.cc
index 9cadd087e..97359cd44 100644
--- a/test/encryption_test.cc
+++ b/test/encryption_test.cc
@@ -36,7 +36,7 @@ using std::make_shared;
BOOST_AUTO_TEST_CASE (smpte_dcp_with_subtitles_can_be_decrypted)
{
auto content = content_factory("test/data/15s.srt");
- auto film = new_test_film2 ("smpte_dcp_with_subtitles_can_be_decrypted", content);
+ auto film = new_test_film("smpte_dcp_with_subtitles_can_be_decrypted", content);
film->set_interop (false);
film->set_encrypted (true);
make_and_verify_dcp (
diff --git a/test/ffmpeg_audio_only_test.cc b/test/ffmpeg_audio_only_test.cc
index ea9a9c4e5..d994de91b 100644
--- a/test/ffmpeg_audio_only_test.cc
+++ b/test/ffmpeg_audio_only_test.cc
@@ -78,12 +78,8 @@ audio (std::shared_ptr<AudioBuffers> audio, int channels)
static shared_ptr<Film>
test (boost::filesystem::path file)
{
- auto film = new_test_film ("ffmpeg_audio_only_test");
- film->set_name ("test_film");
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
auto c = make_shared<FFmpegContent>(file);
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("ffmpeg_audio_only_test", { c });
film->write_metadata ();
/* See if can make a DCP without any errors */
diff --git a/test/ffmpeg_audio_test.cc b/test/ffmpeg_audio_test.cc
index b2a83aad8..01bc38da3 100644
--- a/test/ffmpeg_audio_test.cc
+++ b/test/ffmpeg_audio_test.cc
@@ -51,12 +51,8 @@ using std::string;
BOOST_AUTO_TEST_CASE (ffmpeg_audio_test)
{
- auto film = new_test_film ("ffmpeg_audio_test");
- film->set_name ("ffmpeg_audio_test");
auto c = make_shared<FFmpegContent> ("test/data/staircase.mov");
- film->examine_and_add_content (c);
-
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("ffmpeg_audio_test", { c });
int constexpr audio_channels = 6;
@@ -103,7 +99,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_audio_test)
/** Decode a file containing truehd so we can profile it; this is with the player set to normal */
BOOST_AUTO_TEST_CASE (ffmpeg_audio_test2)
{
- auto film = new_test_film2 ("ffmpeg_audio_test2");
+ auto film = new_test_film("ffmpeg_audio_test2");
auto content = content_factory(TestPaths::private_data() / "wayne.mkv")[0];
film->examine_and_add_content (content);
BOOST_REQUIRE (!wait_for_jobs ());
@@ -116,7 +112,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_audio_test2)
/** Decode a file containing truehd so we can profile it; this is with the player set to fast */
BOOST_AUTO_TEST_CASE (ffmpeg_audio_test3)
{
- auto film = new_test_film2 ("ffmpeg_audio_test3");
+ auto film = new_test_film("ffmpeg_audio_test3");
auto content = content_factory(TestPaths::private_data() / "wayne.mkv")[0];
film->examine_and_add_content (content);
BOOST_REQUIRE (!wait_for_jobs ());
@@ -130,7 +126,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_audio_test3)
/** Decode a file whose audio previously crashed DCP-o-matic (#1857) */
BOOST_AUTO_TEST_CASE (ffmpeg_audio_test4)
{
- auto film = new_test_film2 ("ffmpeg_audio_test4");
+ auto film = new_test_film("ffmpeg_audio_test4");
auto content = content_factory(TestPaths::private_data() / "Actuellement aout 2020.wmv")[0];
film->examine_and_add_content (content);
BOOST_REQUIRE (!wait_for_jobs ());
diff --git a/test/ffmpeg_dcp_test.cc b/test/ffmpeg_dcp_test.cc
index c332759c8..96a3618be 100644
--- a/test/ffmpeg_dcp_test.cc
+++ b/test/ffmpeg_dcp_test.cc
@@ -43,18 +43,10 @@ using std::make_shared;
BOOST_AUTO_TEST_CASE (ffmpeg_dcp_test)
{
- auto film = new_test_film ("ffmpeg_dcp_test");
- film->set_name ("test_film2");
auto c = make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (c);
-
- BOOST_REQUIRE (!wait_for_jobs());
-
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
+ auto film = new_test_film("ffmpeg_dcp_test", { c });
+ film->set_name("test_film2");
make_and_verify_dcp (film);
-
- BOOST_REQUIRE (!wait_for_jobs());
}
diff --git a/test/ffmpeg_decoder_error_test.cc b/test/ffmpeg_decoder_error_test.cc
index 1bb289feb..b53eabac5 100644
--- a/test/ffmpeg_decoder_error_test.cc
+++ b/test/ffmpeg_decoder_error_test.cc
@@ -39,7 +39,7 @@
BOOST_AUTO_TEST_CASE (check_exception_during_flush)
{
auto content = content_factory(TestPaths::private_data() / "3d_thx_broadway_2010_lossless.m2ts");
- auto film = new_test_film2 ("check_exception_during_flush", content);
+ auto film = new_test_film("check_exception_during_flush", content);
content[0]->set_trim_start(film, dcpomatic::ContentTime(2310308));
content[0]->set_trim_end(dcpomatic::ContentTime(116020));
@@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE (check_exception_during_flush)
BOOST_AUTO_TEST_CASE (check_exception_with_multiple_video_frames_per_packet)
{
auto content = content_factory(TestPaths::private_data() / "chk.mkv")[0];
- auto film = new_test_film2 ("check_exception_with_multiple_video_frames_per_packet", { content });
+ auto film = new_test_film("check_exception_with_multiple_video_frames_per_packet", { content });
auto player = std::make_shared<Player>(film, film->playlist());
while (!player->pass()) {}
diff --git a/test/ffmpeg_decoder_seek_test.cc b/test/ffmpeg_decoder_seek_test.cc
index 4dceae86b..796db043f 100644
--- a/test/ffmpeg_decoder_seek_test.cc
+++ b/test/ffmpeg_decoder_seek_test.cc
@@ -64,30 +64,28 @@ store (ContentVideo v)
static void
-check (shared_ptr<FFmpegDecoder> decoder, int frame)
+check (shared_ptr<FFmpegDecoder> decoder, ContentTime time)
{
BOOST_REQUIRE (decoder->ffmpeg_content()->video_frame_rate ());
- decoder->seek (ContentTime::from_frames (frame, decoder->ffmpeg_content()->video_frame_rate().get()), true);
+ decoder->seek(time, true);
stored = optional<ContentVideo> ();
while (!decoder->pass() && !stored) {}
- BOOST_CHECK (stored->frame <= frame);
+ BOOST_CHECK(stored->time <= time);
}
static void
-test (boost::filesystem::path file, vector<int> frames)
+test (boost::filesystem::path file, vector<ContentTime> times)
{
auto path = TestPaths::private_data() / file;
BOOST_REQUIRE (boost::filesystem::exists (path));
- auto film = new_test_film ("ffmpeg_decoder_seek_test_" + file.string());
auto content = make_shared<FFmpegContent>(path);
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("ffmpeg_decoder_seek_test_" + file.string(), { content });
auto decoder = make_shared<FFmpegDecoder>(film, content, false);
decoder->video->Data.connect (bind (&store, _1));
- for (auto i: frames) {
+ for (auto i: times) {
check (decoder, i);
}
}
@@ -95,10 +93,43 @@ test (boost::filesystem::path file, vector<int> frames)
BOOST_AUTO_TEST_CASE (ffmpeg_decoder_seek_test)
{
- vector<int> frames = { 0, 42, 999, 0 };
-
- test ("boon_telly.mkv", frames);
- test ("Sintel_Trailer1.480p.DivX_Plus_HD.mkv", frames);
- test ("prophet_long_clip.mkv", { 15, 42, 999, 15 });
- test ("dolby_aurora.vob", { 0, 125, 250, 41 });
+ test(
+ "boon_telly.mkv",
+ {
+ ContentTime::from_frames(0, 29.97),
+ ContentTime::from_frames(42, 29.97),
+ ContentTime::from_frames(999, 29.97),
+ ContentTime::from_frames(0, 29.97),
+ }
+ );
+
+ test(
+ "Sintel_Trailer1.480p.DivX_Plus_HD.mkv",
+ {
+ ContentTime::from_frames(0, 24),
+ ContentTime::from_frames(42, 24),
+ ContentTime::from_frames(999, 24),
+ ContentTime::from_frames(0, 24),
+ }
+ );
+
+ test(
+ "prophet_long_clip.mkv",
+ {
+ ContentTime::from_frames(15, 23.976),
+ ContentTime::from_frames(42, 23.976),
+ ContentTime::from_frames(999, 23.976),
+ ContentTime::from_frames(15, 23.976)
+ }
+ );
+
+ test(
+ "dolby_aurora.vob",
+ {
+ ContentTime::from_frames(0, 25),
+ ContentTime::from_frames(125, 25),
+ ContentTime::from_frames(250, 25),
+ ContentTime::from_frames(41, 25)
+ }
+ );
}
diff --git a/test/ffmpeg_decoder_sequential_test.cc b/test/ffmpeg_decoder_sequential_test.cc
index 73eea719f..c0fe26ea3 100644
--- a/test/ffmpeg_decoder_sequential_test.cc
+++ b/test/ffmpeg_decoder_sequential_test.cc
@@ -70,11 +70,8 @@ ffmpeg_decoder_sequential_test_one (boost::filesystem::path file, float fps, int
auto path = TestPaths::private_data() / file;
BOOST_REQUIRE (boost::filesystem::exists (path));
- auto film = new_test_film ("ffmpeg_decoder_sequential_test_" + file.string());
auto content = make_shared<FFmpegContent>(path);
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
- film->write_metadata ();
+ auto film = new_test_film("ffmpeg_decoder_sequential_test_" + file.string(), { content });
auto player = make_shared<Player>(film, Image::Alignment::COMPACT);
BOOST_REQUIRE (content->video_frame_rate());
diff --git a/test/ffmpeg_encoder_test.cc b/test/ffmpeg_encoder_test.cc
index dc57b473b..0a48cd745 100644
--- a/test/ffmpeg_encoder_test.cc
+++ b/test/ffmpeg_encoder_test.cc
@@ -28,8 +28,8 @@
#include "lib/dcp_content.h"
#include "lib/dcpomatic_log.h"
#include "lib/ffmpeg_content.h"
-#include "lib/ffmpeg_encoder.h"
#include "lib/ffmpeg_examiner.h"
+#include "lib/ffmpeg_film_encoder.h"
#include "lib/film.h"
#include "lib/image_content.h"
#include "lib/ratio.h"
@@ -69,6 +69,10 @@ ffmpeg_content_test (int number, boost::filesystem::path content, ExportFormat f
name += "prores-hq";
extension = "mov";
break;
+ case ExportFormat::PRORES_LT:
+ name += "prores-lt";
+ extension = "mov";
+ break;
case ExportFormat::SUBTITLES_DCP:
BOOST_REQUIRE (false);
}
@@ -76,7 +80,7 @@ ffmpeg_content_test (int number, boost::filesystem::path content, ExportFormat f
name = String::compose("%1_test%2", name, number);
auto c = make_shared<FFmpegContent>(content);
- auto film = new_test_film2 (name, {c}, &cl);
+ auto film = new_test_film(name, {c}, &cl);
film->set_name (name);
film->set_audio_channels (6);
@@ -84,7 +88,7 @@ ffmpeg_content_test (int number, boost::filesystem::path content, ExportFormat f
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
auto file = boost::filesystem::path("build") / "test" / String::compose("%1.%2", name, extension);
cl.add (file);
- FFmpegEncoder encoder (film, job, file, format, false, false, false, 23);
+ FFmpegFilmEncoder encoder(film, job, file, format, false, false, false, 23);
encoder.go ();
cl.run ();
@@ -122,42 +126,39 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_prores_test4)
/** Still image -> Prores */
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_prores_test5)
{
- auto film = new_test_film ("ffmpeg_encoder_prores_test5");
- film->set_name ("ffmpeg_encoder_prores_test5");
+ boost::filesystem::path const output = "build/test/ffmpeg_encoder_prores_test5.mov";
+ Cleanup cl;
+ cl.add(output);
+
auto c = make_shared<ImageContent>(TestPaths::private_data() / "bbc405.png");
- film->set_container (Ratio::from_id ("185"));
+ auto film = new_test_film("ffmpeg_encoder_prores_test5", { c });
film->set_audio_channels (6);
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs ());
-
c->video->set_length (240);
film->write_metadata ();
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_test5.mov", ExportFormat::PRORES_HQ, false, false, false, 23);
+ FFmpegFilmEncoder encoder(film, job, output, ExportFormat::PRORES_HQ, false, false, false, 23);
encoder.go ();
+
+ cl.run();
}
/** Subs -> Prores */
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_prores_test6)
{
- auto film = new_test_film ("ffmpeg_encoder_prores_test6");
- film->set_name ("ffmpeg_encoder_prores_test6");
- film->set_container (Ratio::from_id ("185"));
+ auto s = make_shared<StringTextFileContent>("test/data/subrip2.srt");
+ auto film = new_test_film("ffmpeg_encoder_prores_test6", { s });
film->set_audio_channels (6);
- auto s = make_shared<StringTextFileContent>("test/data/subrip2.srt");
- film->examine_and_add_content (s);
- BOOST_REQUIRE (!wait_for_jobs ());
s->only_text()->set_colour (dcp::Colour (255, 255, 0));
s->only_text()->set_effect (dcp::Effect::SHADOW);
s->only_text()->set_effect_colour (dcp::Colour (0, 255, 255));
film->write_metadata();
auto job = make_shared<TranscodeJob> (film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_test6.mov", ExportFormat::PRORES_HQ, false, false, false, 23);
+ FFmpegFilmEncoder encoder(film, job, "build/test/ffmpeg_encoder_prores_test6.mov", ExportFormat::PRORES_HQ, false, false, false, 23);
encoder.go ();
}
@@ -165,25 +166,24 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_prores_test6)
/** Video + subs -> Prores */
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_prores_test7)
{
- auto film = new_test_film ("ffmpeg_encoder_prores_test7");
- film->set_name ("ffmpeg_encoder_prores_test7");
- film->set_container (Ratio::from_id ("185"));
- film->set_audio_channels (6);
+ boost::filesystem::path const output = "build/test/ffmpeg_encoder_prores_test7.mov";
+ Cleanup cl;
+ cl.add(output);
auto c = make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs ());
-
auto s = make_shared<StringTextFileContent>("test/data/subrip.srt");
- film->examine_and_add_content (s);
- BOOST_REQUIRE (!wait_for_jobs ());
+ auto film = new_test_film("ffmpeg_encoder_prores_test7", { c, s });
+ film->set_audio_channels (6);
+
s->only_text()->set_colour (dcp::Colour (255, 255, 0));
s->only_text()->set_effect (dcp::Effect::SHADOW);
s->only_text()->set_effect_colour (dcp::Colour (0, 255, 255));
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_test7.mov", ExportFormat::PRORES_HQ, false, false, false, 23);
+ FFmpegFilmEncoder encoder(film, job, output, ExportFormat::PRORES_HQ, false, false, false, 23);
encoder.go ();
+
+ cl.run();
}
@@ -197,21 +197,17 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test1)
/** Just subtitles -> H264 */
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test2)
{
- auto film = new_test_film ("ffmpeg_encoder_h264_test2");
- film->set_name ("ffmpeg_encoder_h264_test2");
- film->set_container (Ratio::from_id ("185"));
+ auto s = make_shared<StringTextFileContent>("test/data/subrip2.srt");
+ auto film = new_test_film("ffmpeg_encoder_h264_test2", { s });
film->set_audio_channels (6);
- auto s = make_shared<StringTextFileContent>("test/data/subrip2.srt");
- film->examine_and_add_content (s);
- BOOST_REQUIRE (!wait_for_jobs ());
s->only_text()->set_colour (dcp::Colour (255, 255, 0));
s->only_text()->set_effect (dcp::Effect::SHADOW);
s->only_text()->set_effect_colour (dcp::Colour (0, 255, 255));
film->write_metadata();
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_h264_test2.mp4", ExportFormat::H264_AAC, false, false, false, 23);
+ FFmpegFilmEncoder encoder(film, job, "build/test/ffmpeg_encoder_h264_test2.mp4", ExportFormat::H264_AAC, false, false, false, 23);
encoder.go ();
}
@@ -219,25 +215,18 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test2)
/** Video + subs -> H264 */
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test3)
{
- auto film = new_test_film ("ffmpeg_encoder_h264_test3");
- film->set_name ("ffmpeg_encoder_h264_test3");
- film->set_container (Ratio::from_id ("185"));
- film->set_audio_channels (6);
-
auto c = make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
-
auto s = make_shared<StringTextFileContent>("test/data/subrip.srt");
- film->examine_and_add_content (s);
- BOOST_REQUIRE (!wait_for_jobs ());
+ auto film = new_test_film("ffmpeg_encoder_h264_test3", { c, s });
+ film->set_audio_channels (6);
+
s->only_text()->set_colour (dcp::Colour (255, 255, 0));
s->only_text()->set_effect (dcp::Effect::SHADOW);
s->only_text()->set_effect_colour (dcp::Colour (0, 255, 255));
film->write_metadata();
auto job = make_shared<TranscodeJob> (film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_h264_test3.mp4", ExportFormat::H264_AAC, false, false, false, 23);
+ FFmpegFilmEncoder encoder(film, job, "build/test/ffmpeg_encoder_h264_test3.mp4", ExportFormat::H264_AAC, false, false, false, 23);
encoder.go ();
}
@@ -245,13 +234,13 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test3)
/** Scope-in-flat DCP -> H264 */
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test4)
{
- auto film = new_test_film2("ffmpeg_encoder_h264_test4", {make_shared<DCPContent>("test/data/scope_dcp")});
+ auto film = new_test_film("ffmpeg_encoder_h264_test4", {make_shared<DCPContent>("test/data/scope_dcp")});
BOOST_REQUIRE(!wait_for_jobs());
film->set_container(Ratio::from_id("185"));
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder(film, job, "build/test/ffmpeg_encoder_h264_test4.mp4", ExportFormat::H264_AAC, false, false, false, 23);
+ FFmpegFilmEncoder encoder(film, job, "build/test/ffmpeg_encoder_h264_test4.mp4", ExportFormat::H264_AAC, false, false, false, 23);
encoder.go();
}
@@ -259,24 +248,15 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test4)
/** Test mixdown from 5.1 to stereo */
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test5)
{
- auto film = new_test_film ("ffmpeg_transcoder_h264_test5");
- film->set_name ("ffmpeg_transcoder_h264_test5");
- film->set_container (Ratio::from_id ("185"));
- film->set_audio_channels (6);
-
auto L = make_shared<FFmpegContent>("test/data/L.wav");
- film->examine_and_add_content (L);
auto R = make_shared<FFmpegContent>("test/data/R.wav");
- film->examine_and_add_content (R);
auto C = make_shared<FFmpegContent>("test/data/C.wav");
- film->examine_and_add_content (C);
auto Ls = make_shared<FFmpegContent>("test/data/Ls.wav");
- film->examine_and_add_content (Ls);
auto Rs = make_shared<FFmpegContent>("test/data/Rs.wav");
- film->examine_and_add_content (Rs);
auto Lfe = make_shared<FFmpegContent>("test/data/Lfe.wav");
- film->examine_and_add_content (Lfe);
- BOOST_REQUIRE (!wait_for_jobs ());
+
+ auto film = new_test_film("ffmpeg_encoder_h264_test5", { L, R, C, Ls, Rs, Lfe });
+ film->set_audio_channels (6);
AudioMapping map (1, MAX_DCP_AUDIO_CHANNELS);
@@ -306,7 +286,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test5)
Rs->audio->set_mapping (map);
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_h264_test5.mp4", ExportFormat::H264_AAC, true, false, false, 23);
+ FFmpegFilmEncoder encoder(film, job, "build/test/ffmpeg_encoder_h264_test5.mp4", ExportFormat::H264_AAC, true, false, false, 23);
encoder.go ();
check_ffmpeg ("build/test/ffmpeg_encoder_h264_test5.mp4", "test/data/ffmpeg_encoder_h264_test5.mp4", 1);
@@ -316,12 +296,12 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test5)
/** Test export of a VF */
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test6)
{
- auto film = new_test_film2 ("ffmpeg_encoder_h264_test6_ov");
+ auto film = new_test_film("ffmpeg_encoder_h264_test6_ov");
film->examine_and_add_content (make_shared<ImageContent>(TestPaths::private_data() / "bbc405.png"));
BOOST_REQUIRE (!wait_for_jobs());
make_and_verify_dcp (film);
- auto film2 = new_test_film2 ("ffmpeg_encoder_h264_test6_vf");
+ auto film2 = new_test_film("ffmpeg_encoder_h264_test6_vf");
auto ov = make_shared<DCPContent>("build/test/ffmpeg_encoder_h264_test6_ov/" + film->dcp_name(false));
film2->examine_and_add_content (ov);
BOOST_REQUIRE (!wait_for_jobs());
@@ -334,7 +314,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test6)
}
auto job = make_shared<TranscodeJob>(film2, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film2, job, "build/test/ffmpeg_encoder_h264_test6_vf.mp4", ExportFormat::H264_AAC, true, false, false, 23);
+ FFmpegFilmEncoder encoder(film2, job, "build/test/ffmpeg_encoder_h264_test6_vf.mp4", ExportFormat::H264_AAC, true, false, false, 23);
encoder.go ();
}
@@ -342,12 +322,18 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test6)
/** Test export of a 3D DCP in a 2D project */
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_3d_dcp_to_h264)
{
+ boost::filesystem::path const output = "build/test/ffmpeg_encoder_3d_dcp_to_h264.mp4";
+ Cleanup cl;
+ cl.add(output);
+
auto dcp = make_shared<DCPContent>(TestPaths::private_data() / "xm");
- auto film2 = new_test_film2 ("ffmpeg_encoder_3d_dcp_to_h264_export", {dcp});
+ auto film2 = new_test_film("ffmpeg_encoder_3d_dcp_to_h264_export", {dcp});
auto job = make_shared<TranscodeJob>(film2, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film2, job, "build/test/ffmpeg_encoder_3d_dcp_to_h264.mp4", ExportFormat::H264_AAC, true, false, false, 23);
+ FFmpegFilmEncoder encoder(film2, job, output, ExportFormat::H264_AAC, true, false, false, 23);
encoder.go ();
+
+ cl.run();
}
@@ -356,7 +342,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test7)
{
auto L = make_shared<ImageContent>(TestPaths::private_data() / "bbc405.png");
auto R = make_shared<ImageContent>(TestPaths::private_data() / "bbc405.png");
- auto film = new_test_film2 ("ffmpeg_encoder_h264_test7_data", {L, R});
+ auto film = new_test_film("ffmpeg_encoder_h264_test7_data", {L, R});
L->video->set_frame_type (VideoFrameType::THREE_D_LEFT);
L->set_position (film, DCPTime());
R->video->set_frame_type (VideoFrameType::THREE_D_RIGHT);
@@ -365,10 +351,10 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test7)
make_and_verify_dcp (film);
auto dcp = make_shared<DCPContent>(film->dir(film->dcp_name()));
- auto film2 = new_test_film2 ("ffmpeg_encoder_h264_test7_export", {dcp});
+ auto film2 = new_test_film("ffmpeg_encoder_h264_test7_export", {dcp});
auto job = make_shared<TranscodeJob> (film2, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film2, job, "build/test/ffmpeg_encoder_h264_test7.mp4", ExportFormat::H264_AAC, true, false, false, 23);
+ FFmpegFilmEncoder encoder(film2, job, "build/test/ffmpeg_encoder_h264_test7.mp4", ExportFormat::H264_AAC, true, false, false, 23);
encoder.go ();
}
@@ -376,11 +362,11 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test7)
BOOST_AUTO_TEST_CASE(ffmpeg_encoder_2d_content_in_3d_project)
{
auto content = make_shared<ImageContent>(TestPaths::private_data() / "bbc405.png");
- auto film = new_test_film2("ffmpeg_encoder_2d_content_in_3d_project", { content });
+ auto film = new_test_film("ffmpeg_encoder_2d_content_in_3d_project", { content });
film->set_three_d(true);
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder(film, job, "build/test/ffmpeg_encoder_2d_content_in_3d_project.mp4", ExportFormat::H264_AAC, true, false, false, 23);
+ FFmpegFilmEncoder encoder(film, job, "build/test/ffmpeg_encoder_2d_content_in_3d_project.mp4", ExportFormat::H264_AAC, true, false, false, 23);
encoder.go();
}
@@ -388,13 +374,13 @@ BOOST_AUTO_TEST_CASE(ffmpeg_encoder_2d_content_in_3d_project)
/** Stereo project with mixdown-to-stereo set */
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test8)
{
- auto film = new_test_film2("ffmpeg_encoder_h264_test4");
+ auto film = new_test_film("ffmpeg_encoder_h264_test4");
film->examine_and_add_content(make_shared<DCPContent>("test/data/scope_dcp"));
BOOST_REQUIRE(!wait_for_jobs());
film->set_audio_channels (2);
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder(film, job, "build/test/ffmpeg_encoder_h264_test8.mp4", ExportFormat::H264_AAC, true, false, false, 23);
+ FFmpegFilmEncoder encoder(film, job, "build/test/ffmpeg_encoder_h264_test8.mp4", ExportFormat::H264_AAC, true, false, false, 23);
encoder.go();
}
@@ -403,7 +389,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test8)
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test9)
{
auto c = make_shared<ImageContent>(TestPaths::private_data() / "bbc405.png");
- auto film = new_test_film2("ffmpeg_encoder_prores_test9", { c });
+ auto film = new_test_film("ffmpeg_encoder_prores_test9", { c });
film->set_name ("ffmpeg_encoder_prores_test9");
film->set_container (Ratio::from_id ("185"));
film->set_audio_channels (12);
@@ -415,7 +401,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test9)
film->write_metadata ();
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_test9.mov", ExportFormat::H264_AAC, false, false, false, 23);
+ FFmpegFilmEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_test9.mov", ExportFormat::H264_AAC, false, false, false, 23);
encoder.go ();
}
@@ -424,13 +410,13 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test9)
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_prores_from_dcp_with_crop)
{
auto dcp = make_shared<DCPContent>("test/data/import_dcp_test2");
- auto film = new_test_film2 ("ffmpeg_encoder_prores_from_dcp_with_crop", { dcp });
+ auto film = new_test_film("ffmpeg_encoder_prores_from_dcp_with_crop", { dcp });
dcp->video->set_left_crop (32);
dcp->video->set_right_crop (32);
film->write_metadata ();
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_from_dcp_with_crop.mov", ExportFormat::PRORES_HQ, false, false, false, 23);
+ FFmpegFilmEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_from_dcp_with_crop.mov", ExportFormat::PRORES_HQ, false, false, false, 23);
encoder.go ();
}
@@ -439,13 +425,13 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_prores_from_dcp_with_crop)
BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_from_dcp_with_crop)
{
auto dcp = make_shared<DCPContent>("test/data/import_dcp_test2");
- auto film = new_test_film2 ("ffmpeg_encoder_h264_from_dcp_with_crop", { dcp });
+ auto film = new_test_film("ffmpeg_encoder_h264_from_dcp_with_crop", { dcp });
dcp->video->set_left_crop (32);
dcp->video->set_right_crop (32);
film->write_metadata ();
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_from_dcp_with_crop.mov", ExportFormat::H264_AAC, false, false, false, 23);
+ FFmpegFilmEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_from_dcp_with_crop.mov", ExportFormat::H264_AAC, false, false, false, 23);
encoder.go ();
}
@@ -455,13 +441,13 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_with_reels)
{
auto content1 = content_factory("test/data/flat_red.png")[0];
auto content2 = content_factory("test/data/flat_red.png")[0];
- auto film = new_test_film2 ("ffmpeg_encoder_h264_with_reels", { content1, content2 });
+ auto film = new_test_film("ffmpeg_encoder_h264_with_reels", { content1, content2 });
film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
content1->video->set_length (240);
content2->video->set_length (240);
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_h264_with_reels.mov", ExportFormat::H264_AAC, false, true, false, 23);
+ FFmpegFilmEncoder encoder (film, job, "build/test/ffmpeg_encoder_h264_with_reels.mov", ExportFormat::H264_AAC, false, true, false, 23);
encoder.go ();
auto check = [](boost::filesystem::path path) {
@@ -482,10 +468,10 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_prores_regression_1)
Cleanup cl;
auto content = content_factory(TestPaths::private_data() / "arrietty_JP-EN.mkv")[0];
- auto film = new_test_film2 ("ffmpeg_encoder_prores_regression_1", { content });
+ auto film = new_test_film("ffmpeg_encoder_prores_regression_1", { content });
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_regression_1.mov", ExportFormat::PRORES_HQ, false, true, false, 23);
+ FFmpegFilmEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_regression_1.mov", ExportFormat::PRORES_HQ, false, true, false, 23);
encoder.go ();
cl.add("build/test/ffmpeg_encoder_prores_regression_1.mov");
@@ -502,10 +488,10 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_prores_regression_2)
dcpomatic_log->set_types(logs | LogEntry::TYPE_DEBUG_PLAYER);
auto content = content_factory(TestPaths::private_data() / "tge_clip.mkv")[0];
- auto film = new_test_film2 ("ffmpeg_encoder_prores_regression_2", { content });
+ auto film = new_test_film("ffmpeg_encoder_prores_regression_2", { content });
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_regression_2.mov", ExportFormat::PRORES_HQ, false, true, false, 23);
+ FFmpegFilmEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_regression_2.mov", ExportFormat::PRORES_HQ, false, true, false, 23);
encoder.go ();
dcpomatic_log->set_types(logs);
@@ -518,13 +504,13 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_prores_regression_2)
BOOST_AUTO_TEST_CASE(ffmpeg_encoder_missing_frame_at_end)
{
auto content = content_factory(TestPaths::private_data() / "1s1f.mov");
- auto film = new_test_film2("ffmpeg_encoder_missing_frame_at_end", content);
+ auto film = new_test_film("ffmpeg_encoder_missing_frame_at_end", content);
boost::filesystem::path output("build/test/ffmpeg_encoder_missing_frame_at_end.mov");
boost::filesystem::path log("build/test/ffmpeg_encoder_missing_frame_at_end.log");
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
- FFmpegEncoder encoder(film, job, output, ExportFormat::PRORES_HQ, false, true, false, 23);
+ FFmpegFilmEncoder encoder(film, job, output, ExportFormat::PRORES_HQ, false, true, false, 23);
encoder.go();
run_ffprobe(output, log, false, "-show_frames -show_format -show_streams -select_streams v:0");
diff --git a/test/ffmpeg_examiner_test.cc b/test/ffmpeg_examiner_test.cc
index c745335ae..c07900db8 100644
--- a/test/ffmpeg_examiner_test.cc
+++ b/test/ffmpeg_examiner_test.cc
@@ -41,8 +41,8 @@ using namespace dcpomatic;
*/
BOOST_AUTO_TEST_CASE (ffmpeg_examiner_test)
{
- auto film = new_test_film ("ffmpeg_examiner_test");
auto content = make_shared<FFmpegContent>("test/data/count300bd24.m2ts");
+ auto film = new_test_film("ffmpeg_examiner_test", { content });
auto examiner = make_shared<FFmpegExaminer>(content);
BOOST_CHECK_EQUAL (examiner->first_video().get().get(), ContentTime::from_seconds(600).get());
@@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_examiner_probesize_test)
BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->frame_rate(), 48000);
BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->channels(), 2);
BOOST_CHECK_EQUAL (examiner->audio_streams()[1]->frame_rate(), 48000);
- BOOST_CHECK_EQUAL (examiner->audio_streams()[1]->channels(), 5);
+ BOOST_CHECK_EQUAL (examiner->audio_streams()[1]->channels(), 6);
}
diff --git a/test/ffmpeg_properties_test.cc b/test/ffmpeg_properties_test.cc
index 15773ec2f..1e0cdc42f 100644
--- a/test/ffmpeg_properties_test.cc
+++ b/test/ffmpeg_properties_test.cc
@@ -37,7 +37,7 @@ colour_range_test(string name, boost::filesystem::path file, string ref)
{
auto content = content_factory(file);
BOOST_REQUIRE(!content.empty());
- auto film = new_test_film2(String::compose("ffmpeg_properties_test_%1", name), { content.front() });
+ auto film = new_test_film(String::compose("ffmpeg_properties_test_%1", name), { content.front() });
auto properties = content.front()->user_properties(film);
auto iter = std::find_if(properties.begin(), properties.end(), [](UserProperty const& property) { return property.key == "Colour range"; });
diff --git a/test/ffmpeg_pts_offset_test.cc b/test/ffmpeg_pts_offset_test.cc
index 1f994374d..642d85ff2 100644
--- a/test/ffmpeg_pts_offset_test.cc
+++ b/test/ffmpeg_pts_offset_test.cc
@@ -41,10 +41,8 @@ using namespace dcpomatic;
BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
{
- auto film = new_test_film ("ffmpeg_pts_offset_test");
auto content = make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("ffmpeg_pts_offset_test", { content });
content->audio = make_shared<AudioContent>(content.get());
content->audio->add_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream));
diff --git a/test/file_extension_test.cc b/test/file_extension_test.cc
index 87b55114a..99075fd1c 100644
--- a/test/file_extension_test.cc
+++ b/test/file_extension_test.cc
@@ -36,7 +36,7 @@ BOOST_AUTO_TEST_CASE (interop_file_extension_test)
auto video = content_factory("test/data/flat_red.png")[0];
auto audio = content_factory("test/data/sine_440.wav")[0];
auto sub = content_factory("test/data/15s.srt")[0];
- auto film = new_test_film2("interop_file_extension_test", { video, audio, sub });
+ auto film = new_test_film("interop_file_extension_test", { video, audio, sub });
film->set_interop(true);
sub->only_text()->set_language(dcp::LanguageTag("de"));
@@ -62,7 +62,7 @@ BOOST_AUTO_TEST_CASE (smpte_file_extension_test)
auto video = content_factory("test/data/flat_red.png")[0];
auto audio = content_factory("test/data/sine_440.wav")[0];
auto sub = content_factory("test/data/15s.srt")[0];
- auto film = new_test_film2("smpte_file_extension_test", { video, audio, sub });
+ auto film = new_test_film("smpte_file_extension_test", { video, audio, sub });
film->set_interop(false);
make_and_verify_dcp(
diff --git a/test/file_naming_test.cc b/test/file_naming_test.cc
index 1b5871bd2..2aa81cd4f 100644
--- a/test/file_naming_test.cc
+++ b/test/file_naming_test.cc
@@ -61,17 +61,11 @@ BOOST_AUTO_TEST_CASE (file_naming_test)
ConfigRestorer cr;
Config::instance()->set_dcp_asset_filename_format (dcp::NameFormat("%c"));
- auto film = new_test_film ("file_naming_test");
- film->set_name ("file_naming_test");
- film->set_video_frame_rate (24);
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
auto r = make_shared<FFmpegContent>("test/data/flat_red.png");
- film->examine_and_add_content (r);
auto g = make_shared<FFmpegContent>("test/data/flat_green.png");
- film->examine_and_add_content (g);
auto b = make_shared<FFmpegContent>("test/data/flat_blue.png");
- film->examine_and_add_content (b);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("file_naming_test", { r, g, b });
+ film->set_video_frame_rate (24);
r->set_position (film, dcpomatic::DCPTime::from_seconds(0));
r->set_video_frame_rate(film, 24);
@@ -115,10 +109,6 @@ BOOST_AUTO_TEST_CASE (file_naming_test2)
Config::instance()->set_dcp_asset_filename_format (dcp::NameFormat ("%c"));
- auto film = new_test_film ("file_naming_test2");
- film->set_name ("file_naming_test2");
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
-
#ifdef DCPOMATIC_WINDOWS
/* This is necessary so that the UTF8 string constant below gets converted properly */
std::locale::global(boost::locale::generator().generate(""));
@@ -126,12 +116,9 @@ BOOST_AUTO_TEST_CASE (file_naming_test2)
#endif
auto r = make_shared<FFmpegContent>("test/data/flät_red.png");
- film->examine_and_add_content (r);
auto g = make_shared<FFmpegContent>("test/data/flat_green.png");
- film->examine_and_add_content (g);
auto b = make_shared<FFmpegContent>("test/data/flat_blue.png");
- film->examine_and_add_content (b);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("file_naming_test2", { r, g, b });
r->set_position (film, dcpomatic::DCPTime::from_seconds(0));
r->set_video_frame_rate(film, 24);
@@ -175,7 +162,7 @@ BOOST_AUTO_TEST_CASE (subtitle_file_naming)
Config::instance()->set_dcp_asset_filename_format(dcp::NameFormat("%t ostrabagalous %c"));
auto content = content_factory("test/data/15s.srt");
- auto film = new_test_film2("subtitle_file_naming", content);
+ auto film = new_test_film("subtitle_file_naming", content);
film->set_interop(false);
make_and_verify_dcp (
@@ -208,7 +195,7 @@ BOOST_AUTO_TEST_CASE(remove_bad_characters_from_template)
Config::instance()->set_dcp_asset_filename_format(dcp::NameFormat("%c%z"));
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2("remove_bad_characters_from_template", content);
+ auto film = new_test_film("remove_bad_characters_from_template", content);
make_and_verify_dcp(
film,
{
diff --git a/test/film_metadata_test.cc b/test/film_metadata_test.cc
index 878e60254..b99d1a572 100644
--- a/test/film_metadata_test.cc
+++ b/test/film_metadata_test.cc
@@ -47,17 +47,17 @@ using std::vector;
BOOST_AUTO_TEST_CASE (film_metadata_test)
{
- auto film = new_test_film ("film_metadata_test");
+ auto film = new_test_film("film_metadata_test");
auto dir = test_film_dir ("film_metadata_test");
film->_isdcf_date = boost::gregorian::from_undelimited_string ("20130211");
BOOST_CHECK (film->container() == Ratio::from_id ("185"));
- BOOST_CHECK (film->dcp_content_type() == nullptr);
+ BOOST_CHECK (film->dcp_content_type() == DCPContentType::from_isdcf_name("TST"));
film->set_name ("fred");
film->set_dcp_content_type (DCPContentType::from_isdcf_name ("SHR"));
film->set_container (Ratio::from_id ("185"));
- film->set_j2k_bandwidth (200000000);
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, 200000000);
film->set_interop (false);
film->set_chain (string(""));
film->set_distributor (string(""));
@@ -66,7 +66,7 @@ BOOST_AUTO_TEST_CASE (film_metadata_test)
film->set_audio_channels(6);
film->write_metadata ();
- list<string> ignore = { "Key", "ContextID", "LastWrittenBy" };
+ list<Glib::ustring> ignore = { "Key", "ContextID", "LastWrittenBy" };
check_xml ("test/data/metadata.xml.ref", dir.string() + "/metadata.xml", ignore);
auto g = make_shared<Film>(dir);
@@ -84,9 +84,11 @@ BOOST_AUTO_TEST_CASE (film_metadata_test)
/** Check a bug where <Content> tags with multiple <Text>s would fail to load */
BOOST_AUTO_TEST_CASE (multiple_text_nodes_are_allowed)
{
+ Cleanup cl;
+
auto subs = content_factory("test/data/15s.srt")[0];
auto caps = content_factory("test/data/15s.srt")[0];
- auto film = new_test_film2("multiple_text_nodes_are_allowed1", { subs, caps });
+ auto film = new_test_film("multiple_text_nodes_are_allowed1", { subs, caps }, &cl);
caps->only_text()->set_type(TextType::CLOSED_CAPTION);
make_and_verify_dcp (
film,
@@ -97,11 +99,13 @@ BOOST_AUTO_TEST_CASE (multiple_text_nodes_are_allowed)
});
auto reload = make_shared<DCPContent>(film->dir(film->dcp_name()));
- auto film2 = new_test_film2("multiple_text_nodes_are_allowed2", { reload });
+ auto film2 = new_test_film("multiple_text_nodes_are_allowed2", { reload });
film2->write_metadata ();
auto test = make_shared<Film>(boost::filesystem::path("build/test/multiple_text_nodes_are_allowed2"));
test->read_metadata();
+
+ cl.run();
}
@@ -217,7 +221,7 @@ BOOST_AUTO_TEST_CASE (metadata_video_range_guessed_for_png)
BOOST_AUTO_TEST_CASE(effect_node_not_inserted_incorrectly)
{
auto sub = content_factory("test/data/15s.srt");
- auto film = new_test_film2("effect_node_not_inserted_incorrectly", sub);
+ auto film = new_test_film("effect_node_not_inserted_incorrectly", sub);
film->write_metadata();
namespace fs = boost::filesystem;
diff --git a/test/film_test.cc b/test/film_test.cc
index e8f04dc93..3493c79ce 100644
--- a/test/film_test.cc
+++ b/test/film_test.cc
@@ -31,16 +31,16 @@ BOOST_AUTO_TEST_CASE(film_contains_atmos_content_test)
auto image = content_factory("test/data/flat_red.png")[0];
auto sound = content_factory("test/data/white.wav")[0];
- auto film1 = new_test_film2("film_contains_atmos_content_test1", { atmos, image, sound });
+ auto film1 = new_test_film("film_contains_atmos_content_test1", { atmos, image, sound });
BOOST_CHECK(film1->contains_atmos_content());
- auto film2 = new_test_film2("film_contains_atmos_content_test2", { sound, atmos, image });
+ auto film2 = new_test_film("film_contains_atmos_content_test2", { sound, atmos, image });
BOOST_CHECK(film2->contains_atmos_content());
- auto film3 = new_test_film2("film_contains_atmos_content_test3", { image, sound, atmos });
+ auto film3 = new_test_film("film_contains_atmos_content_test3", { image, sound, atmos });
BOOST_CHECK(film3->contains_atmos_content());
- auto film4 = new_test_film2("film_contains_atmos_content_test4", { image, sound });
+ auto film4 = new_test_film("film_contains_atmos_content_test4", { image, sound });
BOOST_CHECK(!film4->contains_atmos_content());
}
diff --git a/test/find_missing_test.cc b/test/find_missing_test.cc
index 51ae5800c..c85ddea7f 100644
--- a/test/find_missing_test.cc
+++ b/test/find_missing_test.cc
@@ -48,7 +48,7 @@ BOOST_AUTO_TEST_CASE (find_missing_test_with_single_files)
copy_file ("test/data/flat_red.png", content_dir / "C.png");
/* Make a film with that content */
- auto film = new_test_film2 (name + "_film", {
+ auto film = new_test_film(name + "_film", {
content_factory(content_dir / "A.png")[0],
content_factory(content_dir / "B.png")[0],
content_factory(content_dir / "C.png")[0]
@@ -89,7 +89,7 @@ BOOST_AUTO_TEST_CASE (find_missing_test_with_multiple_files)
}
/* Make a film containing that DCP */
- auto film = new_test_film2 (name + "_film", { make_shared<DCPContent>(content_dir) });
+ auto film = new_test_film(name + "_film", { make_shared<DCPContent>(content_dir) });
film->write_metadata ();
/* Move the DCP's content elsewhere */
@@ -126,7 +126,7 @@ BOOST_AUTO_TEST_CASE (find_missing_test_with_multiple_files_one_incorrect)
}
/* Make a film containing that DCP */
- auto film = new_test_film2 (name + "_film", { make_shared<DCPContent>(content_dir) });
+ auto film = new_test_film(name + "_film", { make_shared<DCPContent>(content_dir) });
film->write_metadata ();
/* Move the DCP's content elsewhere */
diff --git a/test/font_id_allocator_test.cc b/test/font_id_allocator_test.cc
index 07110346b..19c4a2154 100644
--- a/test/font_id_allocator_test.cc
+++ b/test/font_id_allocator_test.cc
@@ -43,12 +43,12 @@ BOOST_AUTO_TEST_CASE(font_id_allocator_test_without_disambiguation)
allocator.allocate();
- BOOST_CHECK(allocator.font_id(0, "asset1", "font") == "0_font");
- BOOST_CHECK(allocator.font_id(0, "asset1", "font2") == "0_font2");
- BOOST_CHECK(allocator.font_id(1, "asset2", "font") == "1_font");
- BOOST_CHECK(allocator.font_id(1, "asset2", "font2") == "1_font2");
- BOOST_CHECK(allocator.font_id(1, "asset3", "font3") == "1_font3");
- BOOST_CHECK(allocator.font_id(1, "asset3", "font4") == "1_font4");
+ BOOST_CHECK_EQUAL(allocator.font_id(0, "asset1", "font"), "font");
+ BOOST_CHECK_EQUAL(allocator.font_id(0, "asset1", "font2"), "font2");
+ BOOST_CHECK_EQUAL(allocator.font_id(1, "asset2", "font"), "0_font");
+ BOOST_CHECK_EQUAL(allocator.font_id(1, "asset2", "font2"), "0_font2");
+ BOOST_CHECK_EQUAL(allocator.font_id(1, "asset3", "font3"), "font3");
+ BOOST_CHECK_EQUAL(allocator.font_id(1, "asset3", "font4"), "font4");
}
@@ -68,10 +68,9 @@ BOOST_AUTO_TEST_CASE(font_id_allocator_test_with_disambiguation)
allocator.allocate();
- BOOST_CHECK(allocator.font_id(0, "asset1", "font") == "0_font");
- /* This should get a prefix that is higher than any reel index */
- BOOST_CHECK(allocator.font_id(0, "asset2", "font") == "2_font");
- BOOST_CHECK(allocator.font_id(1, "asset3", "font1") == "1_font1");
+ BOOST_CHECK(allocator.font_id(0, "asset1", "font") == "font");
+ BOOST_CHECK(allocator.font_id(0, "asset2", "font") == "0_font");
+ BOOST_CHECK(allocator.font_id(1, "asset3", "font1") == "font1");
}
diff --git a/test/frame_rate_test.cc b/test/frame_rate_test.cc
index d54868b3f..d87f7bfc5 100644
--- a/test/frame_rate_test.cc
+++ b/test/frame_rate_test.cc
@@ -44,11 +44,8 @@
*/
BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
{
- auto film = new_test_film("best_dcp_frame_rate_test_single");
- /* Get any piece of content, it doesn't matter what */
auto content = std::make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("best_dcp_frame_rate_test_single", { content });
/* Run some tests with a limited range of allowed rates */
@@ -222,20 +219,13 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
*/
BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_double)
{
- auto film = new_test_film("best_dcp_frame_rate_test_double");
- /* Get any old content, it doesn't matter what */
auto A = std::make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (A);
auto B = std::make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (B);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("best_dcp_frame_rate_test_double", { A, B });
/* Run some tests with a limited range of allowed rates */
- std::list<int> afr;
- afr.push_back (24);
- afr.push_back (25);
- afr.push_back (30);
+ std::list<int> afr = { 24, 25, 30 };
Config::instance()->set_allowed_dcp_frame_rates (afr);
A->_video_frame_rate = 30;
@@ -253,16 +243,10 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_double)
BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
{
- auto film = new_test_film("audio_sampling_rate_test");
- /* Get any piece of content, it doesn't matter what */
auto content = std::make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("audio_sampling_rate_test", { content });
- std::list<int> afr;
- afr.push_back (24);
- afr.push_back (25);
- afr.push_back (30);
+ std::list<int> afr = { 24, 25, 30 };
Config::instance()->set_allowed_dcp_frame_rates (afr);
auto stream = std::make_shared<FFmpegAudioStream>("foo", 0, 0, 0, 0, 0);
diff --git a/test/guess_crop_test.cc b/test/guess_crop_test.cc
index ccced0b3e..c58432382 100644
--- a/test/guess_crop_test.cc
+++ b/test/guess_crop_test.cc
@@ -35,7 +35,7 @@ using namespace dcpomatic;
BOOST_AUTO_TEST_CASE (guess_crop_image_test1)
{
auto content = content_factory(TestPaths::private_data() / "arrietty_724.tiff");
- auto film = new_test_film2 ("guess_crop_image_test1", content);
+ auto film = new_test_film("guess_crop_image_test1", content);
BOOST_CHECK (guess_crop(film, content[0], 0.1, {}) == Crop(0, 0, 11, 11));
}
@@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE (guess_crop_image_test1)
BOOST_AUTO_TEST_CASE (guess_crop_image_test2)
{
auto content = content_factory(TestPaths::private_data() / "prophet_frame.tiff");
- auto film = new_test_film2 ("guess_crop_image_test2", content);
+ auto film = new_test_film("guess_crop_image_test2", content);
BOOST_CHECK(guess_crop(film, content[0], 0.1, {}) == Crop(0, 0, 22, 22));
}
@@ -53,7 +53,7 @@ BOOST_AUTO_TEST_CASE (guess_crop_image_test2)
BOOST_AUTO_TEST_CASE (guess_crop_image_test3)
{
auto content = content_factory(TestPaths::private_data() / "pillarbox.png");
- auto film = new_test_film2 ("guess_crop_image_test3", content);
+ auto film = new_test_film("guess_crop_image_test3", content);
BOOST_CHECK(guess_crop(film, content[0], 0.1, {}) == Crop(113, 262, 0, 0));
}
diff --git a/test/hints_test.cc b/test/hints_test.cc
index 949ba18c0..1b510f9e7 100644
--- a/test/hints_test.cc
+++ b/test/hints_test.cc
@@ -72,7 +72,7 @@ static
void
check (TextType type, string name, optional<string> expected_hint = optional<string>())
{
- auto film = new_test_film2 (name);
+ auto film = new_test_film(name);
auto content = content_factory("test/data/" + name + ".srt")[0];
content->text.front()->set_type (type);
content->text.front()->set_language (dcp::LanguageTag("en-US"));
@@ -183,7 +183,7 @@ BOOST_AUTO_TEST_CASE (hint_subtitle_mxf_too_big)
{
string const name = "hint_subtitle_mxf_too_big";
- auto film = new_test_film2 (name);
+ auto film = new_test_film(name);
for (int i = 0; i < 4; ++i) {
dcp::File fake_font("build/test/hint_subtitle_mxf_too_big.ttf", "w");
@@ -198,7 +198,7 @@ BOOST_AUTO_TEST_CASE (hint_subtitle_mxf_too_big)
content->text[0]->set_language(dcp::LanguageTag("en-US"));
film->examine_and_add_content(content);
BOOST_REQUIRE (!wait_for_jobs());
- auto const font = content->text[0]->get_font(String::compose("0_font_%1", i));
+ auto const font = content->text[0]->get_font(String::compose("font_%1", i));
BOOST_REQUIRE(font);
font->set_file("build/test/hint_subtitle_mxf_too_big.ttf");
}
@@ -218,7 +218,7 @@ BOOST_AUTO_TEST_CASE (hint_closed_caption_xml_too_big)
{
string const name = "hint_closed_caption_xml_too_big";
- auto film = new_test_film2 (name);
+ auto film = new_test_film(name);
dcp::File ccap(String::compose("build/test/%1.srt", name), "w");
BOOST_REQUIRE (ccap);
@@ -249,7 +249,7 @@ BOOST_AUTO_TEST_CASE (hint_closed_caption_xml_too_big)
BOOST_AUTO_TEST_CASE (hints_destroyed_while_running)
{
- auto film = new_test_film2 ("hints_destroyed_while_running");
+ auto film = new_test_film("hints_destroyed_while_running");
auto content = content_factory(TestPaths::private_data() / "boon_telly.mkv")[0];
film->examine_and_add_content (content);
BOOST_REQUIRE (!wait_for_jobs());
@@ -265,7 +265,7 @@ BOOST_AUTO_TEST_CASE (hints_destroyed_while_running)
BOOST_AUTO_TEST_CASE (hints_audio_with_no_language)
{
auto content = content_factory("test/data/sine_440.wav")[0];
- auto film = new_test_film2 ("hints_audio_with_no_language", { content });
+ auto film = new_test_film("hints_audio_with_no_language", { content });
content->audio->set_gain (-6);
auto hints = get_hints (film);
@@ -284,7 +284,7 @@ BOOST_AUTO_TEST_CASE (hints_certificate_validity)
Config::instance()->set_signer_chain(make_shared<dcp::CertificateChain>(openssl_path(), 40 * 365));
- auto film = new_test_film2 ("hints_certificate_validity");
+ auto film = new_test_film("hints_certificate_validity");
auto hints = get_hints (film);
BOOST_REQUIRE_EQUAL (hints.size(), 1U);
BOOST_CHECK_EQUAL (
@@ -296,3 +296,16 @@ BOOST_AUTO_TEST_CASE (hints_certificate_validity)
);
}
+
+BOOST_AUTO_TEST_CASE(hints_mpeg2)
+{
+ auto film = new_test_film("hints_certificate_validity");
+ film->set_video_encoding(VideoEncoding::MPEG2);
+ auto hints = get_hints(film);
+ BOOST_REQUIRE_EQUAL(hints.size(), 1U);
+ BOOST_CHECK_EQUAL(
+ hints[0],
+ "The vast majority of cinemas in Europe, Australasia and North America expect DCPs "
+ "encoded with JPEG2000 rather than MPEG2. Make sure that your cinema really wants an old-style MPEG2 DCP."
+ );
+}
diff --git a/test/image_content_fade_test.cc b/test/image_content_fade_test.cc
index d7f03ad51..5ac63405a 100644
--- a/test/image_content_fade_test.cc
+++ b/test/image_content_fade_test.cc
@@ -33,7 +33,7 @@ using std::list;
BOOST_AUTO_TEST_CASE (image_content_fade_test)
{
- auto film = new_test_film2 ("image_content_fade_test");
+ auto film = new_test_film("image_content_fade_test");
auto content = content_factory("test/data/flat_red.png")[0];
film->examine_and_add_content (content);
BOOST_REQUIRE (!wait_for_jobs());
diff --git a/test/image_filename_sorter_test.cc b/test/image_filename_sorter_test.cc
index eb662a120..29b550753 100644
--- a/test/image_filename_sorter_test.cc
+++ b/test/image_filename_sorter_test.cc
@@ -28,9 +28,10 @@
#include "lib/image_filename_sorter.h"
#include "lib/compose.hpp"
#include <boost/test/unit_test.hpp>
+#include <algorithm>
+#include <random>
-using std::random_shuffle;
using std::sort;
using std::vector;
@@ -69,7 +70,11 @@ BOOST_AUTO_TEST_CASE (image_filename_sorter_test2)
for (int i = 0; i < 100000; ++i) {
paths.push_back(String::compose("some.filename.with.%1.number.tiff", i));
}
- random_shuffle (paths.begin(), paths.end());
+
+ std::random_device rd;
+ std::mt19937 generator(rd());
+ std::shuffle(paths.begin(), paths.end(), generator);
+
sort (paths.begin(), paths.end(), ImageFilenameSorter());
for (int i = 0; i < 100000; ++i) {
BOOST_CHECK_EQUAL(paths[i].string(), String::compose("some.filename.with.%1.number.tiff", i));
diff --git a/test/import_dcp_test.cc b/test/import_dcp_test.cc
index 7387c7be7..e9f7f068b 100644
--- a/test/import_dcp_test.cc
+++ b/test/import_dcp_test.cc
@@ -56,17 +56,10 @@ BOOST_AUTO_TEST_CASE (import_dcp_test)
{
ConfigRestorer cr;
- auto A = new_test_film ("import_dcp_test");
- A->set_container (Ratio::from_id ("185"));
- A->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- A->set_name ("frobozz");
- A->set_interop (false);
-
auto c = make_shared<FFmpegContent>("test/data/test.mp4");
- A->examine_and_add_content (c);
+ auto A = new_test_film("import_dcp_test", { c });
A->set_encrypted (true);
- BOOST_CHECK (!wait_for_jobs ());
-
+ A->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
make_and_verify_dcp (A);
dcp::DCP A_dcp ("build/test/import_dcp_test/" + A->dcp_name());
@@ -80,20 +73,11 @@ BOOST_AUTO_TEST_CASE (import_dcp_test)
auto const decrypted_kdm = A->make_kdm(A_dcp.cpls().front()->file().get(), dcp::LocalTime ("2030-07-21T00:00:00+00:00"), dcp::LocalTime ("2031-07-21T00:00:00+00:00"));
auto const kdm = decrypted_kdm.encrypt(signer, Config::instance()->decryption_chain()->leaf(), {}, dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, 0);
- auto B = new_test_film ("import_dcp_test2");
- B->set_container (Ratio::from_id ("185"));
- B->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- B->set_name ("frobozz");
- B->set_interop (false);
- B->set_audio_channels(16);
-
auto d = make_shared<DCPContent>("build/test/import_dcp_test/" + A->dcp_name());
- B->examine_and_add_content (d);
- BOOST_CHECK (!wait_for_jobs ());
d->add_kdm (kdm);
- JobManager::instance()->add (make_shared<ExamineContentJob>(B, d));
- BOOST_CHECK (!wait_for_jobs ());
-
+ auto B = new_test_film("import_dcp_test2", { d });
+ B->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
+ B->set_audio_channels(16);
make_and_verify_dcp (B);
/* Should be 1s red, 1s green, 1s blue */
@@ -108,7 +92,7 @@ BOOST_AUTO_TEST_CASE (import_dcp_markers_test)
/* Make a DCP with some markers */
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2 ("import_dcp_markers_test", content, &cl);
+ auto film = new_test_film("import_dcp_markers_test", content, &cl);
content[0]->video->set_length (24 * 60 * 10);
@@ -120,7 +104,7 @@ BOOST_AUTO_TEST_CASE (import_dcp_markers_test)
/* Import the DCP to a new film and check the markers */
auto imported = make_shared<DCPContent>(film->dir(film->dcp_name()));
- auto film2 = new_test_film2 ("import_dcp_markers_test2", {imported}, &cl);
+ auto film2 = new_test_film("import_dcp_markers_test2", {imported}, &cl);
film2->write_metadata ();
/* When import_dcp_markers_test was made a LFOC marker will automatically
@@ -157,7 +141,7 @@ BOOST_AUTO_TEST_CASE (import_dcp_markers_test)
BOOST_AUTO_TEST_CASE (import_dcp_metadata_test)
{
/* Make a DCP with some ratings and a content version */
- auto film = new_test_film2 ("import_dcp_metadata_test");
+ auto film = new_test_film("import_dcp_metadata_test");
auto content = content_factory("test/data/flat_red.png")[0];
film->examine_and_add_content (content);
BOOST_REQUIRE (!wait_for_jobs());
@@ -173,7 +157,7 @@ BOOST_AUTO_TEST_CASE (import_dcp_metadata_test)
make_and_verify_dcp (film);
/* Import the DCP to a new film and check the metadata */
- auto film2 = new_test_film2 ("import_dcp_metadata_test2");
+ auto film2 = new_test_film("import_dcp_metadata_test2");
auto imported = make_shared<DCPContent>(film->dir(film->dcp_name()));
film2->examine_and_add_content (imported);
BOOST_REQUIRE (!wait_for_jobs());
diff --git a/test/interrupt_encoder_test.cc b/test/interrupt_encoder_test.cc
index f0755d3a0..c786a3110 100644
--- a/test/interrupt_encoder_test.cc
+++ b/test/interrupt_encoder_test.cc
@@ -45,14 +45,8 @@ using std::make_shared;
*/
BOOST_AUTO_TEST_CASE (interrupt_encoder_test)
{
- auto film = new_test_film ("interrupt_encoder_test");
- film->set_dcp_content_type (DCPContentType::from_isdcf_name("FTR"));
- film->set_container (Ratio::from_id("185"));
- film->set_name ("interrupt_encoder_test");
-
auto content = make_shared<FFmpegContent>(TestPaths::private_data() / "prophet_long_clip.mkv");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("interrupt_encoder_test", { content });
make_dcp (film, TranscodeJob::ChangedBehaviour::IGNORE);
diff --git a/test/isdcf_name_test.cc b/test/isdcf_name_test.cc
index 56686d2ac..3897aabbe 100644
--- a/test/isdcf_name_test.cc
+++ b/test/isdcf_name_test.cc
@@ -46,7 +46,8 @@ using std::string;
BOOST_AUTO_TEST_CASE (isdcf_name_test)
{
- auto film = new_test_film ("isdcf_name_test");
+ auto audio = content_factory("test/data/sine_440.wav")[0];
+ auto film = new_test_film("isdcf_name_test", { audio });
/* A basic test */
@@ -54,9 +55,6 @@ BOOST_AUTO_TEST_CASE (isdcf_name_test)
film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
film->set_container (Ratio::from_id ("185"));
film->_isdcf_date = boost::gregorian::date (2014, boost::gregorian::Jul, 4);
- auto audio = content_factory("test/data/sine_440.wav")[0];
- film->examine_and_add_content (audio);
- BOOST_REQUIRE (!wait_for_jobs());
film->set_audio_language(dcp::LanguageTag("en-US"));
film->set_content_versions({"1"});
film->set_release_territory(dcp::LanguageTag::RegionSubtag("GB"));
@@ -103,7 +101,7 @@ BOOST_AUTO_TEST_CASE (isdcf_name_test)
/* Test interior aspect ratio: shouldn't be shown with trailers */
- shared_ptr<ImageContent> content (new ImageContent ("test/data/simple_testcard_640x480.png"));
+ auto content = std::make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
film->examine_and_add_content (content);
BOOST_REQUIRE (!wait_for_jobs());
content->video->set_custom_ratio (1.33);
@@ -242,7 +240,7 @@ BOOST_AUTO_TEST_CASE (isdcf_name_test)
BOOST_AUTO_TEST_CASE(isdcf_name_with_atmos)
{
auto content = content_factory(TestPaths::private_data() / "atmos_asset.mxf");
- auto film = new_test_film2("isdcf_name_with_atmos", content);
+ auto film = new_test_film("isdcf_name_with_atmos", content);
film->_isdcf_date = boost::gregorian::date(2023, boost::gregorian::Jan, 18);
film->set_name("Hello");
@@ -253,7 +251,7 @@ BOOST_AUTO_TEST_CASE(isdcf_name_with_atmos)
BOOST_AUTO_TEST_CASE(isdcf_name_with_ccap)
{
auto content = content_factory("test/data/short.srt")[0];
- auto film = new_test_film2("isdcf_name_with_ccap", { content });
+ auto film = new_test_film("isdcf_name_with_ccap", { content });
content->text[0]->set_use(true);
content->text[0]->set_type(TextType::CLOSED_CAPTION);
content->text[0]->set_dcp_track(DCPTextTrack("Foo", dcp::LanguageTag("de-DE")));
diff --git a/test/j2k_encode_threading_test.cc b/test/j2k_encode_threading_test.cc
new file mode 100644
index 000000000..1f57f4143
--- /dev/null
+++ b/test/j2k_encode_threading_test.cc
@@ -0,0 +1,117 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "lib/config.h"
+#include "lib/content_factory.h"
+#include "lib/dcp_film_encoder.h"
+#include "lib/dcp_transcode_job.h"
+#include "lib/encode_server_description.h"
+#include "lib/film.h"
+#include "lib/j2k_encoder.h"
+#include "lib/job_manager.h"
+#include "lib/make_dcp.h"
+#include "lib/transcode_job.h"
+#include "test.h"
+#include <dcp/cpl.h>
+#include <dcp/dcp.h>
+#include <dcp/reel.h>
+#include <dcp/reel_picture_asset.h>
+#include <boost/test/unit_test.hpp>
+
+
+using std::dynamic_pointer_cast;
+using std::list;
+
+
+BOOST_AUTO_TEST_CASE(local_threads_created_and_destroyed)
+{
+ auto film = new_test_film("local_threads_created_and_destroyed", {});
+ Writer writer(film, {}, "foo");
+ J2KEncoder encoder(film, writer);
+
+ encoder.remake_threads(32, 0, {});
+ BOOST_CHECK_EQUAL(encoder._threads.size(), 32U);
+
+ encoder.remake_threads(9, 0, {});
+ BOOST_CHECK_EQUAL(encoder._threads.size(), 9U);
+
+ encoder.end();
+ BOOST_CHECK_EQUAL(encoder._threads.size(), 0U);
+}
+
+
+BOOST_AUTO_TEST_CASE(remote_threads_created_and_destroyed)
+{
+ auto film = new_test_film("remote_threads_created_and_destroyed", {});
+ Writer writer(film, {}, "foo");
+ J2KEncoder encoder(film, writer);
+
+ list<EncodeServerDescription> servers = {
+ { "fred", 7, SERVER_LINK_VERSION },
+ { "jim", 2, SERVER_LINK_VERSION },
+ { "sheila", 14, SERVER_LINK_VERSION },
+ };
+
+ encoder.remake_threads(0, 0, servers);
+ BOOST_CHECK_EQUAL(encoder._threads.size(), 7U + 2U + 14U);
+
+ servers = {
+ { "fred", 7, SERVER_LINK_VERSION },
+ { "jim", 5, SERVER_LINK_VERSION },
+ { "sheila", 14, SERVER_LINK_VERSION },
+ };
+
+ encoder.remake_threads(0, 0, servers);
+ BOOST_CHECK_EQUAL(encoder._threads.size(), 7U + 5U + 14U);
+
+ servers = {
+ { "fred", 0, SERVER_LINK_VERSION },
+ { "jim", 0, SERVER_LINK_VERSION },
+ { "sheila", 11, SERVER_LINK_VERSION },
+ };
+
+ encoder.remake_threads(0, 0, servers);
+ BOOST_CHECK_EQUAL(encoder._threads.size(), 11U);
+}
+
+
+BOOST_AUTO_TEST_CASE(frames_not_lost_when_threads_disappear)
+{
+ auto content = content_factory(TestPaths::private_data() / "clapperboard.mp4");
+ auto film = new_test_film("frames_not_lost", content);
+ film->write_metadata();
+ auto job = make_dcp(film, TranscodeJob::ChangedBehaviour::IGNORE);
+ auto encoder = dynamic_cast<J2KEncoder*>(dynamic_pointer_cast<DCPFilmEncoder>(job->_encoder)->_encoder.get());
+
+ while (JobManager::instance()->work_to_do()) {
+ encoder->remake_threads(rand() % 8, 0, {});
+ dcpomatic_sleep_seconds(1);
+ }
+
+ BOOST_CHECK(!JobManager::instance()->errors());
+
+ dcp::DCP dcp(film->dir(film->dcp_name()));
+ dcp.read();
+ BOOST_REQUIRE_EQUAL(dcp.cpls().size(), 1U);
+ BOOST_REQUIRE_EQUAL(dcp.cpls()[0]->reels().size(), 1U);
+ BOOST_REQUIRE_EQUAL(dcp.cpls()[0]->reels()[0]->main_picture()->intrinsic_duration(), 423U);
+}
+
diff --git a/test/j2k_encoder_test.cc b/test/j2k_encoder_test.cc
index bc3bd97b2..083a61cf8 100644
--- a/test/j2k_encoder_test.cc
+++ b/test/j2k_encoder_test.cc
@@ -42,10 +42,10 @@ BOOST_AUTO_TEST_CASE(j2k_encoder_deadlock_test)
{
ConfigRestorer cr;
- auto film = new_test_film2("j2k_encoder_deadlock_test");
+ auto film = new_test_film("j2k_encoder_deadlock_test");
/* Don't call ::start() on this Writer, so it can never write anything */
- Writer writer(film, {});
+ Writer writer(film, {}, {});
writer.set_encoder_threads(4);
/* We want to test the case where the writer queue fills, and this can't happen unless there
@@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(j2k_encoder_deadlock_test)
optional<ColourConversion>(),
VideoRange::VIDEO,
weak_ptr<Content>(),
- optional<Frame>(),
+ optional<dcpomatic::ContentTime>(),
false
),
{}
diff --git a/test/j2k_bandwidth_test.cc b/test/j2k_video_bit_rate_test.cc
index f9e47c4b9..b3eccd98a 100644
--- a/test/j2k_bandwidth_test.cc
+++ b/test/j2k_video_bit_rate_test.cc
@@ -19,7 +19,7 @@
*/
-/** @file test/bandwidth_test.cc
+/** @file test/j2k_video_bit_rate_test.cc
* @brief Test whether we output whatever J2K bandwidth is requested.
* @ingroup feature
*/
@@ -41,16 +41,14 @@ using std::string;
static void
check (int target_bits_per_second)
{
+ Cleanup cl;
+
int const duration = 10;
string const name = "bandwidth_test_" + dcp::raw_convert<string> (target_bits_per_second);
- auto film = new_test_film (name);
- film->set_name (name);
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
- film->set_j2k_bandwidth (target_bits_per_second);
auto content = make_shared<ImageContent>(TestPaths::private_data() / "prophet_frame.tiff");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film(name, { content }, &cl);
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, target_bits_per_second);
content->video->set_length (24 * duration);
make_and_verify_dcp (
film,
@@ -65,15 +63,14 @@ check (int target_bits_per_second)
target_bits_per_second <= 250000000
);
- boost::filesystem::directory_iterator i (boost::filesystem::path("build") / "test" / name / "video");
- boost::filesystem::path test = *i++;
- BOOST_REQUIRE (i == boost::filesystem::directory_iterator());
-
+ auto test = find_file(film->dir(film->dcp_name()), "j2c_");
double actual_bits_per_second = boost::filesystem::file_size(test) * 8.0 / duration;
/* Check that we're within 85% to 115% of target on average */
BOOST_CHECK ((actual_bits_per_second / target_bits_per_second) > 0.85);
BOOST_CHECK ((actual_bits_per_second / target_bits_per_second) < 1.15);
+
+ cl.run();
}
diff --git a/test/kdm_cli_test.cc b/test/kdm_cli_test.cc
index e79e37b2d..f8c85cb5a 100644
--- a/test/kdm_cli_test.cc
+++ b/test/kdm_cli_test.cc
@@ -20,8 +20,11 @@
#include "lib/cinema.h"
+#include "lib/cinema_list.h"
#include "lib/config.h"
#include "lib/content_factory.h"
+#include "lib/cross.h"
+#include "lib/dkdm_wrapper.h"
#include "lib/film.h"
#include "lib/kdm_cli.h"
#include "lib/screen.h"
@@ -33,13 +36,14 @@
#include <iostream>
+using std::dynamic_pointer_cast;
using std::string;
using std::vector;
using boost::optional;
optional<string>
-run(vector<string> const& args, vector<string>& output)
+run(vector<string> const& args, vector<string>& output, bool dump_errors = true)
{
std::vector<char*> argv(args.size());
for (auto i = 0U; i < args.size(); ++i) {
@@ -47,7 +51,7 @@ run(vector<string> const& args, vector<string>& output)
}
auto error = kdm_cli(args.size(), argv.data(), [&output](string s) { output.push_back(s); });
- if (error) {
+ if (error && dump_errors) {
std::cout << *error << "\n";
}
@@ -62,7 +66,7 @@ BOOST_AUTO_TEST_CASE (kdm_cli_test_certificate)
"--verbose",
"--valid-from", "now",
"--valid-duration", "2 weeks",
- "--certificate", "test/data/cert.pem",
+ "--projector-certificate", "test/data/cert.pem",
"-S", "my great screen",
"-o", "build/test",
"test/data/dkdm.xml"
@@ -80,6 +84,70 @@ BOOST_AUTO_TEST_CASE (kdm_cli_test_certificate)
}
+BOOST_AUTO_TEST_CASE(kdm_cli_specify_decryption_key_test)
+{
+ using boost::filesystem::path;
+
+ ConfigRestorer cr;
+
+ path const dir = "build/test/kdm_cli_specify_decryption_key_test";
+
+ boost::system::error_code ec;
+ boost::filesystem::remove_all(dir, ec);
+ boost::filesystem::create_directories(dir);
+
+ dcp::CertificateChain chain(openssl_path(), 365);
+ dcp::write_string_to_file(chain.leaf().certificate(true), dir / "cert.pem");
+ dcp::write_string_to_file(*chain.key(), dir / "key.pem");
+
+ vector<string> make_args = {
+ "kdm_cli",
+ "--valid-from", "now",
+ "--valid-duration", "2 weeks",
+ "--projector-certificate", path(dir / "cert.pem").string(),
+ "-S", "base",
+ "-o", dir.string(),
+ "test/data/dkdm.xml"
+ };
+
+ vector<string> output;
+ auto error = run(make_args, output);
+ BOOST_CHECK(!error);
+
+ vector<string> bad_args = {
+ "kdm_cli",
+ "--valid-from", "now",
+ "--valid-duration", "2 weeks",
+ "--projector-certificate", path(dir / "cert.pem").string(),
+ "-S", "bad",
+ "-o", dir.string(),
+ path(dir / "KDM_Test_FTR-1_F-133_XX-XX_MOS_2K_20220109_SMPTE_OV__base.xml").string()
+ };
+
+ /* This should fail because we're using the wrong decryption certificate */
+ output.clear();
+ error = run(bad_args, output, false);
+ BOOST_REQUIRE(error);
+ BOOST_CHECK_MESSAGE(error->find("Could not decrypt KDM") != string::npos, "Error was " << *error);
+
+ vector<string> good_args = {
+ "kdm_cli",
+ "--valid-from", "now",
+ "--valid-duration", "2 weeks",
+ "--projector-certificate", path(dir / "cert.pem").string(),
+ "--decryption-key", path(dir / "key.pem").string(),
+ "-S", "good",
+ "-o", dir.string(),
+ path(dir / "KDM_Test_FTR-1_F-133_XX-XX_MOS_2K_20220109_SMPTE_OV__base.xml").string()
+ };
+
+ /* This should succeed */
+ output.clear();
+ error = run(good_args, output);
+ BOOST_CHECK(!error);
+}
+
+
static
void
setup_test_config()
@@ -87,16 +155,16 @@ setup_test_config()
auto config = Config::instance();
auto const cert = dcp::Certificate(dcp::file_to_string("test/data/cert.pem"));
- auto cinema_a = std::make_shared<Cinema>("Dean's Screens", vector<string>(), "", 0, 0);
- cinema_a->add_screen(std::make_shared<dcpomatic::Screen>("Screen 1", "", cert, boost::none, std::vector<TrustedDevice>()));
- cinema_a->add_screen(std::make_shared<dcpomatic::Screen>("Screen 2", "", cert, boost::none, std::vector<TrustedDevice>()));
- cinema_a->add_screen(std::make_shared<dcpomatic::Screen>("Screen 3", "", cert, boost::none, std::vector<TrustedDevice>()));
- config->add_cinema(cinema_a);
+ CinemaList cinemas(config->cinemas_file());
- auto cinema_b = std::make_shared<Cinema>("Floyd's Celluloid", vector<string>(), "", 0, 0);
- cinema_b->add_screen(std::make_shared<dcpomatic::Screen>("Foo", "", cert, boost::none, std::vector<TrustedDevice>()));
- cinema_b->add_screen(std::make_shared<dcpomatic::Screen>("Bar", "", cert, boost::none, std::vector<TrustedDevice>()));
- config->add_cinema(cinema_b);
+ auto cinema_a = cinemas.add_cinema({"Dean's Screens", {}, "", dcp::UTCOffset()});
+ cinemas.add_screen(cinema_a, {"Screen 1", "", cert, boost::none, {}});
+ cinemas.add_screen(cinema_a, {"Screen 2", "", cert, boost::none, {}});
+ cinemas.add_screen(cinema_a, {"Screen 3", "", cert, boost::none, {}});
+
+ auto cinema_b = cinemas.add_cinema({"Floyd's Celluloid", {}, "", dcp::UTCOffset()});
+ cinemas.add_screen(cinema_b, {"Foo", "", cert, boost::none, std::vector<TrustedDevice>()});
+ cinemas.add_screen(cinema_b, {"Bar", "", cert, boost::none, std::vector<TrustedDevice>()});
}
@@ -183,8 +251,8 @@ BOOST_AUTO_TEST_CASE(kdm_cli_specify_cinemas_file)
vector<string> args = {
"kdm_cli",
"--cinemas-file",
- "test/data/cinemas.xml",
- "--list-cinemas"
+ "test/data/cinemas.sqlite3",
+ "list-cinemas"
};
vector<string> output;
@@ -192,9 +260,9 @@ BOOST_AUTO_TEST_CASE(kdm_cli_specify_cinemas_file)
BOOST_CHECK(!error);
BOOST_REQUIRE_EQUAL(output.size(), 3U);
- BOOST_CHECK_EQUAL(output[0], "stinking dump ()");
+ BOOST_CHECK_EQUAL(output[0], "Great (julie@tinyscreen.com)");
BOOST_CHECK_EQUAL(output[1], "classy joint ()");
- BOOST_CHECK_EQUAL(output[2], "Great ()");
+ BOOST_CHECK_EQUAL(output[2], "stinking dump (bob@odourscreen.com, alice@whiff.com)");
}
@@ -205,7 +273,7 @@ BOOST_AUTO_TEST_CASE(kdm_cli_specify_cert)
boost::system::error_code ec;
boost::filesystem::remove(kdm_filename, ec);
- auto film = new_test_film2("kdm_cli_specify_cert", content_factory("test/data/flat_red.png"));
+ auto film = new_test_film("kdm_cli_specify_cert", content_factory("test/data/flat_red.png"));
film->set_encrypted(true);
film->set_name("KDMCLI");
film->set_use_isdcf_name(false);
@@ -217,6 +285,7 @@ BOOST_AUTO_TEST_CASE(kdm_cli_specify_cert)
"--valid-duration", "2 weeks",
"-C", "test/data/cert.pem",
"-o", "build/test",
+ "create",
"build/test/kdm_cli_specify_cert"
};
@@ -228,3 +297,66 @@ BOOST_AUTO_TEST_CASE(kdm_cli_specify_cert)
BOOST_CHECK(boost::filesystem::exists(kdm_filename));
}
+
+BOOST_AUTO_TEST_CASE(kdm_cli_time)
+{
+ ConfigRestorer cr;
+
+ setup_test_config();
+
+ boost::filesystem::path kdm_filename = "build/test/KDM_Test_FTR-1_F-133_XX-XX_MOS_2K_20220109_SMPTE_OV_Deans_Screens_Screen_2.xml";
+
+ boost::system::error_code ec;
+ boost::filesystem::remove(kdm_filename, ec);
+
+ dcp::LocalTime now;
+ now.add_days(2);
+
+ vector<string> args = {
+ "kdm_cli",
+ "--verbose",
+ "--valid-from", now.as_string(),
+ "--valid-duration", "2 weeks",
+ "-c", "Dean's Screens",
+ "-S", "Screen 2",
+ "-o", "build/test",
+ "test/data/dkdm.xml"
+ };
+
+ vector<string> output;
+ auto error = run(args, output);
+ BOOST_CHECK(!error);
+
+ BOOST_REQUIRE_EQUAL(output.size(), 2U);
+ BOOST_CHECK(boost::algorithm::starts_with(output[0], "Making KDMs valid from"));
+ BOOST_CHECK_EQUAL(output[1], "Wrote 1 KDM files to build/test");
+
+ BOOST_CHECK(boost::filesystem::exists(kdm_filename));
+}
+
+
+BOOST_AUTO_TEST_CASE(kdm_cli_add_dkdm)
+{
+ ConfigRestorer cr;
+
+ setup_test_config();
+
+ BOOST_CHECK_EQUAL(Config::instance()->dkdms()->children().size(), 0U);
+
+ vector<string> args = {
+ "kdm_cli",
+ "add-dkdm",
+ "test/data/dkdm.xml"
+ };
+
+ vector<string> output;
+ auto error = run(args, output);
+ BOOST_CHECK(!error);
+
+ auto dkdms = Config::instance()->dkdms()->children();
+ BOOST_CHECK_EQUAL(dkdms.size(), 1U);
+ auto dkdm = dynamic_pointer_cast<DKDM>(dkdms.front());
+ BOOST_CHECK(dkdm);
+ BOOST_CHECK_EQUAL(dkdm->dkdm().as_xml(), dcp::file_to_string("test/data/dkdm.xml"));
+}
+
diff --git a/test/kdm_naming_test.cc b/test/kdm_naming_test.cc
index 32500553e..fe7da7f31 100644
--- a/test/kdm_naming_test.cc
+++ b/test/kdm_naming_test.cc
@@ -20,6 +20,7 @@
#include "lib/cinema.h"
+#include "lib/cinema_list.h"
#include "lib/config.h"
#include "lib/content_factory.h"
#include "lib/film.h"
@@ -32,6 +33,7 @@
using std::dynamic_pointer_cast;
using std::list;
using std::make_shared;
+using std::pair;
using std::shared_ptr;
using std::string;
using std::vector;
@@ -46,40 +48,44 @@ confirm_overwrite (boost::filesystem::path)
}
-static shared_ptr<dcpomatic::Screen> cinema_a_screen_1;
-static shared_ptr<dcpomatic::Screen> cinema_a_screen_2;
-static shared_ptr<dcpomatic::Screen> cinema_b_screen_x;
-static shared_ptr<dcpomatic::Screen> cinema_b_screen_y;
-static shared_ptr<dcpomatic::Screen> cinema_b_screen_z;
+struct Context
+{
+ Context()
+ {
+ CinemaList cinemas;
+
+ auto crypt_cert = Config::instance()->decryption_chain()->leaf();
+
+ cinema_a = cinemas.add_cinema({"Cinema A", {}, "", dcp::UTCOffset(4, 30)});
+ cinema_a_screen_1 = cinemas.add_screen(cinema_a, {"Screen 1", "", crypt_cert, boost::none, {}});
+ cinema_a_screen_2 = cinemas.add_screen(cinema_a, {"Screen 2", "", crypt_cert, boost::none, {}});
+
+ cinema_b = cinemas.add_cinema({"Cinema B", {}, "", dcp::UTCOffset(-1, 0)});
+ cinema_b_screen_x = cinemas.add_screen(cinema_b, {"Screen X", "", crypt_cert, boost::none, {}});
+ cinema_b_screen_y = cinemas.add_screen(cinema_b, {"Screen Y", "", crypt_cert, boost::none, {}});
+ cinema_b_screen_z = cinemas.add_screen(cinema_b, {"Screen Z", "", crypt_cert, boost::none, {}});
+ }
+
+ CinemaID cinema_a = 0;
+ CinemaID cinema_b = 0;
+ ScreenID cinema_a_screen_1 = 0;
+ ScreenID cinema_a_screen_2 = 0;
+ ScreenID cinema_b_screen_x = 0;
+ ScreenID cinema_b_screen_y = 0;
+ ScreenID cinema_b_screen_z = 0;
+};
BOOST_AUTO_TEST_CASE (single_kdm_naming_test)
{
auto c = Config::instance();
- auto crypt_cert = c->decryption_chain()->leaf();
-
- /* Cinema A: UTC +4:30 */
- auto cinema_a = make_shared<Cinema>("Cinema A", vector<string>(), "", 4, 30);
- cinema_a_screen_1 = std::make_shared<dcpomatic::Screen>("Screen 1", "", crypt_cert, boost::none, vector<TrustedDevice>());
- cinema_a->add_screen (cinema_a_screen_1);
- cinema_a_screen_2 = std::make_shared<dcpomatic::Screen>("Screen 2", "", crypt_cert, boost::none, vector<TrustedDevice>());
- cinema_a->add_screen (cinema_a_screen_2);
- c->add_cinema (cinema_a);
-
- /* Cinema B: UTC -1:00 */
- auto cinema_b = make_shared<Cinema>("Cinema B", vector<string>(), "", -1, 0);
- cinema_b_screen_x = std::make_shared<dcpomatic::Screen>("Screen X", "", crypt_cert, boost::none, vector<TrustedDevice>());
- cinema_b->add_screen (cinema_b_screen_x);
- cinema_b_screen_y = std::make_shared<dcpomatic::Screen>("Screen Y", "", crypt_cert, boost::none, vector<TrustedDevice>());
- cinema_b->add_screen (cinema_b_screen_y);
- cinema_b_screen_z = std::make_shared<dcpomatic::Screen>("Screen Z", "", crypt_cert, boost::none, vector<TrustedDevice>());
- cinema_b->add_screen (cinema_b_screen_z);
- c->add_cinema (cinema_a);
+ Context context;
+ CinemaList cinemas;
/* Film */
boost::filesystem::remove_all ("build/test/single_kdm_naming_test");
- auto film = new_test_film2 ("single_kdm_naming_test");
+ auto film = new_test_film("single_kdm_naming_test");
film->set_name ("my_great_film");
film->examine_and_add_content (content_factory("test/data/flat_black.png")[0]);
BOOST_REQUIRE (!wait_for_jobs());
@@ -90,14 +96,11 @@ BOOST_AUTO_TEST_CASE (single_kdm_naming_test)
auto sign_cert = c->signer_chain()->leaf();
- dcp::LocalTime from (sign_cert.not_before());
+ dcp::LocalTime from = sign_cert.not_before();
from.add_months (2);
- dcp::LocalTime until (sign_cert.not_after());
+ dcp::LocalTime until = sign_cert.not_after();
until.add_months (-2);
- auto const from_string = from.date() + " " + from.time_of_day(true, false);
- auto const until_string = until.date() + " " + until.time_of_day(true, false);
-
std::vector<KDMCertificatePeriod> period_checks;
auto cpl = cpls.front().cpl_file;
@@ -106,9 +109,11 @@ BOOST_AUTO_TEST_CASE (single_kdm_naming_test)
};
auto kdm = kdm_for_screen (
make_kdm,
- cinema_a_screen_1,
- boost::posix_time::time_from_string(from_string),
- boost::posix_time::time_from_string(until_string),
+ context.cinema_a,
+ *cinemas.cinema(context.cinema_a),
+ *cinemas.screen(context.cinema_a_screen_1),
+ from,
+ until,
dcp::Formulation::MODIFIED_TRANSITIONAL_1,
false,
optional<int>(),
@@ -133,13 +138,16 @@ BOOST_AUTO_TEST_CASE (single_kdm_naming_test)
}
-BOOST_AUTO_TEST_CASE (directory_kdm_naming_test, * boost::unit_test::depends_on("single_kdm_naming_test"))
+BOOST_AUTO_TEST_CASE(directory_kdm_naming_test)
{
using boost::filesystem::path;
+ Context context;
+ CinemaList cinemas;
+
/* Film */
boost::filesystem::remove_all ("build/test/directory_kdm_naming_test");
- auto film = new_test_film2 (
+ auto film = new_test_film(
"directory_kdm_naming_test",
{ content_factory("test/data/flat_black.png")[0] }
);
@@ -157,11 +165,11 @@ BOOST_AUTO_TEST_CASE (directory_kdm_naming_test, * boost::unit_test::depends_on(
dcp::LocalTime until (sign_cert.not_after());
until.add_months (-2);
- string const from_string = from.date() + " " + from.time_of_day(true, false);
- string const until_string = until.date() + " " + until.time_of_day(true, false);
-
- vector<shared_ptr<dcpomatic::Screen>> screens = {
- cinema_a_screen_2, cinema_b_screen_x, cinema_a_screen_1, (cinema_b_screen_z)
+ vector<pair<CinemaID, ScreenID>> screens = {
+ { context.cinema_a, context.cinema_a_screen_2 },
+ { context.cinema_b, context.cinema_b_screen_x },
+ { context.cinema_a, context.cinema_a_screen_1 },
+ { context.cinema_b, context.cinema_b_screen_z }
};
auto const cpl = cpls.front().cpl_file;
@@ -174,12 +182,14 @@ BOOST_AUTO_TEST_CASE (directory_kdm_naming_test, * boost::unit_test::depends_on(
return film->make_kdm(cpls.front().cpl_file, begin, end);
};
- for (auto i: screens) {
+ for (auto screen: screens) {
auto kdm = kdm_for_screen (
make_kdm,
- i,
- boost::posix_time::time_from_string(from_string),
- boost::posix_time::time_from_string(until_string),
+ screen.first,
+ *cinemas.cinema(screen.first),
+ *cinemas.screen(screen.second),
+ from,
+ until,
dcp::Formulation::MODIFIED_TRANSITIONAL_1,
false,
optional<int>(),
diff --git a/test/low_bitrate_test.cc b/test/low_bitrate_test.cc
index 7050dd771..52b8d54be 100644
--- a/test/low_bitrate_test.cc
+++ b/test/low_bitrate_test.cc
@@ -31,6 +31,7 @@ extern "C" {
using std::make_shared;
+using namespace dcpomatic;
BOOST_AUTO_TEST_CASE (low_bitrate_test)
@@ -51,7 +52,7 @@ BOOST_AUTO_TEST_CASE (low_bitrate_test)
boost::optional<ColourConversion>(),
VideoRange::FULL,
std::weak_ptr<Content>(),
- boost::optional<Frame>(),
+ boost::optional<ContentTime>(),
false
);
diff --git a/test/map_cli_test.cc b/test/map_cli_test.cc
index ed2a7e5ec..aaf5b944f 100644
--- a/test/map_cli_test.cc
+++ b/test/map_cli_test.cc
@@ -93,7 +93,7 @@ BOOST_AUTO_TEST_CASE(map_simple_dcp_copy)
string const out = String::compose("build/test/%1_out", name);
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2(name + "_in", content);
+ auto film = new_test_film(name + "_in", content);
make_and_verify_dcp(film);
vector<string> const args = {
@@ -123,7 +123,7 @@ BOOST_AUTO_TEST_CASE(map_simple_dcp_copy_by_id)
string const out = String::compose("build/test/%1_out", name);
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2(name + "_in", content);
+ auto film = new_test_film(name + "_in", content);
make_and_verify_dcp(film);
dcp::CPL cpl(find_cpl(film->dir(film->dcp_name())));
@@ -155,7 +155,7 @@ BOOST_AUTO_TEST_CASE(map_simple_dcp_copy_with_symlinks)
string const out = String::compose("build/test/%1_out", name);
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2(name + "_in", content);
+ auto film = new_test_film(name + "_in", content);
make_and_verify_dcp(film);
vector<string> const args = {
@@ -188,7 +188,7 @@ BOOST_AUTO_TEST_CASE(map_simple_dcp_copy_with_hardlinks)
string const out = String::compose("build/test/%1_out", name);
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2(name + "_in", content);
+ auto film = new_test_film(name + "_in", content);
make_and_verify_dcp(film);
vector<string> const args = {
@@ -207,8 +207,7 @@ BOOST_AUTO_TEST_CASE(map_simple_dcp_copy_with_hardlinks)
verify_dcp(out, {});
- /* The video file will have 3 links because DoM also makes a link into the video directory */
- BOOST_CHECK_EQUAL(boost::filesystem::hard_link_count(find_prefix(out, "j2c_")), 3U);
+ BOOST_CHECK_EQUAL(boost::filesystem::hard_link_count(find_prefix(out, "j2c_")), 2U);
BOOST_CHECK_EQUAL(boost::filesystem::hard_link_count(find_prefix(out, "pcm_")), 2U);
}
@@ -221,7 +220,7 @@ BOOST_AUTO_TEST_CASE(map_simple_interop_dcp_with_subs)
auto picture = content_factory("test/data/flat_red.png").front();
auto subs = content_factory("test/data/15s.srt").front();
- auto film = new_test_film2(name + "_in", { picture, subs });
+ auto film = new_test_film(name + "_in", { picture, subs });
film->set_interop(true);
subs->only_text()->set_language(dcp::LanguageTag("de"));
make_and_verify_dcp(film, {dcp::VerificationNote::Code::INVALID_STANDARD});
@@ -251,13 +250,13 @@ test_map_ov_vf_copy(vector<string> extra_args = {})
string const out = String::compose("build/test/%1_out", name);
auto ov_content = content_factory("test/data/flat_red.png");
- auto ov_film = new_test_film2(name + "_ov", ov_content);
+ auto ov_film = new_test_film(name + "_ov", ov_content);
make_and_verify_dcp(ov_film);
auto const ov_dir = ov_film->dir(ov_film->dcp_name());
auto vf_ov = make_shared<DCPContent>(ov_dir);
auto vf_sound = content_factory("test/data/sine_440.wav").front();
- auto vf_film = new_test_film2(name + "_vf", { vf_ov, vf_sound });
+ auto vf_film = new_test_film(name + "_vf", { vf_ov, vf_sound });
vf_ov->set_reference_video(true);
make_and_verify_dcp(vf_film, {dcp::VerificationNote::Code::EXTERNAL_ASSET}, false);
@@ -302,7 +301,7 @@ BOOST_AUTO_TEST_CASE(map_ov_vf_copy_multiple_reference)
string const out = String::compose("build/test/%1_out", name);
auto ov_content = content_factory("test/data/flat_red.png");
- auto ov_film = new_test_film2(name + "_ov", ov_content);
+ auto ov_film = new_test_film(name + "_ov", ov_content);
make_and_verify_dcp(ov_film);
auto const ov_dir = ov_film->dir(ov_film->dcp_name());
@@ -310,7 +309,7 @@ BOOST_AUTO_TEST_CASE(map_ov_vf_copy_multiple_reference)
auto vf_ov1 = make_shared<DCPContent>(ov_dir);
auto vf_ov2 = make_shared<DCPContent>(ov_dir);
auto vf_sound = content_factory("test/data/sine_440.wav").front();
- auto vf_film = new_test_film2(name + "_vf", { vf_ov1, vf_ov2, vf_sound });
+ auto vf_film = new_test_film(name + "_vf", { vf_ov1, vf_ov2, vf_sound });
vf_film->set_reel_type(ReelType::BY_VIDEO_CONTENT);
vf_ov2->set_position(vf_film, vf_ov1->end(vf_film));
vf_ov1->set_reference_video(true);
@@ -350,7 +349,7 @@ BOOST_AUTO_TEST_CASE(map_simple_dcp_copy_with_rename)
string const out = String::compose("build/test/%1_out", name);
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2(name + "_in", content);
+ auto film = new_test_film(name + "_in", content);
make_and_verify_dcp(film);
vector<string> const args = {
@@ -409,7 +408,7 @@ test_two_cpls_each_with_subs(string name, bool interop)
for (auto i = 0; i < 2; ++i) {
auto picture = content_factory("test/data/flat_red.png").front();
auto subs = content_factory("test/data/15s.srt").front();
- films[i] = new_test_film2(String::compose("%1_%2_in", name, i), { picture, subs });
+ films[i] = new_test_film(String::compose("%1_%2_in", name, i), { picture, subs });
films[i]->set_interop(interop);
subs->only_text()->set_language(dcp::LanguageTag("de"));
make_and_verify_dcp(films[i], acceptable_errors);
@@ -454,7 +453,7 @@ BOOST_AUTO_TEST_CASE(map_with_given_config)
string const out = String::compose("build/test/%1_out", name);
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2(name + "_in", content);
+ auto film = new_test_film(name + "_in", content);
make_and_verify_dcp(film);
vector<string> const args = {
@@ -466,6 +465,7 @@ BOOST_AUTO_TEST_CASE(map_with_given_config)
};
boost::filesystem::remove_all(out);
+ boost::filesystem::remove_all("test/data/map_with_given_config/2.18");
Config::instance()->drop();
vector<string> output_messages;
@@ -488,7 +488,7 @@ BOOST_AUTO_TEST_CASE(map_multireel_interop_ov_and_vf_adding_ccaps)
content_factory("test/data/flat_red.png")[0]
};
- auto ov = new_test_film2(name + "_ov", { video[0], video[1], video[2] });
+ auto ov = new_test_film(name + "_ov", { video[0], video[1], video[2] });
ov->set_reel_type(ReelType::BY_VIDEO_CONTENT);
ov->set_interop(true);
make_and_verify_dcp(ov, { dcp::VerificationNote::Code::INVALID_STANDARD });
@@ -501,7 +501,7 @@ BOOST_AUTO_TEST_CASE(map_multireel_interop_ov_and_vf_adding_ccaps)
content_factory("test/data/short.srt")[0]
};
- auto vf = new_test_film2(name + "_vf", { ov_dcp, ccap[0], ccap[1], ccap[2] });
+ auto vf = new_test_film(name + "_vf", { ov_dcp, ccap[0], ccap[1], ccap[2] });
vf->set_interop(true);
vf->set_reel_type(ReelType::BY_VIDEO_CONTENT);
ov_dcp->set_reference_video(true);
@@ -548,7 +548,7 @@ BOOST_AUTO_TEST_CASE(map_uses_config_for_issuer_and_creator)
string const out = String::compose("build/test/%1_out", name);
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2(name + "_in", content);
+ auto film = new_test_film(name + "_in", content);
make_and_verify_dcp(film);
vector<string> const args = {
@@ -580,7 +580,7 @@ BOOST_AUTO_TEST_CASE(map_handles_interop_png_subs)
{
string const name = "map_handles_interop_png_subs";
auto arrietty = content_factory(TestPaths::private_data() / "arrietty_JP-EN.mkv")[0];
- auto film = new_test_film2(name + "_input", { arrietty });
+ auto film = new_test_film(name + "_input", { arrietty });
film->set_interop(true);
arrietty->set_trim_end(dcpomatic::ContentTime::from_seconds(110));
arrietty->text[0]->set_use(true);
diff --git a/test/markers_test.cc b/test/markers_test.cc
index a3c46af44..f7ff3a6b5 100644
--- a/test/markers_test.cc
+++ b/test/markers_test.cc
@@ -43,7 +43,7 @@ using boost::optional;
BOOST_AUTO_TEST_CASE (automatic_ffoc_lfoc_markers_test1)
{
string const name = "automatic_ffoc_lfoc_markers_test1";
- auto film = new_test_film2 (name);
+ auto film = new_test_film(name);
film->examine_and_add_content (content_factory("test/data/flat_red.png")[0]);
BOOST_REQUIRE (!wait_for_jobs());
@@ -72,7 +72,7 @@ BOOST_AUTO_TEST_CASE (automatic_ffoc_lfoc_markers_test1)
BOOST_AUTO_TEST_CASE (automatic_ffoc_lfoc_markers_test2)
{
string const name = "automatic_ffoc_lfoc_markers_test2";
- auto film = new_test_film2 (name);
+ auto film = new_test_film(name);
film->examine_and_add_content (content_factory("test/data/flat_red.png")[0]);
BOOST_REQUIRE (!wait_for_jobs());
@@ -110,7 +110,7 @@ BOOST_AUTO_TEST_CASE(markers_correct_with_reels)
string const name = "markers_correct_with_reels";
auto content1 = content_factory("test/data/flat_red.png")[0];
auto content2 = content_factory("test/data/flat_red.png")[0];
- auto film = new_test_film2(name, { content1, content2});
+ auto film = new_test_film(name, { content1, content2});
film->set_interop(false);
film->set_reel_type(ReelType::BY_VIDEO_CONTENT);
@@ -143,7 +143,7 @@ BOOST_AUTO_TEST_CASE(markers_correct_with_reels)
BOOST_AUTO_TEST_CASE(no_markers_with_interop)
{
string const name = "no_markers_with_interop";
- auto film = new_test_film2(name, content_factory("test/data/flat_red.png"));
+ auto film = new_test_film(name, content_factory("test/data/flat_red.png"));
film->set_interop(true);
make_and_verify_dcp(film, { dcp::VerificationNote::Code::INVALID_STANDARD });
diff --git a/test/mca_subdescriptors_test.cc b/test/mca_subdescriptors_test.cc
index e34a909f4..ad146700e 100644
--- a/test/mca_subdescriptors_test.cc
+++ b/test/mca_subdescriptors_test.cc
@@ -42,7 +42,7 @@ test_descriptors(int mxf_channels, vector<dcp::Channel> active_channels, vector<
for (auto i = 0; i < mxf_channels; ++i) {
content.push_back(content_factory("test/data/C.wav").front());
}
- auto film = new_test_film2("mca_subdescriptors_written_correctly", content);
+ auto film = new_test_film("mca_subdescriptors_written_correctly", content);
film->set_interop(false);
film->set_audio_channels(mxf_channels);
diff --git a/test/no_use_video_test.cc b/test/no_use_video_test.cc
index d9e2c7c00..441d64166 100644
--- a/test/no_use_video_test.cc
+++ b/test/no_use_video_test.cc
@@ -50,7 +50,7 @@ using std::make_shared;
*/
BOOST_AUTO_TEST_CASE (no_use_video_test1)
{
- auto film = new_test_film2 ("no_use_video_test1");
+ auto film = new_test_film("no_use_video_test1");
auto A = content_factory("test/data/flat_red.png")[0];
auto B = content_factory("test/data/flat_green.png")[0];
film->examine_and_add_content (A);
@@ -72,13 +72,12 @@ BOOST_AUTO_TEST_CASE (no_use_video_test1)
/** Overlay two muxed sources and disable the video on one */
BOOST_AUTO_TEST_CASE (no_use_video_test2)
{
- auto film = new_test_film2 ("no_use_video_test2");
+ Cleanup cl;
+
auto A = content_factory(TestPaths::private_data() / "dolby_aurora.vob")[0];
auto B = content_factory(TestPaths::private_data() / "big_buck_bunny_trailer_480p.mov")[0];
- film->examine_and_add_content (A);
- film->examine_and_add_content (B);
- BOOST_REQUIRE (!wait_for_jobs());
-
+ auto film = new_test_film("no_use_video_test2", { A, B }, &cl);
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, 100000000);
A->set_position (film, dcpomatic::DCPTime());
B->set_position (film, dcpomatic::DCPTime());
A->video->set_use (false);
@@ -88,13 +87,15 @@ BOOST_AUTO_TEST_CASE (no_use_video_test2)
make_and_verify_dcp (film);
check_dcp (TestPaths::private_data() / "no_use_video_test2", film);
+
+ cl.run();
}
/** Make two DCPs and make a VF with the audio from one and the video from another */
BOOST_AUTO_TEST_CASE (no_use_video_test3)
{
- auto ov_a = new_test_film2 ("no_use_video_test3_ov_a");
+ auto ov_a = new_test_film("no_use_video_test3_ov_a");
auto ov_a_pic = content_factory("test/data/flat_red.png")[0];
BOOST_REQUIRE (ov_a_pic);
auto ov_a_snd = content_factory("test/data/sine_16_48_220_10.wav")[0];
@@ -104,7 +105,7 @@ BOOST_AUTO_TEST_CASE (no_use_video_test3)
BOOST_REQUIRE (!wait_for_jobs());
make_and_verify_dcp (ov_a);
- auto ov_b = new_test_film2("no_use_video_test3_ov_b");
+ auto ov_b = new_test_film("no_use_video_test3_ov_b");
auto ov_b_pic = content_factory("test/data/flat_green.png")[0];
BOOST_REQUIRE (ov_b_pic);
auto ov_b_snd = content_factory("test/data/sine_16_48_880_10.wav")[0];
@@ -114,7 +115,7 @@ BOOST_AUTO_TEST_CASE (no_use_video_test3)
BOOST_REQUIRE (!wait_for_jobs());
make_and_verify_dcp (ov_b);
- auto vf = new_test_film2 ("no_use_video_test3_vf");
+ auto vf = new_test_film("no_use_video_test3_vf");
auto A = make_shared<DCPContent>(ov_a->dir(ov_a->dcp_name()));
auto B = make_shared<DCPContent>(ov_b->dir(ov_b->dcp_name()));
vf->examine_and_add_content (A);
diff --git a/test/optimise_stills_test.cc b/test/optimise_stills_test.cc
index cad7d7d26..c0049507d 100644
--- a/test/optimise_stills_test.cc
+++ b/test/optimise_stills_test.cc
@@ -22,7 +22,6 @@
#include "lib/content.h"
#include "lib/content_factory.h"
#include "lib/dcp_content_type.h"
-#include "lib/dcp_encoder.h"
#include "lib/dcpomatic_log.h"
#include "lib/film.h"
#include "lib/job_manager.h"
@@ -75,14 +74,9 @@ check (string name, int check_full, int check_repeat)
/** Make a 2D DCP out of a 2D still and check that the J2K encoding is only done once for each frame */
BOOST_AUTO_TEST_CASE (optimise_stills_test1)
{
- auto film = new_test_film ("optimise_stills_test1");
- LogSwitcher ls (film->log());
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
auto content = content_factory("test/data/flat_red.png")[0];
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs ());
+ auto film = new_test_film("optimise_stills_test1", { content });
+ LogSwitcher ls (film->log());
make_and_verify_dcp (film);
check ("optimise_stills_test1", 1, 10 * 24 - 1);
@@ -92,14 +86,9 @@ BOOST_AUTO_TEST_CASE (optimise_stills_test1)
/** Make a 3D DCP out of a 3D L/R still and check that the J2K encoding is only done once for L and R */
BOOST_AUTO_TEST_CASE (optimise_stills_test2)
{
- auto film = new_test_film ("optimise_stills_test2");
- LogSwitcher ls (film->log());
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name("TLR"));
- film->set_name ("frobozz");
auto content = content_factory("test/data/flat_red.png")[0];
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs ());
+ auto film = new_test_film("optimise_stills_test2", { content });
+ LogSwitcher ls (film->log());
content->video->set_frame_type (VideoFrameType::THREE_D_LEFT_RIGHT);
film->set_three_d (true);
make_and_verify_dcp (film);
diff --git a/test/overlap_video_test.cc b/test/overlap_video_test.cc
index cb5fcb430..01d7a9fcb 100644
--- a/test/overlap_video_test.cc
+++ b/test/overlap_video_test.cc
@@ -30,8 +30,8 @@
#include <dcp/cpl.h>
#include <dcp/dcp.h>
#include <dcp/j2k_transcode.h>
-#include <dcp/mono_picture_asset.h>
-#include <dcp/mono_picture_frame.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/mono_j2k_picture_frame.h>
#include <dcp/openjpeg_image.h>
#include <dcp/reel.h>
#include <dcp/reel_mono_picture_asset.h>
@@ -49,7 +49,7 @@ BOOST_AUTO_TEST_CASE (overlap_video_test1)
auto A = content_factory("test/data/flat_red.png")[0];
auto B = content_factory("test/data/flat_green.png")[0];
auto C = content_factory("test/data/flat_blue.png")[0];
- auto film = new_test_film2("overlap_video_test1", { A, B, C });
+ auto film = new_test_film("overlap_video_test1", { A, B, C });
film->set_sequence (false);
auto const fps = 24;
@@ -89,7 +89,7 @@ BOOST_AUTO_TEST_CASE (overlap_video_test1)
BOOST_REQUIRE (reel->main_picture());
auto mono_picture = dynamic_pointer_cast<dcp::ReelMonoPictureAsset>(reel->main_picture());
BOOST_REQUIRE (mono_picture);
- auto asset = mono_picture->mono_asset();
+ auto asset = mono_picture->mono_j2k_asset();
BOOST_REQUIRE (asset);
BOOST_CHECK_EQUAL (asset->intrinsic_duration(), fps * 5);
auto reader = asset->start_read ();
diff --git a/test/player_test.cc b/test/player_test.cc
index a13b50352..cac5dffe9 100644
--- a/test/player_test.cc
+++ b/test/player_test.cc
@@ -55,6 +55,7 @@ using std::cout;
using std::list;
using std::shared_ptr;
using std::make_shared;
+using std::vector;
using boost::bind;
using boost::optional;
#if BOOST_VERSION >= 106100
@@ -77,15 +78,10 @@ accumulate (shared_ptr<AudioBuffers> audio, DCPTime)
/** Check that the Player correctly generates silence when used with a silent FFmpegContent */
BOOST_AUTO_TEST_CASE (player_silence_padding_test)
{
- auto film = new_test_film ("player_silence_padding_test");
- film->set_name ("player_silence_padding_test");
auto c = std::make_shared<FFmpegContent>("test/data/test.mp4");
- film->set_container (Ratio::from_id ("185"));
+ auto film = new_test_film("player_silence_padding_test", { c });
film->set_audio_channels (6);
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
-
accumulated = std::make_shared<AudioBuffers>(film->audio_channels(), 0);
Player player(film, Image::Alignment::COMPACT);
@@ -105,18 +101,11 @@ BOOST_AUTO_TEST_CASE (player_silence_padding_test)
/* Test insertion of black frames between separate bits of video content */
BOOST_AUTO_TEST_CASE (player_black_fill_test)
{
- auto film = new_test_film ("black_fill_test");
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
- film->set_name ("black_fill_test");
- film->set_container (Ratio::from_id ("185"));
- film->set_sequence (false);
- film->set_interop (false);
auto contentA = std::make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
auto contentB = std::make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
-
- film->examine_and_add_content (contentA);
- film->examine_and_add_content (contentB);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("black_fill_test", { contentA, contentB });
+ film->set_dcp_content_type(DCPContentType::from_isdcf_name("FTR"));
+ film->set_sequence (false);
contentA->video->set_length (3);
contentA->set_position (film, DCPTime::from_frames(2, film->video_frame_rate()));
@@ -154,16 +143,9 @@ BOOST_AUTO_TEST_CASE (player_black_fill_test)
/** Check behaviour with an awkward playlist whose data does not end on a video frame start */
BOOST_AUTO_TEST_CASE (player_subframe_test)
{
- auto film = new_test_film ("reels_test7");
- film->set_name ("reels_test7");
- film->set_container (Ratio::from_id("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name("TST"));
auto A = content_factory("test/data/flat_red.png")[0];
- film->examine_and_add_content (A);
- BOOST_REQUIRE (!wait_for_jobs());
auto B = content_factory("test/data/awkward_length.wav")[0];
- film->examine_and_add_content (B);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("reels_test7", { A, B });
film->set_video_frame_rate (24);
A->video->set_length (3 * 24);
@@ -201,18 +183,10 @@ audio (shared_ptr<AudioBuffers> audio, DCPTime)
/** Check with a video-only file that the video and audio emissions happen more-or-less together */
BOOST_AUTO_TEST_CASE (player_interleave_test)
{
- auto film = new_test_film ("ffmpeg_transcoder_basic_test_subs");
- film->set_name ("ffmpeg_transcoder_basic_test");
- film->set_container (Ratio::from_id ("185"));
- film->set_audio_channels (6);
-
auto c = std::make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs ());
-
auto s = std::make_shared<StringTextFileContent>("test/data/subrip.srt");
- film->examine_and_add_content (s);
- BOOST_REQUIRE (!wait_for_jobs ());
+ auto film = new_test_film("ffmpeg_transcoder_basic_test_subs", { c, s });
+ film->set_audio_channels (6);
Player player(film, Image::Alignment::COMPACT);
player.Video.connect(bind(&video, _1, _2));
@@ -297,7 +271,7 @@ BOOST_AUTO_TEST_CASE (player_seek_test2)
/** Test a bug when trimmed content follows other content */
BOOST_AUTO_TEST_CASE (player_trim_test)
{
- auto film = new_test_film2 ("player_trim_test");
+ auto film = new_test_film("player_trim_test");
auto A = content_factory("test/data/flat_red.png")[0];
film->examine_and_add_content (A);
BOOST_REQUIRE (!wait_for_jobs ());
@@ -336,7 +310,7 @@ store (list<Sub>* out, PlayerText text, TextType type, optional<DCPTextTrack> tr
/** Test ignoring both video and audio */
BOOST_AUTO_TEST_CASE (player_ignore_video_and_audio_test)
{
- auto film = new_test_film2 ("player_ignore_video_and_audio_test");
+ auto film = new_test_film("player_ignore_video_and_audio_test");
auto ff = content_factory(TestPaths::private_data() / "boon_telly.mkv")[0];
film->examine_and_add_content (ff);
auto text = content_factory("test/data/subrip.srt")[0];
@@ -360,7 +334,7 @@ BOOST_AUTO_TEST_CASE (player_ignore_video_and_audio_test)
/** Trigger a crash due to the assertion failure in Player::emit_audio */
BOOST_AUTO_TEST_CASE (player_trim_crash)
{
- auto film = new_test_film2 ("player_trim_crash");
+ auto film = new_test_film("player_trim_crash");
auto boon = content_factory(TestPaths::private_data() / "boon_telly.mkv")[0];
film->examine_and_add_content (boon);
BOOST_REQUIRE (!wait_for_jobs());
@@ -388,21 +362,21 @@ BOOST_AUTO_TEST_CASE (player_trim_crash)
/** Test a crash when the gap between the last audio and the start of a silent period is more than 1 sample */
BOOST_AUTO_TEST_CASE (player_silence_crash)
{
- auto film = new_test_film2 ("player_silence_crash");
- auto sine = content_factory("test/data/impulse_train.wav")[0];
- film->examine_and_add_content (sine);
- BOOST_REQUIRE (!wait_for_jobs());
+ Cleanup cl;
+ auto sine = content_factory("test/data/impulse_train.wav")[0];
+ auto film = new_test_film("player_silence_crash", { sine }, &cl);
sine->set_video_frame_rate(film, 23.976);
- film->write_metadata ();
make_and_verify_dcp (film, {dcp::VerificationNote::Code::MISSING_CPL_METADATA});
+
+ cl.run();
}
/** Test a crash when processing a 3D DCP */
BOOST_AUTO_TEST_CASE (player_3d_test_1)
{
- auto film = new_test_film2 ("player_3d_test_1a");
+ auto film = new_test_film("player_3d_test_1a");
auto left = content_factory("test/data/flat_red.png")[0];
film->examine_and_add_content (left);
auto right = content_factory("test/data/flat_blue.png")[0];
@@ -418,7 +392,7 @@ BOOST_AUTO_TEST_CASE (player_3d_test_1)
make_and_verify_dcp (film);
auto dcp = std::make_shared<DCPContent>(film->dir(film->dcp_name()));
- auto film2 = new_test_film2 ("player_3d_test_1b", {dcp});
+ auto film2 = new_test_film("player_3d_test_1b", {dcp});
film2->set_three_d (true);
make_and_verify_dcp (film2);
@@ -430,7 +404,7 @@ BOOST_AUTO_TEST_CASE (player_3d_test_2)
{
auto left = content_factory("test/data/flat_red.png")[0];
auto right = content_factory("test/data/flat_blue.png")[0];
- auto film = new_test_film2 ("player_3d_test_2a", {left, right});
+ auto film = new_test_film("player_3d_test_2a", {left, right});
left->video->set_frame_type (VideoFrameType::THREE_D_LEFT);
left->set_position (film, DCPTime());
@@ -441,7 +415,7 @@ BOOST_AUTO_TEST_CASE (player_3d_test_2)
make_and_verify_dcp (film);
auto dcp = std::make_shared<DCPContent>(film->dir(film->dcp_name()));
- auto film2 = new_test_film2 ("player_3d_test_2b", {dcp});
+ auto film2 = new_test_film("player_3d_test_2b", {dcp});
make_and_verify_dcp (film2);
}
@@ -454,14 +428,14 @@ BOOST_AUTO_TEST_CASE (player_silence_at_end_crash)
{
/* 25fps DCP with some audio */
auto content1 = content_factory("test/data/flat_red.png")[0];
- auto film1 = new_test_film2 ("player_silence_at_end_crash_1", {content1});
+ auto film1 = new_test_film("player_silence_at_end_crash_1", {content1});
content1->video->set_length (25);
film1->set_video_frame_rate (25);
make_and_verify_dcp (film1);
/* Make another project importing this DCP */
auto content2 = std::make_shared<DCPContent>(film1->dir(film1->dcp_name()));
- auto film2 = new_test_film2 ("player_silence_at_end_crash_2", {content2});
+ auto film2 = new_test_film("player_silence_at_end_crash_2", {content2});
/* and importing just the video MXF on its own at the end */
optional<boost::filesystem::path> video;
@@ -485,7 +459,7 @@ BOOST_AUTO_TEST_CASE (player_silence_at_end_crash)
BOOST_AUTO_TEST_CASE (encrypted_dcp_with_no_kdm_gives_no_butler_error)
{
auto content = content_factory("test/data/flat_red.png")[0];
- auto film = new_test_film2 ("encrypted_dcp_with_no_kdm_gives_no_butler_error", { content });
+ auto film = new_test_film("encrypted_dcp_with_no_kdm_gives_no_butler_error", { content });
int constexpr length = 24 * 25;
content->video->set_length(length);
film->set_encrypted (true);
@@ -496,7 +470,7 @@ BOOST_AUTO_TEST_CASE (encrypted_dcp_with_no_kdm_gives_no_butler_error)
});
auto content2 = std::make_shared<DCPContent>(film->dir(film->dcp_name()));
- auto film2 = new_test_film2 ("encrypted_dcp_with_no_kdm_gives_no_butler_error2", { content2 });
+ auto film2 = new_test_film("encrypted_dcp_with_no_kdm_gives_no_butler_error2", { content2 });
Player player(film, Image::Alignment::COMPACT);
Butler butler(film2, player, AudioMapping(), 2, bind(PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, Image::Alignment::PADDED, true, false, Butler::Audio::ENABLED);
@@ -531,7 +505,7 @@ BOOST_AUTO_TEST_CASE (interleaved_subtitle_are_emitted_correctly)
auto subs1 = content_factory(paths[0])[0];
auto subs2 = content_factory(paths[1])[0];
- auto film = new_test_film2("interleaved_subtitle_are_emitted_correctly", { subs1, subs2 });
+ auto film = new_test_film("interleaved_subtitle_are_emitted_correctly", { subs1, subs2 });
film->set_sequence(false);
subs1->set_position(film, DCPTime());
subs2->set_position(film, DCPTime());
@@ -558,7 +532,7 @@ BOOST_AUTO_TEST_CASE(multiple_sound_files_bug)
auto B = content_factory(TestPaths::private_data() / "kook" / "2.wav").front();
auto C = content_factory(TestPaths::private_data() / "kook" / "3.wav").front();
- auto film = new_test_film2("multiple_sound_files_bug", { A, B, C }, &cl);
+ auto film = new_test_film("multiple_sound_files_bug", { A, B, C }, &cl);
film->set_audio_channels(16);
C->set_position(film, DCPTime(3840000));
@@ -574,7 +548,7 @@ BOOST_AUTO_TEST_CASE(trimmed_sound_mix_bug_13)
{
auto A = content_factory("test/data/sine_16_48_440_10.wav").front();
auto B = content_factory("test/data/sine_16_44.1_440_10.wav").front();
- auto film = new_test_film2("trimmed_sound_mix_bug_13", { A, B });
+ auto film = new_test_film("trimmed_sound_mix_bug_13", { A, B });
film->set_audio_channels(16);
A->set_position(film, DCPTime());
@@ -592,7 +566,7 @@ BOOST_AUTO_TEST_CASE(trimmed_sound_mix_bug_13_frame_rate_change)
{
auto A = content_factory("test/data/sine_16_48_440_10.wav").front();
auto B = content_factory("test/data/sine_16_44.1_440_10.wav").front();
- auto film = new_test_film2("trimmed_sound_mix_bug_13_frame_rate_change", { A, B });
+ auto film = new_test_film("trimmed_sound_mix_bug_13_frame_rate_change", { A, B });
A->set_position(film, DCPTime());
A->audio->set_gain(-12);
@@ -614,7 +588,7 @@ BOOST_AUTO_TEST_CASE(two_d_in_three_d_duplicates)
{
auto A = content_factory("test/data/flat_red.png").front();
auto B = content_factory("test/data/flat_green.png").front();
- auto film = new_test_film2("two_d_in_three_d_duplicates", { A, B });
+ auto film = new_test_film("two_d_in_three_d_duplicates", { A, B });
film->set_three_d(true);
B->video->set_frame_type(VideoFrameType::THREE_D_LEFT_RIGHT);
@@ -668,7 +642,7 @@ BOOST_AUTO_TEST_CASE(three_d_in_two_d_chooses_left)
auto right = content_factory("test/data/flat_green.png").front();
auto mono = content_factory("test/data/flat_blue.png").front();
- auto film = new_test_film2("three_d_in_two_d_chooses_left", { left, right, mono });
+ auto film = new_test_film("three_d_in_two_d_chooses_left", { left, right, mono });
left->video->set_frame_type(VideoFrameType::THREE_D_LEFT);
left->set_position(film, dcpomatic::DCPTime());
@@ -712,10 +686,38 @@ BOOST_AUTO_TEST_CASE(three_d_in_two_d_chooses_left)
}
+BOOST_AUTO_TEST_CASE(check_seek_with_no_video)
+{
+ auto content = content_factory(TestPaths::private_data() / "Fight.Club.1999.720p.BRRip.x264-x0r.srt")[0];
+ auto film = new_test_film("check_seek_with_no_video", { content });
+ auto player = std::make_shared<Player>(film, film->playlist());
+
+ boost::signals2::signal<void (std::shared_ptr<PlayerVideo>, dcpomatic::DCPTime)> Video;
+
+ optional<dcpomatic::DCPTime> earliest;
+
+ player->Video.connect(
+ [&earliest](shared_ptr<PlayerVideo>, dcpomatic::DCPTime time) {
+ if (!earliest || time < *earliest) {
+ earliest = time;
+ }
+ });
+
+ player->seek(dcpomatic::DCPTime::from_seconds(60 * 60), false);
+
+ for (int i = 0; i < 10; ++i) {
+ player->pass();
+ }
+
+ BOOST_REQUIRE(earliest);
+ BOOST_CHECK(*earliest >= dcpomatic::DCPTime(60 * 60));
+}
+
+
BOOST_AUTO_TEST_CASE(unmapped_audio_does_not_raise_buffer_error)
{
auto content = content_factory(TestPaths::private_data() / "arrietty_JP-EN.mkv")[0];
- auto film = new_test_film2("unmapped_audio_does_not_raise_buffer_error", { content });
+ auto film = new_test_film("unmapped_audio_does_not_raise_buffer_error", { content });
content->audio->set_mapping(AudioMapping(6 * 2, MAX_DCP_AUDIO_CHANNELS));
diff --git a/test/playlist_test.cc b/test/playlist_test.cc
index e2b9f3ff8..e279e9cc8 100644
--- a/test/playlist_test.cc
+++ b/test/playlist_test.cc
@@ -39,7 +39,7 @@ setup(vector<shared_ptr<Content>>& content, vector<dcpomatic::DCPTime>& position
content.push_back(content_factory("test/data/flat_red.png")[0]);
}
- auto film = new_test_film2("playlist_move_later_test", content);
+ auto film = new_test_film("playlist_move_later_test", content);
for (auto i: content) {
positions.push_back(i->position());
diff --git a/test/pulldown_detect_test.cc b/test/pulldown_detect_test.cc
index 432bdf26f..9830d12cd 100644
--- a/test/pulldown_detect_test.cc
+++ b/test/pulldown_detect_test.cc
@@ -29,7 +29,7 @@
BOOST_AUTO_TEST_CASE (pulldown_detect_test1)
{
auto content = content_factory(TestPaths::private_data() / "greatbrain.mkv");
- auto film = new_test_film2("pulldown_detect_test1", content);
+ auto film = new_test_film("pulldown_detect_test1", content);
BOOST_REQUIRE(static_cast<bool>(content[0]->video_frame_rate()));
BOOST_CHECK_CLOSE(content[0]->video_frame_rate().get(), 23.976, 0.1);
diff --git a/test/recover_test.cc b/test/recover_test.cc
index 63c611831..ba06fcde9 100644
--- a/test/recover_test.cc
+++ b/test/recover_test.cc
@@ -33,8 +33,8 @@
#include "lib/video_content.h"
#include "lib/ratio.h"
#include <dcp/equality_options.h>
-#include <dcp/mono_picture_asset.h>
-#include <dcp/stereo_picture_asset.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/stereo_j2k_picture_asset.h>
#include <boost/test/unit_test.hpp>
#include <iostream>
@@ -58,15 +58,9 @@ note (dcp::NoteType t, string n)
BOOST_AUTO_TEST_CASE (recover_test_2d)
{
- auto film = new_test_film("recover_test_2d");
- film->set_interop (false);
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
- film->set_container (Ratio::from_id ("185"));
- film->set_name ("recover_test");
-
auto content = make_shared<FFmpegContent>("test/data/count300bd24.m2ts");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("recover_test_2d", { content });
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, 100000000);
make_and_verify_dcp (
film,
@@ -75,13 +69,16 @@ BOOST_AUTO_TEST_CASE (recover_test_2d)
dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
});
- boost::filesystem::path const video = "build/test/recover_test_2d/video/185_2K_4650f318cea570763a0c6411c8c098ce_24_100000000_P_S_L21_0_1200000.mxf";
+ auto video = [film]() {
+ return find_file(boost::filesystem::path("build/test/recover_test_2d") / film->dcp_name(false), "j2c_");
+ };
+
boost::filesystem::copy_file (
- video,
+ video(),
"build/test/recover_test_2d/original.mxf"
);
- boost::filesystem::resize_file (video, 2 * 1024 * 1024);
+ boost::filesystem::resize_file(video(), 2 * 1024 * 1024);
make_and_verify_dcp(
film,
@@ -94,8 +91,8 @@ BOOST_AUTO_TEST_CASE (recover_test_2d)
false
);
- auto A = make_shared<dcp::MonoPictureAsset>("build/test/recover_test_2d/original.mxf");
- auto B = make_shared<dcp::MonoPictureAsset>(video);
+ auto A = make_shared<dcp::MonoJ2KPictureAsset>("build/test/recover_test_2d/original.mxf");
+ auto B = make_shared<dcp::MonoJ2KPictureAsset>(video());
dcp::EqualityOptions eq;
BOOST_CHECK (A->equals (B, eq, boost::bind (&note, _1, _2)));
@@ -104,28 +101,24 @@ BOOST_AUTO_TEST_CASE (recover_test_2d)
BOOST_AUTO_TEST_CASE (recover_test_3d, * boost::unit_test::depends_on("recover_test_2d"))
{
- auto film = new_test_film ("recover_test_3d");
- film->set_interop (false);
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
- film->set_container (Ratio::from_id ("185"));
- film->set_name ("recover_test");
- film->set_three_d (true);
-
auto content = make_shared<ImageContent>("test/data/3d_test");
content->video->set_frame_type (VideoFrameType::THREE_D_LEFT_RIGHT);
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("recover_test_3d", { content });
+ film->set_three_d (true);
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, 100000000);
make_and_verify_dcp (film, { dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE });
- boost::filesystem::path const video = "build/test/recover_test_3d/video/185_2K_60a75a531ca9546bdd513163117e2214_24_100000000_P_S_L21_3D_0_96000.mxf";
+ auto video = [film]() {
+ return find_file(boost::filesystem::path("build/test/recover_test_3d") / film->dcp_name(false), "j2c_");
+ };
boost::filesystem::copy_file (
- video,
+ video(),
"build/test/recover_test_3d/original.mxf"
);
- boost::filesystem::resize_file (video, 2 * 1024 * 1024);
+ boost::filesystem::resize_file(video(), 2 * 1024 * 1024);
make_and_verify_dcp(
film,
@@ -137,8 +130,8 @@ BOOST_AUTO_TEST_CASE (recover_test_3d, * boost::unit_test::depends_on("recover_t
false
);
- auto A = make_shared<dcp::StereoPictureAsset>("build/test/recover_test_3d/original.mxf");
- auto B = make_shared<dcp::StereoPictureAsset>(video);
+ auto A = make_shared<dcp::StereoJ2KPictureAsset>("build/test/recover_test_3d/original.mxf");
+ auto B = make_shared<dcp::StereoJ2KPictureAsset>(video());
dcp::EqualityOptions eq;
BOOST_CHECK (A->equals (B, eq, boost::bind (&note, _1, _2)));
@@ -147,29 +140,24 @@ BOOST_AUTO_TEST_CASE (recover_test_3d, * boost::unit_test::depends_on("recover_t
BOOST_AUTO_TEST_CASE (recover_test_2d_encrypted, * boost::unit_test::depends_on("recover_test_3d"))
{
- auto film = new_test_film ("recover_test_2d_encrypted");
- film->set_interop (false);
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
- film->set_container (Ratio::from_id ("185"));
- film->set_name ("recover_test");
+ auto content = make_shared<FFmpegContent>("test/data/count300bd24.m2ts");
+ auto film = new_test_film("recover_test_2d_encrypted", { content });
film->set_encrypted (true);
film->_key = dcp::Key("eafcb91c9f5472edf01f3a2404c57258");
-
- auto content = make_shared<FFmpegContent>("test/data/count300bd24.m2ts");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, 100000000);
make_and_verify_dcp (film, { dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE });
- boost::filesystem::path const video =
- "build/test/recover_test_2d_encrypted/video/185_2K_4650f318cea570763a0c6411c8c098ce_24_100000000_Eeafcb91c9f5472edf01f3a2404c57258_S_L21_0_1200000.mxf";
+ auto video = [film]() {
+ return find_file(boost::filesystem::path("build/test/recover_test_2d_encrypted") / film->dcp_name(false), "j2c_");
+ };
boost::filesystem::copy_file (
- video,
+ video(),
"build/test/recover_test_2d_encrypted/original.mxf"
);
- boost::filesystem::resize_file (video, 2 * 1024 * 1024);
+ boost::filesystem::resize_file(video(), 2 * 1024 * 1024);
make_and_verify_dcp(
film,
@@ -181,9 +169,9 @@ BOOST_AUTO_TEST_CASE (recover_test_2d_encrypted, * boost::unit_test::depends_on(
false
);
- auto A = make_shared<dcp::MonoPictureAsset>("build/test/recover_test_2d_encrypted/original.mxf");
+ auto A = make_shared<dcp::MonoJ2KPictureAsset>("build/test/recover_test_2d_encrypted/original.mxf");
A->set_key (film->key ());
- auto B = make_shared<dcp::MonoPictureAsset>(video);
+ auto B = make_shared<dcp::MonoJ2KPictureAsset>(video());
B->set_key (film->key ());
dcp::EqualityOptions eq;
diff --git a/test/reel_writer_test.cc b/test/reel_writer_test.cc
index ec2469898..411de9b1f 100644
--- a/test/reel_writer_test.cc
+++ b/test/reel_writer_test.cc
@@ -30,6 +30,7 @@
#include "lib/content_factory.h"
#include "lib/cross.h"
#include "lib/film.h"
+#include "lib/frame_info.h"
#include "lib/reel_writer.h"
#include "lib/video_content.h"
#include "test.h"
@@ -46,59 +47,59 @@ using std::string;
using boost::optional;
-static bool equal (dcp::FrameInfo a, ReelWriter const & writer, shared_ptr<InfoFileHandle> file, Frame frame, Eyes eyes)
+static bool equal(J2KFrameInfo a, shared_ptr<InfoFileHandle> file, Frame frame, Eyes eyes)
{
- auto b = writer.read_frame_info(file, frame, eyes);
+ auto b = J2KFrameInfo(file, frame, eyes);
return a.offset == b.offset && a.size == b.size && a.hash == b.hash;
}
BOOST_AUTO_TEST_CASE (write_frame_info_test)
{
- auto film = new_test_film2 ("write_frame_info_test");
+ auto film = new_test_film("write_frame_info_test");
dcpomatic::DCPTimePeriod const period (dcpomatic::DCPTime(0), dcpomatic::DCPTime(96000));
- ReelWriter writer (film, period, shared_ptr<Job>(), 0, 1, false);
+ ReelWriter writer(film, period, shared_ptr<Job>(), 0, 1, false, "foo");
/* Write the first one */
- dcp::FrameInfo info1(0, 123, "12345678901234567890123456789012");
- writer.write_frame_info (0, Eyes::LEFT, info1);
+ J2KFrameInfo info1(0, 123, "12345678901234567890123456789012");
+ info1.write(film->info_file_handle(period, false), 0, Eyes::LEFT);
{
auto file = film->info_file_handle(period, true);
- BOOST_CHECK (equal(info1, writer, file, 0, Eyes::LEFT));
+ BOOST_CHECK(equal(info1, file, 0, Eyes::LEFT));
}
/* Write some more */
- dcp::FrameInfo info2(596, 14921, "123acb789f1234ae782012n456339522");
- writer.write_frame_info (5, Eyes::RIGHT, info2);
+ J2KFrameInfo info2(596, 14921, "123acb789f1234ae782012n456339522");
+ info2.write(film->info_file_handle(period, false), 5, Eyes::RIGHT);
{
auto file = film->info_file_handle(period, true);
- BOOST_CHECK (equal(info1, writer, file, 0, Eyes::LEFT));
- BOOST_CHECK (equal(info2, writer, file, 5, Eyes::RIGHT));
+ BOOST_CHECK(equal(info1, file, 0, Eyes::LEFT));
+ BOOST_CHECK(equal(info2, file, 5, Eyes::RIGHT));
}
- dcp::FrameInfo info3(12494, 99157123, "xxxxyyyyabc12356ffsfdsf456339522");
- writer.write_frame_info (10, Eyes::LEFT, info3);
+ J2KFrameInfo info3(12494, 99157123, "xxxxyyyyabc12356ffsfdsf456339522");
+ info3.write(film->info_file_handle(period, false), 10, Eyes::LEFT);
{
auto file = film->info_file_handle(period, true);
- BOOST_CHECK (equal(info1, writer, file, 0, Eyes::LEFT));
- BOOST_CHECK (equal(info2, writer, file, 5, Eyes::RIGHT));
- BOOST_CHECK (equal(info3, writer, file, 10, Eyes::LEFT));
+ BOOST_CHECK(equal(info1, file, 0, Eyes::LEFT));
+ BOOST_CHECK(equal(info2, file, 5, Eyes::RIGHT));
+ BOOST_CHECK(equal(info3, file, 10, Eyes::LEFT));
}
/* Overwrite one */
- dcp::FrameInfo info4(55512494, 123599157123, "ABCDEFGyabc12356ffsfdsf4563395ZZ");
- writer.write_frame_info (5, Eyes::RIGHT, info4);
+ J2KFrameInfo info4(55512494, 123599157123, "ABCDEFGyabc12356ffsfdsf4563395ZZ");
+ info4.write(film->info_file_handle(period, false), 5, Eyes::RIGHT);
{
auto file = film->info_file_handle(period, true);
- BOOST_CHECK (equal(info1, writer, file, 0, Eyes::LEFT));
- BOOST_CHECK (equal(info4, writer, file, 5, Eyes::RIGHT));
- BOOST_CHECK (equal(info3, writer, file, 10, Eyes::LEFT));
+ BOOST_CHECK(equal(info1, file, 0, Eyes::LEFT));
+ BOOST_CHECK(equal(info4, file, 5, Eyes::RIGHT));
+ BOOST_CHECK(equal(info3, file, 10, Eyes::LEFT));
}
}
@@ -111,7 +112,7 @@ BOOST_AUTO_TEST_CASE (reel_reuse_video_test)
/* Make a DCP */
auto video = content_factory("test/data/flat_red.png")[0];
auto audio = content_factory("test/data/white.wav")[0];
- auto film = new_test_film2 ("reel_reuse_video_test", { video, audio });
+ auto film = new_test_film("reel_reuse_video_test", { video, audio });
make_and_verify_dcp (film);
/* Find main picture and sound asset IDs */
diff --git a/test/reels_test.cc b/test/reels_test.cc
index d4a783f91..7ba01b8a9 100644
--- a/test/reels_test.cc
+++ b/test/reels_test.cc
@@ -54,16 +54,20 @@ using std::vector;
using namespace dcpomatic;
+static
+void
+filter_ok(std::vector<dcp::VerificationNote>& notes)
+{
+ notes.erase(std::remove_if(notes.begin(), notes.end(), [](dcp::VerificationNote const& note) { return note.type() == dcp::VerificationNote::Type::OK; }), notes.end());
+}
+
+
/** Test Film::reels() */
BOOST_AUTO_TEST_CASE (reels_test1)
{
- auto film = new_test_film ("reels_test1");
- film->set_container (Ratio::from_id ("185"));
auto A = make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (A);
auto B = make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (B);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("reels_test1", { A, B });
BOOST_CHECK_EQUAL (A->full_length(film).get(), 288000);
film->set_reel_type (ReelType::SINGLE);
@@ -80,7 +84,7 @@ BOOST_AUTO_TEST_CASE (reels_test1)
BOOST_CHECK_EQUAL (r.back().from.get(), 288000);
BOOST_CHECK_EQUAL (r.back().to.get(), 288000 * 2);
- film->set_j2k_bandwidth (100000000);
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, 100000000);
film->set_reel_type (ReelType::BY_LENGTH);
/* This is just over 2.5s at 100Mbit/s; should correspond to 60 frames */
film->set_reel_length (31253154);
@@ -103,32 +107,13 @@ BOOST_AUTO_TEST_CASE (reels_test1)
*/
BOOST_AUTO_TEST_CASE (reels_test2)
{
- auto film = new_test_film ("reels_test2");
- film->set_name ("reels_test2");
- film->set_container (Ratio::from_id ("185"));
- film->set_interop (false);
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
-
- {
- auto c = make_shared<ImageContent>("test/data/flat_red.png");
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
- c->video->set_length (24);
- }
-
- {
- auto c = make_shared<ImageContent>("test/data/flat_green.png");
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
- c->video->set_length (24);
- }
-
- {
- auto c = make_shared<ImageContent>("test/data/flat_blue.png");
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
- c->video->set_length (24);
- }
+ auto r = make_shared<ImageContent>("test/data/flat_red.png");
+ auto g = make_shared<ImageContent>("test/data/flat_green.png");
+ auto b = make_shared<ImageContent>("test/data/flat_blue.png");
+ auto film = new_test_film("reels_test2", { r, g, b });
+ r->video->set_length(24);
+ g->video->set_length(24);
+ b->video->set_length(24);
film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
BOOST_CHECK_EQUAL (film->reels().size(), 3U);
@@ -141,13 +126,13 @@ BOOST_AUTO_TEST_CASE (reels_test2)
check_dcp ("test/data/reels_test2", film->dir (film->dcp_name()));
auto c = make_shared<DCPContent>(film->dir(film->dcp_name()));
- auto film2 = new_test_film2 ("reels_test2b", {c});
+ auto film2 = new_test_film("reels_test2b", {c});
film2->set_reel_type (ReelType::BY_VIDEO_CONTENT);
film2->set_audio_channels(16);
- auto r = film2->reels ();
- BOOST_CHECK_EQUAL (r.size(), 3U);
- auto i = r.begin ();
+ auto reels = film2->reels ();
+ BOOST_CHECK_EQUAL(reels.size(), 3U);
+ auto i = reels.begin();
BOOST_CHECK_EQUAL (i->from.get(), 0);
BOOST_CHECK_EQUAL (i->to.get(), 96000);
++i;
@@ -171,7 +156,7 @@ BOOST_AUTO_TEST_CASE (reels_test3)
{
auto dcp = make_shared<DCPContent>("test/data/reels_test2");
auto sub = make_shared<StringTextFileContent>("test/data/subrip.srt");
- auto film = new_test_film2 ("reels_test3", {dcp, sub});
+ auto film = new_test_film("reels_test3", {dcp, sub});
film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
auto reels = film->reels();
@@ -196,7 +181,7 @@ BOOST_AUTO_TEST_CASE (reels_test3)
*/
BOOST_AUTO_TEST_CASE (reels_test4)
{
- auto film = new_test_film2 ("reels_test4");
+ auto film = new_test_film("reels_test4");
film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
film->set_interop (false);
@@ -246,7 +231,7 @@ BOOST_AUTO_TEST_CASE (reels_test5)
{
auto dcp = make_shared<DCPContent>("test/data/reels_test4");
dcp->check_font_ids();
- auto film = new_test_film2 ("reels_test5", {dcp});
+ auto film = new_test_film("reels_test5", {dcp});
film->set_sequence (false);
/* Set to 2123 but it will be rounded up to the next frame (4000) */
@@ -300,9 +285,9 @@ BOOST_AUTO_TEST_CASE (reels_test5)
BOOST_AUTO_TEST_CASE (reels_test6)
{
auto A = make_shared<FFmpegContent>("test/data/test2.mp4");
- auto film = new_test_film2 ("reels_test6", {A});
+ auto film = new_test_film("reels_test6", {A});
- film->set_j2k_bandwidth (100000000);
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, 100000000);
film->set_reel_type (ReelType::BY_LENGTH);
/* This is just over 2.5s at 100Mbit/s; should correspond to 60 frames */
film->set_reel_length (31253154);
@@ -326,7 +311,7 @@ BOOST_AUTO_TEST_CASE (reels_test7)
{
auto A = content_factory("test/data/flat_red.png")[0];
auto B = content_factory("test/data/awkward_length.wav")[0];
- auto film = new_test_film2 ("reels_test7", { A, B });
+ auto film = new_test_film("reels_test7", { A, B });
film->set_video_frame_rate (24);
A->video->set_length (2 * 24);
@@ -343,7 +328,7 @@ BOOST_AUTO_TEST_CASE (reels_test7)
BOOST_AUTO_TEST_CASE (reels_test8)
{
auto A = make_shared<FFmpegContent>("test/data/test2.mp4");
- auto film = new_test_film2 ("reels_test8", {A});
+ auto film = new_test_film("reels_test8", {A});
A->set_trim_end (ContentTime::from_seconds (1));
make_and_verify_dcp (film);
@@ -354,13 +339,13 @@ BOOST_AUTO_TEST_CASE (reels_test8)
BOOST_AUTO_TEST_CASE (reels_test9)
{
auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
- auto film = new_test_film2("reels_test9a", {A});
+ auto film = new_test_film("reels_test9a", {A});
A->video->set_length(5 * 24);
film->set_video_frame_rate(24);
make_and_verify_dcp (film);
auto B = make_shared<DCPContent>(film->dir(film->dcp_name()));
- auto film2 = new_test_film2("reels_test9b", {B, content_factory("test/data/dcp_sub4.xml")[0]});
+ auto film2 = new_test_film("reels_test9b", {B, content_factory("test/data/dcp_sub4.xml")[0]});
B->set_reference_video(true);
B->set_reference_audio(true);
film2->set_reel_type(ReelType::BY_VIDEO_CONTENT);
@@ -385,7 +370,7 @@ BOOST_AUTO_TEST_CASE (reels_test10)
/* Make the OV */
auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
auto B = make_shared<FFmpegContent>("test/data/flat_red.png");
- auto ov = new_test_film2("reels_test10_ov", {A, B});
+ auto ov = new_test_film("reels_test10_ov", {A, B});
A->video->set_length (5 * 24);
B->video->set_length (5 * 24);
@@ -395,7 +380,7 @@ BOOST_AUTO_TEST_CASE (reels_test10)
/* Now try to make the VF; this used to fail */
auto ov_dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
- auto vf = new_test_film2("reels_test10_vf", {ov_dcp, content_factory("test/data/15s.srt")[0]});
+ auto vf = new_test_film("reels_test10_vf", {ov_dcp, content_factory("test/data/15s.srt")[0]});
vf->set_reel_type (ReelType::BY_VIDEO_CONTENT);
ov_dcp->set_reference_video (true);
ov_dcp->set_reference_audio (true);
@@ -418,7 +403,7 @@ BOOST_AUTO_TEST_CASE (reels_test10)
BOOST_AUTO_TEST_CASE (reels_test11)
{
auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
- auto film = new_test_film2 ("reels_test11", {A});
+ auto film = new_test_film("reels_test11", {A});
film->set_video_frame_rate (24);
A->video->set_length (240);
A->set_video_frame_rate(film, 24);
@@ -444,7 +429,7 @@ BOOST_AUTO_TEST_CASE (reels_test12)
{
auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
auto B = make_shared<FFmpegContent>("test/data/flat_red.png");
- auto film = new_test_film2 ("reels_test12", {A, B});
+ auto film = new_test_film("reels_test12", {A, B});
film->set_video_frame_rate (24);
film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
film->set_sequence (false);
@@ -497,7 +482,7 @@ BOOST_AUTO_TEST_CASE (reels_should_not_be_short1)
{
auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
auto B = make_shared<FFmpegContent>("test/data/flat_red.png");
- auto film = new_test_film2 ("reels_should_not_be_short1", {A, B});
+ auto film = new_test_film("reels_should_not_be_short1", {A, B});
film->set_video_frame_rate (24);
A->video->set_length (23);
@@ -508,9 +493,10 @@ BOOST_AUTO_TEST_CASE (reels_should_not_be_short1)
make_and_verify_dcp (film);
vector<boost::filesystem::path> dirs = { film->dir(film->dcp_name(false)) };
- auto notes = dcp::verify(dirs, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd());
- dump_notes (notes);
- BOOST_REQUIRE (notes.empty());
+ auto result = dcp::verify(dirs, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd());
+ filter_ok(result.notes);
+ dump_notes(result.notes);
+ BOOST_REQUIRE(result.notes.empty());
}
@@ -521,7 +507,7 @@ BOOST_AUTO_TEST_CASE (reels_should_not_be_short2)
{
auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
auto B = make_shared<FFmpegContent>("test/data/flat_red.png");
- auto film = new_test_film2 ("reels_should_not_be_short2", {A, B});
+ auto film = new_test_film("reels_should_not_be_short2", {A, B});
film->set_video_frame_rate (24);
film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
@@ -533,9 +519,10 @@ BOOST_AUTO_TEST_CASE (reels_should_not_be_short2)
make_and_verify_dcp (film);
vector<boost::filesystem::path> dirs = { film->dir(film->dcp_name(false)) };
- auto const notes = dcp::verify(dirs, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd());
- dump_notes (notes);
- BOOST_REQUIRE (notes.empty());
+ auto result = dcp::verify(dirs, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd());
+ filter_ok(result.notes);
+ dump_notes(result.notes);
+ BOOST_REQUIRE(result.notes.empty());
}
@@ -545,7 +532,7 @@ BOOST_AUTO_TEST_CASE (reels_should_not_be_short2)
BOOST_AUTO_TEST_CASE (reels_should_not_be_short3)
{
auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
- auto film = new_test_film2 ("reels_should_not_be_short3", {A});
+ auto film = new_test_film("reels_should_not_be_short3", {A});
film->set_video_frame_rate (24);
film->set_reel_type (ReelType::BY_LENGTH);
film->set_reel_length (1024 * 1024 * 10);
@@ -554,9 +541,10 @@ BOOST_AUTO_TEST_CASE (reels_should_not_be_short3)
make_and_verify_dcp (film);
- auto const notes = dcp::verify({}, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd());
- dump_notes (notes);
- BOOST_REQUIRE (notes.empty());
+ auto result = dcp::verify({}, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd());
+ filter_ok(result.notes);
+ dump_notes(result.notes);
+ BOOST_REQUIRE(result.notes.empty());
}
@@ -567,7 +555,7 @@ BOOST_AUTO_TEST_CASE (reels_should_not_be_short4)
{
auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
auto B = make_shared<FFmpegContent>("test/data/flat_red.png");
- auto film = new_test_film2 ("reels_should_not_be_short4", {A, B});
+ auto film = new_test_film("reels_should_not_be_short4", {A, B});
film->set_video_frame_rate (24);
film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
@@ -584,9 +572,10 @@ BOOST_AUTO_TEST_CASE (reels_should_not_be_short4)
BOOST_REQUIRE (!wait_for_jobs());
vector<boost::filesystem::path> dirs = { film->dir(film->dcp_name(false)) };
- auto const notes = dcp::verify(dirs, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd());
- dump_notes (notes);
- BOOST_REQUIRE (notes.empty());
+ auto result = dcp::verify(dirs, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd());
+ filter_ok(result.notes);
+ dump_notes(result.notes);
+ BOOST_REQUIRE(result.notes.empty());
}
@@ -598,7 +587,7 @@ BOOST_AUTO_TEST_CASE (repeated_dcp_into_reels)
{
/* Make a 20s DCP */
auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
- auto film1 = new_test_film2("repeated_dcp_into_reels1", { A });
+ auto film1 = new_test_film("repeated_dcp_into_reels1", { A });
auto constexpr frame_rate = 24;
auto constexpr length_in_seconds = 20;
auto constexpr total_frames = frame_rate * length_in_seconds;
@@ -620,7 +609,7 @@ BOOST_AUTO_TEST_CASE (repeated_dcp_into_reels)
make_shared<DCPContent>(film1->dir(film1->dcp_name(false)))
};
- auto film2 = new_test_film2("repeated_dcp_into_reels2", { original_dcp[0], original_dcp[1], original_dcp[2], original_dcp[3] });
+ auto film2 = new_test_film("repeated_dcp_into_reels2", { original_dcp[0], original_dcp[1], original_dcp[2], original_dcp[3] });
film2->set_reel_type(ReelType::BY_VIDEO_CONTENT);
film2->set_video_frame_rate(frame_rate);
film2->set_sequence(false);
diff --git a/test/release_notes_test.cc b/test/release_notes_test.cc
index c05ad2618..c303985de 100644
--- a/test/release_notes_test.cc
+++ b/test/release_notes_test.cc
@@ -38,11 +38,11 @@ BOOST_AUTO_TEST_CASE(release_notes_test1)
}
-// Once we're running 2.16.19 we have no more release notes (for now, at least)
+// Once we're running 2.17.19 we have no more release notes (for now, at least)
BOOST_AUTO_TEST_CASE(release_notes_test2)
{
- for (auto version: { "2.16.19", "2.16.20", "2.18.0", "2.18.1devel6" }) {
- Config::instance()->set_last_release_notes_version("2.16.19");
+ for (auto version: { "2.17.19", "2.17.20", "2.18.0", "2.18.1devel6" }) {
+ Config::instance()->set_last_release_notes_version("2.17.19");
auto notes = find_release_notes(false, string(version));
BOOST_CHECK(!static_cast<bool>(notes));
}
diff --git a/test/remake_id_test.cc b/test/remake_id_test.cc
index 816a43feb..a919fa12a 100644
--- a/test/remake_id_test.cc
+++ b/test/remake_id_test.cc
@@ -46,7 +46,7 @@ BOOST_AUTO_TEST_CASE (remake_id_test1)
{
/* Make a DCP */
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2 ("remake_id_test1_1", content);
+ auto film = new_test_film("remake_id_test1_1", content);
make_and_verify_dcp (film);
/* Copy the video file */
@@ -67,7 +67,7 @@ BOOST_AUTO_TEST_CASE (remake_id_test2)
{
/* Make a DCP */
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2 ("remake_id_test2_1", content);
+ auto film = new_test_film("remake_id_test2_1", content);
film->set_encrypted (true);
make_and_verify_dcp (film);
@@ -93,7 +93,7 @@ BOOST_AUTO_TEST_CASE (remake_id_test2)
/* Import the DCP into a new film */
auto dcp_content = make_shared<DCPContent>(film->dir(film->dcp_name()));
- auto film2 = new_test_film2("remake_id_test2_2", { dcp_content });
+ auto film2 = new_test_film("remake_id_test2_2", { dcp_content });
dcp_content->add_kdm(kdm);
JobManager::instance()->add(make_shared<ExamineContentJob>(film2, dcp_content));
BOOST_REQUIRE(!wait_for_jobs());
diff --git a/test/remake_video_test.cc b/test/remake_video_test.cc
index a07659844..5ad9f1a5e 100644
--- a/test/remake_video_test.cc
+++ b/test/remake_video_test.cc
@@ -26,7 +26,7 @@
#include <dcp/colour_conversion.h>
#include <dcp/cpl.h>
#include <dcp/dcp.h>
-#include <dcp/mono_picture_asset_reader.h>
+#include <dcp/mono_j2k_picture_asset_reader.h>
#include <dcp/reel.h>
#include <dcp/reel_mono_picture_asset.h>
#include <boost/test/unit_test.hpp>
@@ -41,7 +41,7 @@ using std::vector;
BOOST_AUTO_TEST_CASE(remake_video_after_yub_rgb_matrix_changed)
{
auto content = content_factory("test/data/rgb_grey_testcard.mp4")[0];
- auto film = new_test_film2("remake_video_after_yub_rgb_matrix_changed", { content });
+ auto film = new_test_film("remake_video_after_yub_rgb_matrix_changed", { content });
auto conversion = content->video->colour_conversion();
BOOST_REQUIRE(static_cast<bool>(conversion));
@@ -58,7 +58,7 @@ BOOST_AUTO_TEST_CASE(remake_video_after_yub_rgb_matrix_changed)
BOOST_REQUIRE(!cpl->reels().empty());
auto reel = cpl->reels()[0];
BOOST_REQUIRE(reel->main_picture());
- auto mono = dynamic_pointer_cast<dcp::MonoPictureAsset>(reel->main_picture()->asset());
+ auto mono = dynamic_pointer_cast<dcp::MonoJ2KPictureAsset>(reel->main_picture()->asset());
BOOST_REQUIRE(mono);
auto reader = mono->start_read();
diff --git a/test/remake_with_subtitle_test.cc b/test/remake_with_subtitle_test.cc
index 57322d3ac..c46a4f70d 100644
--- a/test/remake_with_subtitle_test.cc
+++ b/test/remake_with_subtitle_test.cc
@@ -35,7 +35,8 @@ using std::dynamic_pointer_cast;
*/
BOOST_AUTO_TEST_CASE (remake_with_subtitle_test)
{
- auto film = new_test_film2 ("remake_with_subtitle_test");
+ auto film = new_test_film("remake_with_subtitle_test");
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, 100000000);
auto content = dynamic_pointer_cast<FFmpegContent>(content_factory(TestPaths::private_data() / "prophet_short_clip.mkv")[0]);
film->examine_and_add_content (content);
BOOST_REQUIRE (!wait_for_jobs ());
@@ -48,5 +49,9 @@ BOOST_AUTO_TEST_CASE (remake_with_subtitle_test)
content->only_text()->set_use (false);
make_and_verify_dcp (film);
- check_one_frame (film->dir(film->dcp_name()), 325, TestPaths::private_data() / "prophet_frame_325_no_subs.j2c");
+#ifdef DCPOMATIC_OSX
+ check_one_frame(film->dir(film->dcp_name()), 325, TestPaths::private_data() / "v2.18.x" / "prophet_frame_325_no_subs_mac.j2c");
+#else
+ check_one_frame(film->dir(film->dcp_name()), 325, TestPaths::private_data() / "v2.18.x" / "prophet_frame_325_no_subs.j2c");
+#endif
}
diff --git a/test/repeat_frame_test.cc b/test/repeat_frame_test.cc
index 0d0f38616..3baed100d 100644
--- a/test/repeat_frame_test.cc
+++ b/test/repeat_frame_test.cc
@@ -43,7 +43,7 @@ using std::make_shared;
BOOST_AUTO_TEST_CASE (repeat_frame_test)
{
auto c = make_shared<FFmpegContent>("test/data/red_24.mp4");
- auto film = new_test_film2 ("repeat_frame_test", {c});
+ auto film = new_test_film("repeat_frame_test", {c});
film->set_interop (false);
c->video->set_custom_ratio (1.85);
diff --git a/test/required_disk_space_test.cc b/test/required_disk_space_test.cc
index b704ccef2..4148126cf 100644
--- a/test/required_disk_space_test.cc
+++ b/test/required_disk_space_test.cc
@@ -44,17 +44,12 @@ void check_within_n (int64_t a, int64_t b, int64_t n)
BOOST_AUTO_TEST_CASE (required_disk_space_test)
{
- auto film = new_test_film ("required_disk_space_test");
- film->set_j2k_bandwidth (100000000);
- film->set_audio_channels(8);
- film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
auto content_a = content_factory("test/data/flat_blue.png")[0];
- BOOST_REQUIRE (content_a);
- film->examine_and_add_content (content_a);
auto content_b = make_shared<DCPContent>("test/data/burnt_subtitle_test_dcp");
- film->examine_and_add_content (content_b);
- BOOST_REQUIRE (!wait_for_jobs());
- film->write_metadata ();
+ auto film = new_test_film("required_disk_space_test", { content_a, content_b });
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, 100000000);
+ film->set_audio_channels(8);
+ film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
check_within_n (
film->required_disk_space(),
diff --git a/test/scaling_test.cc b/test/scaling_test.cc
index 2d453600e..69e81cc69 100644
--- a/test/scaling_test.cc
+++ b/test/scaling_test.cc
@@ -73,15 +73,9 @@ static void scaling_test_for (shared_ptr<Film> film, shared_ptr<Content> content
BOOST_AUTO_TEST_CASE (scaling_test)
{
- auto film = new_test_film ("scaling_test");
- film->set_dcp_content_type (DCPContentType::from_isdcf_name("FTR"));
- film->set_name ("scaling_test");
auto imc = make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
-
- film->examine_and_add_content (imc);
-
- BOOST_REQUIRE (!wait_for_jobs());
-
+ auto film = new_test_film("scaling_test", { imc });
+ film->set_dcp_content_type(DCPContentType::from_isdcf_name("FTR"));
imc->video->set_length (1);
/* F-133: 133 image in a flat container */
@@ -103,7 +97,7 @@ BOOST_AUTO_TEST_CASE (scaling_test)
BOOST_AUTO_TEST_CASE(assertion_failure_when_scaling)
{
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2("assertion_failure_when_scaling", content);
+ auto film = new_test_film("assertion_failure_when_scaling", content);
content[0]->video->set_custom_size(dcp::Size{3996, 2180});
film->set_resolution(Resolution::FOUR_K);
diff --git a/test/shuffler_test.cc b/test/shuffler_test.cc
index 2099a0923..37d6749c3 100644
--- a/test/shuffler_test.cc
+++ b/test/shuffler_test.cc
@@ -33,14 +33,15 @@ using boost::optional;
#if BOOST_VERSION >= 106100
using namespace boost::placeholders;
#endif
+using namespace dcpomatic;
static void
-push (Shuffler& s, int frame, Eyes eyes)
+push(Shuffler& s, int frame, Eyes eyes)
{
auto piece = make_shared<Piece>(shared_ptr<Content>(), shared_ptr<Decoder>(), FrameRateChange(24, 24));
ContentVideo cv;
- cv.frame = frame;
+ cv.time = ContentTime::from_frames(frame, 24);
cv.eyes = eyes;
s.video (piece, cv);
}
@@ -56,8 +57,9 @@ receive (weak_ptr<Piece>, ContentVideo cv)
static void
check (int frame, Eyes eyes, int line)
{
+ auto const time = ContentTime::from_frames(frame, 24);
BOOST_REQUIRE_MESSAGE (!pending_cv.empty(), "Check at " << line << " failed.");
- BOOST_CHECK_MESSAGE (pending_cv.front().frame == frame, "Check at " << line << " failed.");
+ BOOST_CHECK_MESSAGE (pending_cv.front().time == time, "Check at " << line << " failed.");
BOOST_CHECK_MESSAGE (pending_cv.front().eyes == eyes, "Check at " << line << " failed.");
pending_cv.pop_front();
}
diff --git a/test/silence_padding_test.cc b/test/silence_padding_test.cc
index 86db117c6..a927510fb 100644
--- a/test/silence_padding_test.cc
+++ b/test/silence_padding_test.cc
@@ -50,7 +50,7 @@ static void
test_silence_padding(int channels, dcp::Standard standard)
{
string const film_name = "silence_padding_test_" + lexical_cast<string> (channels);
- auto film = new_test_film2 (
+ auto film = new_test_film(
film_name,
{
make_shared<FFmpegContent>("test/data/flat_red.png"),
@@ -134,7 +134,7 @@ BOOST_AUTO_TEST_CASE (silence_padding_test2)
Cleanup cl;
auto content = make_shared<FFmpegContent>(TestPaths::private_data() / "cars.mov");
- auto film = new_test_film2 ("silence_padding_test2", { content }, &cl);
+ auto film = new_test_film("silence_padding_test2", { content }, &cl);
film->set_video_frame_rate (24);
content->set_trim_start(film, dcpomatic::ContentTime(4003));
diff --git a/test/skip_frame_test.cc b/test/skip_frame_test.cc
index 8df2fa1d5..6eafd064f 100644
--- a/test/skip_frame_test.cc
+++ b/test/skip_frame_test.cc
@@ -42,17 +42,8 @@ using std::make_shared;
BOOST_AUTO_TEST_CASE (skip_frame_test)
{
- auto film = new_test_film ("skip_frame_test");
- film->set_name ("skip_frame_test");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
- film->set_interop (false);
auto c = make_shared<FFmpegContent>("test/data/count300bd48.m2ts");
- film->examine_and_add_content (c);
-
- BOOST_REQUIRE (!wait_for_jobs());
-
- film->write_metadata ();
+ auto film = new_test_film("skip_frame_test", { c });
film->set_video_frame_rate (24);
make_and_verify_dcp (film);
diff --git a/test/srt_subtitle_test.cc b/test/srt_subtitle_test.cc
index bac0bedec..031749a8c 100644
--- a/test/srt_subtitle_test.cc
+++ b/test/srt_subtitle_test.cc
@@ -49,16 +49,11 @@ using namespace dcpomatic;
/** Make a very short DCP with a single subtitle from .srt with no specified fonts */
BOOST_AUTO_TEST_CASE (srt_subtitle_test)
{
- auto film = new_test_film ("srt_subtitle_test");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
- film->set_audio_channels (6);
- film->set_interop (false);
- film->set_audio_channels(16);
auto content = make_shared<StringTextFileContent>("test/data/subrip2.srt");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("srt_subtitle_test", { content });
+ film->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
+ film->set_name("frobozz");
+ film->set_audio_channels(16);
content->only_text()->set_use (true);
content->only_text()->set_burn (false);
@@ -79,15 +74,11 @@ BOOST_AUTO_TEST_CASE (srt_subtitle_test)
/** Same again but with a `font' specified */
BOOST_AUTO_TEST_CASE (srt_subtitle_test2)
{
- auto film = new_test_film ("srt_subtitle_test2");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
- film->set_audio_channels (6);
- film->set_interop (false);
auto content = make_shared<StringTextFileContent> ("test/data/subrip2.srt");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("srt_subtitle_test2", { content });
+ film->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
+ film->set_name("frobozz");
+ film->set_audio_channels (6);
content->only_text()->set_use (true);
content->only_text()->set_burn (false);
@@ -125,7 +116,7 @@ BOOST_AUTO_TEST_CASE (srt_subtitle_test3)
Cleanup cl;
auto content = make_shared<StringTextFileContent>(TestPaths::private_data() / "Ankoemmling_short.srt");
- auto film = new_test_film2 ("srt_subtitle_test3", { content }, &cl);
+ auto film = new_test_film("srt_subtitle_test3", { content }, &cl);
film->set_name ("frobozz");
film->set_interop (true);
@@ -146,16 +137,12 @@ BOOST_AUTO_TEST_CASE (srt_subtitle_test3)
/** Build a small DCP with no picture and a single subtitle overlaid onto it */
BOOST_AUTO_TEST_CASE (srt_subtitle_test4)
{
- auto film = new_test_film ("srt_subtitle_test4");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
- film->set_interop (false);
auto content = make_shared<StringTextFileContent>("test/data/subrip2.srt");
+ auto film = new_test_film("srt_subtitle_test4", { content });
+ film->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
+ film->set_name("frobozz");
content->only_text()->set_use (true);
content->only_text()->set_burn (false);
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
make_and_verify_dcp (
film,
{
@@ -172,10 +159,9 @@ BOOST_AUTO_TEST_CASE (srt_subtitle_test4)
/** Check the subtitle XML when there are two subtitle files in the project */
BOOST_AUTO_TEST_CASE (srt_subtitle_test5)
{
- auto film = new_test_film ("srt_subtitle_test5");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
+ auto film = new_test_film("srt_subtitle_test5");
+ film->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
+ film->set_name("frobozz");
film->set_interop (true);
film->set_sequence (false);
film->set_audio_channels(6);
@@ -197,7 +183,7 @@ BOOST_AUTO_TEST_CASE (srt_subtitle_test5)
BOOST_AUTO_TEST_CASE (srt_subtitle_test6)
{
auto content = make_shared<StringTextFileContent>("test/data/frames.srt");
- auto film = new_test_film2 ("srt_subtitle_test6", {content});
+ auto film = new_test_film("srt_subtitle_test6", {content});
film->set_interop (false);
content->only_text()->set_use (true);
content->only_text()->set_burn (false);
@@ -229,7 +215,7 @@ BOOST_AUTO_TEST_CASE(srt_subtitle_entity)
srt.close();
auto content = make_shared<StringTextFileContent>("build/test/srt_subtitle_entity.srt");
- auto film = new_test_film2("srt_subtitle_entity", { content });
+ auto film = new_test_film("srt_subtitle_entity", { content });
film->set_interop(false);
content->only_text()->set_use(true);
content->only_text()->set_burn(false);
@@ -270,7 +256,7 @@ BOOST_AUTO_TEST_CASE(srt_subtitle_control_code)
srt.close();
auto content = make_shared<StringTextFileContent>("build/test/srt_subtitle_control_code.srt");
- auto film = new_test_film2("srt_subtitle_control_code", { content });
+ auto film = new_test_film("srt_subtitle_control_code", { content });
film->set_interop(false);
content->only_text()->set_use(true);
content->only_text()->set_burn(false);
@@ -292,8 +278,8 @@ BOOST_AUTO_TEST_CASE(srt_subtitle_control_code)
/** Test rendering of a SubRip subtitle */
BOOST_AUTO_TEST_CASE (srt_subtitle_test4)
{
- shared_ptr<Film> film = new_test_film ("subrip_render_test");
shared_ptr<StringTextFile> content (new StringTextFile("test/data/subrip.srt"));
+ shared_ptr<Film> film = new_test_film("subrip_render_test", { content });
content->examine (shared_ptr<Job> (), true);
BOOST_CHECK_EQUAL (content->full_length(), DCPTime::from_seconds ((3 * 60) + 56.471));
diff --git a/test/ssa_subtitle_test.cc b/test/ssa_subtitle_test.cc
index 2565fc035..ffd374d45 100644
--- a/test/ssa_subtitle_test.cc
+++ b/test/ssa_subtitle_test.cc
@@ -48,7 +48,7 @@ BOOST_AUTO_TEST_CASE (ssa_subtitle_test1)
{
Cleanup cl;
- auto film = new_test_film2 ("ssa_subtitle_test1", {}, &cl);
+ auto film = new_test_film("ssa_subtitle_test1", {}, &cl);
film->set_container (Ratio::from_id ("185"));
film->set_name ("frobozz");
diff --git a/test/stream_test.cc b/test/stream_test.cc
index a180739ab..ecd2deac8 100644
--- a/test/stream_test.cc
+++ b/test/stream_test.cc
@@ -43,38 +43,38 @@ BOOST_AUTO_TEST_CASE (stream_test)
{
xmlpp::Document doc;
auto root = doc.create_root_node("FFmpegAudioStream");
- root->add_child("Name")->add_child_text ("hello there world");
- root->add_child("Id")->add_child_text ("4");
- root->add_child("FrameRate")->add_child_text ("44100");
- root->add_child("Channels")->add_child_text ("2");
+ cxml::add_text_child(root, "Name", "hello there world");
+ cxml::add_text_child(root, "Id", "4");
+ cxml::add_text_child(root, "FrameRate", "44100");
+ cxml::add_text_child(root, "Channels", "2");
/* This is the state file version 5 description of the mapping */
- auto mapping = root->add_child("Mapping");
- mapping->add_child("ContentChannels")->add_child_text ("2");
+ auto mapping = cxml::add_child(root, "Mapping");
+ cxml::add_text_child(mapping, "ContentChannels", "2");
{
/* L -> L */
- auto map = mapping->add_child("Map");
- map->add_child("ContentIndex")->add_child_text ("0");
- map->add_child("DCP")->add_child_text ("0");
+ auto map = cxml::add_child(mapping, "Map");
+ cxml::add_text_child(map, "ContentIndex", "0");
+ cxml::add_text_child(map, "DCP", "0");
}
{
/* L -> C */
- auto map = mapping->add_child("Map");
- map->add_child("ContentIndex")->add_child_text ("0");
- map->add_child("DCP")->add_child_text ("2");
+ auto map = cxml::add_child(mapping, "Map");
+ cxml::add_text_child(map, "ContentIndex", "0");
+ cxml::add_text_child(map, "DCP", "2");
}
{
/* R -> R */
- auto map = mapping->add_child("Map");
- map->add_child("ContentIndex")->add_child_text ("1");
- map->add_child("DCP")->add_child_text ("1");
+ auto map = cxml::add_child(mapping, "Map");
+ cxml::add_text_child(map, "ContentIndex", "1");
+ cxml::add_text_child(map, "DCP", "1");
}
{
/* R -> C */
- auto map = mapping->add_child("Map");
- map->add_child("ContentIndex")->add_child_text ("1");
- map->add_child("DCP")->add_child_text ("2");
+ auto map = cxml::add_child(mapping, "Map");
+ cxml::add_text_child(map, "ContentIndex", "1");
+ cxml::add_text_child(map, "DCP", "2");
}
FFmpegAudioStream a (cxml::NodePtr (new cxml::Node (root)), 5);
diff --git a/test/subtitle_charset_test.cc b/test/subtitle_charset_test.cc
index 84f249c65..f4324a646 100644
--- a/test/subtitle_charset_test.cc
+++ b/test/subtitle_charset_test.cc
@@ -35,7 +35,7 @@ using std::dynamic_pointer_cast;
BOOST_AUTO_TEST_CASE (subtitle_charset_test1)
{
auto content = content_factory(TestPaths::private_data() / "PADDINGTON soustitresVFdef.srt");
- auto film = new_test_film2 ("subtitle_charset_test1", content);
+ auto film = new_test_film("subtitle_charset_test1", content);
}
@@ -43,7 +43,7 @@ BOOST_AUTO_TEST_CASE (subtitle_charset_test1)
BOOST_AUTO_TEST_CASE (subtitle_charset_test2)
{
auto content = content_factory("test/data/osx.srt");
- auto film = new_test_film2 ("subtitle_charset_test2", content);
+ auto film = new_test_film("subtitle_charset_test2", content);
auto ts = dynamic_pointer_cast<StringTextFileContent>(content[0]);
BOOST_REQUIRE (ts);
/* Make sure we got the subtitle data from the file */
diff --git a/test/subtitle_font_id_change_test.cc b/test/subtitle_font_id_change_test.cc
index 385dd1aae..17d732772 100644
--- a/test/subtitle_font_id_change_test.cc
+++ b/test/subtitle_font_id_change_test.cc
@@ -40,7 +40,7 @@ using std::string;
BOOST_AUTO_TEST_CASE(subtitle_font_id_change_test1)
{
- auto film = new_test_film2("subtitle_font_id_change_test1");
+ auto film = new_test_film("subtitle_font_id_change_test1");
boost::filesystem::remove(film->file("metadata.xml"));
boost::filesystem::copy_file("test/data/subtitle_font_id_change_test1.xml", film->file("metadata.xml"));
film->read_metadata();
@@ -62,7 +62,7 @@ BOOST_AUTO_TEST_CASE(subtitle_font_id_change_test1)
BOOST_AUTO_TEST_CASE(subtitle_font_id_change_test2)
{
- auto film = new_test_film2("subtitle_font_id_change_test2");
+ auto film = new_test_film("subtitle_font_id_change_test2");
boost::filesystem::remove(film->file("metadata.xml"));
boost::filesystem::copy_file("test/data/subtitle_font_id_change_test2.xml", film->file("metadata.xml"));
{
@@ -95,7 +95,9 @@ BOOST_AUTO_TEST_CASE(subtitle_font_id_change_test2)
BOOST_AUTO_TEST_CASE(subtitle_font_id_change_test3)
{
- auto film = new_test_film2("subtitle_font_id_change_test3");
+ Cleanup cl;
+
+ auto film = new_test_film("subtitle_font_id_change_test3", {}, &cl);
boost::filesystem::remove(film->file("metadata.xml"));
boost::filesystem::copy_file("test/data/subtitle_font_id_change_test3.xml", film->file("metadata.xml"));
{
@@ -126,6 +128,8 @@ BOOST_AUTO_TEST_CASE(subtitle_font_id_change_test3)
BOOST_CHECK_EQUAL(*font->file(), "test/data/Inconsolata-VF.ttf");
make_and_verify_dcp(film, { dcp::VerificationNote::Code::INVALID_STANDARD });
+
+ cl.run();
}
@@ -133,7 +137,7 @@ BOOST_AUTO_TEST_CASE(subtitle_font_id_change_test4)
{
Cleanup cl;
- auto film = new_test_film2("subtitle_font_id_change_test4", {}, &cl);
+ auto film = new_test_film("subtitle_font_id_change_test4", {}, &cl);
boost::filesystem::remove(film->file("metadata.xml"));
boost::filesystem::copy_file("test/data/subtitle_font_id_change_test4.xml", film->file("metadata.xml"));
diff --git a/test/subtitle_font_id_test.cc b/test/subtitle_font_id_test.cc
index 12d804d20..9a901727b 100644
--- a/test/subtitle_font_id_test.cc
+++ b/test/subtitle_font_id_test.cc
@@ -19,6 +19,7 @@
*/
+#include "lib/check_content_job.h"
#include "lib/content_factory.h"
#include "lib/dcp_content.h"
#include "lib/film.h"
@@ -41,7 +42,7 @@ using std::make_shared;
BOOST_AUTO_TEST_CASE(full_dcp_subtitle_font_id_test)
{
auto dcp = make_shared<DCPContent>(TestPaths::private_data() / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV");
- auto film = new_test_film2("full_dcp_subtitle_font_id_test", { dcp });
+ auto film = new_test_film("full_dcp_subtitle_font_id_test", { dcp });
auto content = film->content();
BOOST_REQUIRE_EQUAL(content.size(), 1U);
@@ -50,7 +51,7 @@ BOOST_AUTO_TEST_CASE(full_dcp_subtitle_font_id_test)
BOOST_REQUIRE_EQUAL(text->fonts().size(), 1U);
auto font = text->fonts().front();
- BOOST_CHECK_EQUAL(font->id(), "0_theFontId");
+ BOOST_CHECK_EQUAL(font->id(), "theFontId");
BOOST_REQUIRE(font->data());
BOOST_CHECK_EQUAL(font->data()->size(), 367112);
}
@@ -59,7 +60,7 @@ BOOST_AUTO_TEST_CASE(full_dcp_subtitle_font_id_test)
BOOST_AUTO_TEST_CASE(dcp_subtitle_font_id_test)
{
auto subs = content_factory(TestPaths::private_data() / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV" / "8b48f6ae-c74b-4b80-b994-a8236bbbad74_sub.mxf");
- auto film = new_test_film2("dcp_subtitle_font_id_test", subs);
+ auto film = new_test_film("dcp_subtitle_font_id_test", subs);
auto content = film->content();
BOOST_REQUIRE_EQUAL(content.size(), 1U);
@@ -68,7 +69,7 @@ BOOST_AUTO_TEST_CASE(dcp_subtitle_font_id_test)
BOOST_REQUIRE_EQUAL(text->fonts().size(), 1U);
auto font = text->fonts().front();
- BOOST_CHECK_EQUAL(font->id(), "0_theFontId");
+ BOOST_CHECK_EQUAL(font->id(), "theFontId");
BOOST_REQUIRE(font->data());
BOOST_CHECK_EQUAL(font->data()->size(), 367112);
}
@@ -77,7 +78,7 @@ BOOST_AUTO_TEST_CASE(dcp_subtitle_font_id_test)
BOOST_AUTO_TEST_CASE(make_dcp_with_subs_from_interop_dcp)
{
auto dcp = make_shared<DCPContent>("test/data/Iopsubs_FTR-1_F_XX-XX_MOS_2K_20220710_IOP_OV");
- auto film = new_test_film2("make_dcp_with_subs_from_interop_dcp", { dcp });
+ auto film = new_test_film("make_dcp_with_subs_from_interop_dcp", { dcp });
dcp->text.front()->set_use(true);
make_and_verify_dcp(
film,
@@ -94,7 +95,7 @@ BOOST_AUTO_TEST_CASE(make_dcp_with_subs_from_smpte_dcp)
Cleanup cl;
auto dcp = make_shared<DCPContent>(TestPaths::private_data() / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV");
- auto film = new_test_film2("make_dcp_with_subs_from_smpte_dcp", { dcp }, &cl);
+ auto film = new_test_film("make_dcp_with_subs_from_smpte_dcp", { dcp }, &cl);
dcp->text.front()->set_use(true);
make_and_verify_dcp(film);
@@ -105,7 +106,7 @@ BOOST_AUTO_TEST_CASE(make_dcp_with_subs_from_smpte_dcp)
BOOST_AUTO_TEST_CASE(make_dcp_with_subs_from_mkv)
{
auto subs = content_factory(TestPaths::private_data() / "clapperboard_with_subs.mkv");
- auto film = new_test_film2("make_dcp_with_subs_from_mkv", subs);
+ auto film = new_test_film("make_dcp_with_subs_from_mkv", subs);
subs[0]->text.front()->set_use(true);
subs[0]->text.front()->set_language(dcp::LanguageTag("en"));
make_and_verify_dcp(film, { dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K });
@@ -115,7 +116,7 @@ BOOST_AUTO_TEST_CASE(make_dcp_with_subs_from_mkv)
BOOST_AUTO_TEST_CASE(make_dcp_with_subs_without_font_tag)
{
auto subs = content_factory("test/data/no_font.xml");
- auto film = new_test_film2("make_dcp_with_subs_without_font_tag", { subs });
+ auto film = new_test_film("make_dcp_with_subs_without_font_tag", { subs });
subs[0]->text.front()->set_use(true);
subs[0]->text.front()->set_language(dcp::LanguageTag("de"));
make_and_verify_dcp(
@@ -139,7 +140,7 @@ BOOST_AUTO_TEST_CASE(make_dcp_with_subs_in_dcp_without_font_tag)
{
/* Make a DCP with some subs in */
auto source_subs = content_factory("test/data/short.srt");
- auto source = new_test_film2("make_dcp_with_subs_in_dcp_without_font_tag_source", { source_subs });
+ auto source = new_test_film("make_dcp_with_subs_in_dcp_without_font_tag_source", { source_subs });
source->set_interop(true);
source_subs[0]->only_text()->set_language(dcp::LanguageTag("de"));
make_and_verify_dcp(
@@ -175,7 +176,7 @@ BOOST_AUTO_TEST_CASE(make_dcp_with_subs_in_dcp_without_font_tag)
/* Now make a project which imports that DCP and makes another DCP from it */
auto dcp_content = make_shared<DCPContent>(source->dir(source->dcp_name()));
- auto film = new_test_film2("make_dcp_with_subs_without_font_tag", { dcp_content });
+ auto film = new_test_film("make_dcp_with_subs_without_font_tag", { dcp_content });
BOOST_REQUIRE(!dcp_content->text.empty());
dcp_content->text.front()->set_use(true);
make_and_verify_dcp(
@@ -203,7 +204,7 @@ BOOST_AUTO_TEST_CASE(filler_subtitle_reels_have_load_font_tags)
auto video1 = content_factory("test/data/flat_red.png")[0];
auto video2 = content_factory("test/data/flat_red.png")[0];
- auto film = new_test_film2(name, { video1, video2, subs });
+ auto film = new_test_film(name, { video1, video2, subs });
film->set_reel_type(ReelType::BY_VIDEO_CONTENT);
make_and_verify_dcp(
@@ -225,7 +226,7 @@ BOOST_AUTO_TEST_CASE(subtitle_with_no_font_test)
auto video2 = content_factory("test/data/flat_red.png")[0];
auto subs = content_factory("test/data/short.srt")[0];
- auto bad_film = new_test_film2(name_base + "_bad", { video1, video2, subs });
+ auto bad_film = new_test_film(name_base + "_bad", { video1, video2, subs });
bad_film->set_reel_type(ReelType::BY_VIDEO_CONTENT);
video2->set_position(bad_film, video1->end(bad_film));
subs->set_position(bad_film, video1->end(bad_film));
@@ -258,7 +259,7 @@ BOOST_AUTO_TEST_CASE(subtitle_with_no_font_test)
BOOST_REQUIRE_EQUAL(check_subs->subtitles().size(), 1U);
BOOST_CHECK(!std::dynamic_pointer_cast<const dcp::SubtitleString>(check_subs->subtitles()[0])->font().has_value());
- auto check_film = new_test_film2(name_base + "_check", { make_shared<DCPContent>(bad_film->dir(bad_film->dcp_name())) });
+ auto check_film = new_test_film(name_base + "_check", { make_shared<DCPContent>(bad_film->dir(bad_film->dcp_name())) });
make_and_verify_dcp(check_film);
}
@@ -266,14 +267,14 @@ BOOST_AUTO_TEST_CASE(subtitle_with_no_font_test)
BOOST_AUTO_TEST_CASE(load_dcp_with_empty_font_id_test)
{
auto dcp = std::make_shared<DCPContent>(TestPaths::private_data() / "kr_vf");
- auto film = new_test_film2("load_dcp_with_empty_font_id_test", { dcp });
+ auto film = new_test_film("load_dcp_with_empty_font_id_test", { dcp });
}
BOOST_AUTO_TEST_CASE(use_first_loadfont_as_default)
{
auto dcp = std::make_shared<DCPContent>("test/data/use_default_font");
- auto film = new_test_film2("use_first_loadfont_as_default", { dcp });
+ auto film = new_test_film("use_first_loadfont_as_default", { dcp });
dcp->only_text()->set_use(true);
dcp->only_text()->set_language(dcp::LanguageTag("de"));
make_and_verify_dcp(
@@ -297,22 +298,52 @@ BOOST_AUTO_TEST_CASE(use_first_loadfont_as_default)
BOOST_AUTO_TEST_CASE(no_error_with_ccap_that_mentions_no_font)
{
auto dcp = make_shared<DCPContent>("test/data/ccap_only");
- auto film = new_test_film2("no_error_with_ccap_that_mentions_no_font", { dcp });
+ auto film = new_test_film("no_error_with_ccap_that_mentions_no_font", { dcp });
auto player = Player(film, film->playlist());
while (!player.pass()) {}
}
+BOOST_AUTO_TEST_CASE(subtitle_font_ids_survive_project_save)
+{
+ std::string const name = "subtitle_font_ids_survive_project_save";
+
+ auto subs = content_factory("test/data/short.srt")[0];
+ auto film = new_test_film(name + "_film", { subs });
+ film->set_interop(false);
+ make_and_verify_dcp(
+ film,
+ {
+ dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE,
+ dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME,
+ dcp::VerificationNote::Code::MISSING_CPL_METADATA
+ });
+
+ auto dcp = std::make_shared<DCPContent>(film->dir(film->dcp_name()));
+ auto film2 = new_test_film(name + "_film2", { dcp });
+ film2->write_metadata();
+
+ auto film3 = std::make_shared<Film>(film2->dir("."));
+ film3->read_metadata();
+ BOOST_REQUIRE(!film3->content().empty());
+ auto check_dcp = std::dynamic_pointer_cast<DCPContent>(film3->content()[0]);
+ BOOST_REQUIRE(check_dcp);
+
+ check_dcp->check_font_ids();
+}
+
+
BOOST_AUTO_TEST_CASE(cope_with_unloaded_font_id)
{
/* This file has a <Font> with an ID that corresponds to no <LoadFont> */
auto subs = content_factory("test/data/unloaded_font.xml")[0];
- auto film = new_test_film2("cope_with_unloaded_font_id", { subs });
+ auto film = new_test_film("cope_with_unloaded_font_id", { subs });
+
make_and_verify_dcp(
film,
{
dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE,
- dcp::VerificationNote::Code::MISSING_CPL_METADATA,
+ dcp::VerificationNote::Code::MISSING_CPL_METADATA
});
}
diff --git a/test/subtitle_language_test.cc b/test/subtitle_language_test.cc
index 6ae0647e9..a1a45eb42 100644
--- a/test/subtitle_language_test.cc
+++ b/test/subtitle_language_test.cc
@@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE (subtitle_language_interop_test)
{
string const name = "subtitle_language_interop_test";
auto fr = content_factory("test/data/frames.srt");
- auto film = new_test_film2 (name, fr);
+ auto film = new_test_film(name, fr);
fr[0]->only_text()->set_language(dcp::LanguageTag("fr"));
film->set_interop (true);
@@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE (subtitle_language_smpte_test)
{
string const name = "subtitle_language_smpte_test";
auto fr = content_factory("test/data/frames.srt");
- auto film = new_test_film2 (name, fr);
+ auto film = new_test_film(name, fr);
fr[0]->only_text()->set_language(dcp::LanguageTag("fr"));
film->set_interop (false);
@@ -96,7 +96,7 @@ BOOST_AUTO_TEST_CASE(subtitle_language_in_cpl_test)
auto subs = content_factory("test/data/frames.srt")[0];
auto video1 = content_factory("test/data/flat_red.png")[0];
auto video2 = content_factory("test/data/flat_red.png")[0];
- auto film = new_test_film2(boost::unit_test::framework::current_test_unit().full_name(), { subs, video1, video2 });
+ auto film = new_test_film(boost::unit_test::framework::current_test_unit().full_name(), { subs, video1, video2 });
video2->set_position(film, dcpomatic::DCPTime::from_seconds(5));
film->set_reel_type(ReelType::BY_VIDEO_CONTENT);
subs->only_text()->set_language(dcp::LanguageTag("fr"));
diff --git a/test/subtitle_position_test.cc b/test/subtitle_position_test.cc
index d9dd0c61a..9c71b0d70 100644
--- a/test/subtitle_position_test.cc
+++ b/test/subtitle_position_test.cc
@@ -41,7 +41,7 @@ BOOST_AUTO_TEST_CASE(srt_correctly_placed_in_interop)
{
string const name = "srt_in_interop_position_test";
auto fr = content_factory("test/data/short.srt");
- auto film = new_test_film2(name, fr);
+ auto film = new_test_film(name, fr);
fr[0]->only_text()->set_language(dcp::LanguageTag("de"));
film->set_interop(true);
@@ -69,7 +69,7 @@ BOOST_AUTO_TEST_CASE(srt_correctly_placed_in_smpte)
{
string const name = "srt_in_smpte_position_test";
auto fr = content_factory("test/data/short.srt");
- auto film = new_test_film2(name, fr);
+ auto film = new_test_film(name, fr);
fr[0]->text[0]->set_language(dcp::LanguageTag("en"));
film->set_interop(false);
@@ -114,7 +114,7 @@ vpos_test(dcp::VAlign reference, float position, dcp::SubtitleStandard from, dcp
auto name = String::compose("vpos_test_%1_%2", standard, valign_to_string(reference));
auto in = content_factory(String::compose("test/data/%1.xml", name));
- auto film = new_test_film2(name, in);
+ auto film = new_test_film(name, in);
film->set_interop(to == dcp::Standard::INTEROP);
diff --git a/test/subtitle_reel_number_test.cc b/test/subtitle_reel_number_test.cc
index a48057577..0648ec477 100644
--- a/test/subtitle_reel_number_test.cc
+++ b/test/subtitle_reel_number_test.cc
@@ -42,19 +42,17 @@ using std::string;
/* Check that ReelNumber is setup correctly when making multi-reel subtitled DCPs */
BOOST_AUTO_TEST_CASE (subtitle_reel_number_test)
{
- auto film = new_test_film ("subtitle_reel_number_test");
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- film->set_name ("frobozz");
+ Cleanup cl;
+
auto content = make_shared<StringTextFileContent>("test/data/subrip5.srt");
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs ());
+ auto film = new_test_film("subtitle_reel_number_test", { content }, &cl);
content->only_text()->set_use (true);
content->only_text()->set_burn (false);
content->only_text()->set_language(dcp::LanguageTag("de"));
film->set_reel_type (ReelType::BY_LENGTH);
film->set_interop (true);
film->set_reel_length (1024 * 1024 * 512);
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, 100000000);
make_and_verify_dcp (film, {dcp::VerificationNote::Code::INVALID_STANDARD});
dcp::DCP dcp ("build/test/subtitle_reel_number_test/" + film->dcp_name());
@@ -72,4 +70,6 @@ BOOST_AUTO_TEST_CASE (subtitle_reel_number_test)
++n;
}
}
+
+ cl.run();
}
diff --git a/test/subtitle_reel_test.cc b/test/subtitle_reel_test.cc
index 147f5c523..8a1a8bece 100644
--- a/test/subtitle_reel_test.cc
+++ b/test/subtitle_reel_test.cc
@@ -43,7 +43,7 @@ using boost::optional;
/* Check that timings are done correctly for multi-reel DCPs with PNG subs */
BOOST_AUTO_TEST_CASE (subtitle_reel_test)
{
- auto film = new_test_film2 ("subtitle_reel_test");
+ auto film = new_test_film("subtitle_reel_test");
film->set_interop (true);
auto red_a = make_shared<ImageContent>("test/data/flat_red.png");
auto red_b = make_shared<ImageContent>("test/data/flat_red.png");
@@ -102,7 +102,7 @@ BOOST_AUTO_TEST_CASE (subtitle_reel_test)
*/
BOOST_AUTO_TEST_CASE (subtitle_in_all_reels_test)
{
- auto film = new_test_film2 ("subtitle_in_all_reels_test");
+ auto film = new_test_film("subtitle_in_all_reels_test");
film->set_interop (false);
film->set_sequence (false);
film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
@@ -141,7 +141,7 @@ BOOST_AUTO_TEST_CASE (subtitle_in_all_reels_test)
*/
BOOST_AUTO_TEST_CASE (closed_captions_in_all_reels_test)
{
- auto film = new_test_film2 ("closed_captions_in_all_reels_test");
+ auto film = new_test_film("closed_captions_in_all_reels_test");
film->set_interop (false);
film->set_sequence (false);
film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
@@ -199,7 +199,7 @@ BOOST_AUTO_TEST_CASE (closed_captions_in_all_reels_test)
BOOST_AUTO_TEST_CASE (subtitles_split_at_reel_boundaries)
{
- auto film = new_test_film2 ("subtitles_split_at_reel_boundaries");
+ auto film = new_test_film("subtitles_split_at_reel_boundaries");
film->set_interop (true);
film->set_sequence (false);
diff --git a/test/subtitle_timing_test.cc b/test/subtitle_timing_test.cc
index 8bb18f304..2e6b319cb 100644
--- a/test/subtitle_timing_test.cc
+++ b/test/subtitle_timing_test.cc
@@ -54,7 +54,8 @@ BOOST_AUTO_TEST_CASE (test_subtitle_timing_with_frame_rate_change)
auto sub = content_factory("test/data/hour.srt")[0];
sub->text.front()->set_language(dcp::LanguageTag("en"));
- auto film = new_test_film2(name, { picture, sub }, &cl);
+ auto film = new_test_film(name, { picture, sub }, &cl);
+ film->set_video_bit_rate(VideoEncoding::JPEG2000, 10000000);
picture->set_video_frame_rate(film, content_frame_rate);
auto const dcp_frame_rate = film->video_frame_rate();
@@ -90,7 +91,7 @@ BOOST_AUTO_TEST_CASE(dvb_subtitles_replace_the_last)
*/
auto content = content_factory(TestPaths::private_data() / "roh.mkv");
BOOST_REQUIRE(!content.empty());
- auto film = new_test_film2("dvb_subtitles_replace_the_last", { content[0] });
+ auto film = new_test_film("dvb_subtitles_replace_the_last", { content[0] });
FFmpegDecoder decoder(film, dynamic_pointer_cast<FFmpegContent>(content[0]), false);
BOOST_REQUIRE(!decoder.text.empty());
diff --git a/test/subtitle_trim_test.cc b/test/subtitle_trim_test.cc
index b9eccbb6f..752d14d9b 100644
--- a/test/subtitle_trim_test.cc
+++ b/test/subtitle_trim_test.cc
@@ -32,7 +32,7 @@ using std::make_shared;
BOOST_AUTO_TEST_CASE (subtitle_trim_test1)
{
auto content = make_shared<DCPSubtitleContent>("test/data/dcp_sub5.xml");
- auto film = new_test_film2 ("subtitle_trim_test1", {content});
+ auto film = new_test_film("subtitle_trim_test1", {content});
content->set_trim_end (dcpomatic::ContentTime::from_seconds(2));
film->write_metadata ();
diff --git a/test/template_test.cc b/test/template_test.cc
index 6af7751cb..064a070c1 100644
--- a/test/template_test.cc
+++ b/test/template_test.cc
@@ -27,15 +27,16 @@
#include <boost/test/unit_test.hpp>
+using std::string;
+
+
/* Bug #2491 */
BOOST_AUTO_TEST_CASE(template_wrong_channel_counts)
{
- ConfigRestorer cr;
-
- Config::override_path = "test/data";
+ ConfigRestorer cr("test/data");
- auto film = new_test_film2("template_wrong_channel_counts", {});
- film->use_template("Bug");
+ auto film = new_test_film("template_wrong_channel_counts", {});
+ film->use_template(string("Bug"));
auto mono = content_factory("test/data/C.wav").front();
film->examine_and_add_content(mono);
diff --git a/test/test.cc b/test/test.cc
index fc5d9dc83..acbc97ea1 100644
--- a/test/test.cc
+++ b/test/test.cc
@@ -47,8 +47,8 @@
#include <dcp/dcp.h>
#include <dcp/equality_options.h>
#include <dcp/filesystem.h>
-#include <dcp/mono_picture_asset.h>
-#include <dcp/mono_picture_frame.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/mono_j2k_picture_frame.h>
#include <dcp/openjpeg_image.h>
#include <dcp/reel.h>
#include <dcp/reel_picture_asset.h>
@@ -120,12 +120,8 @@ setup_test_config ()
Config::instance()->set_master_encoding_threads (boost::thread::hardware_concurrency() / 2);
Config::instance()->set_server_encoding_threads (1);
Config::instance()->set_server_port_base (61921);
- Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
Config::instance()->set_default_audio_delay (0);
- Config::instance()->set_default_j2k_bandwidth (100000000);
- Config::instance()->set_default_interop (false);
Config::instance()->set_default_still_length (10);
- Config::instance()->set_default_dcp_audio_channels(8);
Config::instance()->set_log_types (
LogEntry::TYPE_GENERAL | LogEntry::TYPE_WARNING |
LogEntry::TYPE_ERROR | LogEntry::TYPE_DISK
@@ -138,7 +134,8 @@ setup_test_config ()
decryption->set_key(dcp::file_to_string("test/data/decryption_key"));
Config::instance()->set_decryption_chain (decryption);
Config::instance()->set_dcp_asset_filename_format(dcp::NameFormat("%t"));
- Config::instance()->set_cinemas_file("test/data/empty_cinemas.xml");
+ Config::instance()->set_cinemas_file("build/test/cinemas.sqlite3");
+ Config::instance()->set_dkdm_recipients_file("build/test/dkdm_recipients.sqlite3");
}
@@ -163,11 +160,21 @@ struct TestConfig
setup_test_config ();
capture_ffmpeg_logs();
- EncodeServerFinder::instance()->stop ();
+ EncodeServerFinder::drop();
signal_manager = new TestSignalManager ();
dcpomatic_log.reset (new FileLog("build/test/log"));
+
+ auto const& suite = boost::unit_test::framework::master_test_suite();
+ int types = LogEntry::TYPE_GENERAL | LogEntry::TYPE_WARNING | LogEntry::TYPE_ERROR;
+ for (int i = 1; i < suite.argc; ++i) {
+ if (string(suite.argv[i]) == "--log=debug-player") {
+ types |= LogEntry::TYPE_DEBUG_PLAYER;
+ }
+ }
+
+ dcpomatic_log->set_types(types);
}
~TestConfig ()
@@ -192,21 +199,7 @@ test_film_dir (string name)
shared_ptr<Film>
-new_test_film (string name)
-{
- auto p = test_film_dir (name);
- if (boost::filesystem::exists (p)) {
- boost::filesystem::remove_all (p);
- }
-
- auto film = make_shared<Film>(p);
- film->write_metadata ();
- return film;
-}
-
-
-shared_ptr<Film>
-new_test_film2 (string name, vector<shared_ptr<Content>> content, Cleanup* cleanup)
+new_test_film(string name, vector<shared_ptr<Content>> content, Cleanup* cleanup)
{
auto p = test_film_dir (name);
if (boost::filesystem::exists (p)) {
@@ -217,6 +210,7 @@ new_test_film2 (string name, vector<shared_ptr<Content>> content, Cleanup* clean
}
auto film = make_shared<Film>(p);
+ film->use_template({});
film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
film->set_container (Ratio::from_id ("185"));
film->write_metadata ();
@@ -277,13 +271,14 @@ check_wav_file (boost::filesystem::path ref, boost::filesystem::path check)
void
check_mxf_audio_file (boost::filesystem::path ref, boost::filesystem::path check)
{
- ASDCP::PCM::MXFReader ref_reader;
+ Kumu::FileReaderFactory factory;
+ ASDCP::PCM::MXFReader ref_reader(factory);
BOOST_REQUIRE (!ASDCP_FAILURE (ref_reader.OpenRead (ref.string().c_str())));
ASDCP::PCM::AudioDescriptor ref_desc;
BOOST_REQUIRE (!ASDCP_FAILURE (ref_reader.FillAudioDescriptor (ref_desc)));
- ASDCP::PCM::MXFReader check_reader;
+ ASDCP::PCM::MXFReader check_reader(factory);
BOOST_REQUIRE (!ASDCP_FAILURE (check_reader.OpenRead (check.string().c_str())));
ASDCP::PCM::AudioDescriptor check_desc;
@@ -306,13 +301,14 @@ check_mxf_audio_file (boost::filesystem::path ref, boost::filesystem::path check
bool
mxf_atmos_files_same (boost::filesystem::path ref, boost::filesystem::path check, bool verbose)
{
- ASDCP::ATMOS::MXFReader ref_reader;
+ Kumu::FileReaderFactory factory;
+ ASDCP::ATMOS::MXFReader ref_reader(factory);
BOOST_REQUIRE (!ASDCP_FAILURE(ref_reader.OpenRead(ref.string().c_str())));
ASDCP::ATMOS::AtmosDescriptor ref_desc;
BOOST_REQUIRE (!ASDCP_FAILURE(ref_reader.FillAtmosDescriptor(ref_desc)));
- ASDCP::ATMOS::MXFReader check_reader;
+ ASDCP::ATMOS::MXFReader check_reader(factory);
BOOST_REQUIRE (!ASDCP_FAILURE(check_reader.OpenRead(check.string().c_str())));
ASDCP::ATMOS::AtmosDescriptor check_desc;
@@ -574,7 +570,13 @@ check_dcp(boost::filesystem::path ref, boost::filesystem::path check, bool sound
}
void
-check_xml (xmlpp::Element* ref, xmlpp::Element* test, list<string> ignore)
+check_xml(
+ xmlpp::Element* ref,
+ xmlpp::Element* test,
+ list<Glib::ustring> ignore,
+ optional<boost::filesystem::path> ref_file,
+ optional<boost::filesystem::path> test_file
+ )
{
BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
@@ -585,9 +587,13 @@ check_xml (xmlpp::Element* ref, xmlpp::Element* test, list<string> ignore)
auto ref_children = ref->get_children ();
auto test_children = test->get_children ();
+ string context;
+ if (ref_file && test_file) {
+ context = String::compose(" comparing %1 and %2", ref_file->string(), test_file->string());
+ }
BOOST_REQUIRE_MESSAGE (
ref_children.size() == test_children.size(),
- ref->get_name() << " has " << ref_children.size() << " or " << test_children.size() << " children"
+ ref->get_name() << " has " << ref_children.size() << " or " << test_children.size() << " children" << context
);
auto k = ref_children.begin ();
@@ -600,7 +606,7 @@ check_xml (xmlpp::Element* ref, xmlpp::Element* test, list<string> ignore)
auto test_el = dynamic_cast<xmlpp::Element*>(*l);
BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
if (ref_el && test_el) {
- check_xml (ref_el, test_el, ignore);
+ check_xml(ref_el, test_el, ignore, ref_file, test_file);
}
auto ref_cn = dynamic_cast<xmlpp::ContentNode*>(*k);
@@ -630,14 +636,14 @@ check_xml (xmlpp::Element* ref, xmlpp::Element* test, list<string> ignore)
}
void
-check_xml (boost::filesystem::path ref, boost::filesystem::path test, list<string> ignore)
+check_xml(boost::filesystem::path ref, boost::filesystem::path test, list<Glib::ustring> ignore)
{
auto ref_parser = new xmlpp::DomParser(ref.string());
auto ref_root = ref_parser->get_document()->get_root_node();
auto test_parser = new xmlpp::DomParser(test.string());
auto test_root = test_parser->get_document()->get_root_node();
- check_xml (ref_root, test_root, ignore);
+ check_xml(ref_root, test_root, ignore, ref, test);
}
bool
@@ -817,22 +823,25 @@ check_one_frame (boost::filesystem::path dcp_dir, int64_t index, boost::filesyst
{
dcp::DCP dcp (dcp_dir);
dcp.read ();
- auto asset = dynamic_pointer_cast<dcp::MonoPictureAsset> (dcp.cpls().front()->reels().front()->main_picture()->asset());
+ auto asset = dynamic_pointer_cast<dcp::MonoJ2KPictureAsset>(dcp.cpls().front()->reels().front()->main_picture()->asset());
BOOST_REQUIRE (asset);
auto frame = asset->start_read()->get_frame(index);
- auto ref_frame (new dcp::MonoPictureFrame (ref));
+ dcp::MonoJ2KPictureFrame ref_frame(ref);
auto image = frame->xyz_image ();
- auto ref_image = ref_frame->xyz_image ();
+ auto ref_image = ref_frame.xyz_image();
BOOST_REQUIRE (image->size() == ref_image->size());
int off = 0;
for (int y = 0; y < ref_image->size().height; ++y) {
for (int x = 0; x < ref_image->size().width; ++x) {
- BOOST_REQUIRE_EQUAL (ref_image->data(0)[off], image->data(0)[off]);
- BOOST_REQUIRE_EQUAL (ref_image->data(1)[off], image->data(1)[off]);
- BOOST_REQUIRE_EQUAL (ref_image->data(2)[off], image->data(2)[off]);
+ auto x_error = std::abs(ref_image->data(0)[off] - image->data(0)[off]);
+ BOOST_REQUIRE_MESSAGE(x_error == 0, "x component at " << x << "," << y << " differs by " << x_error);
+ auto y_error = std::abs(ref_image->data(1)[off] - image->data(1)[off]);
+ BOOST_REQUIRE_MESSAGE(y_error == 0, "y component at " << x << "," << y << " differs by " << y_error);
+ auto z_error = std::abs(ref_image->data(2)[off] - image->data(2)[off]);
+ BOOST_REQUIRE_MESSAGE(z_error == 0, "z component at " << x << "," << y << " differs by " << z_error);
++off;
}
}
@@ -950,10 +959,10 @@ void progress (float) {}
void
verify_dcp(boost::filesystem::path dir, vector<dcp::VerificationNote::Code> ignore)
{
- auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, TestPaths::xsd());
+ auto result = dcp::verify({dir}, {}, &stage, &progress, {}, TestPaths::xsd());
bool ok = true;
- for (auto i: notes) {
- if (find(ignore.begin(), ignore.end(), i.code()) == ignore.end()) {
+ for (auto i: result.notes) {
+ if (i.type() != dcp::VerificationNote::Type::OK && find(ignore.begin(), ignore.end(), i.code()) == ignore.end()) {
std::cout << "\t" << dcp::note_to_string(i) << "\n";
ok = false;
}
@@ -1027,8 +1036,16 @@ check_int_close (std::pair<int, int> a, std::pair<int, int> b, int d)
}
+ConfigRestorer::ConfigRestorer(boost::filesystem::path override_path)
+{
+ Config::override_path = override_path;
+ Config::drop();
+}
+
+
ConfigRestorer::~ConfigRestorer()
{
+ Config::override_path = boost::none;
setup_test_config();
}
diff --git a/test/test.h b/test/test.h
index 6687affea..73c77c98d 100644
--- a/test/test.h
+++ b/test/test.h
@@ -22,6 +22,7 @@
#include "lib/video_frame_type.h"
#include <dcp/types.h>
#include <dcp/verify.h>
+#include <glibmm.h>
#include <boost/filesystem.hpp>
#include <vector>
@@ -57,8 +58,7 @@ private:
extern bool wait_for_jobs ();
-extern std::shared_ptr<Film> new_test_film (std::string);
-extern std::shared_ptr<Film> new_test_film2 (std::string, std::vector<std::shared_ptr<Content>> content = {}, Cleanup* cleanup = nullptr);
+extern std::shared_ptr<Film> new_test_film(std::string, std::vector<std::shared_ptr<Content>> content = {}, Cleanup* cleanup = nullptr);
extern void check_dcp(boost::filesystem::path, boost::filesystem::path, bool sound_can_differ = false);
extern void check_dcp (boost::filesystem::path, std::shared_ptr<const Film>);
extern void check_file (boost::filesystem::path ref, boost::filesystem::path check);
@@ -66,7 +66,7 @@ extern void check_text_file (boost::filesystem::path ref, boost::filesystem::pat
extern void check_wav_file (boost::filesystem::path ref, boost::filesystem::path check);
extern void check_mxf_audio_file (boost::filesystem::path ref, boost::filesystem::path check);
extern bool mxf_atmos_files_same (boost::filesystem::path ref, boost::filesystem::path check, bool verbose = false);
-extern void check_xml (boost::filesystem::path, boost::filesystem::path, std::list<std::string>);
+extern void check_xml(boost::filesystem::path, boost::filesystem::path, std::list<Glib::ustring>);
extern void check_ffmpeg (boost::filesystem::path, boost::filesystem::path, int audio_tolerance);
extern void check_image (boost::filesystem::path, boost::filesystem::path, double threshold = 4);
extern boost::filesystem::path test_film_dir (std::string);
@@ -96,7 +96,14 @@ private:
class ConfigRestorer
{
public:
+ ConfigRestorer(boost::filesystem::path override_path);
+
+ ConfigRestorer() {}
+
~ConfigRestorer();
+
+private:
+ boost::optional<boost::filesystem::path> _old_path;
};
diff --git a/test/text_entry_point_test.cc b/test/text_entry_point_test.cc
new file mode 100644
index 000000000..b48111782
--- /dev/null
+++ b/test/text_entry_point_test.cc
@@ -0,0 +1,70 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "test.h"
+#include "lib/dcp_content.h"
+#include "lib/film.h"
+#include <dcp/cpl.h>
+#include <dcp/dcp.h>
+#include <dcp/reel.h>
+#include <dcp/reel_smpte_subtitle_asset.h>
+#include <dcp/smpte_subtitle_asset.h>
+#include <boost/test/unit_test.hpp>
+
+
+using std::make_shared;
+using std::string;
+
+
+BOOST_AUTO_TEST_CASE(test_text_entry_point)
+{
+ auto const path = boost::filesystem::path("build/test/test_text_entry_point");
+ boost::filesystem::remove_all(path);
+ boost::filesystem::create_directories(path);
+
+ /* Make a "bad" DCP with a non-zero text entry point */
+ dcp::DCP bad_dcp(path / "dcp");
+ auto sub = make_shared<dcp::SMPTESubtitleAsset>();
+ sub->write(path / "dcp" / "subs.mxf");
+ auto reel_sub = make_shared<dcp::ReelSMPTESubtitleAsset>(sub, dcp::Fraction{24, 1}, 42, 6);
+ auto reel = make_shared<dcp::Reel>();
+ reel->add(reel_sub);
+
+ auto cpl = make_shared<dcp::CPL>("foo", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
+ bad_dcp.add(cpl);
+ cpl->add(reel);
+
+ bad_dcp.write_xml();
+
+ /* Make a film and add the bad DCP, so that the examiner spots the problem */
+ auto dcp_content = make_shared<DCPContent>(path / "dcp");
+ auto film = new_test_film("test_text_entry_point/film", { dcp_content });
+ film->write_metadata();
+
+ /* Reload the film to check that the examiner's output is saved and recovered */
+ auto film2 = make_shared<Film>(path / "film");
+ film2->read_metadata();
+
+ string why_not;
+ BOOST_CHECK(!dcp_content->can_reference_text(film2, TextType::OPEN_SUBTITLE, why_not));
+ BOOST_CHECK_EQUAL(why_not, "one of its subtitle reels has a non-zero entry point so it must be re-written.");
+}
+
diff --git a/test/threed_test.cc b/test/threed_test.cc
index 78d74add4..fda883164 100644
--- a/test/threed_test.cc
+++ b/test/threed_test.cc
@@ -42,8 +42,8 @@
#include "lib/util.h"
#include "lib/video_content.h"
#include "test.h"
-#include <dcp/mono_picture_asset.h>
-#include <dcp/stereo_picture_asset.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/stereo_j2k_picture_asset.h>
#include <boost/test/unit_test.hpp>
#include <iostream>
@@ -56,11 +56,8 @@ using std::shared_ptr;
/** Basic sanity check of THREE_D_LEFT_RIGHT */
BOOST_AUTO_TEST_CASE (threed_test1)
{
- auto film = new_test_film ("threed_test1");
- film->set_name ("test_film1");
auto c = make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("threed_test1", { c });
c->video->set_frame_type (VideoFrameType::THREE_D_LEFT_RIGHT);
@@ -76,16 +73,10 @@ BOOST_AUTO_TEST_CASE (threed_test1)
*/
BOOST_AUTO_TEST_CASE (threed_test2)
{
- auto film = new_test_film ("threed_test2");
- film->set_name ("test_film2");
auto c = make_shared<FFmpegContent>("test/data/test.mp4");
- film->examine_and_add_content (c);
- BOOST_REQUIRE (!wait_for_jobs());
-
+ auto film = new_test_film("threed_test2", { c });
c->video->set_frame_type (VideoFrameType::THREE_D_ALTERNATE);
- film->set_container (Ratio::from_id ("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
film->set_three_d (true);
make_and_verify_dcp (film);
}
@@ -96,7 +87,7 @@ BOOST_AUTO_TEST_CASE (threed_test2)
*/
BOOST_AUTO_TEST_CASE (threed_test3)
{
- auto film = new_test_film2 ("threed_test3");
+ auto film = new_test_film("threed_test3");
auto L = make_shared<FFmpegContent>("test/data/test.mp4");
film->examine_and_add_content (L);
auto R = make_shared<FFmpegContent>("test/data/test.mp4");
@@ -118,7 +109,7 @@ BOOST_AUTO_TEST_CASE (threed_test4)
/* Try to stop out-of-memory crashes on my laptop */
Config::instance()->set_master_encoding_threads (boost::thread::hardware_concurrency() / 4);
- auto film = new_test_film2 ("threed_test4");
+ auto film = new_test_film("threed_test4");
auto L = make_shared<FFmpegContent>(TestPaths::private_data() / "LEFT_TEST_DCP3D4K.mov");
film->examine_and_add_content (L);
auto R = make_shared<FFmpegContent>(TestPaths::private_data() / "RIGHT_TEST_DCP3D4K.mov");
@@ -147,7 +138,7 @@ BOOST_AUTO_TEST_CASE (threed_test4)
BOOST_AUTO_TEST_CASE (threed_test5)
{
- auto film = new_test_film2 ("threed_test5");
+ auto film = new_test_film("threed_test5");
auto L = make_shared<FFmpegContent>(TestPaths::private_data() / "boon_telly.mkv");
film->examine_and_add_content (L);
auto R = make_shared<FFmpegContent>(TestPaths::private_data() / "boon_telly.mkv");
@@ -169,7 +160,7 @@ BOOST_AUTO_TEST_CASE (threed_test5)
BOOST_AUTO_TEST_CASE (threed_test6)
{
- auto film = new_test_film2 ("threed_test6");
+ auto film = new_test_film("threed_test6");
auto L = make_shared<FFmpegContent>("test/data/3dL.mp4");
film->examine_and_add_content (L);
auto R = make_shared<FFmpegContent>("test/data/3dR.mp4");
@@ -191,7 +182,7 @@ BOOST_AUTO_TEST_CASE (threed_test7)
{
using boost::filesystem::path;
- auto film = new_test_film2 ("threed_test7");
+ auto film = new_test_film("threed_test7");
path const content_path = "test/data/flat_red.png";
auto c = content_factory(content_path)[0];
film->examine_and_add_content (c);
@@ -234,7 +225,7 @@ BOOST_AUTO_TEST_CASE (threed_test7)
*/
BOOST_AUTO_TEST_CASE (threed_test_separate_files_slightly_different_lengths)
{
- auto film = new_test_film2("threed_test3");
+ auto film = new_test_film("threed_test3");
auto L = make_shared<FFmpegContent>("test/data/test.mp4");
film->examine_and_add_content (L);
auto R = make_shared<FFmpegContent>("test/data/test.mp4");
@@ -255,7 +246,7 @@ BOOST_AUTO_TEST_CASE (threed_test_separate_files_slightly_different_lengths)
*/
BOOST_AUTO_TEST_CASE (threed_test_separate_files_very_different_lengths)
{
- auto film = new_test_film2("threed_test3");
+ auto film = new_test_film("threed_test3");
auto L = make_shared<FFmpegContent>("test/data/test.mp4");
film->examine_and_add_content (L);
auto R = make_shared<FFmpegContent>("test/data/test.mp4");
@@ -273,7 +264,7 @@ BOOST_AUTO_TEST_CASE (threed_test_separate_files_very_different_lengths)
BOOST_AUTO_TEST_CASE (threed_test_butler_overfill)
{
- auto film = new_test_film2("threed_test_butler_overfill");
+ auto film = new_test_film("threed_test_butler_overfill");
auto A = make_shared<FFmpegContent>(TestPaths::private_data() / "arrietty_JP-EN.mkv");
film->examine_and_add_content(A);
auto B = make_shared<FFmpegContent>(TestPaths::private_data() / "arrietty_JP-EN.mkv");
@@ -318,7 +309,7 @@ BOOST_AUTO_TEST_CASE(threed_passthrough_test, * boost::unit_test::depends_on("th
BOOST_REQUIRE(input_dcp);
auto content = make_shared<DCPContent>(*input_dcp);
- auto film = new_test_film2("threed_passthrough_test", { content });
+ auto film = new_test_film("threed_passthrough_test", { content });
film->set_three_d(false);
make_and_verify_dcp(film);
@@ -330,10 +321,10 @@ BOOST_AUTO_TEST_CASE(threed_passthrough_test, * boost::unit_test::depends_on("th
BOOST_REQUIRE_EQUAL(matches.size(), 1U);
- auto stereo = dcp::StereoPictureAsset(matches[0]);
+ auto stereo = dcp::StereoJ2KPictureAsset(matches[0]);
auto stereo_reader = stereo.start_read();
- auto mono = dcp::MonoPictureAsset(dcp_file(film, "j2c"));
+ auto mono = dcp::MonoJ2KPictureAsset(dcp_file(film, "j2c"));
auto mono_reader = mono.start_read();
BOOST_REQUIRE_EQUAL(stereo.intrinsic_duration(), mono.intrinsic_duration());
@@ -352,7 +343,7 @@ BOOST_AUTO_TEST_CASE(threed_test_when_padding_needed)
auto left = content_factory("test/data/flat_red.png").front();
auto right = content_factory("test/data/flat_red.png").front();
auto sound = content_factory("test/data/sine_440.wav").front();
- auto film = new_test_film2("threed_test_when_padding_needed", { left, right, sound });
+ auto film = new_test_film("threed_test_when_padding_needed", { left, right, sound });
left->video->set_frame_type(VideoFrameType::THREE_D_LEFT);
left->set_position(film, dcpomatic::DCPTime());
diff --git a/test/time_calculation_test.cc b/test/time_calculation_test.cc
index 7ace7f8c2..0e09ae2c3 100644
--- a/test/time_calculation_test.cc
+++ b/test/time_calculation_test.cc
@@ -132,7 +132,7 @@ static string const xml = "<Content>"
BOOST_AUTO_TEST_CASE (ffmpeg_time_calculation_test)
{
- auto film = new_test_film ("ffmpeg_time_calculation_test");
+ auto film = new_test_film("ffmpeg_time_calculation_test");
auto doc = make_shared<cxml::Document>();
doc->read_string (xml);
@@ -186,7 +186,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_time_calculation_test)
/** Test Player::dcp_to_content_video */
BOOST_AUTO_TEST_CASE (player_time_calculation_test1)
{
- auto film = new_test_film ("player_time_calculation_test1");
+ auto film = new_test_film("player_time_calculation_test1");
auto doc = make_shared<cxml::Document>();
doc->read_string (xml);
@@ -392,7 +392,7 @@ BOOST_AUTO_TEST_CASE (player_time_calculation_test1)
/** Test Player::content_video_to_dcp */
BOOST_AUTO_TEST_CASE (player_time_calculation_test2)
{
- auto film = new_test_film ("player_time_calculation_test2");
+ auto film = new_test_film("player_time_calculation_test2");
auto doc = make_shared<cxml::Document>();
doc->read_string (xml);
@@ -568,7 +568,7 @@ BOOST_AUTO_TEST_CASE (player_time_calculation_test2)
/** Test Player::dcp_to_content_audio */
BOOST_AUTO_TEST_CASE (player_time_calculation_test3)
{
- auto film = new_test_film ("player_time_calculation_test3");
+ auto film = new_test_film("player_time_calculation_test3");
auto doc = make_shared<cxml::Document>();
doc->read_string (xml);
diff --git a/test/torture_test.cc b/test/torture_test.cc
index 0c781fdb1..e2325955b 100644
--- a/test/torture_test.cc
+++ b/test/torture_test.cc
@@ -35,8 +35,8 @@
#include "lib/video_content.h"
#include "test.h"
#include <dcp/cpl.h>
-#include <dcp/mono_picture_asset.h>
-#include <dcp/mono_picture_frame.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/mono_j2k_picture_frame.h>
#include <dcp/openjpeg_image.h>
#include <dcp/reel.h>
#include <dcp/reel_picture_asset.h>
@@ -57,7 +57,7 @@ using namespace dcpomatic;
/** Test start/end trim and positioning of some audio content */
BOOST_AUTO_TEST_CASE (torture_test1)
{
- auto film = new_test_film2 ("torture_test1");
+ auto film = new_test_film("torture_test1");
film->set_sequence (false);
/* Staircase at an offset of 2000 samples, trimmed both start and end, with a gain of exactly 2 (linear) */
@@ -230,7 +230,7 @@ BOOST_AUTO_TEST_CASE (torture_test1)
auto reel_picture = reels.front()->main_picture();
BOOST_REQUIRE (reel_picture);
- auto picture = dynamic_pointer_cast<dcp::MonoPictureAsset> (reel_picture->asset());
+ auto picture = dynamic_pointer_cast<dcp::MonoJ2KPictureAsset>(reel_picture->asset());
BOOST_REQUIRE (picture);
BOOST_CHECK_EQUAL (picture->intrinsic_duration(), 144);
@@ -248,7 +248,7 @@ BOOST_AUTO_TEST_CASE (torture_test1)
for (int c = 0; c < 3; ++c) {
for (int y = 0; y < size.height; ++y) {
for (int x = 0; x < size.width; ++x) {
- BOOST_REQUIRE (image->data(c)[y * size.height + x] <= 3);
+ BOOST_REQUIRE (image->data(c)[y * size.height + x] <= 5);
}
}
}
@@ -307,7 +307,7 @@ BOOST_AUTO_TEST_CASE(multi_reel_interop_ccap_test)
auto ccap1 = content_factory("test/data/15s.srt").front();
auto pic2 = content_factory("test/data/flat_red.png").front();
auto ccap2 = content_factory("test/data/15s.srt").front();
- auto film1 = new_test_film2("multi_reel_interop_ccap_test1", { pic1, ccap1, pic2, ccap2 });
+ auto film1 = new_test_film("multi_reel_interop_ccap_test1", { pic1, ccap1, pic2, ccap2 });
film1->set_interop(true);
film1->set_reel_type(ReelType::BY_VIDEO_CONTENT);
ccap1->text[0]->set_type(TextType::CLOSED_CAPTION);
@@ -317,7 +317,7 @@ BOOST_AUTO_TEST_CASE(multi_reel_interop_ccap_test)
make_and_verify_dcp(film1, { dcp::VerificationNote::Code::INVALID_STANDARD, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING });
auto reload = make_shared<DCPContent>(film1->dir(film1->dcp_name()));
- auto film2 = new_test_film2("multi_reel_interop_ccap_test2", { reload });
+ auto film2 = new_test_film("multi_reel_interop_ccap_test2", { reload });
for (auto i: reload->text) {
i->set_use(true);
}
diff --git a/test/upmixer_a_test.cc b/test/upmixer_a_test.cc
index af6c8b9e2..29af9805b 100644
--- a/test/upmixer_a_test.cc
+++ b/test/upmixer_a_test.cc
@@ -68,15 +68,9 @@ write (shared_ptr<AudioBuffers> b, DCPTime)
BOOST_AUTO_TEST_CASE (upmixer_a_test)
{
- auto film = new_test_film ("upmixer_a_test");
- film->set_container (Ratio::from_id("185"));
- film->set_dcp_content_type (DCPContentType::from_isdcf_name("TLR"));
- film->set_name ("frobozz");
- film->set_audio_processor (AudioProcessor::from_id("stereo-5.1-upmix-a"));
auto content = make_shared<FFmpegContent>("test/data/white.wav");
- film->examine_and_add_content (content);
-
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("upmixer_a_test", { content });
+ film->set_audio_processor (AudioProcessor::from_id("stereo-5.1-upmix-a"));
SF_INFO info;
info.samplerate = 48000;
diff --git a/test/vf_kdm_test.cc b/test/vf_kdm_test.cc
index d8efa7ab7..3fd8bc327 100644
--- a/test/vf_kdm_test.cc
+++ b/test/vf_kdm_test.cc
@@ -51,16 +51,11 @@ BOOST_AUTO_TEST_CASE (vf_kdm_test)
/* Make an encrypted DCP from test.mp4 */
- auto A = new_test_film ("vf_kdm_test_ov");
- A->set_container (Ratio::from_id ("185"));
- A->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- A->set_name ("frobozz");
- A->set_interop (true);
-
auto c = make_shared<FFmpegContent>("test/data/test.mp4");
- A->examine_and_add_content (c);
+ auto A = new_test_film("vf_kdm_test_ov", { c });
+ A->set_interop (true);
+ A->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
A->set_encrypted (true);
- BOOST_REQUIRE (!wait_for_jobs());
make_and_verify_dcp (A, {dcp::VerificationNote::Code::INVALID_STANDARD});
dcp::DCP A_dcp ("build/test/vf_kdm_test_ov/" + A->dcp_name());
@@ -76,18 +71,15 @@ BOOST_AUTO_TEST_CASE (vf_kdm_test)
/* Import A into a new project, with the required KDM, and make a VF that refers to it */
- auto B = new_test_film ("vf_kdm_test_vf");
- B->set_container (Ratio::from_id("185"));
- B->set_dcp_content_type (DCPContentType::from_isdcf_name("TLR"));
- B->set_name ("frobozz");
- B->set_interop (true);
-
auto d = make_shared<DCPContent>("build/test/vf_kdm_test_ov/" + A->dcp_name());
- d->add_kdm (A_kdm);
+ d->add_kdm(A_kdm);
+
+ auto B = new_test_film("vf_kdm_test_vf", { d });
+ B->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
+ B->set_interop(true);
+
d->set_reference_video (true);
- B->examine_and_add_content (d);
B->set_encrypted (true);
- BOOST_REQUIRE (!wait_for_jobs());
make_and_verify_dcp (B, {dcp::VerificationNote::Code::INVALID_STANDARD, dcp::VerificationNote::Code::EXTERNAL_ASSET});
dcp::DCP B_dcp ("build/test/vf_kdm_test_vf/" + B->dcp_name());
@@ -100,18 +92,14 @@ BOOST_AUTO_TEST_CASE (vf_kdm_test)
This KDM should decrypt assets from the OV too.
*/
- auto C = new_test_film ("vf_kdm_test_check");
- C->set_container (Ratio::from_id ("185"));
- C->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR"));
- C->set_name ("frobozz");
- C->set_interop (true);
-
auto e = make_shared<DCPContent>("build/test/vf_kdm_test_vf/" + B->dcp_name());
- e->add_kdm (B_kdm);
e->add_ov ("build/test/vf_kdm_test_ov/" + A->dcp_name());
- C->examine_and_add_content (e);
+ e->add_kdm(B_kdm);
+ auto C = new_test_film("vf_kdm_test_check", { e });
+ C->set_interop (true);
C->set_audio_channels(6);
- BOOST_REQUIRE (!wait_for_jobs());
+ C->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
+
make_and_verify_dcp (C, {dcp::VerificationNote::Code::INVALID_STANDARD});
/* Should be 1s red, 1s green, 1s blue */
diff --git a/test/vf_test.cc b/test/vf_test.cc
index ca987b22d..589e12d83 100644
--- a/test/vf_test.cc
+++ b/test/vf_test.cc
@@ -40,8 +40,8 @@
#include "lib/video_content.h"
#include "test.h"
#include <dcp/cpl.h>
-#include <dcp/mono_picture_asset.h>
-#include <dcp/picture_asset_writer.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/j2k_picture_asset_writer.h>
#include <dcp/reel.h>
#include <dcp/reel_mono_picture_asset.h>
#include <dcp/reel_sound_asset.h>
@@ -65,11 +65,8 @@ using namespace dcpomatic;
/** Test the logic which decides whether a DCP can be referenced or not */
BOOST_AUTO_TEST_CASE (vf_test1)
{
- auto film = new_test_film ("vf_test1");
- film->set_interop (false);
auto dcp = make_shared<DCPContent>("test/data/reels_test2");
- film->examine_and_add_content (dcp);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("vf_test1", { dcp });
/* Multi-reel DCP can't be referenced if we are using a single reel for the project */
film->set_reel_type (ReelType::SINGLE);
@@ -111,31 +108,19 @@ BOOST_AUTO_TEST_CASE (vf_test1)
BOOST_AUTO_TEST_CASE (vf_test2)
{
/* Make the OV */
- auto ov = new_test_film ("vf_test2_ov");
- ov->set_dcp_content_type (DCPContentType::from_isdcf_name("TST"));
- ov->set_name ("vf_test2_ov");
auto video = content_factory("test/data/flat_red.png")[0];
- ov->examine_and_add_content (video);
- BOOST_REQUIRE (!wait_for_jobs());
- video->video->set_length (24 * 5);
auto audio = content_factory("test/data/white.wav")[0];
- ov->examine_and_add_content (audio);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto ov = new_test_film("vf_test2_ov", { video, audio });
+ video->video->set_length (24 * 5);
make_and_verify_dcp (ov);
/* Make the VF */
- auto vf = new_test_film ("vf_test2_vf");
- vf->set_name ("vf_test2_vf");
- vf->set_dcp_content_type (DCPContentType::from_isdcf_name("TST"));
- vf->set_reel_type (ReelType::BY_VIDEO_CONTENT);
auto dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
- vf->examine_and_add_content (dcp);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto sub = content_factory("test/data/subrip4.srt")[0];
+ auto vf = new_test_film("vf_test2_vf", { dcp, sub });
+ vf->set_reel_type (ReelType::BY_VIDEO_CONTENT);
dcp->set_reference_video (true);
dcp->set_reference_audio (true);
- auto sub = content_factory("test/data/subrip4.srt")[0];
- vf->examine_and_add_content (sub);
- BOOST_REQUIRE (!wait_for_jobs());
make_and_verify_dcp (
vf,
{
@@ -175,29 +160,18 @@ BOOST_AUTO_TEST_CASE (vf_test2)
BOOST_AUTO_TEST_CASE (vf_test3)
{
/* Make the OV */
- auto ov = new_test_film ("vf_test3_ov");
- ov->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
- ov->set_name ("vf_test3_ov");
auto video = content_factory("test/data/flat_red.png")[0];
- ov->examine_and_add_content (video);
- BOOST_REQUIRE (!wait_for_jobs());
- video->video->set_length (24 * 5);
auto audio = content_factory("test/data/white.wav")[0];
- ov->examine_and_add_content (audio);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto ov = new_test_film("vf_test3_ov", { video, audio });
+ video->video->set_length (24 * 5);
make_and_verify_dcp (ov);
/* Make the VF */
- auto vf = new_test_film ("vf_test3_vf");
- vf->set_name ("vf_test3_vf");
- vf->set_dcp_content_type (DCPContentType::from_isdcf_name("TST"));
- vf->set_reel_type (ReelType::BY_VIDEO_CONTENT);
auto dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
- BOOST_REQUIRE (dcp);
+ auto vf = new_test_film("vf_test3_vf", { dcp });
+ vf->set_reel_type (ReelType::BY_VIDEO_CONTENT);
dcp->set_trim_start(vf, ContentTime::from_seconds (1));
dcp->set_trim_end (ContentTime::from_seconds (1));
- vf->examine_and_add_content (dcp);
- BOOST_REQUIRE (!wait_for_jobs());
dcp->set_reference_video (true);
dcp->set_reference_audio (true);
make_and_verify_dcp(vf, {dcp::VerificationNote::Code::EXTERNAL_ASSET}, false);
@@ -219,28 +193,17 @@ BOOST_AUTO_TEST_CASE (vf_test3)
BOOST_AUTO_TEST_CASE (vf_test4)
{
/* Make the OV */
- auto ov = new_test_film ("vf_test4_ov");
- ov->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
- ov->set_name ("vf_test4_ov");
auto video = content_factory("test/data/flat_red.png")[0];
- ov->examine_and_add_content (video);
- BOOST_REQUIRE (!wait_for_jobs());
- video->video->set_length (24 * 5);
auto audio = content_factory("test/data/white.wav")[0];
- ov->examine_and_add_content (audio);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto ov = new_test_film("vf_test4_ov", { video, audio });
+ video->video->set_length (24 * 5);
make_and_verify_dcp (ov);
/* Make the VF */
- auto vf = new_test_film ("vf_test4_vf");
- vf->set_name ("vf_test4_vf");
- vf->set_dcp_content_type (DCPContentType::from_isdcf_name("TST"));
+ auto dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
+ auto vf = new_test_film("vf_test4_vf", { dcp });
vf->set_reel_type (ReelType::BY_VIDEO_CONTENT);
vf->set_sequence (false);
- auto dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
- BOOST_REQUIRE (dcp);
- vf->examine_and_add_content (dcp);
- BOOST_REQUIRE (!wait_for_jobs());
dcp->set_position(vf, DCPTime::from_seconds(10));
dcp->set_reference_video (true);
dcp->set_reference_audio (true);
@@ -248,7 +211,6 @@ BOOST_AUTO_TEST_CASE (vf_test4)
vf->examine_and_add_content (more_video);
BOOST_REQUIRE (!wait_for_jobs());
more_video->set_position (vf, DCPTime());
- vf->write_metadata ();
make_and_verify_dcp(vf, {dcp::VerificationNote::Code::EXTERNAL_ASSET}, false);
dcp::DCP ov_c (ov->dir(ov->dcp_name()));
@@ -276,8 +238,7 @@ BOOST_AUTO_TEST_CASE (vf_test4)
BOOST_AUTO_TEST_CASE (vf_test5)
{
/* Make the OV */
- auto ov = new_test_film ("vf_test5_ov");
- ov->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
+ auto ov = new_test_film("vf_test5_ov");
ov->set_reel_type (ReelType::BY_VIDEO_CONTENT);
for (int i = 0; i < 3; ++i) {
auto video = content_factory("test/data/flat_red.png")[0];
@@ -290,15 +251,10 @@ BOOST_AUTO_TEST_CASE (vf_test5)
make_and_verify_dcp (ov);
/* Make the VF */
- auto vf = new_test_film ("vf_test5_vf");
- vf->set_name ("vf_test5_vf");
- vf->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
+ auto dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
+ auto vf = new_test_film("vf_test5_vf", { dcp });
vf->set_reel_type (ReelType::BY_VIDEO_CONTENT);
vf->set_sequence (false);
- auto dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
- BOOST_REQUIRE (dcp);
- vf->examine_and_add_content (dcp);
- BOOST_REQUIRE (!wait_for_jobs());
dcp->set_reference_video (true);
dcp->set_reference_audio (true);
dcp->set_trim_end (ContentTime::from_seconds(15));
@@ -323,24 +279,17 @@ BOOST_AUTO_TEST_CASE (vf_test5)
BOOST_AUTO_TEST_CASE (vf_test6)
{
/* Make the OV */
- auto ov = new_test_film ("vf_test6_ov");
- ov->set_dcp_content_type (DCPContentType::from_isdcf_name("TST"));
- ov->set_reel_type (ReelType::BY_VIDEO_CONTENT);
auto video = content_factory("test/data/flat_red.png")[0];
- ov->examine_and_add_content (video);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto ov = new_test_film("vf_test6_ov", { video });
+ ov->set_reel_type (ReelType::BY_VIDEO_CONTENT);
video->video->set_length (24 * 10);
make_and_verify_dcp (ov);
/* Make the VF */
- auto vf = new_test_film ("vf_test6_vf");
- vf->set_name ("vf_test6_vf");
- vf->set_dcp_content_type (DCPContentType::from_isdcf_name("TST"));
+ auto dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
+ auto vf = new_test_film("vf_test6_vf", { dcp });
vf->set_reel_type (ReelType::BY_VIDEO_CONTENT);
vf->set_sequence (false);
- auto dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
- vf->examine_and_add_content (dcp);
- BOOST_REQUIRE (!wait_for_jobs());
dcp->set_reference_video (true);
dcp->set_reference_audio (true);
@@ -364,19 +313,19 @@ BOOST_AUTO_TEST_CASE (vf_test6)
BOOST_AUTO_TEST_CASE (vf_test7)
{
/* First OV */
- auto ov1 = new_test_film2 ("vf_test7_ov1", {content_factory("test/data/flat_red.png")[0]});
+ auto ov1 = new_test_film("vf_test7_ov1", {content_factory("test/data/flat_red.png")[0]});
ov1->set_video_frame_rate (24);
make_and_verify_dcp (ov1);
/* Second OV */
- auto ov2 = new_test_film2 ("vf_test7_ov2", {content_factory("test/data/flat_red.png")[0]});
+ auto ov2 = new_test_film("vf_test7_ov2", {content_factory("test/data/flat_red.png")[0]});
ov2->set_video_frame_rate (24);
make_and_verify_dcp (ov2);
/* VF */
auto ov1_dcp = make_shared<DCPContent>(ov1->dir(ov1->dcp_name()));
auto ov2_dcp = make_shared<DCPContent>(ov2->dir(ov2->dcp_name()));
- auto vf = new_test_film2 ("vf_test7_vf", {ov1_dcp, ov2_dcp});
+ auto vf = new_test_film("vf_test7_vf", {ov1_dcp, ov2_dcp});
vf->set_reel_type (ReelType::BY_VIDEO_CONTENT);
ov1_dcp->set_reference_video (true);
ov2_dcp->set_reference_video (true);
@@ -397,14 +346,14 @@ BOOST_AUTO_TEST_CASE (test_vf_with_trimmed_multi_reel_dcp)
c->video->set_length(240);
ov_content.push_back(c);
}
- auto ov = new_test_film2 ("test_vf_with_trimmed_multi_reel_dcp_ov", ov_content);
+ auto ov = new_test_film("test_vf_with_trimmed_multi_reel_dcp_ov", ov_content);
ov->set_reel_type(ReelType::BY_VIDEO_CONTENT);
make_and_verify_dcp (ov);
/* Make a VF with a specific arrangement */
auto vf_image = content_factory("test/data/flat_red.png")[0];
auto vf_dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
- auto vf = new_test_film2 ("test_vf_with_trimmed_multi_reel_dcp_vf", { vf_image, vf_dcp });
+ auto vf = new_test_film("test_vf_with_trimmed_multi_reel_dcp_vf", { vf_image, vf_dcp });
vf->set_reel_type(ReelType::BY_VIDEO_CONTENT);
vf_dcp->set_reference_video(true);
vf_dcp->set_reference_audio(true);
@@ -419,7 +368,7 @@ BOOST_AUTO_TEST_CASE(test_referencing_ov_with_subs_when_adding_ccaps)
{
string const name("test_referencing_ov_with_subs_when_adding_ccaps");
auto subs = content_factory("test/data/15s.srt");
- auto ov = new_test_film2(name + "_ov", subs);
+ auto ov = new_test_film(name + "_ov", subs);
make_and_verify_dcp(
ov,
{
@@ -430,7 +379,7 @@ BOOST_AUTO_TEST_CASE(test_referencing_ov_with_subs_when_adding_ccaps)
auto ccaps = content_factory("test/data/15s.srt")[0];
auto ov_dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name(false)));
- auto vf = new_test_film2(name + "_vf", { ov_dcp, ccaps });
+ auto vf = new_test_film(name + "_vf", { ov_dcp, ccaps });
ccaps->text[0]->set_type(TextType::CLOSED_CAPTION);
string why_not;
@@ -443,7 +392,7 @@ BOOST_AUTO_TEST_CASE(test_duplicate_font_id_in_vf)
{
string const name("test_duplicate_font_id_in_vf");
auto subs = content_factory("test/data/15s.srt");
- auto ov = new_test_film2(name + "_ov", subs);
+ auto ov = new_test_film(name + "_ov", subs);
make_and_verify_dcp(
ov,
{
@@ -454,7 +403,7 @@ BOOST_AUTO_TEST_CASE(test_duplicate_font_id_in_vf)
auto ccaps = content_factory("test/data/15s.srt")[0];
auto ov_dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name(false)));
- auto vf = new_test_film2(name + "_vf", { ov_dcp, ccaps });
+ auto vf = new_test_film(name + "_vf", { ov_dcp, ccaps });
ov_dcp->set_reference_audio(true);
ov_dcp->set_reference_video(true);
ov_dcp->text[0]->set_use(true);
@@ -468,7 +417,7 @@ BOOST_AUTO_TEST_CASE(test_duplicate_font_id_in_vf)
auto vf_dcp = make_shared<DCPContent>(vf->dir(vf->dcp_name(false)));
- auto test = new_test_film2(name + "_test", { vf_dcp });
+ auto test = new_test_film(name + "_test", { vf_dcp });
vf_dcp->add_ov(ov->dir(ov->dcp_name(false)));
JobManager::instance()->add(make_shared<ExamineContentJob>(test, vf_dcp));
BOOST_CHECK(!wait_for_jobs());
@@ -492,8 +441,8 @@ BOOST_AUTO_TEST_CASE(test_referencing_ov_with_missing_subtitle_in_some_reels)
dcp::DCP ov(path / "ov");
auto make_picture = [path](string filename) {
- auto pic = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
- auto writer = pic->start_write(path / "ov" / filename, dcp::PictureAsset::Behaviour::MAKE_NEW);
+ auto pic = make_shared<dcp::MonoJ2KPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
+ auto writer = pic->start_write(path / "ov" / filename, dcp::Behaviour::MAKE_NEW);
auto frame = dcp::ArrayData("test/data/picture.j2c");
for (int i = 0; i < 240; ++i) {
writer->write(frame);
@@ -565,7 +514,8 @@ BOOST_AUTO_TEST_CASE(test_referencing_ov_with_missing_subtitle_in_some_reels)
BOOST_AUTO_TEST_CASE(ov_subs_in_vf_name)
{
auto subs = content_factory("test/data/short.srt")[0];
- auto ov = new_test_film2("ov_subs_in_vf_name_ov", { subs });
+ auto ov = new_test_film("ov_subs_in_vf_name_ov", { subs });
+ ov->set_audio_channels(8);
subs->only_text()->set_language(dcp::LanguageTag("de"));
make_and_verify_dcp(
ov,
@@ -575,8 +525,9 @@ BOOST_AUTO_TEST_CASE(ov_subs_in_vf_name)
});
auto ov_dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
- auto vf = new_test_film2("ov_subs_in_vf_name_vf", { ov_dcp });
+ auto vf = new_test_film("ov_subs_in_vf_name_vf", { ov_dcp });
vf->set_name("foo");
+ vf->set_audio_channels(8);
ov_dcp->set_reference_text(TextType::OPEN_SUBTITLE, true);
vf->_isdcf_date = boost::gregorian::date(2023, boost::gregorian::Jan, 18);
diff --git a/test/video_level_test.cc b/test/video_level_test.cc
index e2419d8e7..f210f2add 100644
--- a/test/video_level_test.cc
+++ b/test/video_level_test.cc
@@ -36,7 +36,7 @@
#include "lib/image.h"
#include "lib/image_content.h"
#include "lib/image_decoder.h"
-#include "lib/ffmpeg_encoder.h"
+#include "lib/ffmpeg_film_encoder.h"
#include "lib/job_manager.h"
#include "lib/player.h"
#include "lib/player_video.h"
@@ -45,8 +45,8 @@
#include "test.h"
#include <dcp/cpl.h>
#include <dcp/dcp.h>
-#include <dcp/mono_picture_asset.h>
-#include <dcp/mono_picture_frame.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/mono_j2k_picture_frame.h>
#include <dcp/openjpeg_image.h>
#include <dcp/reel.h>
#include <dcp/reel_picture_asset.h>
@@ -115,7 +115,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded)
write_image(grey_image(size, grey_pixel), file);
auto content = content_factory(file);
- auto film = new_test_film2 ("ffmpeg_image_video_range_expanded", content);
+ auto film = new_test_film("ffmpeg_image_video_range_expanded", content);
content[0]->video->set_range (VideoRange::VIDEO);
auto player = make_shared<Player>(film, film->playlist());
@@ -275,7 +275,7 @@ pixel_range (boost::filesystem::path dcp_path)
dcp::DCP dcp (dcp_path);
dcp.read ();
- auto picture = dynamic_pointer_cast<dcp::MonoPictureAsset>(dcp.cpls().front()->reels().front()->main_picture()->asset());
+ auto picture = dynamic_pointer_cast<dcp::MonoJ2KPictureAsset>(dcp.cpls().front()->reels().front()->main_picture()->asset());
BOOST_REQUIRE (picture);
auto frame = picture->start_read()->get_frame(0)->xyz_image();
@@ -310,7 +310,7 @@ static
shared_ptr<Film>
movie_V (string name)
{
- auto film = new_test_film2 (name);
+ auto film = new_test_film(name);
auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4")[0]);
BOOST_REQUIRE (content);
film->examine_and_add_content (content);
@@ -324,11 +324,12 @@ movie_V (string name)
}
+/** @return Film containing video-range content set as full-range */
static
shared_ptr<Film>
movie_VoF (string name)
{
- auto film = new_test_film2 (name);
+ auto film = new_test_film(name);
auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4")[0]);
BOOST_REQUIRE (content);
film->examine_and_add_content (content);
@@ -347,12 +348,14 @@ static
shared_ptr<Film>
movie_F (string name)
{
- auto film = new_test_film2 (name);
+ auto film = new_test_film(name);
auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov")[0]);
BOOST_REQUIRE (content);
film->examine_and_add_content (content);
BOOST_REQUIRE (!wait_for_jobs());
+ BOOST_CHECK(content->video->range() == VideoRange::FULL);
+
auto range = pixel_range (film, content);
BOOST_CHECK_EQUAL (range.first, 0);
BOOST_CHECK_EQUAL (range.second, 1023);
@@ -365,7 +368,7 @@ static
shared_ptr<Film>
movie_FoV (string name)
{
- auto film = new_test_film2 (name);
+ auto film = new_test_film(name);
auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov")[0]);
BOOST_REQUIRE (content);
film->examine_and_add_content (content);
@@ -384,7 +387,7 @@ static
shared_ptr<Film>
image_F (string name)
{
- auto film = new_test_film2 (name);
+ auto film = new_test_film(name);
auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png")[0]);
BOOST_REQUIRE (content);
film->examine_and_add_content (content);
@@ -402,7 +405,7 @@ static
shared_ptr<Film>
image_FoV (string name)
{
- auto film = new_test_film2 (name);
+ auto film = new_test_film(name);
auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png")[0]);
BOOST_REQUIRE (content);
film->examine_and_add_content (content);
@@ -425,7 +428,7 @@ shared_ptr<Film>
dcp_F (string name)
{
boost::filesystem::path const dcp = "test/data/RgbGreyTestcar_TST-1_F_MOS_2K_20201115_SMPTE_OV";
- auto film = new_test_film2 (name);
+ auto film = new_test_film(name);
auto content = make_shared<DCPContent>(dcp);
film->examine_and_add_content (content);
BOOST_REQUIRE (!wait_for_jobs());
@@ -459,7 +462,7 @@ V_movie_range (shared_ptr<Film> film)
{
auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
job->set_encoder (
- make_shared<FFmpegEncoder>(film, job, film->file("export.mov"), ExportFormat::PRORES_HQ, true, false, false, 23)
+ make_shared<FFmpegFilmEncoder>(film, job, film->file("export.mov"), ExportFormat::PRORES_HQ, true, false, false, 23)
);
JobManager::instance()->add (job);
BOOST_REQUIRE (!wait_for_jobs());
@@ -480,7 +483,7 @@ BOOST_AUTO_TEST_CASE (movie_V_to_dcp)
{
auto range = dcp_range (movie_V("movie_V_to_dcp"));
/* Video range has been correctly expanded to full for the DCP */
- check_int_close (range, {0, 4083}, 2);
+ check_int_close(range, {0, 4081}, 2);
}
@@ -498,7 +501,7 @@ BOOST_AUTO_TEST_CASE (movie_F_to_dcp)
{
auto range = dcp_range (movie_F("movie_F_to_dcp"));
/* The nearly-full-range of the input has been preserved */
- check_int_close (range, {0, 4083}, 2);
+ check_int_close(range, {0, 4080}, 2);
}
@@ -506,14 +509,14 @@ BOOST_AUTO_TEST_CASE (video_FoV_to_dcp)
{
auto range = dcp_range (movie_FoV("video_FoV_to_dcp"));
/* The nearly-full-range of the input has become even more full, and clipped */
- check_int_close (range, {0, 4095}, 2);
+ check_int_close(range, {0, 4093}, 2);
}
BOOST_AUTO_TEST_CASE (image_F_to_dcp)
{
auto range = dcp_range (image_F("image_F_to_dcp"));
- check_int_close (range, {0, 4083}, 3);
+ check_int_close(range, {0, 4080}, 3);
}
@@ -546,8 +549,11 @@ BOOST_AUTO_TEST_CASE (movie_VoF_to_V_movie)
BOOST_AUTO_TEST_CASE (movie_F_to_V_movie)
{
auto range = V_movie_range (movie_F("movie_F_to_V_movie"));
- BOOST_CHECK_EQUAL (range.first, 4);
- BOOST_CHECK_EQUAL (range.second, 1019);
+ /* A full range input has been converted to video range, so that what was black at 0
+ * is not black at 64 (with the corresponding change to white)
+ */
+ BOOST_CHECK_EQUAL(range.first, 64);
+ BOOST_CHECK_EQUAL(range.second, 963);
}
diff --git a/test/video_mxf_content_test.cc b/test/video_mxf_content_test.cc
index f3766e4af..df83db41c 100644
--- a/test/video_mxf_content_test.cc
+++ b/test/video_mxf_content_test.cc
@@ -33,7 +33,7 @@
#include "lib/video_mxf_content.h"
#include "test.h"
#include <dcp/equality_options.h>
-#include <dcp/mono_picture_asset.h>
+#include <dcp/mono_j2k_picture_asset.h>
#include <boost/test/unit_test.hpp>
@@ -50,18 +50,10 @@ static void note (dcp::NoteType, std::string)
/** Basic test of using video MXF content */
BOOST_AUTO_TEST_CASE (video_mxf_content_test)
{
- auto film = new_test_film ("video_mxf_content_test");
- film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
- film->set_container (Ratio::from_id ("185"));
- film->set_name ("video_mxf_content_test");
-
auto const ref_mxf = find_file("test/data/scaling_test_185_185", "j2c");
+ auto content = content_factory(ref_mxf);
- auto content = content_factory(ref_mxf)[0];
- auto check = dynamic_pointer_cast<VideoMXFContent> (content);
- BOOST_REQUIRE (check);
- film->examine_and_add_content (content);
- BOOST_REQUIRE (!wait_for_jobs());
+ auto film = new_test_film("video_mxf_content_test", content);
make_and_verify_dcp (
film,
{
@@ -70,9 +62,9 @@ BOOST_AUTO_TEST_CASE (video_mxf_content_test)
dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K
});
- auto ref = make_shared<dcp::MonoPictureAsset>(ref_mxf);
- boost::filesystem::directory_iterator i ("build/test/video_mxf_content_test/video");
- auto comp = make_shared<dcp::MonoPictureAsset>(*i);
+ auto ref = make_shared<dcp::MonoJ2KPictureAsset>(ref_mxf);
+ auto comp_mxf = find_file(film->file(film->dcp_name()), "j2c_");
+ auto comp = make_shared<dcp::MonoJ2KPictureAsset>(comp_mxf);
dcp::EqualityOptions op;
BOOST_CHECK (ref->equals (comp, op, note));
}
diff --git a/test/writer_test.cc b/test/writer_test.cc
index 86b60818f..b4ef93c1d 100644
--- a/test/writer_test.cc
+++ b/test/writer_test.cc
@@ -23,7 +23,7 @@
#include "lib/content.h"
#include "lib/content_factory.h"
#include "lib/cross.h"
-#include "lib/dcp_encoder.h"
+#include "lib/dcp_film_encoder.h"
#include "lib/film.h"
#include "lib/job.h"
#include "lib/video_content.h"
@@ -44,9 +44,9 @@ using std::vector;
BOOST_AUTO_TEST_CASE (test_write_odd_amount_of_silence)
{
auto content = content_factory("test/data/flat_red.png");
- auto film = new_test_film2 ("test_write_odd_amount_of_silence", content);
+ auto film = new_test_film("test_write_odd_amount_of_silence", content);
content[0]->video->set_length(24);
- auto writer = make_shared<Writer>(film, shared_ptr<Job>());
+ auto writer = make_shared<Writer>(film, shared_ptr<Job>(), "foo");
auto audio = make_shared<AudioBuffers>(6, 48000);
audio->make_silent ();
@@ -58,7 +58,7 @@ BOOST_AUTO_TEST_CASE (interrupt_writer)
{
Cleanup cl;
- auto film = new_test_film2 ("test_interrupt_writer", {}, &cl);
+ auto film = new_test_film("test_interrupt_writer", {}, &cl);
auto content = content_factory("test/data/check_image0.png")[0];
film->examine_and_add_content (content);
@@ -82,7 +82,7 @@ BOOST_AUTO_TEST_CASE (interrupt_writer)
auto video_ptr = make_shared<dcp::ArrayData>(video.data(), video.size());
auto audio = make_shared<AudioBuffers>(6, 48000 / 24);
- auto writer = make_shared<Writer>(film, shared_ptr<Job>());
+ auto writer = make_shared<Writer>(film, shared_ptr<Job>(), film->dir(film->dcp_name()));
writer->start ();
for (int i = 0; i < frames; ++i) {
@@ -92,7 +92,7 @@ BOOST_AUTO_TEST_CASE (interrupt_writer)
/* Start digest calculations then abort them; there should be no crash or error */
boost::thread thread([film, writer]() {
- writer->finish(film->dir(film->dcp_name()));
+ writer->finish();
});
dcpomatic_sleep_seconds (1);
@@ -131,7 +131,7 @@ BOOST_AUTO_TEST_CASE(writer_progress_test)
auto picture1 = content_factory("test/data/flat_red.png")[0];
auto picture2 = content_factory("test/data/flat_red.png")[0];
- auto film = new_test_film2("writer_progress_test", { picture1, picture2 });
+ auto film = new_test_film("writer_progress_test", { picture1, picture2 });
film->set_reel_type(ReelType::BY_VIDEO_CONTENT);
picture1->video->set_length(240);
picture2->video->set_length(240);
@@ -149,7 +149,7 @@ BOOST_AUTO_TEST_CASE(writer_progress_test)
last_sub_name = job->sub_name();
});
- DCPEncoder encoder(film, job);
+ DCPFilmEncoder encoder(film, job);
encoder.go();
}
diff --git a/test/wscript b/test/wscript
index cd23badc0..2802a4678 100644
--- a/test/wscript
+++ b/test/wscript
@@ -37,7 +37,7 @@ def build(bld):
obj.name = 'unit-tests'
obj.uselib = 'BOOST_TEST BOOST_THREAD BOOST_FILESYSTEM BOOST_DATETIME SNDFILE SAMPLERATE DCP FONTCONFIG CAIROMM PANGOMM XMLPP '
obj.uselib += 'AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE SWRESAMPLE POSTPROC CXML SUB GLIB CURL SSH XMLSEC BOOST_REGEX ICU NETTLE PNG JPEG '
- obj.uselib += 'LEQM_NRT ZIP '
+ obj.uselib += 'LEQM_NRT ZIP SQLITE3 '
if bld.env.TARGET_WINDOWS_64 or bld.env.TARGET_WINDOWS_32:
obj.uselib += 'WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE '
if bld.env.TARGET_LINUX:
@@ -60,6 +60,7 @@ def build(bld):
burnt_subtitle_test.cc
butler_test.cc
bv20_test.cc
+ cinema_list_test.cc
cinema_sound_processor_test.cc
client_server_test.cc
closed_caption_test.cc
@@ -78,6 +79,7 @@ def build(bld):
dcp_playback_test.cc
dcp_subtitle_test.cc
digest_test.cc
+ dkdm_recipient_list_test.cc
empty_caption_test.cc
empty_test.cc
encryption_test.cc
@@ -111,9 +113,10 @@ def build(bld):
import_dcp_test.cc
interrupt_encoder_test.cc
isdcf_name_test.cc
- j2k_bandwidth_test.cc
+ j2k_encode_threading_test.cc
j2k_encoder_test.cc
job_manager_test.cc
+ j2k_video_bit_rate_test.cc
kdm_cli_test.cc
kdm_naming_test.cc
kdm_util_test.cc
@@ -162,6 +165,7 @@ def build(bld):
template_test.cc
test.cc
text_decoder_test.cc
+ text_entry_point_test.cc
threed_test.cc
time_calculation_test.cc
torture_test.cc
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 000000000..0eea15aab
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+<script>
+ setInterval(function() {
+ status = fetch("/api/v1/status").then(response => {
+ response.json().then(data => {
+ if (data.playing) {
+ document.getElementById('playing').innerHTML = "Playing";
+ } else {
+ document.getElementById('playing').innerHTML = "Stopped";
+ }
+ document.getElementById('dcp_name').innerHTML = data.dcp_name;
+ document.getElementById('position').innerHTML = data.position;
+ });
+ });
+ }, 250);
+ function play() {
+ fetch("/api/v1/play", { method: "POST" });
+ }
+ function stop() {
+ fetch("/api/v1/stop", { method: "POST" });
+ }
+</script>
+<style>
+button {
+ border: 1px solid rgba(27, 31, 35, 0.15);
+ border-radius: 6px;
+ color: #24292E;
+ display: inline-block;
+ line-height: 20px;
+ padding: 6px 16px;
+ vertical-align: middle;
+ white-space: nowrap;
+ word-wrap: break-word;
+}
+
+button:hover {
+ background-color: #F3F4F6;
+ text-decoration: none;
+ transition-duration: 0.1s;
+}
+
+button:active {
+ background-color: #EDEFF2;
+ box-shadow: rgba(225, 228, 232, 0.2) 0 1px 0 inset;
+ transition: none 0s;
+}
+
+button:focus {
+ outline: 1px transparent;
+}
+
+button:before {
+ display: none;
+}
+
+table {
+ border-collapse: collapse;
+ margin: 25px 0;
+ font-size: 0.9em;
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
+ border: 2px solid black;
+}
+
+tr {
+ text-align: left;
+ border: 1px solid black;
+}
+
+td {
+ padding: 4px 16px;
+ text-align: left;
+ border: 1px solid black;
+}
+
+
+</style>
+ <head>
+ <title>%1</title>
+ </head>
+ <body>
+ <button name="play" value="play" onclick="play()">Play</button>
+ <button name="Stop" value="Stop" onclick="stop()">Stop</button>
+ <table>
+ <tr><td>DCP</td><td><p id="dcp_name"></td></tr>
+ <tr><td>State</td><td><p id="playing"></td></tr>
+ <tr><td>Position</td><td><p id="position"></td></tr>
+ </table>
+ </body>
+</html>
diff --git a/web/wscript b/web/wscript
new file mode 100644
index 000000000..d9ebc76ab
--- /dev/null
+++ b/web/wscript
@@ -0,0 +1,5 @@
+
+def build(bld):
+ for file in ('index.html',):
+ bld.install_files('${PREFIX}/share/dcpomatic2/web', file)
+
diff --git a/wscript b/wscript
index d418ab6f4..79c369a9c 100644
--- a/wscript
+++ b/wscript
@@ -75,10 +75,13 @@ def options(opt):
opt.add_option('--workaround-gssapi', action='store_true', default=False, help='link to gssapi_krb5')
opt.add_option('--use-lld', action='store_true', default=False, help='use lld linker')
opt.add_option('--enable-disk', action='store_true', default=False, help='build dcpomatic2_disk tool; requires Boost process, lwext4 and nanomsg libraries')
+ opt.add_option('--enable-grok', action='store_true', default=False, help='build with support for grok J2K encoder')
opt.add_option('--warnings-are-errors', action='store_true', default=False, help='build with -Werror')
opt.add_option('--wx-config', help='path to wx-config')
opt.add_option('--enable-asan', action='store_true', help='build with asan')
opt.add_option('--disable-more-warnings', action='store_true', default=False, help='disable some warnings raised by Xcode 15 with the 2.16 branch')
+ opt.add_option('--c++17', action='store_true', default=False, help='build with C++17 and libxml++-4.0')
+ opt.add_option('--variant', help="build with variant")
def configure(conf):
conf.load('compiler_cxx')
@@ -86,6 +89,17 @@ def configure(conf):
if conf.options.target_windows_64 or conf.options.target_windows_32:
conf.load('winres')
+ if vars(conf.options)['c++17']:
+ cpp_std = '17'
+ conf.env.XMLPP_API = '4.0'
+ conf.env.PANGOMM_API = '2.48'
+ conf.env.CAIROMM_API = '1.16'
+ else:
+ cpp_std = '11'
+ conf.env.XMLPP_API = '2.6'
+ conf.env.PANGOMM_API = '1.4'
+ conf.env.CAIROMM_API = '1.0'
+
# Save conf.options that we need elsewhere in conf.env
conf.env.DISABLE_GUI = conf.options.disable_gui
conf.env.DISABLE_TESTS = conf.options.disable_tests
@@ -97,10 +111,12 @@ def configure(conf):
conf.env.DEBUG = conf.options.enable_debug
conf.env.STATIC_DCPOMATIC = conf.options.static_dcpomatic
conf.env.ENABLE_DISK = conf.options.enable_disk
+ conf.env.ENABLE_GROK = conf.options.enable_grok
if conf.options.destdir == '':
conf.env.INSTALL_PREFIX = conf.options.prefix
else:
conf.env.INSTALL_PREFIX = conf.options.destdir
+ conf.env.VARIANT = conf.options.variant if conf.options.variant else "dcpomatic"
conf.check_cxx(cxxflags=['-msse', '-mfpmath=sse'], msg='Checking for SSE support', mandatory=False, define_name='SSE')
@@ -112,11 +128,13 @@ def configure(conf):
'-Wall',
'-Wextra',
'-Wwrite-strings',
+ # getMessengerLogger() in the grok code triggers these warnings
+ '-Wno-nonnull',
'-Wno-error=deprecated',
# I tried and failed to ignore these with _Pragma
'-Wno-ignored-qualifiers',
'-D_FILE_OFFSET_BITS=64',
- '-std=c++11'])
+ '-std=c++' + cpp_std])
if conf.options.disable_more_warnings:
# These are for Xcode 15.0.1 with the v2.16.x-era
@@ -144,8 +162,10 @@ def configure(conf):
conf.env.append_value('CXXFLAGS', ['-Wno-cast-function-type'])
# Most gccs still give these warnings from boost::optional
conf.env.append_value('CXXFLAGS', ['-Wno-maybe-uninitialized'])
- if int(gcc[0]) > 4:
+ if int(gcc[0]) > 8:
# gcc 4.8.5 on Centos 7 does not have this warning
+ # gcc 7.5.0 on Ubuntu 18.04 and gcc 8.3.0 on Debian 10 do, but
+ # I didn't manage to turn it back off again with a pragma
conf.env.append_value('CXXFLAGS', ['-Wsuggest-override'])
if conf.options.enable_debug:
@@ -156,6 +176,9 @@ def configure(conf):
if conf.options.enable_disk:
conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_DISK')
+ if conf.options.enable_grok:
+ conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_GROK')
+
if conf.options.use_lld:
try:
conf.find_program('ld.lld')
@@ -228,6 +251,16 @@ def configure(conf):
# linked and others that should be dynamic. This doesn't work too well with waf
# as it wants them separate.
+ def check_via_pkg_config(conf, package, uselib_store, mandatory, static, minimum_version):
+ args = package if minimum_version is None else '%s >= %s' % (package, minimum_version)
+ args += ' --cflags'
+ if not static:
+ args += ' --libs'
+ msg = 'Checking for %s' % package
+ if minimum_version is not None:
+ msg += ' >= %s' % minimum_version
+ conf.check_cfg(package=package, args=args, uselib_store=uselib_store, mandatory=mandatory, msg=msg)
+
# libcurl
if conf.options.static_curl:
conf.env.STLIB_CURL = ['curl']
@@ -320,10 +353,10 @@ def configure(conf):
conf.check_cfg(package='fontconfig', args='--cflags --libs', uselib_store='FONTCONFIG', mandatory=True)
# pangomm
- conf.check_cfg(package='pangomm-1.4', args='--cflags --libs', uselib_store='PANGOMM', mandatory=True)
+ conf.check_cfg(package='pangomm-' + conf.env.PANGOMM_API, args='--cflags --libs', uselib_store='PANGOMM', mandatory=True)
# cairomm
- conf.check_cfg(package='cairomm-1.0', args='--cflags --libs', uselib_store='CAIROMM', mandatory=True)
+ conf.check_cfg(package='cairomm-' + conf.env.CAIROMM_API, args='--cflags --libs', uselib_store='CAIROMM', mandatory=True)
# leqm_nrt
conf.check_cfg(package='leqm_nrt', args='--cflags --libs', uselib_store='LEQM_NRT', mandatory=True)
@@ -355,12 +388,12 @@ def configure(conf):
# libdcp
if conf.options.static_dcp:
- conf.check_cfg(package='libdcp-1.0', args='libdcp-1.0 >= %s --cflags' % libdcp_version, uselib_store='DCP', mandatory=True)
+ check_via_pkg_config(conf, 'libdcp-1.0', 'DCP', mandatory=True, static=True, minimum_version=libdcp_version)
conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
- conf.env.STLIB_DCP = ['dcp-1.0', 'asdcp-carl', 'kumu-carl', 'openjp2']
+ conf.env.STLIB_DCP = ['dcp-1.0', 'asdcp-dcpomatic', 'kumu-dcpomatic', 'openjp2']
conf.env.LIB_DCP = ['glibmm-2.4', 'ssl', 'crypto', 'bz2', 'xslt', 'xerces-c']
else:
- conf.check_cfg(package='libdcp-1.0', args='libdcp-1.0 >= %s --cflags --libs' % libdcp_version, uselib_store='DCP', mandatory=True)
+ check_via_pkg_config(conf, 'libdcp-1.0', 'DCP', mandatory=True, static=False, minimum_version=libdcp_version)
conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
# libsub
@@ -374,10 +407,10 @@ def configure(conf):
# libxml++
if conf.options.static_xmlpp:
- conf.env.STLIB_XMLPP = ['xml++-2.6']
+ conf.env.STLIB_XMLPP = ['xml++-' + conf.env.XMLPP_API]
conf.env.LIB_XMLPP = ['xml2']
else:
- conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XMLPP', mandatory=True)
+ conf.check_cfg(package='libxml++-' + conf.env.XMLPP_API, args='--cflags --libs', uselib_store='XMLPP', mandatory=True)
# libxmlsec
if conf.options.static_xmlsec:
@@ -467,7 +500,7 @@ def configure(conf):
int main () { av_ebur128_get_true_peaks (0); }\n
""",
msg='Checking for EBUR128-patched FFmpeg',
- uselib='AVCODEC AVFILTER',
+ uselib='AVCODEC AVFILTER AVUTIL SWRESAMPLE',
define_name='DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG',
mandatory=False)
@@ -600,6 +633,18 @@ def configure(conf):
lib=deps,
uselib_store='BOOST_PROCESS')
+ # sqlite3
+ conf.check_cfg(package="sqlite3", args='--cflags --libs', uselib_store='SQLITE3', mandatory=True)
+ conf.check_cxx(fragment="""
+ #include <sqlite3.h>
+ int main() { sqlite3_prepare_v3(nullptr, "", -1, 0, nullptr, nullptr); }
+ """,
+ msg='Checking for sqlite3_prepare_v3',
+ uselib='SQLITE3',
+ define_name="DCPOMATIC_HAVE_SQLITE3_PREPARE_V3",
+ mandatory=False)
+
+
# Other stuff
conf.find_program('msgfmt', var='MSGFMT')
@@ -648,8 +693,24 @@ def configure(conf):
def build(bld):
create_version_cc(VERSION, bld.env.CXXFLAGS)
+ # waf can't find these dependencies by itself because they are only included if DCPOMATIC_GROK is defined,
+ # and I can't find a way to pass that to waf's dependency scanner
+ if bld.env.ENABLE_GROK:
+ for dep in (
+ 'src/lib/j2k_encoder.cc',
+ 'src/tools/dcpomatic.cc',
+ 'src/tools/dcpomatic_server.cc',
+ 'src/tools/dcpomatic_server_cli.cc',
+ 'src/tools/dcpomatic_batch.cc'
+ ):
+ bld.add_manual_dependency(bld.path.find_node(dep), bld.path.find_node('src/lib/grok/context.h'))
+ bld.add_manual_dependency(bld.path.find_node(dep), bld.path.find_node('src/lib/grok/messenger.h'))
+
+ bld.add_manual_dependency(bld.path.find_node('src/wx/full_config_dialog.cc'), bld.path.find_node('src/wx/grok/gpu_config_panel.h'))
+
bld.recurse('src')
bld.recurse('graphics')
+ bld.recurse('web')
if not bld.env.DISABLE_TESTS:
bld.recurse('test')