1a83c5c67fe868eace79fa82e1ccff390bab3a41
[dcpomatic.git] / src / lib / subtitle_encoder.cc
1 /*
2     Copyright (C) 2019-2020 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "font_data.h"
22 #include "subtitle_encoder.h"
23 #include "player.h"
24 #include "compose.hpp"
25 #include "job.h"
26 #include <dcp/interop_subtitle_asset.h>
27 #include <dcp/raw_convert.h>
28 #include <dcp/smpte_subtitle_asset.h>
29 #include <boost/filesystem.hpp>
30 #include <boost/bind/bind.hpp>
31
32 #include "i18n.h"
33
34 using std::string;
35 using std::make_pair;
36 using std::pair;
37 using std::vector;
38 using std::shared_ptr;
39 using boost::optional;
40 #if BOOST_VERSION >= 106100
41 using namespace boost::placeholders;
42 #endif
43 using dcp::raw_convert;
44
45 /** @param output Directory, if there will be multiple output files, or a filename.
46  *  @param initial_name Hint that may be used to create filenames, if @ref output is a directory.
47  *  @param include_font true to refer to and export any font file (for Interop; ignored for SMPTE).
48  */
49 SubtitleEncoder::SubtitleEncoder (shared_ptr<const Film> film, shared_ptr<Job> job, boost::filesystem::path output, string initial_name, bool split_reels, bool include_font)
50         : Encoder (film, job)
51         , _split_reels (split_reels)
52         , _include_font (include_font)
53         , _reel_index (0)
54         , _length (film->length())
55 {
56         _player->set_play_referenced ();
57         _player->set_ignore_video ();
58         _player->set_ignore_audio ();
59         _player->Text.connect (boost::bind(&SubtitleEncoder::text, this, _1, _2, _3, _4));
60
61         string const extension = film->interop() ? ".xml" : ".mxf";
62
63         int const files = split_reels ? film->reels().size() : 1;
64         for (int i = 0; i < files; ++i) {
65
66                 boost::filesystem::path filename = output;
67                 if (boost::filesystem::is_directory(filename)) {
68                         if (files > 1) {
69                                 /// TRANSLATORS: _reel%1 here is to be added to an export filename to indicate
70                                 /// which reel it is.  Preserve the %1; it will be replaced with the reel number.
71                                 filename /= String::compose("%1_reel%2", initial_name, i + 1);
72                         } else {
73                                 filename /= initial_name;
74                         }
75                 }
76
77                 _assets.push_back (make_pair(shared_ptr<dcp::SubtitleAsset>(), boost::filesystem::change_extension(filename, extension)));
78         }
79
80         BOOST_FOREACH (dcpomatic::DCPTimePeriod i, film->reels()) {
81                 _reels.push_back (i);
82         }
83
84         _default_font = dcp::ArrayData (default_font_file());
85 }
86
87 void
88 SubtitleEncoder::go ()
89 {
90         {
91                 shared_ptr<Job> job = _job.lock ();
92                 DCPOMATIC_ASSERT (job);
93                 job->sub (_("Extracting"));
94         }
95
96         _reel_index = 0;
97
98         while (!_player->pass()) {}
99
100         int reel = 0;
101         for (vector<pair<shared_ptr<dcp::SubtitleAsset>, boost::filesystem::path> >::iterator i = _assets.begin(); i != _assets.end(); ++i) {
102                 if (!i->first) {
103                         /* No subtitles arrived for this asset; make an empty one so we write something to the output */
104                         if (_film->interop()) {
105                                 shared_ptr<dcp::InteropSubtitleAsset> s (new dcp::InteropSubtitleAsset());
106                                 s->set_movie_title (_film->name());
107                                 s->set_reel_number (raw_convert<string>(reel + 1));
108                                 i->first = s;
109                         } else {
110                                 shared_ptr<dcp::SMPTESubtitleAsset> s (new dcp::SMPTESubtitleAsset());
111                                 s->set_content_title_text (_film->name());
112                                 s->set_reel_number (reel + 1);
113                                 i->first = s;
114                         }
115                 }
116
117                 if (!_film->interop() || _include_font) {
118                         BOOST_FOREACH (dcpomatic::FontData j, _player->get_subtitle_fonts()) {
119                                 i->first->add_font (j.id, _default_font);
120                         }
121                 }
122
123                 i->first->write (i->second);
124                 ++reel;
125         }
126 }
127
128 void
129 SubtitleEncoder::text (PlayerText subs, TextType type, optional<DCPTextTrack> track, dcpomatic::DCPTimePeriod period)
130 {
131         if (type != TEXT_OPEN_SUBTITLE) {
132                 return;
133         }
134
135         if (!_assets[_reel_index].first) {
136                 shared_ptr<dcp::SubtitleAsset> asset;
137                 vector<dcp::LanguageTag> lang = _film->subtitle_languages ();
138                 if (_film->interop ()) {
139                         shared_ptr<dcp::InteropSubtitleAsset> s (new dcp::InteropSubtitleAsset());
140                         s->set_movie_title (_film->name());
141                         if (!lang.empty()) {
142                                 s->set_language (lang.front().to_string());
143                         }
144                         s->set_reel_number (raw_convert<string>(_reel_index + 1));
145                         _assets[_reel_index].first = s;
146                 } else {
147                         shared_ptr<dcp::SMPTESubtitleAsset> s (new dcp::SMPTESubtitleAsset());
148                         s->set_content_title_text (_film->name());
149                         if (!lang.empty()) {
150                                 s->set_language (lang.front());
151                         } else if (!track->language.empty()) {
152                                 s->set_language (dcp::LanguageTag(track->language));
153                         }
154                         s->set_edit_rate (dcp::Fraction (_film->video_frame_rate(), 1));
155                         s->set_reel_number (_reel_index + 1);
156                         s->set_time_code_rate (_film->video_frame_rate());
157                         s->set_start_time (dcp::Time());
158                         if (_film->encrypted ()) {
159                                 s->set_key (_film->key ());
160                         }
161                         _assets[_reel_index].first = s;
162                 }
163         }
164
165         BOOST_FOREACH (StringText i, subs.string) {
166                 /* XXX: couldn't / shouldn't we use period here rather than getting time from the subtitle? */
167                 i.set_in  (i.in());
168                 i.set_out (i.out());
169                 if (_film->interop() && !_include_font) {
170                         i.unset_font ();
171                 }
172                 _assets[_reel_index].first->add (shared_ptr<dcp::Subtitle>(new dcp::SubtitleString(i)));
173         }
174
175         if (_split_reels && (_reel_index < int(_reels.size()) - 1) && period.from > _reels[_reel_index].from) {
176                 ++_reel_index;
177         }
178
179         _last = period.from;
180
181         shared_ptr<Job> job = _job.lock ();
182         if (job) {
183                 job->set_progress (float(period.from.get()) / _length.get());
184         }
185 }
186
187 Frame
188 SubtitleEncoder::frames_done () const
189 {
190         if (!_last) {
191                 return 0;
192         }
193
194         /* XXX: assuming 24fps here but I don't think it matters */
195         return _last->seconds() * 24;
196 }