Support encoding of MPEG2 DCPs.
authorCarl Hetherington <cth@carlh.net>
Tue, 19 Mar 2024 13:02:47 +0000 (14:02 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 22 Apr 2024 11:03:04 +0000 (13:03 +0200)
17 files changed:
src/lib/dcp_film_encoder.cc
src/lib/film.cc
src/lib/film.h
src/lib/film_property.h
src/lib/mpeg2_encoder.cc [new file with mode: 0644]
src/lib/mpeg2_encoder.h [new file with mode: 0644]
src/lib/reel_writer.cc
src/lib/reel_writer.h
src/lib/video_encoding.cc [new file with mode: 0644]
src/lib/video_encoding.h [new file with mode: 0644]
src/lib/writer.cc
src/lib/writer.h
src/lib/wscript
src/wx/dcp_panel.cc
test/burnt_subtitle_test.cc
test/data
test/overlap_video_test.cc

index b508b66b659287ba20d5e62717390a2e978115ea..17f5316931a3a66bfd37d3b68c54bd9d25d0d3dc 100644 (file)
@@ -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));
index 3de536a6d95fcec2aeb70a8afcbdd522820b53d0..54c68d756fdb1ec129ceb1df8c2ad0bcbd6ee728 100644 (file)
@@ -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 ());
@@ -1219,6 +1227,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)
 {
index 392f1dc5e39fd2faf41eb5e7457e60bad68c42d5..ff3dee6fcf62d81d05242d93bac28a845271429a 100644 (file)
@@ -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;
index b755a4887418f4e684a1cca3b22a8556cdfb3e15..ebda0e8078cf7bf3d26f33c6426b7855e7b92da0 100644 (file)
@@ -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 (file)
index 0000000..49bff19
--- /dev/null
@@ -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 (file)
index 0000000..1b2259d
--- /dev/null
@@ -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;
+};
+
index 201c23e71763d40fe9e70992b6b7a6984450a436..8dc0d7f06ff9936530c0520458ecd08977f38cf8 100644 (file)
@@ -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);
index 30abdd5635dea0eeee65ab91a711d4af88d7998f..f6273f8e9349b320d826e9bfa79f2d88afdf50b4 100644 (file)
 #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 (file)
index 0000000..ead1a2e
--- /dev/null
@@ -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 (file)
index 0000000..45ee0b2
--- /dev/null
@@ -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);
+
index 1c04f40657f7bcdc25fdcacb6db9d8ce71690678..3dd22718f3ff2b412217d3f0653abf550d21607a 100644 (file)
@@ -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:
index 1cd278221738397523b1d6f1bcdc2e2c0aea8c3d..1dd88d8a9a1c2679ab5be8e9c7e91fa8b155c693 100644 (file)
@@ -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);
index 8e839cb49e66f1e074044e89b8ff5d08d0305ae9..86cf2e0b623ecb843e1af7eaa2efc04de74700a1 100644 (file)
@@ -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
index c2f32fbb9572191c069b0540b491b96ad60eada1..4ae89d443d499701ab38251cc0f90323d70646ea 100644 (file)
@@ -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;
index ac14de2c452ce941bbad1c76f55689738a0bb1ad..e8ecc0048f1972280221c4289c90207af7495fff 100644 (file)
@@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE (burnt_subtitle_test_onto_dcp)
        BOOST_REQUIRE (dcp.cpls().front()->reels().front()->main_picture()->asset());
        auto pic = dynamic_pointer_cast<dcp::ReelMonoPictureAsset> (
                dcp.cpls().front()->reels().front()->main_picture()
-               )->mono_asset();
+               )->mono_j2k_asset();
        BOOST_REQUIRE (pic);
        auto frame = pic->start_read()->get_frame(12);
        auto xyz = frame->xyz_image ();
index ddf878730354cdec8a802a59543591f6f943f5c0..3ab3245220bd2a11cdf2e16a28221e4de063befc 160000 (submodule)
--- a/test/data
+++ b/test/data
@@ -1 +1 @@
-Subproject commit ddf878730354cdec8a802a59543591f6f943f5c0
+Subproject commit 3ab3245220bd2a11cdf2e16a28221e4de063befc
index 9bef93d6783155ed68c5e005d45fdd4b2250ea12..638f606d3fb4b6e5af4793920a6efec214808d01 100644 (file)
@@ -89,7 +89,7 @@ BOOST_AUTO_TEST_CASE (overlap_video_test1)
        BOOST_REQUIRE (reel->main_picture());
        auto mono_picture = dynamic_pointer_cast<dcp::ReelMonoPictureAsset>(reel->main_picture());
        BOOST_REQUIRE (mono_picture);
-       auto asset = mono_picture->mono_asset();
+       auto asset = mono_picture->mono_j2k_asset();
        BOOST_REQUIRE (asset);
        BOOST_CHECK_EQUAL (asset->intrinsic_duration(), fps * 5);
        auto reader = asset->start_read ();