Primitive subtitle export feature. v2.15.16
authorCarl Hetherington <cth@carlh.net>
Sat, 31 Aug 2019 01:03:01 +0000 (02:03 +0100)
committerCarl Hetherington <cth@carlh.net>
Sat, 31 Aug 2019 01:03:01 +0000 (02:03 +0100)
13 files changed:
src/lib/dcp_encoder.cc
src/lib/dcp_encoder.h
src/lib/encoder.h
src/lib/ffmpeg_encoder.cc
src/lib/ffmpeg_encoder.h
src/lib/player.cc
src/lib/subtitle_encoder.cc [new file with mode: 0644]
src/lib/subtitle_encoder.h [new file with mode: 0644]
src/lib/transcode_job.cc
src/lib/types.h
src/lib/wscript
src/tools/dcpomatic.cc
src/wx/export_dialog.cc

index 99edb00..448fc2a 100644 (file)
@@ -152,11 +152,11 @@ DCPEncoder::text (PlayerText data, TextType type, optional<DCPTextTrack> track,
        }
 }
 
-float
+optional<float>
 DCPEncoder::current_rate () const
 {
        if (!_j2k_encoder) {
-               return 0;
+               return optional<float>();
        }
 
        return _j2k_encoder->current_encoding_rate ();
index 61cdcee..cc2de3e 100644 (file)
@@ -41,7 +41,7 @@ public:
 
        void go ();
 
-       float current_rate () const;
+       boost::optional<float> current_rate () const;
        Frame frames_done () const;
 
        /** @return true if we are in the process of calling Encoder::process_end */
index f4116c5..792029a 100644 (file)
@@ -43,7 +43,10 @@ public:
        virtual void go () = 0;
 
        /** @return the current frame rate over the last short while */
-       virtual float current_rate () const = 0;
+       virtual boost::optional<float> current_rate () const {
+               return boost::optional<float>();
+       }
+
        /** @return the number of frames that are done */
        virtual Frame frames_done () const = 0;
        virtual bool finishing () const = 0;
index 572e7ae..4990858 100644 (file)
@@ -183,7 +183,7 @@ FFmpegEncoder::go ()
        }
 }
 
-float
+optional<float>
 FFmpegEncoder::current_rate () const
 {
        return _history.rate ();
index d8ffd2c..df2dcfc 100644 (file)
@@ -47,7 +47,7 @@ public:
 
        void go ();
 
-       float current_rate () const;
+       boost::optional<float> current_rate () const;
        Frame frames_done () const;
        bool finishing () const {
                return false;
index 1a88505..23fe65c 100644 (file)
@@ -146,17 +146,13 @@ Player::setup_pieces_unlocked ()
        _shuffler = new Shuffler();
        _shuffler->Video.connect(bind(&Player::video, this, _1, _2));
 
-       cout << "SPU " << _playlist->content().size() << ".\n";
-
        BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
 
                if (!i->paths_valid ()) {
-                       cout << "not valid.\n";
                        continue;
                }
 
                if (_ignore_video && _ignore_audio && i->text.empty()) {
-                       cout << "text only.\n";
                        /* We're only interested in text and this content has none */
                        continue;
                }
@@ -169,7 +165,6 @@ Player::setup_pieces_unlocked ()
                        }
                }
 
-               cout << " DF " << _tolerant << "\n";
                shared_ptr<Decoder> decoder = decoder_factory (_film, i, _fast, _tolerant, old_decoder);
                FrameRateChange frc (_film, i);
 
diff --git a/src/lib/subtitle_encoder.cc b/src/lib/subtitle_encoder.cc
new file mode 100644 (file)
index 0000000..43c68bc
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+    Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "subtitle_encoder.h"
+#include "player.h"
+#include "compose.hpp"
+#include "job.h"
+#include <dcp/interop_subtitle_asset.h>
+#include <dcp/raw_convert.h>
+#include <dcp/smpte_subtitle_asset.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/bind.hpp>
+
+#include "i18n.h"
+
+using std::string;
+using std::make_pair;
+using std::pair;
+using std::vector;
+using boost::shared_ptr;
+using boost::optional;
+using dcp::raw_convert;
+
+SubtitleEncoder::SubtitleEncoder (shared_ptr<const Film> film, shared_ptr<Job> job, boost::filesystem::path output, bool split_reels)
+       : Encoder (film, job)
+       , _split_reels (split_reels)
+       , _reel_index (0)
+       , _length (film->length())
+{
+       _player->set_play_referenced ();
+       _player->set_ignore_video ();
+       _player->set_ignore_audio ();
+       _player->Text.connect (boost::bind(&SubtitleEncoder::text, this, _1, _2, _3, _4));
+
+       int const files = split_reels ? film->reels().size() : 1;
+       for (int i = 0; i < files; ++i) {
+
+               boost::filesystem::path filename = output;
+               string extension = boost::filesystem::extension (filename);
+               filename = boost::filesystem::change_extension (filename, "");
+
+               if (files > 1) {
+                       /// TRANSLATORS: _reel%1 here is to be added to an export filename to indicate
+                       /// which reel it is.  Preserve the %1; it will be replaced with the reel number.
+                       filename = filename.string() + String::compose(_("_reel%1"), i + 1);
+               }
+
+               _assets.push_back (make_pair(shared_ptr<dcp::SubtitleAsset>(), filename));
+       }
+
+       BOOST_FOREACH (dcpomatic::DCPTimePeriod i, film->reels()) {
+               _reels.push_back (i);
+       }
+}
+
+void
+SubtitleEncoder::go ()
+{
+       {
+               shared_ptr<Job> job = _job.lock ();
+               DCPOMATIC_ASSERT (job);
+               job->sub (_("Extracting"));
+       }
+
+       _reel_index = 0;
+
+       while (!_player->pass()) {}
+
+       for (vector<pair<shared_ptr<dcp::SubtitleAsset>, boost::filesystem::path> >::iterator i = _assets.begin(); i != _assets.end(); ++i) {
+               if (i->first) {
+                       i->first->write (i->second);
+               }
+       }
+}
+
+void
+SubtitleEncoder::text (PlayerText subs, TextType type, optional<DCPTextTrack> track, dcpomatic::DCPTimePeriod period)
+{
+       if (type != TEXT_OPEN_SUBTITLE) {
+               return;
+       }
+
+       if (!_assets[_reel_index].first) {
+               shared_ptr<dcp::SubtitleAsset> asset;
+               string lang = _film->subtitle_language ();
+               if (_film->interop ()) {
+                       shared_ptr<dcp::InteropSubtitleAsset> s (new dcp::InteropSubtitleAsset());
+                       s->set_movie_title (_film->name());
+                       s->set_language (lang.empty() ? "Unknown" : lang);
+                       s->set_reel_number (raw_convert<string>(_reel_index + 1));
+                       _assets[_reel_index].first = s;
+               } else {
+                       shared_ptr<dcp::SMPTESubtitleAsset> s (new dcp::SMPTESubtitleAsset());
+                       s->set_content_title_text (_film->name());
+                       if (!lang.empty()) {
+                               s->set_language (lang);
+                       } else {
+                               s->set_language (track->language);
+                       }
+                       s->set_edit_rate (dcp::Fraction (_film->video_frame_rate(), 1));
+                       s->set_reel_number (_reel_index + 1);
+                       s->set_time_code_rate (_film->video_frame_rate());
+                       s->set_start_time (dcp::Time());
+                       if (_film->encrypted ()) {
+                               s->set_key (_film->key ());
+                       }
+                       _assets[_reel_index].first = s;
+               }
+       }
+
+       BOOST_FOREACH (StringText i, subs.string) {
+               /* XXX: couldn't / shouldn't we use period here rather than getting time from the subtitle? */
+               i.set_in  (i.in());
+               i.set_out (i.out());
+               _assets[_reel_index].first->add (shared_ptr<dcp::Subtitle>(new dcp::SubtitleString(i)));
+       }
+
+       if (_split_reels && (_reel_index < int(_reels.size()) - 1) && period.from > _reels[_reel_index].from) {
+               ++_reel_index;
+       }
+
+       _last = period.from;
+
+       shared_ptr<Job> job = _job.lock ();
+       if (job) {
+               job->set_progress (float(period.from.get()) / _length.get());
+       }
+}
+
+Frame
+SubtitleEncoder::frames_done () const
+{
+       if (!_last) {
+               return 0;
+       }
+
+       /* XXX: assuming 24fps here but I don't think it matters */
+       return _last->seconds() * 24;
+}
diff --git a/src/lib/subtitle_encoder.h b/src/lib/subtitle_encoder.h
new file mode 100644 (file)
index 0000000..5048575
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+    Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "types.h"
+#include "player_text.h"
+#include "dcp_text_track.h"
+#include "encoder.h"
+#include "dcpomatic_time.h"
+#include <boost/weak_ptr.hpp>
+
+namespace dcp {
+       class SubtitleAsset;
+}
+
+class Film;
+
+/** @class SubtitleEncoder.
+ *  @brief An `encoder' which extracts a film's subtitles to DCP XML format.
+ */
+class SubtitleEncoder : public Encoder
+{
+public:
+       SubtitleEncoder (boost::shared_ptr<const Film> film, boost::shared_ptr<Job> job, boost::filesystem::path output, bool split_reels);
+
+       void go ();
+
+       /** @return the number of frames that are done */
+       Frame frames_done () const;
+
+       bool finishing () const {
+               return false;
+       }
+
+private:
+       void text (PlayerText subs, TextType type, boost::optional<DCPTextTrack> track, dcpomatic::DCPTimePeriod period);
+
+       std::vector<std::pair<boost::shared_ptr<dcp::SubtitleAsset>, boost::filesystem::path> > _assets;
+       std::vector<dcpomatic::DCPTimePeriod> _reels;
+       bool _split_reels;
+       int _reel_index;
+       boost::optional<dcpomatic::DCPTime> _last;
+       dcpomatic::DCPTime _length;
+};
index 981816f..55b4ef9 100644 (file)
@@ -42,6 +42,7 @@ using std::fixed;
 using std::setprecision;
 using std::cout;
 using boost::shared_ptr;
+using boost::optional;
 using boost::dynamic_pointer_cast;
 
 /** @param film Film to use */
@@ -138,11 +139,11 @@ TranscodeJob::status () const
                        _film->length().frames_round (_film->video_frame_rate ())
                        );
 
-               float const fps = _encoder->current_rate ();
+               optional<float> const fps = _encoder->current_rate ();
                if (fps) {
                        char fps_buffer[64];
                        /// TRANSLATORS: fps here is an abbreviation for frames per second
-                       snprintf (fps_buffer, sizeof(fps_buffer), _("; %.1f fps"), fps);
+                       snprintf (fps_buffer, sizeof(fps_buffer), _("; %.1f fps"), *fps);
                        strncat (buffer, fps_buffer, strlen(buffer) - 1);
                }
        }
@@ -164,12 +165,12 @@ TranscodeJob::remaining_time () const
 
        /* We're encoding so guess based on the current encoding rate */
 
-       float fps = e->current_rate ();
+       optional<float> fps = e->current_rate ();
 
-       if (fps == 0) {
+       if (!fps) {
                return 0;
        }
 
        /* Compute approximate proposed length here, as it's only here that we need it */
-       return (_film->length().frames_round (_film->video_frame_rate ()) - e->frames_done()) / fps;
+       return (_film->length().frames_round(_film->video_frame_rate()) - e->frames_done()) / *fps;
 }
index ccb47dc..5a8b650 100644 (file)
@@ -171,7 +171,8 @@ enum ExportFormat
 {
        EXPORT_FORMAT_PRORES,
        EXPORT_FORMAT_H264_AAC,
-       EXPORT_FORMAT_H264_PCM
+       EXPORT_FORMAT_H264_PCM,
+       EXPORT_FORMAT_SUBTITLES_DCP
 };
 
 /** @struct Crop
index 177a58b..b785868 100644 (file)
@@ -155,6 +155,7 @@ sources = """
           string_text_file.cc
           string_text_file_content.cc
           string_text_file_decoder.cc
+          subtitle_encoder.cc
           text_ring_buffers.cc
           timer.cc
           transcode_job.cc
index 331e299..a9c93e9 100644 (file)
@@ -83,6 +83,7 @@
 #include "lib/check_content_change_job.h"
 #include "lib/text_content.h"
 #include "lib/dcpomatic_log.h"
+#include "lib/subtitle_encoder.h"
 #include <dcp/exceptions.h>
 #include <dcp/raw_convert.h>
 #include <wx/generic/aboutdlgg.h>
@@ -911,15 +912,21 @@ private:
                ExportDialog* d = new ExportDialog (this, _film->isdcf_name(true));
                if (d->ShowModal() == wxID_OK) {
                        shared_ptr<TranscodeJob> job (new TranscodeJob (_film));
-                       job->set_encoder (
-                               shared_ptr<FFmpegEncoder> (
-                                       new FFmpegEncoder (_film, job, d->path(), d->format(), d->mixdown_to_stereo(), d->split_reels(), d->x264_crf()
+                       if (d->format() == EXPORT_FORMAT_SUBTITLES_DCP) {
+                               job->set_encoder (
+                                       shared_ptr<SubtitleEncoder>(new SubtitleEncoder(_film, job, d->path(), d->split_reels()))
+                                       );
+                       } else {
+                               job->set_encoder (
+                                       shared_ptr<FFmpegEncoder> (
+                                               new FFmpegEncoder (_film, job, d->path(), d->format(), d->mixdown_to_stereo(), d->split_reels(), d->x264_crf()
 #ifdef DCPOMATIC_VARIANT_SWAROOP
-                                                          , optional<dcp::Key>(), optional<string>()
+                                                                  , optional<dcp::Key>(), optional<string>()
 #endif
+                                                       )
                                                )
-                                       )
-                               );
+                                       );
+                       }
                        JobManager::instance()->add (job);
                }
                d->Destroy ();
index 8042005..b23583d 100644 (file)
 using std::string;
 using boost::bind;
 
-#define FORMATS 2
+#define FORMATS 3
 
 wxString format_names[] = {
        _("ProRes"),
-       _("MP4 / H.264")
+       _("MP4 / H.264"),
+       _("DCP subtitles")
 };
 
 wxString format_filters[] = {
        _("MOV files (*.mov)|*.mov"),
        _("MP4 files (*.mp4)|*.mp4"),
+       _("Subtitle files (*.xml)|*.xml"),
 };
 
 wxString format_extensions[] = {
        "mov",
-       "mp4"
+       "mp4",
+       "xml",
 };
 
 ExportFormat formats[] = {
        EXPORT_FORMAT_PRORES,
-       EXPORT_FORMAT_H264_AAC
+       EXPORT_FORMAT_H264_AAC,
+       EXPORT_FORMAT_SUBTITLES_DCP
 };
 
 ExportDialog::ExportDialog (wxWindow* parent, string name)
@@ -106,6 +110,7 @@ ExportDialog::format_changed ()
        for (int i = 0; i < 2; ++i) {
                _x264_crf_label[i]->Enable (_format->GetSelection() == 1);
        }
+       _mixdown->Enable (_format->GetSelection() != 2);
 }
 
 boost::filesystem::path