summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2024-03-19 14:02:47 +0100
committerCarl Hetherington <cth@carlh.net>2024-04-22 13:03:04 +0200
commitf72a79c93626e773214f1a0318adf2445ac2ee72 (patch)
tree7b84a9c0c000618893a3fa3d609507c855735669 /src
parent5b2e3126602d508498a99bce256f5f465f095d43 (diff)
Support encoding of MPEG2 DCPs.
Diffstat (limited to 'src')
-rw-r--r--src/lib/dcp_film_encoder.cc11
-rw-r--r--src/lib/film.cc16
-rw-r--r--src/lib/film.h7
-rw-r--r--src/lib/film_property.h1
-rw-r--r--src/lib/mpeg2_encoder.cc80
-rw-r--r--src/lib/mpeg2_encoder.h42
-rw-r--r--src/lib/reel_writer.cc114
-rw-r--r--src/lib/reel_writer.h13
-rw-r--r--src/lib/video_encoding.cc56
-rw-r--r--src/lib/video_encoding.h34
-rw-r--r--src/lib/writer.cc14
-rw-r--r--src/lib/writer.h7
-rw-r--r--src/lib/wscript2
-rw-r--r--src/wx/dcp_panel.cc16
14 files changed, 366 insertions, 47 deletions
diff --git a/src/lib/dcp_film_encoder.cc b/src/lib/dcp_film_encoder.cc
index b508b66b6..17f531693 100644
--- a/src/lib/dcp_film_encoder.cc
+++ b/src/lib/dcp_film_encoder.cc
@@ -33,6 +33,7 @@
#include "film.h"
#include "j2k_encoder.h"
#include "job.h"
+#include "mpeg2_encoder.h"
#include "player.h"
#include "player_video.h"
#include "referenced_reel_asset.h"
@@ -67,10 +68,18 @@ using namespace dcpomatic;
DCPFilmEncoder::DCPFilmEncoder(shared_ptr<const Film> film, weak_ptr<Job> job)
: FilmEncoder(film, job)
, _writer(film, job)
- , _encoder(new J2KEncoder(film, _writer))
, _finishing (false)
, _non_burnt_subtitles (false)
{
+ switch (_film->video_encoding()) {
+ case VideoEncoding::JPEG2000:
+ _encoder.reset(new J2KEncoder(film, _writer));
+ break;
+ case VideoEncoding::MPEG2:
+ _encoder.reset(new MPEG2Encoder(film, _writer));
+ break;
+ }
+
_player_video_connection = _player.Video.connect(bind(&DCPFilmEncoder::video, this, _1, _2));
_player_audio_connection = _player.Audio.connect(bind(&DCPFilmEncoder::audio, this, _1, _2));
_player_text_connection = _player.Text.connect(bind(&DCPFilmEncoder::text, this, _1, _2, _3, _4));
diff --git a/src/lib/film.cc b/src/lib/film.cc
index 3de536a6d..54c68d756 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -169,6 +169,7 @@ Film::Film (optional<boost::filesystem::path> dir)
, _three_d (false)
, _sequence (true)
, _interop (Config::instance()->default_interop ())
+ , _video_encoding(VideoEncoding::JPEG2000)
, _limit_to_smpte_bv20(false)
, _audio_processor (0)
, _reel_type (ReelType::SINGLE)
@@ -251,6 +252,9 @@ Film::video_identifier () const
if (_interop) {
s += "_I";
+ if (_video_encoding == VideoEncoding::MPEG2) {
+ s += "_M";
+ }
} else {
s += "_S";
if (_limit_to_smpte_bv20) {
@@ -405,6 +409,7 @@ Film::metadata (bool with_content_paths) const
cxml::add_text_child(root, "ThreeD", _three_d ? "1" : "0");
cxml::add_text_child(root, "Sequence", _sequence ? "1" : "0");
cxml::add_text_child(root, "Interop", _interop ? "1" : "0");
+ cxml::add_text_child(root, "VideoEncoding", video_encoding_to_string(_video_encoding));
cxml::add_text_child(root, "LimitToSMPTEBv20", _limit_to_smpte_bv20 ? "1" : "0");
cxml::add_text_child(root, "Encrypted", _encrypted ? "1" : "0");
cxml::add_text_child(root, "Key", _key.hex ());
@@ -595,6 +600,9 @@ Film::read_metadata (optional<boost::filesystem::path> path)
_three_d = f.bool_child ("ThreeD");
_interop = f.bool_child ("Interop");
+ if (auto encoding = f.optional_string_child("VideoEncoding")) {
+ _video_encoding = video_encoding_from_string(*encoding);
+ }
_limit_to_smpte_bv20 = f.optional_bool_child("LimitToSMPTEBv20").get_value_or(false);
_key = dcp::Key (f.string_child ("Key"));
_context_id = f.optional_string_child("ContextID").get_value_or (dcp::make_uuid ());
@@ -1220,6 +1228,14 @@ Film::set_interop (bool i)
void
+Film::set_video_encoding(VideoEncoding encoding)
+{
+ FilmChangeSignaller ch(this, FilmProperty::VIDEO_ENCODING);
+ _video_encoding = encoding;
+}
+
+
+void
Film::set_limit_to_smpte_bv20(bool limit)
{
FilmChangeSignaller ch(this, FilmProperty::LIMIT_TO_SMPTE_BV20);
diff --git a/src/lib/film.h b/src/lib/film.h
index 392f1dc5e..ff3dee6fc 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -41,6 +41,7 @@
#include "transcode_job.h"
#include "types.h"
#include "util.h"
+#include "video_encoding.h"
#include <dcp/encrypted_kdm.h>
#include <dcp/file.h>
#include <dcp/key.h>
@@ -275,6 +276,10 @@ public:
return _interop;
}
+ VideoEncoding video_encoding() const {
+ return _video_encoding;
+ }
+
bool limit_to_smpte_bv20() const {
return _limit_to_smpte_bv20;
}
@@ -408,6 +413,7 @@ public:
void set_isdcf_date_today ();
void set_sequence (bool);
void set_interop (bool);
+ void set_video_encoding(VideoEncoding encoding);
void set_limit_to_smpte_bv20(bool);
void set_audio_processor (AudioProcessor const * processor);
void set_reel_type (ReelType);
@@ -529,6 +535,7 @@ private:
bool _three_d;
bool _sequence;
bool _interop;
+ VideoEncoding _video_encoding;
bool _limit_to_smpte_bv20;
AudioProcessor const * _audio_processor;
ReelType _reel_type;
diff --git a/src/lib/film_property.h b/src/lib/film_property.h
index b755a4887..ebda0e807 100644
--- a/src/lib/film_property.h
+++ b/src/lib/film_property.h
@@ -47,6 +47,7 @@ enum class FilmProperty {
THREE_D,
SEQUENCE,
INTEROP,
+ VIDEO_ENCODING,
LIMIT_TO_SMPTE_BV20,
AUDIO_PROCESSOR,
REEL_TYPE,
diff --git a/src/lib/mpeg2_encoder.cc b/src/lib/mpeg2_encoder.cc
new file mode 100644
index 000000000..49bff19d4
--- /dev/null
+++ b/src/lib/mpeg2_encoder.cc
@@ -0,0 +1,80 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "mpeg2_encoder.h"
+#include "writer.h"
+#include <dcp/ffmpeg_image.h>
+extern "C" {
+#include <libavutil/pixfmt.h>
+}
+
+
+using std::shared_ptr;
+
+
+MPEG2Encoder::MPEG2Encoder(shared_ptr<const Film> film, Writer& writer)
+ : VideoEncoder(film, writer)
+ , _transcoder(film->frame_size(), film->video_frame_rate(), film->video_bit_rate())
+{
+
+}
+
+
+void
+MPEG2Encoder::encode(shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time)
+{
+ VideoEncoder::encode(pv, time);
+
+ auto image = pv->image(
+ [](AVPixelFormat) { return AV_PIX_FMT_YUV420P; },
+ VideoRange::VIDEO,
+ false
+ );
+
+ dcp::FFmpegImage ffmpeg_image(time.get() * _film->video_frame_rate() / dcpomatic::DCPTime::HZ);
+
+ DCPOMATIC_ASSERT(image->size() == ffmpeg_image.size());
+
+ auto height = image->size().height;
+
+ for (int y = 0; y < height; ++y) {
+ memcpy(ffmpeg_image.y() + ffmpeg_image.y_stride() * y, image->data()[0] + image->stride()[0] * y, ffmpeg_image.y_stride());
+ }
+
+ for (int y = 0; y < height / 2; ++y) {
+ memcpy(ffmpeg_image.u() + ffmpeg_image.u_stride() * y, image->data()[1] + image->stride()[1] * y, ffmpeg_image.u_stride());
+ memcpy(ffmpeg_image.v() + ffmpeg_image.v_stride() * y, image->data()[2] + image->stride()[2] * y, ffmpeg_image.v_stride());
+ }
+
+ if (auto compressed = _transcoder.compress_frame(std::move(ffmpeg_image))) {
+ _writer.write(compressed->first, compressed->second);
+ }
+}
+
+
+void
+MPEG2Encoder::end()
+{
+ if (auto compressed = _transcoder.flush()) {
+ _writer.write(compressed->first, compressed->second);
+ }
+}
+
diff --git a/src/lib/mpeg2_encoder.h b/src/lib/mpeg2_encoder.h
new file mode 100644
index 000000000..1b2259d26
--- /dev/null
+++ b/src/lib/mpeg2_encoder.h
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "video_encoder.h"
+#include <dcp/mpeg2_transcode.h>
+
+
+class MPEG2Encoder : public VideoEncoder
+{
+public:
+ MPEG2Encoder(std::shared_ptr<const Film> film, Writer& writer);
+
+ void encode(std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time) override;
+
+ void pause() override {}
+ void resume() override {}
+
+ /** Called when a processing run has finished */
+ void end() override;
+
+private:
+ dcp::MPEG2Compressor _transcoder;
+};
+
diff --git a/src/lib/reel_writer.cc b/src/lib/reel_writer.cc
index 201c23e71..8dc0d7f06 100644
--- a/src/lib/reel_writer.cc
+++ b/src/lib/reel_writer.cc
@@ -146,30 +146,45 @@ ReelWriter::ReelWriter (
dcp::filesystem::rename(asset.string() + ".tmp", asset);
}
+ auto const rate = dcp::Fraction(film()->video_frame_rate(), 1);
- if (film()->three_d()) {
- _picture_asset = std::make_shared<dcp::StereoJ2KPictureAsset>(dcp::Fraction(film()->video_frame_rate(), 1), standard);
- } else {
- _picture_asset = std::make_shared<dcp::MonoJ2KPictureAsset>(dcp::Fraction(film()->video_frame_rate(), 1), standard);
- }
+ auto setup = [this](shared_ptr<dcp::PictureAsset> asset) {
+ asset->set_size(film()->frame_size());
+ asset->set_metadata(mxf_metadata());
- _picture_asset->set_size (film()->frame_size());
- _picture_asset->set_metadata (mxf_metadata());
+ if (film()->encrypted()) {
+ asset->set_key(film()->key());
+ asset->set_context_id(film()->context_id());
+ }
+ };
- if (film()->encrypted()) {
- _picture_asset->set_key (film()->key());
- _picture_asset->set_context_id (film()->context_id());
+ if (film()->video_encoding() == VideoEncoding::JPEG2000) {
+ if (film()->three_d()) {
+ _j2k_picture_asset = std::make_shared<dcp::StereoJ2KPictureAsset>(rate, standard);
+ } else {
+ _j2k_picture_asset = std::make_shared<dcp::MonoJ2KPictureAsset>(rate, standard);
+ }
+ setup(_j2k_picture_asset);
+ _j2k_picture_asset->set_file(asset);
+ _j2k_picture_asset_writer = _j2k_picture_asset->start_write(asset, _first_nonexistent_frame > 0 ? dcp::Behaviour::OVERWRITE_EXISTING : dcp::Behaviour::MAKE_NEW);
+ } else {
+ _mpeg2_picture_asset = std::make_shared<dcp::MonoMPEG2PictureAsset>(rate);
+ setup(_mpeg2_picture_asset);
+ _mpeg2_picture_asset->set_file(asset);
+ _mpeg2_picture_asset_writer = _mpeg2_picture_asset->start_write(asset, _first_nonexistent_frame > 0 ? dcp::Behaviour::OVERWRITE_EXISTING : dcp::Behaviour::MAKE_NEW);
}
- _picture_asset->set_file (asset);
- _picture_asset_writer = _picture_asset->start_write(asset, _first_nonexistent_frame > 0 ? dcp::Behaviour::OVERWRITE_EXISTING : dcp::Behaviour::MAKE_NEW);
} else if (!text_only) {
/* We already have a complete picture asset that we can just re-use */
/* XXX: what about if the encryption key changes? */
- if (film()->three_d()) {
- _picture_asset = make_shared<dcp::StereoJ2KPictureAsset>(asset);
+ if (film()->video_encoding() == VideoEncoding::JPEG2000) {
+ if (film()->three_d()) {
+ _j2k_picture_asset = make_shared<dcp::StereoJ2KPictureAsset>(asset);
+ } else {
+ _j2k_picture_asset = make_shared<dcp::MonoJ2KPictureAsset>(asset);
+ }
} else {
- _picture_asset = make_shared<dcp::MonoJ2KPictureAsset>(asset);
+ _mpeg2_picture_asset = make_shared<dcp::MonoMPEG2PictureAsset>(asset);
}
}
@@ -272,7 +287,7 @@ ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
void
ReelWriter::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
{
- if (!_picture_asset_writer) {
+ if (!_j2k_picture_asset_writer) {
/* We're not writing any data */
return;
}
@@ -300,21 +315,28 @@ ReelWriter::write (shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metada
void
-ReelWriter::fake_write (int size)
+ReelWriter::write(shared_ptr<dcp::MonoMPEG2PictureFrame> image)
+{
+ _mpeg2_picture_asset_writer->write(image->data(), image->size());
+}
+
+
+void
+ReelWriter::fake_write(dcp::J2KFrameInfo const& info)
{
- if (!_picture_asset_writer) {
+ if (!_j2k_picture_asset_writer) {
/* We're not writing any data */
return;
}
- _picture_asset_writer->fake_write (size);
+ _j2k_picture_asset_writer->fake_write(info);
}
void
ReelWriter::repeat_write (Frame frame, Eyes eyes)
{
- if (!_picture_asset_writer) {
+ if (!_j2k_picture_asset_writer) {
/* We're not writing any data */
return;
}
@@ -327,10 +349,16 @@ ReelWriter::repeat_write (Frame frame, Eyes eyes)
void
ReelWriter::finish (boost::filesystem::path output_dcp)
{
- if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
- /* Nothing was written to the picture asset */
- LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
- _picture_asset.reset ();
+ if (_j2k_picture_asset_writer && !_j2k_picture_asset_writer->finalize()) {
+ /* Nothing was written to the J2K picture asset */
+ LOG_GENERAL("Nothing was written to J2K asset for reel %1 of %2", _reel_index, _reel_count);
+ _j2k_picture_asset.reset();
+ }
+
+ if (_mpeg2_picture_asset_writer && !_mpeg2_picture_asset_writer->finalize()) {
+ /* Nothing was written to the MPEG2 picture asset */
+ LOG_GENERAL("Nothing was written to MPEG2 asset for reel %1 of %2", _reel_index, _reel_count);
+ _mpeg2_picture_asset.reset();
}
if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
@@ -338,12 +366,21 @@ ReelWriter::finish (boost::filesystem::path output_dcp)
_sound_asset.reset ();
}
+ shared_ptr<dcp::PictureAsset> picture_asset;
+ if (_j2k_picture_asset) {
+ picture_asset = _j2k_picture_asset;
+ } else if (_mpeg2_picture_asset) {
+ picture_asset = _mpeg2_picture_asset;
+ }
+
/* Hard-link any video asset file into the DCP */
- if (_picture_asset) {
- DCPOMATIC_ASSERT (_picture_asset->file());
- boost::filesystem::path video_from = _picture_asset->file().get();
- boost::filesystem::path video_to = output_dcp;
- video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
+ if (picture_asset) {
+ auto const file = picture_asset->file();
+ DCPOMATIC_ASSERT(file);
+
+ auto video_from = *file;
+ auto video_to = output_dcp;
+ video_to /= video_asset_filename(picture_asset, _reel_index, _reel_count, _content_summary);
/* There may be an existing "to" file if we are recreating a DCP in the same place without
changing any video.
*/
@@ -371,7 +408,7 @@ ReelWriter::finish (boost::filesystem::path output_dcp)
}
}
- _picture_asset->set_file (video_to);
+ picture_asset->set_file(video_to);
}
/* Move the audio asset into the DCP */
@@ -495,17 +532,17 @@ ReelWriter::create_reel_picture (shared_ptr<dcp::Reel> reel, list<ReferencedReel
{
shared_ptr<dcp::ReelPictureAsset> reel_asset;
- if (_picture_asset) {
+ if (_j2k_picture_asset) {
/* We have made a picture asset of our own. Put it into the reel */
- auto mono = dynamic_pointer_cast<dcp::MonoJ2KPictureAsset>(_picture_asset);
- if (mono) {
+ if (auto mono = dynamic_pointer_cast<dcp::MonoJ2KPictureAsset>(_j2k_picture_asset)) {
reel_asset = make_shared<dcp::ReelMonoPictureAsset>(mono, 0);
}
- auto stereo = dynamic_pointer_cast<dcp::StereoJ2KPictureAsset>(_picture_asset);
- if (stereo) {
+ if (auto stereo = dynamic_pointer_cast<dcp::StereoJ2KPictureAsset>(_j2k_picture_asset)) {
reel_asset = make_shared<dcp::ReelStereoPictureAsset>(stereo, 0);
}
+ } else if (_mpeg2_picture_asset) {
+ reel_asset = make_shared<dcp::ReelMonoPictureAsset>(_mpeg2_picture_asset, 0);
} else {
LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
/* We don't have a picture asset of our own; hopefully we have one to reference */
@@ -734,8 +771,11 @@ try
{
vector<shared_ptr<const dcp::Asset>> assets;
- if (_picture_asset) {
- assets.push_back(_picture_asset);
+ if (_j2k_picture_asset) {
+ assets.push_back(_j2k_picture_asset);
+ }
+ if (_mpeg2_picture_asset) {
+ assets.push_back(_mpeg2_picture_asset);
}
if (_sound_asset) {
assets.push_back(_sound_asset);
diff --git a/src/lib/reel_writer.h b/src/lib/reel_writer.h
index 30abdd563..f6273f8e9 100644
--- a/src/lib/reel_writer.h
+++ b/src/lib/reel_writer.h
@@ -31,7 +31,10 @@
#include "weak_film.h"
#include <dcp/atmos_asset_writer.h>
#include <dcp/file.h>
+#include <dcp/ffmpeg_image.h>
#include <dcp/j2k_picture_asset_writer.h>
+#include <dcp/mono_mpeg2_picture_frame.h>
+#include <dcp/mpeg2_picture_asset_writer.h>
class AudioBuffers;
@@ -46,6 +49,7 @@ namespace dcp {
class MonoJ2KPictureAssetWriter;
class J2KPictureAsset;
class J2KPictureAssetWriter;
+ class MPEG2PictureAsset;
class Reel;
class ReelAsset;
class ReelPictureAsset;
@@ -70,11 +74,12 @@ public:
);
void write (std::shared_ptr<const dcp::Data> encoded, Frame frame, Eyes eyes);
- void fake_write (int size);
+ void fake_write(dcp::J2KFrameInfo const& info);
void repeat_write (Frame frame, Eyes eyes);
void write (std::shared_ptr<const AudioBuffers> audio);
void write(PlayerText text, TextType type, boost::optional<DCPTextTrack> track, dcpomatic::DCPTimePeriod period, FontIdMap const& fonts, std::shared_ptr<dcpomatic::Font> chosen_interop_font);
void write (std::shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadata);
+ void write(std::shared_ptr<dcp::MonoMPEG2PictureFrame> image);
void finish (boost::filesystem::path output_dcp);
std::shared_ptr<dcp::Reel> create_reel (
@@ -131,9 +136,11 @@ private:
dcp::ArrayData _default_font;
- std::shared_ptr<dcp::J2KPictureAsset> _picture_asset;
+ std::shared_ptr<dcp::J2KPictureAsset> _j2k_picture_asset;
+ std::shared_ptr<dcp::MPEG2PictureAsset> _mpeg2_picture_asset;
/** picture asset writer, or 0 if we are not writing any picture because we already have one */
- std::shared_ptr<dcp::J2KPictureAssetWriter> _picture_asset_writer;
+ std::shared_ptr<dcp::J2KPictureAssetWriter> _j2k_picture_asset_writer;
+ std::shared_ptr<dcp::MPEG2PictureAssetWriter> _mpeg2_picture_asset_writer;
std::shared_ptr<dcp::SoundAsset> _sound_asset;
std::shared_ptr<dcp::SoundAssetWriter> _sound_asset_writer;
std::shared_ptr<dcp::SubtitleAsset> _subtitle_asset;
diff --git a/src/lib/video_encoding.cc b/src/lib/video_encoding.cc
new file mode 100644
index 000000000..ead1a2eaf
--- /dev/null
+++ b/src/lib/video_encoding.cc
@@ -0,0 +1,56 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcpomatic_assert.h"
+#include "video_encoding.h"
+
+
+using std::string;
+
+
+string
+video_encoding_to_string(VideoEncoding encoding)
+{
+ switch (encoding) {
+ case VideoEncoding::JPEG2000:
+ return "jpeg2000";
+ case VideoEncoding::MPEG2:
+ return "mpeg2";
+ }
+
+ DCPOMATIC_ASSERT(false);
+}
+
+
+VideoEncoding
+video_encoding_from_string(string const& encoding)
+{
+ if (encoding == "jpeg2000") {
+ return VideoEncoding::JPEG2000;
+ }
+
+ if (encoding == "mpeg2") {
+ return VideoEncoding::MPEG2;
+ }
+
+ DCPOMATIC_ASSERT(false);
+}
+
diff --git a/src/lib/video_encoding.h b/src/lib/video_encoding.h
new file mode 100644
index 000000000..45ee0b21e
--- /dev/null
+++ b/src/lib/video_encoding.h
@@ -0,0 +1,34 @@
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <string>
+
+
+enum class VideoEncoding
+{
+ JPEG2000,
+ MPEG2
+};
+
+
+std::string video_encoding_to_string(VideoEncoding encoding);
+VideoEncoding video_encoding_from_string(std::string const& encoding);
+
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
index 1c04f4065..3dd22718f 100644
--- a/src/lib/writer.cc
+++ b/src/lib/writer.cc
@@ -40,6 +40,7 @@
#include "version.h"
#include "writer.h"
#include <dcp/cpl.h>
+#include <dcp/mono_mpeg2_picture_frame.h>
#include <dcp/locale_convert.h>
#include <dcp/raw_convert.h>
#include <dcp/reel_closed_caption_asset.h>
@@ -169,6 +170,13 @@ Writer::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
}
+void
+Writer::write(shared_ptr<dcp::MonoMPEG2PictureFrame> image, Frame frame)
+{
+ _reels[video_reel(frame)].write(image);
+}
+
+
bool
Writer::can_repeat (Frame frame) const
{
@@ -230,7 +238,7 @@ Writer::fake_write (Frame frame, Eyes eyes)
QueueItem qi;
qi.type = QueueItem::Type::FAKE;
- qi.size = J2KFrameInfo(film()->info_file_handle(_reels[reel].period(), true), frame_in_reel, eyes).size;
+ qi.info = J2KFrameInfo(film()->info_file_handle(_reels[reel].period(), true), frame_in_reel, eyes);
DCPOMATIC_ASSERT((film()->three_d() && eyes != Eyes::BOTH) || (!film()->three_d() && eyes == Eyes::BOTH));
@@ -401,7 +409,7 @@ try
if (i.type == QueueItem::Type::FULL) {
LOG_WARNING (N_("- type FULL, frame %1, eyes %2"), i.frame, (int) i.eyes);
} else {
- LOG_WARNING (N_("- type FAKE, size %1, frame %2, eyes %3"), i.size, i.frame, (int) i.eyes);
+ LOG_WARNING (N_("- type FAKE, size %1, frame %2, eyes %3"), i.info.size, i.frame, (int) i.eyes);
}
}
}
@@ -432,7 +440,7 @@ try
break;
case QueueItem::Type::FAKE:
LOG_DEBUG_ENCODE (N_("Writer FAKE-writes %1"), qi.frame);
- reel.fake_write (qi.size);
+ reel.fake_write(qi.info);
++_fake_written;
break;
case QueueItem::Type::REPEAT:
diff --git a/src/lib/writer.h b/src/lib/writer.h
index 1cd278221..1dd88d8a9 100644
--- a/src/lib/writer.h
+++ b/src/lib/writer.h
@@ -37,6 +37,8 @@
#include "text_type.h"
#include "weak_film.h"
#include <dcp/atmos_frame.h>
+#include <dcp/frame_info.h>
+#include <dcp/mono_mpeg2_picture_frame.h>
#include <boost/thread.hpp>
#include <boost/thread/condition.hpp>
#include <list>
@@ -74,8 +76,8 @@ public:
/** encoded data for FULL */
std::shared_ptr<const dcp::Data> encoded;
- /** size of data for FAKE */
- int size = 0;
+ /** info for FAKE */
+ dcp::J2KFrameInfo info;
/** reel index */
size_t reel = 0;
/** frame index within the reel */
@@ -122,6 +124,7 @@ public:
void write (std::vector<std::shared_ptr<dcpomatic::Font>> fonts);
void write (ReferencedReelAsset asset);
void write (std::shared_ptr<const dcp::AtmosFrame> atmos, dcpomatic::DCPTime time, AtmosMetadata metadata);
+ void write (std::shared_ptr<dcp::MonoMPEG2PictureFrame> image, Frame frame);
void finish (boost::filesystem::path output_dcp);
void set_encoder_threads (int threads);
diff --git a/src/lib/wscript b/src/lib/wscript
index 8e839cb49..86cf2e0b6 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -154,6 +154,7 @@ sources = """
maths_util.cc
memory_util.cc
mid_side_decoder.cc
+ mpeg2_encoder.cc
named_channel.cc
overlaps.cc
pixel_quanta.cc
@@ -212,6 +213,7 @@ sources = """
video_content.cc
video_decoder.cc
video_encoder.cc
+ video_encoding.cc
video_filter_graph.cc
video_filter_graph_set.cc
video_frame_type.cc
diff --git a/src/wx/dcp_panel.cc b/src/wx/dcp_panel.cc
index c2f32fbb9..4ae89d443 100644
--- a/src/wx/dcp_panel.cc
+++ b/src/wx/dcp_panel.cc
@@ -151,6 +151,7 @@ DCPPanel::add_standards()
_standard->add(_("SMPTE (Bv2.0 only)"), N_("smpte-bv20"));
}
_standard->add(_("Interop"), N_("interop"));
+ _standard->add(_("MPEG2 Interop"), N_("mpeg2-interop"));
_sizer->Layout();
}
@@ -162,7 +163,11 @@ DCPPanel::set_standard()
DCPOMATIC_ASSERT(!_film->limit_to_smpte_bv20() || _standard->GetCount() == 3);
if (_film->interop()) {
- checked_set(_standard, "interop");
+ if (_film->video_encoding() == VideoEncoding::JPEG2000) {
+ checked_set(_standard, "interop");
+ } else {
+ checked_set(_standard, "mpeg2-interop");
+ }
} else {
checked_set(_standard, _film->limit_to_smpte_bv20() ? "smpte-bv20" : "smpte");
}
@@ -184,12 +189,18 @@ DCPPanel::standard_changed ()
if (*data == N_("interop")) {
_film->set_interop(true);
_film->set_limit_to_smpte_bv20(false);
+ _film->set_video_encoding(VideoEncoding::JPEG2000);
} else if (*data == N_("smpte")) {
_film->set_interop(false);
_film->set_limit_to_smpte_bv20(false);
+ _film->set_video_encoding(VideoEncoding::JPEG2000);
} else if (*data == N_("smpte-bv20")) {
_film->set_interop(false);
_film->set_limit_to_smpte_bv20(true);
+ _film->set_video_encoding(VideoEncoding::JPEG2000);
+ } else if (*data == N_("mpeg2-interop")) {
+ _film->set_interop(true);
+ _film->set_video_encoding(VideoEncoding::MPEG2);
}
}
@@ -445,6 +456,9 @@ DCPPanel::film_changed(FilmProperty p)
setup_dcp_name ();
_markers->Enable (!_film->interop());
break;
+ case FilmProperty::VIDEO_ENCODING:
+ set_standard();
+ break;
case FilmProperty::LIMIT_TO_SMPTE_BV20:
set_standard();
break;