2 Copyright (C) 2014-2020 Carl Hetherington <cth@carlh.net>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 /** @file src/stl_binary_writer.cc
21 * @brief Writer for STL binary files.
24 #include "stl_binary_writer.h"
28 #include "compose.hpp"
29 #include "sub_assert.h"
30 #include <boost/locale.hpp>
31 #include <boost/algorithm/string.hpp>
32 #include <boost/foreach.hpp>
47 using boost::locale::conv::utf_to_utf;
48 using boost::optional;
51 /** Arbitrary number which to divide the screen into rows; e.g.
52 * 64 here would mean that there are 64 addressable vertical positions
53 * on the screen, each 1/64th of the screen height tall.
55 * The magic 23 makes our output agree more closely with
56 * AnnotationEdit, which makes life easier when testing.
58 static int const ROWS = 23;
61 put_string (char* p, string s)
63 memcpy (p, s.c_str (), s.length ());
67 put_string (char* p, unsigned int n, string s)
69 SUB_ASSERT (s.length() <= n);
71 memcpy (p, s.c_str (), s.length ());
72 memset (p + s.length(), ' ', n - s.length ());
76 * @param n Width to zero-pad v to.
79 put_int_as_string (char* p, int v, unsigned int n)
85 snprintf (buffer, sizeof(buffer), "%02d", v);
88 snprintf (buffer, sizeof(buffer), "%05d", v);
96 struct lconv* lc = localeconv ();
97 boost::algorithm::replace_all (s, lc->thousands_sep, "");
98 boost::algorithm::replace_all (s, lc->decimal_point, ".");
104 put_int_as_int (char* p, int v, unsigned int n)
106 for (unsigned int i = 0; i < n; ++i) {
107 *p++ = (v & ((1 << ((i + 1) * 8)) - 1)) >> (i * 8);
112 vertical_position (sub::Line const & line)
115 if (line.vertical_position.proportional) {
116 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
118 vp = rint (line.vertical_position.proportional.get() * ROWS);
120 case VERTICAL_CENTRE_OF_SCREEN:
121 vp = rint (line.vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
123 case BOTTOM_OF_SCREEN:
124 vp = rint (ROWS - (line.vertical_position.proportional.get() * ROWS));
129 } else if (line.vertical_position.line) {
130 float const prop = float (line.vertical_position.line.get()) / line.vertical_position.lines.get ();
131 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
135 case VERTICAL_CENTRE_OF_SCREEN:
136 vp = (prop + 0.5) * ROWS;
138 case BOTTOM_OF_SCREEN:
139 vp = (1 - prop) * ROWS;
150 make_tti_blocks (vector<Subtitle> const& subtitles, STLBinaryTables const& tables, float frames_per_second)
152 static int const tti_size = 128;
155 /* Buffer to build the TTI blocks in */
156 char buffer[tti_size];
158 BOOST_FOREACH (Subtitle const& i, subtitles) {
160 /* Find the top vertical position of this subtitle */
162 BOOST_FOREACH (Line const& j, i.lines) {
163 int const vp = vertical_position (j);
164 if (!top || vp < top.get ()) {
169 /* Work out the text */
172 bool underline = false;
173 optional<int> last_vp;
175 BOOST_FOREACH (Line const& j, i.lines) {
177 /* CR/LF down to this line */
178 int const vp = vertical_position (j);
181 for (int k = last_vp.get(); k < vp; ++k) {
188 BOOST_FOREACH (Block const& k, j.blocks) {
189 if (k.underline && !underline) {
192 } else if (underline && !k.underline) {
196 if (k.italic && !italic) {
199 } else if (italic && !k.italic) {
204 text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k.text));
208 /* Turn italic/underline off before the end of this subtitle */
216 /* Make sure there's at least one end-of-line */
219 /* Now write this text in 112 byte chunks (TTI blocks). Only the first TTI
220 block's cumulative status, timecodes, vertical position, justification code
221 and comment flag are taken into account by the reader.
224 /* Set up the first part of the block */
226 /* XXX: these should increment, surely! */
227 /* Subtitle group number */
228 put_int_as_int (buffer + 0, 1, 1);
229 /* Subtitle number */
230 put_int_as_int (buffer + 1, 0, 2);
231 /* Cumulative status */
232 put_int_as_int (buffer + 4, tables.cumulative_status_enum_to_file (CUMULATIVE_STATUS_NOT_CUMULATIVE), 1);
234 put_int_as_int (buffer + 5, i.from.hours(), 1);
235 put_int_as_int (buffer + 6, i.from.minutes(), 1);
236 put_int_as_int (buffer + 7, i.from.seconds(), 1);
237 put_int_as_int (buffer + 8, i.from.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
239 put_int_as_int (buffer + 9, i.to.hours(), 1);
240 put_int_as_int (buffer + 10, i.to.minutes(), 1);
241 put_int_as_int (buffer + 11, i.to.seconds(), 1);
242 put_int_as_int (buffer + 12, i.to.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
243 /* Vertical position */
244 put_int_as_int (buffer + 13, top.get(), 1);
246 /* Justification code */
247 /* XXX: this assumes the first line has the right value */
248 switch (i.lines.front().horizontal_position.reference) {
250 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_LEFT), 1);
252 case HORIZONTAL_CENTRE_OF_SCREEN:
253 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_CENTRE), 1);
255 case RIGHT_OF_SCREEN:
256 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_RIGHT), 1);
261 put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
263 /* Now make as many blocks as are needed to add all the text */
264 size_t const block_size = 112;
266 int block_number = 0;
267 while (offset < text.length()) {
268 size_t this_time = std::min(block_size, text.length() - offset);
269 put_string (buffer + 16, text.substr(offset, this_time) + string(block_size - this_time, '\x8f'));
272 /* Extension block number. Count up from 0 but use 0xff for the last one */
273 put_int_as_int (buffer + 3, offset == text.length() ? 0xff : block_number, 1);
276 char* finished = new char[tti_size];
277 memcpy (finished, buffer, tti_size);
278 tti.push_back (finished);
287 /** @param language ISO 3-character country code for the language of the subtitles */
289 sub::write_stl_binary (
290 vector<Subtitle> subtitles,
291 float frames_per_second,
293 string original_programme_title,
294 string original_episode_title,
295 string translated_programme_title,
296 string translated_episode_title,
297 string translator_name,
298 string translator_contact_details,
299 string creation_date,
300 string revision_date,
302 string country_of_origin,
305 string editor_contact_details,
306 boost::filesystem::path file_name
309 SUB_ASSERT (original_programme_title.size() <= 32);
310 SUB_ASSERT (original_episode_title.size() <= 32);
311 SUB_ASSERT (translated_programme_title.size() <= 32);
312 SUB_ASSERT (translated_episode_title.size() <= 32);
313 SUB_ASSERT (translator_name.size() <= 32);
314 SUB_ASSERT (translator_contact_details.size() <= 32);
315 SUB_ASSERT (creation_date.size() == 6);
316 SUB_ASSERT (revision_date.size() == 6);
317 SUB_ASSERT (revision_number <= 99);
318 SUB_ASSERT (country_of_origin.size() == 3);
319 SUB_ASSERT (publisher.size() <= 32);
320 SUB_ASSERT (editor_name.size() <= 32);
321 SUB_ASSERT (editor_contact_details.size() <= 32);
324 memset (buffer, 0, 1024);
325 STLBinaryTables tables;
327 /* Find the longest subtitle in characters */
331 BOOST_FOREACH (Subtitle const& i, subtitles) {
332 BOOST_FOREACH (Line const& j, i.lines) {
334 BOOST_FOREACH (Block const& k, j.blocks) {
337 longest = std::max (longest, t);
341 vector<char*> tti_blocks = make_tti_blocks (subtitles, tables, frames_per_second);
344 put_string (buffer + 0, "850");
345 /* Disk format code */
346 put_string (buffer + 3, stl_frame_rate_to_dfc (frames_per_second));
347 /* Display standard code: open subtitling */
348 put_string (buffer + 11, "0");
349 /* Character code table: Latin (ISO 6937) */
350 put_string (buffer + 12, "00");
351 put_string (buffer + 14, tables.language_enum_to_file (language));
352 put_string (buffer + 16, 32, original_programme_title);
353 put_string (buffer + 48, 32, original_episode_title);
354 put_string (buffer + 80, 32, translated_programme_title);
355 put_string (buffer + 112, 32, translated_episode_title);
356 put_string (buffer + 144, 32, translator_name);
357 put_string (buffer + 176, 32, translator_contact_details);
358 /* Subtitle list reference code */
359 put_string (buffer + 208, "0000000000000000");
360 put_string (buffer + 224, creation_date);
361 put_string (buffer + 230, revision_date);
362 put_int_as_string (buffer + 236, revision_number, 2);
364 put_int_as_string (buffer + 238, tti_blocks.size(), 5);
365 /* Total number of subtitles */
366 put_int_as_string (buffer + 243, subtitles.size(), 5);
367 /* Total number of subtitle groups */
368 put_string (buffer + 248, "001");
369 /* Maximum number of displayable characters in any text row */
370 put_int_as_string (buffer + 251, longest, 2);
371 /* Maximum number of displayable rows */
372 put_int_as_string (buffer + 253, ROWS, 2);
373 /* Time code status */
374 put_string (buffer + 255, "1");
375 /* Start-of-programme time code */
376 put_string (buffer + 256, "00000000");
377 /* First-in-cue time code */
378 put_string (buffer + 264, "00000000");
379 /* Total number of disks */
380 put_string (buffer + 272, "1");
381 /* Disk sequence number */
382 put_string (buffer + 273, "1");
383 put_string (buffer + 274, 3, country_of_origin);
384 put_string (buffer + 277, 32, publisher);
385 put_string (buffer + 309, 32, editor_name);
386 put_string (buffer + 341, 32, editor_contact_details);
388 ofstream output (file_name.string().c_str());
389 output.write (buffer, 1024);
390 BOOST_FOREACH (char* i, tti_blocks) {
391 output.write (i, 128);