From: Carl Hetherington Date: Sat, 31 Aug 2019 01:03:01 +0000 (+0100) Subject: Primitive subtitle export feature. X-Git-Tag: v2.15.16 X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=2cdf3d9f461b12d0925cc54368105bbd177bbbb3 Primitive subtitle export feature. --- diff --git a/src/lib/dcp_encoder.cc b/src/lib/dcp_encoder.cc index 99edb0050..448fc2a52 100644 --- a/src/lib/dcp_encoder.cc +++ b/src/lib/dcp_encoder.cc @@ -152,11 +152,11 @@ DCPEncoder::text (PlayerText data, TextType type, optional track, } } -float +optional DCPEncoder::current_rate () const { if (!_j2k_encoder) { - return 0; + return optional(); } return _j2k_encoder->current_encoding_rate (); diff --git a/src/lib/dcp_encoder.h b/src/lib/dcp_encoder.h index 61cdcee68..cc2de3e0c 100644 --- a/src/lib/dcp_encoder.h +++ b/src/lib/dcp_encoder.h @@ -41,7 +41,7 @@ public: void go (); - float current_rate () const; + boost::optional current_rate () const; Frame frames_done () const; /** @return true if we are in the process of calling Encoder::process_end */ diff --git a/src/lib/encoder.h b/src/lib/encoder.h index f4116c50c..792029a91 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -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 current_rate () const { + return boost::optional(); + } + /** @return the number of frames that are done */ virtual Frame frames_done () const = 0; virtual bool finishing () const = 0; diff --git a/src/lib/ffmpeg_encoder.cc b/src/lib/ffmpeg_encoder.cc index 572e7ae16..49908587b 100644 --- a/src/lib/ffmpeg_encoder.cc +++ b/src/lib/ffmpeg_encoder.cc @@ -183,7 +183,7 @@ FFmpegEncoder::go () } } -float +optional FFmpegEncoder::current_rate () const { return _history.rate (); diff --git a/src/lib/ffmpeg_encoder.h b/src/lib/ffmpeg_encoder.h index d8ffd2c6b..df2dcfcc8 100644 --- a/src/lib/ffmpeg_encoder.h +++ b/src/lib/ffmpeg_encoder.h @@ -47,7 +47,7 @@ public: void go (); - float current_rate () const; + boost::optional current_rate () const; Frame frames_done () const; bool finishing () const { return false; diff --git a/src/lib/player.cc b/src/lib/player.cc index 1a88505af..23fe65cf4 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -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 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_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 index 000000000..43c68bc22 --- /dev/null +++ b/src/lib/subtitle_encoder.cc @@ -0,0 +1,157 @@ +/* + Copyright (C) 2019 Carl Hetherington + + 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 . + +*/ + +#include "subtitle_encoder.h" +#include "player.h" +#include "compose.hpp" +#include "job.h" +#include +#include +#include +#include +#include +#include + +#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 film, shared_ptr 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(), filename)); + } + + BOOST_FOREACH (dcpomatic::DCPTimePeriod i, film->reels()) { + _reels.push_back (i); + } +} + +void +SubtitleEncoder::go () +{ + { + shared_ptr job = _job.lock (); + DCPOMATIC_ASSERT (job); + job->sub (_("Extracting")); + } + + _reel_index = 0; + + while (!_player->pass()) {} + + for (vector, 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 track, dcpomatic::DCPTimePeriod period) +{ + if (type != TEXT_OPEN_SUBTITLE) { + return; + } + + if (!_assets[_reel_index].first) { + shared_ptr asset; + string lang = _film->subtitle_language (); + if (_film->interop ()) { + shared_ptr s (new dcp::InteropSubtitleAsset()); + s->set_movie_title (_film->name()); + s->set_language (lang.empty() ? "Unknown" : lang); + s->set_reel_number (raw_convert(_reel_index + 1)); + _assets[_reel_index].first = s; + } else { + shared_ptr 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(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.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 index 000000000..50485750d --- /dev/null +++ b/src/lib/subtitle_encoder.h @@ -0,0 +1,60 @@ +/* + Copyright (C) 2019 Carl Hetherington + + 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 . + +*/ + +#include "types.h" +#include "player_text.h" +#include "dcp_text_track.h" +#include "encoder.h" +#include "dcpomatic_time.h" +#include + +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 film, boost::shared_ptr 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 track, dcpomatic::DCPTimePeriod period); + + std::vector, boost::filesystem::path> > _assets; + std::vector _reels; + bool _split_reels; + int _reel_index; + boost::optional _last; + dcpomatic::DCPTime _length; +}; diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index 981816fd5..55b4ef9b6 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -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 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 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; } diff --git a/src/lib/types.h b/src/lib/types.h index ccb47dc64..5a8b650b8 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -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 diff --git a/src/lib/wscript b/src/lib/wscript index 177a58b64..b78586843 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -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 diff --git a/src/tools/dcpomatic.cc b/src/tools/dcpomatic.cc index 331e2995a..a9c93e9ac 100644 --- a/src/tools/dcpomatic.cc +++ b/src/tools/dcpomatic.cc @@ -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 #include #include @@ -911,15 +912,21 @@ private: ExportDialog* d = new ExportDialog (this, _film->isdcf_name(true)); if (d->ShowModal() == wxID_OK) { shared_ptr job (new TranscodeJob (_film)); - job->set_encoder ( - shared_ptr ( - 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(new SubtitleEncoder(_film, job, d->path(), d->split_reels())) + ); + } else { + job->set_encoder ( + shared_ptr ( + new FFmpegEncoder (_film, job, d->path(), d->format(), d->mixdown_to_stereo(), d->split_reels(), d->x264_crf() #ifdef DCPOMATIC_VARIANT_SWAROOP - , optional(), optional() + , optional(), optional() #endif + ) ) - ) - ); + ); + } JobManager::instance()->add (job); } d->Destroy (); diff --git a/src/wx/export_dialog.cc b/src/wx/export_dialog.cc index 804200596..b23583d44 100644 --- a/src/wx/export_dialog.cc +++ b/src/wx/export_dialog.cc @@ -28,26 +28,30 @@ 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