ffmpeg_options = {}
if target.platform != 'linux' or target.distro != 'arch':
- deps = [('ffmpeg', '7276e269a93c2ae30e302c34708e8095ac5475e8', ffmpeg_options)]
+ deps = [('ffmpeg', '0b73d2f5e70a04a67aa902902c42e3025ef3bb77', ffmpeg_options)]
else:
# Use distro-provided FFmpeg on Arch
deps = []
deps.append(('openssl', '54298369cacfe0ae01c5aa42ace8a463fd2e7a2e'))
if can_build_disk(target):
deps.append(('lwext4', 'ab082923a791b58478d1d9939d65a0583566ac1f'))
- deps.append(('ffcmp', '53c853d2935de3f2b0d53777529e48c102afd237'))
+ deps.append(('ffcmp', '5ab6ed3b75d8ca7cf1f66bb9fb08792b92f4b419'))
return deps
if target.platform == 'osx' and target.arch == 'arm64':
opt += ' --target-macos-arm64 --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
def build(target, options, for_package):
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_lib_env libboost_atomic "$dest"
copy_lib_env libboost_system "$dest"
copy_lib_env libboost_filesystem "$dest"
copy_lib_env libboost_thread "$dest"
# 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
}
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"
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"
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"
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=f)
if bits == 32:
""", 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"
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"
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"
-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\
}
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")) {
for (size_t i = 0; i < _sample_peak.size(); ++i) {
auto n = root->add_child("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) {
/* 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) {
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 (
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 };
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) {
_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->ch_layout = _channel_layout;
+LIBDCP_DISABLE_WARNINGS
_in_frame->channels = process_channels;
+LIBDCP_ENABLE_WARNINGS
int r = av_buffersrc_write_frame (_buffer_src_context, _in_frame);
#include "filter_graph.h"
extern "C" {
#include <libavfilter/buffersink.h>
+#include <libavutil/channel_layout.h>
}
class AudioBuffers;
private:
int _sample_rate;
int _channels;
- int64_t _channel_layout;
+ AVChannelLayout _channel_layout;
AVFrame* _in_frame;
};
#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>
);
} 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())
);
}
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));
+ 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)));
}
}
{
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()));
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
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::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;
-}
class Cinema : public std::enable_shared_from_this<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_)
: name (name_)
, emails (e)
, notes (notes_)
- , _utc_offset_hour (utc_offset_hour)
- , _utc_offset_minute (utc_offset_minute)
{}
explicit Cinema (cxml::ConstNodePtr);
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;
};
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 = {};
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());
}
_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());
}
_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);
+#ifdef DCPOMATIC_GROK
+ if (auto grok = f.optional_node_child("Grok")) {
+ _grok = Grok(grok);
+ }
+#endif
+
_export.read(f.optional_node_child("Export"));
}
catch (...) {
/* [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));
+ 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. */
/* [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));
+ e->set_attribute("id", raw_convert<string>(i));
e->add_child_text (_notification[i] ? "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));
+#ifdef DCPOMATIC_GROK
+ if (_grok) {
+ _grok->as_xml(root->add_child("Grok"));
+ }
+#endif
+
_export.write(root->add_child("Export"));
auto target = config_write_file();
return iter->second;
}
+
+#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
AUTO_CROP_THRESHOLD,
ALLOW_SMPTE_BV20,
ISDCF_NAME_PART_LENGTH,
+#ifdef DCPOMATIC_GROK
+ GROK,
+#endif
OTHER
};
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;
}
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 changed (Property p = OTHER);
boost::signals2::signal<void (Property)> Changed;
/** Emitted if read() failed on an existing Config file. There is nothing
bool _allow_smpte_bv20;
int _isdcf_name_part_length;
+#ifdef DCPOMATIC_GROK
+ boost::optional<Grok> _grok;
+#endif
+
ExportConfig _export;
static int const _current_version;
#define DCPOMATIC_CONTENT_VIDEO_H
+#include "dcpomatic_time.h"
#include "types.h"
{
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
--- /dev/null
+/*
+ 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 {};
+}
+
--- /dev/null
+#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;
+};
+
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:
using boost::optional;
-auto constexpr MEDIA_PATH_REQUIRED_MATCHES = 3;
-
-
Drive::Drive (string xml)
{
cxml::Document doc;
);
}
-
-
-/* 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;
-}
}
-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)
{
}
-/* 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)
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);
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) {
}
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) {
}
}
- 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
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);
}
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->audio) && !c->audio->mapping().mapped_output_channels().empty();
- },
- _("it overlaps other audio content; remove the other content."),
- why_not
- );
+ auto part = [](shared_ptr<const Content> c) {
+ return static_cast<bool>(c->audio) && !c->audio->mapping().mapped_output_channels().empty();
+ };
+
+ 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);
}
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
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 {
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 */
AV_PIX_FMT_XYZ12LE,
_forced_reduction
),
- _offset + frame
+ ContentTime::from_frames(_offset + frame, vfr)
);
} else {
video->emit (
AV_PIX_FMT_XYZ12LE,
_forced_reduction
),
- _offset + frame
+ ContentTime::from_frames(_offset + frame, vfr)
);
video->emit (
AV_PIX_FMT_XYZ12LE,
_forced_reduction
),
- _offset + frame
+ ContentTime::from_frames(_offset + frame, vfr)
);
}
}
*/
+
/** @file src/dcp_encoder.cc
* @brief A class which takes a Film and some Options, then uses those to encode the film into a DCP.
*
* as a parameter to the constructor.
*/
+
+#include "audio_decoder.h"
+#include "compose.hpp"
#include "dcp_encoder.h"
-#include "j2k_encoder.h"
#include "film.h"
-#include "video_decoder.h"
-#include "audio_decoder.h"
-#include "player.h"
+#include "j2k_encoder.h"
#include "job.h"
-#include "writer.h"
-#include "compose.hpp"
+#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;
_writer.finish(_film->dir(_film->dcp_name()));
}
+
+void
+DCPEncoder::pause()
+{
+ _j2k_encoder.pause();
+}
+
+
+void
+DCPEncoder::resume()
+{
+ _j2k_encoder.resume();
+}
+
void
DCPEncoder::video (shared_ptr<PlayerVideo> data, DCPTime time)
{
class Player;
class PlayerVideo;
+struct frames_not_lost_when_threads_disappear;
+
/** @class DCPEncoder */
class DCPEncoder : public Encoder
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);
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.
*/
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"
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;
int _j2k_bandwidth; ///< J2K bandwidth to use
Resolution _resolution; ///< Resolution (2K or 4K)
};
+
+#endif
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);
}
#include "film.h"
#include "kdm_with_metadata.h"
#include <dcp/raw_convert.h>
-#include <dcp/utc_offset.h>
using std::make_shared;
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");
}
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));
}
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
+ dcp::LocalTime valid_from,
+ dcp::LocalTime valid_to
)
{
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 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);
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_)
{
}
void as_xml (xmlpp::Element *) const override;
std::vector<std::string> emails;
- int utc_offset_hour;
- int utc_offset_minute;
};
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
+ dcp::LocalTime valid_from,
+ dcp::LocalTime valid_to
);
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);
DKDMGroup::as_xml (xmlpp::Element* node) const
{
auto f = node->add_child("DKDMGroup");
- f->set_attribute ("Name", _name);
+ f->set_attribute("name", _name);
for (auto i: _children) {
i->as_xml (f);
}
#endif
, _verbose (verbose)
, _num_threads (num_threads)
+ , _frames_encoded(0)
{
}
throw;
}
+ ++_frames_encoded;
+
return dcp_video_frame.index ();
}
#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>
void run () override;
+ int frames_encoded() const {
+ return _frames_encoded;
+ }
+
private:
void handle (std::shared_ptr<Socket>) override;
void worker_thread ();
bool _verbose;
int _num_threads;
Waker _waker;
+ boost::atomic<int> _frames_encoded;
struct Broadcast {
static EncodeServerFinder* instance ();
static void drop ();
- void stop ();
-
std::list<EncodeServerDescription> servers () const;
/** Emitted whenever the list of servers changes */
~EncodeServerFinder ();
void start ();
+ void stop ();
void search_thread ();
void listen_thread ();
/** @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;
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;
}
/* 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);
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");
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);
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
)
);
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;
}
/* 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);
_rotation = 0;
if (rotate_tag && *rotate_tag->value && strcmp(rotate_tag->value, "0")) {
}
}
- 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));
}
_rotation = *_rotation - 360 * floor (*_rotation / 360 + 0.9 / 360);
).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");
}
_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) {
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);
static constexpr char metadata_file[] = "metadata.xml";
+static constexpr char ui_state_file[] = "ui.xml";
/* 5 -> 6
root->add_child("UserExplicitVideoFrameRate")->add_child_text(_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));
+ m->set_attribute("type", dcp::marker_to_string(marker.first));
m->add_child_text(raw_convert<string>(marker.second.get()));
}
for (auto i: _ratings) {
_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")) {
_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) {
+ root->add_child(state.first)->add_child_text(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 (...) {}
+}
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();
+
/** Emitted when some property has of the Film is about to change or has changed */
mutable boost::signals2::signal<void (ChangeType, FilmProperty)> Change;
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;
*/
bool _tolerant;
+ std::map<std::string, std::string> _ui_state;
+
mutable boost::mutex _info_file_mutex;
boost::signals2::scoped_connection _playlist_change_connection;
--- /dev/null
+/*
+ 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->j2k_bandwidth(),
+ 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;
+};
+
+}
+
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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();
+}
--- /dev/null
+/*
+ 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;
+};
+
}
}
- 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;
}
#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"
using std::cout;
+using std::dynamic_pointer_cast;
using std::exception;
using std::list;
using std::make_shared;
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.
, _history (200)
, _writer (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
}
{
_server_found_connection.disconnect();
- 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());
}
void
-J2KEncoder::end ()
+J2KEncoder::pause()
+{
+#ifdef DCPOMATIC_GROK
+ if (!Config::instance()->grok().get_value_or({}).enable) {
+ return;
+ }
+ return;
+
+ 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);
/* 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 ();
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 ());
+ }
}
}
+
+#ifdef DCPOMATIC_GROK
+ delete _context;
+ _context = nullptr;
+#endif
}
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);
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->resolution()
- ));
+ );
+ _queue.push_back (dcpv);
/* The queue might not be empty any more, so notify anything which is
waiting on that.
}
-/** 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();
}
#include "enum_indexed_vector.h"
#include "event_history.h"
#include "exception_store.h"
+#include "j2k_encoder_thread.h"
#include "writer.h"
#include <boost/optional.hpp>
#include <boost/signals2.hpp>
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.
/** 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 pause();
+ void resume();
+
/** Called when a processing run has finished */
- void end ();
+ void end();
boost::optional<float> current_encoding_rate () const;
int video_frames_enqueued () const;
- 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 */
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;
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;
};
--- /dev/null
+/*
+ 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");
+ }
+}
+
+
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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();
+}
+
--- /dev/null
+#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
Job::cancel ()
{
if (_thread.joinable()) {
- resume();
+ Job::resume();
_thread.interrupt ();
_thread.join ();
}
if (paused) {
+ pause();
_pause_changed.notify_all ();
}
{
if (running ()) {
set_state (PAUSED_BY_PRIORITY);
+ pause();
_pause_changed.notify_all ();
}
}
}
void start ();
+ virtual void pause() {}
bool pause_by_user ();
void pause_by_priority ();
- void resume ();
+ virtual void resume ();
void cancel ();
bool is_new () const;
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");
};
-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)
{
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,
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,
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,
+ valid_from,
+ valid_to,
formulation,
disable_forensic_marking_picture,
disable_forensic_marking_audio
name_values['c'] = i->cinema ? i->cinema->name : "";
name_values['s'] = i->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));
}
+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);
+}
+
+
optional<string>
kdm_cli (int argc, char* argv[], std::function<void (string)> out)
try
optional<string> screen;
vector<shared_ptr<Screen>> screens;
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;
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;
(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);
+ cinema = make_shared<Cinema>(optarg, vector<string>(), "");
break;
case 'S':
/* Similarly, this could be the name of a new (temporary) screen or the name of a 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];
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) {
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());
auto tj = make_shared<DCPTranscodeJob>(film, behaviour);
tj->set_encoder (make_shared<DCPEncoder>(film, tj));
JobManager::instance()->add (tj);
+
+ return tj;
}
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);
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);
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
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
);
+
+
+}
+
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;
_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;
}
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,
PresetColourConversion::all().front().conversion,
VideoRange::FULL,
std::weak_ptr<Content>(),
- boost::optional<Frame>(),
+ boost::optional<dcpomatic::ContentTime>(),
false
);
}
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;
}
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;
}
if (done) {
+ 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);
}
}
}
-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));
+ }
+ }
}
return;
}
- FrameRateChange frc(film, piece->content);
- if (frc.skip && (video.frame % 2) == 1) {
- return;
- }
-
vector<Eyes> eyes_to_emit;
if (!film->three_d()) {
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;
}
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;
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));
}
}
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;
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); });
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);
void video (std::weak_ptr<Piece>, ContentVideo);
void audio (std::weak_ptr<Piece>, AudioStreamPtr, ContentAudio);
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;
/** 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;
using boost::optional;
using dcp::Data;
using dcp::raw_convert;
+using namespace dcpomatic;
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)
, _colour_conversion (colour_conversion)
, _video_range (video_range)
, _content (content)
- , _video_frame (video_frame)
+ , _video_time(video_time)
, _error (error)
{
_colour_conversion,
_video_range,
_content,
- _video_frame,
+ _video_time,
_error
);
}
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;
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
);
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;
--- /dev/null
+/*
+ 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;
+}
+
--- /dev/null
+#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;
+};
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,
+ dcp::LocalTime valid_from,
+ dcp::LocalTime valid_to,
dcp::Formulation formulation,
bool disable_forensic_marking_picture,
optional<int> disable_forensic_marking_audio,
}
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 ? 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(
+ 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
);
}
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);
#define DCPOMATIC_SCREEN_H
-#include "kdm_with_metadata.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>
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,
+ dcp::LocalTime valid_from,
+ dcp::LocalTime valid_to,
dcp::Formulation formulation,
bool disable_forensic_marking_picture,
boost::optional<int> disable_forensic_marking_audio,
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;
}
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 */
!_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);
}
}
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 ();
/* 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
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 (_language) {
auto lang = text->add_child("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");
}
}
}
+void
+TranscodeJob::pause()
+{
+ _encoder->pause();
+}
+
+
+void TranscodeJob::resume()
+{
+ _encoder->resume();
+ Job::resume();
+}
+
+
string
TranscodeJob::status () const
{
class Encoder;
+struct frames_not_lost_when_threads_disappear;
+
/** @class TranscodeJob
* @brief A job which transcodes a Film to another format.
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);
private:
+ friend struct ::frames_not_lost_when_threads_disappear;
+
virtual void post_transcode () {}
float frames_per_second() const;
{
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);
}
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 ();
return mapped;
}
-Eyes
-increment_eyes (Eyes e)
-{
- if (e == Eyes::LEFT) {
- return Eyes::RIGHT;
- }
-
- return Eyes::LEFT;
-}
-
size_t
utf8_strlen (string s)
return output;
}
+
+
+#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
#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>
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
+
+
+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;
+}
#endif
}
-/** @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
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);
#include "compose.hpp"
-#include "film.h"
#include "frame_interval_checker.h"
#include "image.h"
#include "j2k_image_proxy.h"
}
-/** 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;
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(
}
}
- 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;
}
}
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;
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
);
}
#include "exception_store.h"
#include "font_id_map.h"
#include "player_text.h"
+#include "text_type.h"
#include "weak_film.h"
#include <dcp/atmos_frame.h>
#include <boost/thread.hpp>
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
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
referenced_reel_asset.cc
release_notes.cc
render_text.cc
+ remote_j2k_encoder_thread.cc
resampler.cc
resolution.cc
rgba.cc
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'
#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"
#include "lib/ffmpeg_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"
#define NEEDS_SELECTED_VIDEO_CONTENT 0x20
#define NEEDS_CLIPBOARD 0x40
#define NEEDS_ENCRYPTION 0x80
+#define NEEDS_DCP_CONTENT 0x100
map<wxMenuItem*, int> menu_items;
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,
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);
{
auto film = make_shared<Film>(file);
auto const notes = film->read_metadata ();
+ film->read_ui_state();
if (film->state_version() == 4) {
error_dialog (
_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) {
FontConfig::drop();
ev.Skip ();
+ JobManager::drop ();
}
void active_jobs_changed()
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) {
enabled = false;
}
+ if ((j.second & NEEDS_DCP_CONTENT) && !have_dcp_content) {
+ enabled = false;
+ }
+
j.first->Enable (enabled);
}
}
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);
_history_items = history.size ();
dcpomatic_log->set_types (Config::instance()->log_types());
+
+#ifdef DCPOMATIC_GROK
+ if (what == Config::GROK) {
+ setup_grok_library_path();
+ }
+#endif
}
void update_checker_state_changed ()
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;
notes.Centre();
notes.ShowModal();
}
+
+#ifdef DCPOMATIC_GROK
+ grk_plugin::setMessengerLogger(new grk_plugin::GrokLogger("[GROK] "));
+ setup_grok_library_path();
+#endif
}
catch (exception& e)
{
#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"
}
ev.Skip ();
+ JobManager::drop ();
}
void file_add_film ()
);
}
}
+
+#ifdef DCPOMATIC_GROK
+ if (what == Config::GROK) {
+ setup_grok_library_path();
+ }
+#endif
}
boost::optional<boost::filesystem::path> _last_parent;
}
}
+#ifdef DCPOMATIC_GROK
+ grk_plugin::setMessengerLogger(new grk_plugin::GrokLogger("[GROK] "));
+ setup_grok_library_path();
+#endif
+
return true;
}
#include "lib/ffmpeg_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"
signal_manager = new SignalManager ();
if (no_remote || export_format) {
- EncodeServerFinder::instance()->stop ();
+ EncodeServerFinder::drop();
}
if (json_port) {
}
}
+#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";
#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>
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,
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;
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;
_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.
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));
}
ev.Skip ();
+ JobManager::drop ();
}
void copy ()
#include "wx/wx_signal_manager.h"
#include "wx/wx_util.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"
SetExitOnFrameDelete (false);
+#ifdef DCPOMATIC_GROK
+ grk_plugin::setMessengerLogger(new grk_plugin::GrokLogger("[GROK] "));
+ setup_grok_library_path();
+#endif
+
return true;
}
#include "lib/config.h"
#include "lib/image.h"
#include "lib/file_log.h"
+#ifdef DCPOMATIC_GROK
+#include "lib/grok/context.h"
+#endif
#include "lib/null_log.h"
#include "lib/version.h"
#include "lib/encode_server.h"
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 {
t = new StaticText (
this,
- _("(C) 2012-2024 Carl Hetherington, Terrence Meiczinger\n Ole Laursen"),
+ _("(C) 2012-2024 Carl Hetherington, Terrence Meiczinger\nOle Laursen, Aaron Boxer"),
wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER
);
written_by.Add (wxT ("Terrence Meiczinger"));
written_by.Add (wxT ("Mart Jansink"));
written_by.Add (wxT ("Ole Laursen"));
+ written_by.Add (wxT ("Aaron Boxer"));
add_section (_("Written by"), written_by);
wxArrayString with_help_from;
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 (""));
_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);
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));
{
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;
/* 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) {
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);
}
-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>)
{
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;
return GetValue();
}
+
+void
+CheckBox::set(bool state)
+{
+ SetValue(state);
+}
+
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) {
#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)
: wxDialog (parent, wxID_ANY, title)
{
auto overall_sizer = new wxBoxSizer (wxVERTICAL);
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));
- ++r;
-
add_label_to_sizer (sizer, this, _("Notes"), true, wxGBPosition(r, 0));
_notes = new wxTextCtrl (this, wxID_ANY, std_to_wx(notes), wxDefaultPosition, wxSize(500, -1));
sizer->Add (_notes, wxGBPosition(r, 1));
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);
}
-int
-CinemaDialog::utc_offset_hour () const
-{
- int const sel = _utc_offset->GetSelection();
- if (sel < 0 || sel > int(_offsets.size())) {
- return 0;
- }
-
- return _offsets[sel].hour;
-}
-
-
-int
-CinemaDialog::utc_offset_minute () const
-{
- int const sel = _utc_offset->GetSelection();
- if (sel < 0 || sel > int(_offsets.size())) {
- return 0;
- }
-
- return _offsets[sel].minute;
-}
-
-
string
CinemaDialog::notes () const
{
wxString,
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
+ std::string notes = ""
);
std::string name () const;
std::string notes () const;
std::vector<std::string> emails () const;
- int utc_offset_hour () const;
- int utc_offset_minute () const;
private:
void set_emails (std::vector<std::string>);
wxTextCtrl* _notes;
EditableList<std::string, EmailDialog>* _email_list;
std::vector<std::string> _emails;
- wxChoice* _utc_offset;
- std::vector<Offset> _offsets;
};
_sizer->Add (_grid, 0, wxALL, 8);
}
-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 ()
{
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;
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);
_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));
-
for (auto i: DCPContentType::all()) {
_dcp_content_type->add(i->pretty_name());
}
_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));
_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;
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:
_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));
_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) {
_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;
}
--- /dev/null
+/*
+ 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);
+ }
+}
+
--- /dev/null
+/*
+ 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;
+};
+
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);
}
{
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)
}
+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.
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);
+ }
}
#include <boost/signals2.hpp>
-class wxNotebook;
-class Film;
class ContentPanel;
class DCPPanel;
+class Film;
class FilmViewer;
+class wxBookCtrlEvent;
+class wxNotebook;
/** @class FilmEditor
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;
#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 "lib/config.h"
#include "lib/cross.h"
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));
--- /dev/null
+/*
+ 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;
+};
/* 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 */
*/
+#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>
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(""));
_warning->SetForegroundColour (wxColour (255, 0, 0));
_warning->SetFont(font);
+ /* Default to UTC */
+ size_t sel = get_offsets(_offsets);
+ for (size_t i = 0; i < _offsets.size(); ++i) {
+ _utc_offset->add(_offsets[i].name);
+ if (_offsets[i].hour == 0 && _offsets[i].minute == 0) {
+ sel = i;
+ }
+ }
+
+ _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.
*/
_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::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());
}
TimingChanged ();
}
+
+
+dcp::UTCOffset
+KDMTimingPanel::utc_offset() const
+{
+ auto const sel = _utc_offset->get();
+ if (!sel || *sel >= int(_offsets.size())) {
+ return {};
+ }
+
+ auto const& offset = _offsets[*sel];
+ int const minute_scale = offset.hour < 0 ? -1 : 1;
+
+ return { offset.hour, minute_scale * offset.minute };
+}
+
+
*/
+
+#include "wx_util.h"
+#include <dcp/utc_offset.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
#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;
private:
void changed () const;
- static boost::posix_time::ptime posix_time (wxDatePickerCtrl *, TimePicker *);
+ 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;
wxStaticText* _warning;
+ std::vector<Offset> _offsets;
};
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)
_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;
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);
{
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;
-}
-
-
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>()
);
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 ();
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;
};
{
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());
+ auto r = std::make_shared<DKDMRecipient>(dialog.name(), dialog.notes(), dialog.recipient(), dialog.emails());
Config::instance()->add_dkdm_recipient (r);
add_recipient (r);
}
auto c = *_selected.begin();
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"), c.second->name, c.second->notes, c.second->emails, c.second->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);
}
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 = make_shared<Cinema>(dialog.name(), dialog.emails(), dialog.notes());
auto cinemas = sorted_cinemas();
void
ScreensPanel::edit_cinema(shared_ptr<Cinema> cinema)
{
- CinemaDialog dialog(
- GetParent(), _("Edit cinema"), cinema->name, cinema->emails, cinema->notes, cinema->utc_offset_hour(), cinema->utc_offset_minute()
- );
+ CinemaDialog dialog(GetParent(), _("Edit cinema"), cinema->name, cinema->emails, cinema->notes);
if (dialog.ShowModal() == wxID_OK) {
cinema->name = dialog.name();
cinema->emails = dialog.emails();
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);
DCPOMATIC_ASSERT(item);
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"));
_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);
{
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);
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) {
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 ();
}
-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 ()
{
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;
void update_outline_subtitles_in_viewer ();
void clear_outline_subtitles ();
- CheckBox* _reference;
- wxStaticText* _reference_note;
CheckBox* _outline_subtitles = nullptr;
CheckBox* _use;
wxChoice* _type;
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,
_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);
_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));
{
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;
} 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);
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()) {
}
}
- bool const enable = !_reference->GetValue() && any_use;
+ bool const enable = !reference && any_use;
if (!enable) {
_frame_type->wrapped()->Enable (false);
}
-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 ()
{
void content_selection_changed () override;
private:
- void reference_clicked ();
void colour_conversion_changed ();
void edit_colour_conversion_clicked ();
void range_changed ();
void setup_description ();
void setup_sensitivity ();
- CheckBox* _reference;
- wxStaticText* _reference_note;
wxStaticText* _type_label;
ContentChoice<VideoContent, VideoFrameType>* _frame_type;
wxStaticText* _crop_label;
controls.cc
credentials_download_certificate_panel.cc
custom_scale_dialog.cc
+ dcp_referencing_dialog.cc
dcp_panel.cc
dcp_text_track_dialog.cc
dcpomatic_button.cc
/** 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
/** @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/image.h"
#include "lib/j2k_image_proxy.h"
using boost::thread;
using boost::optional;
using dcp::ArrayData;
+using namespace dcpomatic;
void
ColourConversion(),
VideoRange::FULL,
weak_ptr<Content>(),
- optional<Frame>(),
+ optional<ContentTime>(),
false
);
ColourConversion(),
VideoRange::FULL,
weak_ptr<Content>(),
- optional<Frame>(),
+ optional<ContentTime>(),
false
);
ColourConversion(),
VideoRange::FULL,
weak_ptr<Content>(),
- optional<Frame>(),
+ optional<ContentTime>(),
false
);
PresetColourConversion::all().front().conversion,
VideoRange::FULL,
weak_ptr<Content>(),
- optional<Frame>(),
+ optional<ContentTime>(),
false
);
}
+BOOST_AUTO_TEST_CASE(real_encode_with_server)
+{
+ auto content = content_factory(TestPaths::private_data() / "dolby_aurora.vob");
+ auto film = new_test_film2("real_encode_with_server", content);
+
+ 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();
+}
+
{
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;
*/
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);
}
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();
}
-BOOST_AUTO_TEST_CASE (config_upgrade_test)
+/* 2.14 -> 2.18 */
+BOOST_AUTO_TEST_CASE (config_upgrade_test1)
{
ConfigRestorer cr;
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.xml", {});
+#else
+ check_xml(dir / "2.18" / "config.xml", "test/data/2.18.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"));
+}
+
+
+/* 2.16 -> 2.18 */
+BOOST_AUTO_TEST_CASE (config_upgrade_test2)
+{
+ ConfigRestorer cr;
+
+ 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);
+
+#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");
+ Config::instance();
+ try {
+ /* This will fail to write cinemas.xml since the link is to a non-existent directory */
+ Config::instance()->write();
+ } catch (...) {}
+
+ 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.16" / "config.xml", "test/data/2.16.config.xml", {});
+ 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.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"));
}
Config::instance()->write();
- Config::instance()->add_cinema(make_shared<Cinema>("My Great Cinema", vector<string>(), "", 0, 0));
+ Config::instance()->add_cinema(make_shared<Cinema>("My Great Cinema", vector<string>(), ""));
Config::instance()->write();
boost::filesystem::copy_file (dir / "cinemas.xml", dir / "backup_for_test.xml");
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");
auto const cinemas = dir / "cinemas.xml";
/* Back things up */
- boost::filesystem::copy_file(dir / "2.16" / "config.xml", dir / "config_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.xml");
/* Corrupt the cinemas */
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(dir / "2.18" / "config.xml", dir / "config_backup_for_test.xml");
check_text_file(cinemas, dir / "cinemas_backup_for_test.xml");
}
film->set_audio_channels(16);
make_and_verify_dcp (film);
- check_dcp (TestPaths::private_data() / "fha", film);
+ check_dcp (TestPaths::private_data() / "v2.18.x" / "fha", film);
cl.run ();
}
-Subproject commit 4cb08962ba07e99c442cb12091c0347d84d8fd89
+Subproject commit ddf878730354cdec8a802a59543591f6f943f5c0
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");
-}
-
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 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);
}
}
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)
+ }
+ );
}
/** 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");
+ auto film = new_test_film("ffmpeg_encoder_h264_test5");
+ film->set_name("ffmpeg_encoder_h264_test5");
film->set_container (Ratio::from_id ("185"));
film->set_audio_channels (6);
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);
}
--- /dev/null
+/*
+ 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_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_film2("local_threads_created_and_destroyed", {});
+ Writer writer(film, {});
+ 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_film2("remote_threads_created_and_destroyed", {});
+ Writer writer(film, {});
+ 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_film2("frames_not_lost", content);
+ film->write_metadata();
+ auto job = make_dcp(film, TranscodeJob::ChangedBehaviour::IGNORE);
+ auto& encoder = dynamic_pointer_cast<DCPEncoder>(job->_encoder)->_j2k_encoder;
+
+ 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);
+}
+
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);
+ auto cinema_a = std::make_shared<Cinema>("Dean's Screens", vector<string>(), "");
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);
- auto cinema_b = std::make_shared<Cinema>("Floyd's Celluloid", vector<string>(), "", 0, 0);
+ auto cinema_b = std::make_shared<Cinema>("Floyd's Celluloid", vector<string>(), "");
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);
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));
+}
+
auto crypt_cert = c->decryption_chain()->leaf();
- /* Cinema A: UTC +4:30 */
- auto cinema_a = make_shared<Cinema>("Cinema A", vector<string>(), "", 4, 30);
+ auto cinema_a = make_shared<Cinema>("Cinema A", vector<string>(), "");
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);
+ auto cinema_b = make_shared<Cinema>("Cinema B", vector<string>(), "");
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>());
auto sign_cert = c->signer_chain()->leaf();
- dcp::LocalTime from (sign_cert.not_before());
+ dcp::LocalTime from = sign_cert.not_before();
+ from.set_offset({ 4, 30 });
from.add_months (2);
- dcp::LocalTime until (sign_cert.not_after());
+ dcp::LocalTime until = sign_cert.not_after();
+ until.set_offset({ 4, 30 });
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;
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),
+ from,
+ until,
dcp::Formulation::MODIFIED_TRANSITIONAL_1,
false,
optional<int>(),
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)
};
auto kdm = kdm_for_screen (
make_kdm,
i,
- boost::posix_time::time_from_string(from_string),
- boost::posix_time::time_from_string(until_string),
+ from,
+ until,
dcp::Formulation::MODIFIED_TRANSITIONAL_1,
false,
optional<int>(),
using std::make_shared;
+using namespace dcpomatic;
BOOST_AUTO_TEST_CASE (low_bitrate_test)
boost::optional<ColourConversion>(),
VideoRange::FULL,
std::weak_ptr<Content>(),
- boost::optional<Frame>(),
+ boost::optional<ContentTime>(),
false
);
};
boost::filesystem::remove_all(out);
+ boost::filesystem::remove_all("test/data/map_with_given_config/2.18");
Config::instance()->drop();
vector<string> output_messages;
using std::list;
using std::shared_ptr;
using std::make_shared;
+using std::vector;
using boost::bind;
using boost::optional;
#if BOOST_VERSION >= 106100
while (!player->pass()) {}
}
+
+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_film2("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));
+}
+
#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);
}
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();
}
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 ()
interrupt_encoder_test.cc
isdcf_name_test.cc
j2k_bandwidth_test.cc
+ j2k_encode_threading_test.cc
job_manager_test.cc
kdm_cli_test.cc
kdm_naming_test.cc
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')
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:
'-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',
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:
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')
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)
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')