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>
46 using boost::locale::conv::utf_to_utf;
47 using boost::optional;
50 /** Arbitrary number which to divide the screen into rows; e.g.
51 * 64 here would mean that there are 64 addressable vertical positions
52 * on the screen, each 1/64th of the screen height tall.
54 * The magic 23 makes our output agree more closely with
55 * AnnotationEdit, which makes life easier when testing.
57 static int const ROWS = 23;
60 put_string (char* p, string s)
62 memcpy (p, s.c_str (), s.length ());
66 put_string (char* p, unsigned int n, string s)
68 SUB_ASSERT (s.length() <= n);
70 memcpy (p, s.c_str (), s.length ());
71 memset (p + s.length(), ' ', n - s.length ());
75 * @param n Width to zero-pad v to.
78 put_int_as_string (char* p, int v, unsigned int n)
84 snprintf (buffer, sizeof(buffer), "%02d", v);
87 snprintf (buffer, sizeof(buffer), "%05d", v);
95 struct lconv* lc = localeconv ();
96 boost::algorithm::replace_all (s, lc->thousands_sep, "");
97 boost::algorithm::replace_all (s, lc->decimal_point, ".");
103 put_int_as_int (char* p, int v, unsigned int n)
105 for (unsigned int i = 0; i < n; ++i) {
106 *p++ = (v & ((1 << ((i + 1) * 8)) - 1)) >> (i * 8);
111 vertical_position (sub::Line const & line)
114 if (line.vertical_position.proportional) {
115 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
117 vp = rint (line.vertical_position.proportional.get() * ROWS);
119 case VERTICAL_CENTRE_OF_SCREEN:
120 vp = rint (line.vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
122 case BOTTOM_OF_SCREEN:
123 vp = rint (ROWS - (line.vertical_position.proportional.get() * ROWS));
128 } else if (line.vertical_position.line) {
129 float const prop = float (line.vertical_position.line.get()) / line.vertical_position.lines.get ();
130 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
134 case VERTICAL_CENTRE_OF_SCREEN:
135 vp = (prop + 0.5) * ROWS;
137 case BOTTOM_OF_SCREEN:
138 vp = (1 - prop) * ROWS;
149 make_tti_blocks (vector<Subtitle> const& subtitles, STLBinaryTables const& tables, float frames_per_second)
151 static int const tti_size = 128;
154 /* Buffer to build the TTI blocks in */
155 char buffer[tti_size];
157 for (auto const& i: subtitles) {
159 /* Find the top vertical position of this subtitle */
161 for (auto const& j: i.lines) {
162 int const vp = vertical_position (j);
163 if (!top || vp < top.get ()) {
168 /* Work out the text */
171 bool underline = false;
172 optional<int> last_vp;
174 for (auto const& j: i.lines) {
176 /* CR/LF down to this line */
177 int const vp = vertical_position (j);
180 for (int k = last_vp.get(); k < vp; ++k) {
187 for (auto const& k: j.blocks) {
188 if (k.underline && !underline) {
191 } else if (underline && !k.underline) {
195 if (k.italic && !italic) {
198 } else if (italic && !k.italic) {
203 text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k.text));
207 /* Turn italic/underline off before the end of this subtitle */
215 /* Make sure there's at least one end-of-line */
218 /* Now write this text in 112 byte chunks (TTI blocks). Only the first TTI
219 block's cumulative status, timecodes, vertical position, justification code
220 and comment flag are taken into account by the reader.
223 /* Set up the first part of the block */
225 /* XXX: these should increment, surely! */
226 /* Subtitle group number */
227 put_int_as_int (buffer + 0, 1, 1);
228 /* Subtitle number */
229 put_int_as_int (buffer + 1, 0, 2);
230 /* Cumulative status */
231 put_int_as_int (buffer + 4, tables.cumulative_status_enum_to_file (CUMULATIVE_STATUS_NOT_CUMULATIVE), 1);
233 put_int_as_int (buffer + 5, i.from.hours(), 1);
234 put_int_as_int (buffer + 6, i.from.minutes(), 1);
235 put_int_as_int (buffer + 7, i.from.seconds(), 1);
236 put_int_as_int (buffer + 8, i.from.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
238 put_int_as_int (buffer + 9, i.to.hours(), 1);
239 put_int_as_int (buffer + 10, i.to.minutes(), 1);
240 put_int_as_int (buffer + 11, i.to.seconds(), 1);
241 put_int_as_int (buffer + 12, i.to.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
242 /* Vertical position */
243 put_int_as_int (buffer + 13, top.get(), 1);
245 /* Justification code */
246 /* XXX: this assumes the first line has the right value */
247 switch (i.lines.front().horizontal_position.reference) {
249 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_LEFT), 1);
251 case HORIZONTAL_CENTRE_OF_SCREEN:
252 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_CENTRE), 1);
254 case RIGHT_OF_SCREEN:
255 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_RIGHT), 1);
260 put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
262 /* Now make as many blocks as are needed to add all the text */
263 size_t const block_size = 112;
265 int block_number = 0;
266 while (offset < text.length()) {
267 size_t this_time = std::min(block_size, text.length() - offset);
268 put_string (buffer + 16, text.substr(offset, this_time) + string(block_size - this_time, '\x8f'));
271 /* Extension block number. Count up from 0 but use 0xff for the last one */
272 put_int_as_int (buffer + 3, offset == text.length() ? 0xff : block_number, 1);
275 char* finished = new char[tti_size];
276 memcpy (finished, buffer, tti_size);
277 tti.push_back (finished);
286 /** @param language ISO 3-character country code for the language of the subtitles */
288 sub::write_stl_binary (
289 vector<Subtitle> subtitles,
290 float frames_per_second,
292 string original_programme_title,
293 string original_episode_title,
294 string translated_programme_title,
295 string translated_episode_title,
296 string translator_name,
297 string translator_contact_details,
298 string creation_date,
299 string revision_date,
301 string country_of_origin,
304 string editor_contact_details,
305 boost::filesystem::path file_name
308 SUB_ASSERT (original_programme_title.size() <= 32);
309 SUB_ASSERT (original_episode_title.size() <= 32);
310 SUB_ASSERT (translated_programme_title.size() <= 32);
311 SUB_ASSERT (translated_episode_title.size() <= 32);
312 SUB_ASSERT (translator_name.size() <= 32);
313 SUB_ASSERT (translator_contact_details.size() <= 32);
314 SUB_ASSERT (creation_date.size() == 6);
315 SUB_ASSERT (revision_date.size() == 6);
316 SUB_ASSERT (revision_number <= 99);
317 SUB_ASSERT (country_of_origin.size() == 3);
318 SUB_ASSERT (publisher.size() <= 32);
319 SUB_ASSERT (editor_name.size() <= 32);
320 SUB_ASSERT (editor_contact_details.size() <= 32);
323 memset (buffer, 0, 1024);
324 STLBinaryTables tables;
326 /* Find the longest subtitle in characters */
330 for (auto const& i: subtitles) {
331 for (auto const& j: i.lines) {
333 for (auto const& k: j.blocks) {
336 longest = std::max (longest, t);
340 vector<char*> tti_blocks = make_tti_blocks (subtitles, tables, frames_per_second);
343 put_string (buffer + 0, "850");
344 /* Disk format code */
345 put_string (buffer + 3, stl_frame_rate_to_dfc (frames_per_second));
346 /* Display standard code: open subtitling */
347 put_string (buffer + 11, "0");
348 /* Character code table: Latin (ISO 6937) */
349 put_string (buffer + 12, "00");
350 put_string (buffer + 14, tables.language_enum_to_file (language));
351 put_string (buffer + 16, 32, original_programme_title);
352 put_string (buffer + 48, 32, original_episode_title);
353 put_string (buffer + 80, 32, translated_programme_title);
354 put_string (buffer + 112, 32, translated_episode_title);
355 put_string (buffer + 144, 32, translator_name);
356 put_string (buffer + 176, 32, translator_contact_details);
357 /* Subtitle list reference code */
358 put_string (buffer + 208, "0000000000000000");
359 put_string (buffer + 224, creation_date);
360 put_string (buffer + 230, revision_date);
361 put_int_as_string (buffer + 236, revision_number, 2);
363 put_int_as_string (buffer + 238, tti_blocks.size(), 5);
364 /* Total number of subtitles */
365 put_int_as_string (buffer + 243, subtitles.size(), 5);
366 /* Total number of subtitle groups */
367 put_string (buffer + 248, "001");
368 /* Maximum number of displayable characters in any text row */
369 put_int_as_string (buffer + 251, longest, 2);
370 /* Maximum number of displayable rows */
371 put_int_as_string (buffer + 253, ROWS, 2);
372 /* Time code status */
373 put_string (buffer + 255, "1");
374 /* Start-of-programme time code */
375 put_string (buffer + 256, "00000000");
376 /* First-in-cue time code */
377 put_string (buffer + 264, "00000000");
378 /* Total number of disks */
379 put_string (buffer + 272, "1");
380 /* Disk sequence number */
381 put_string (buffer + 273, "1");
382 put_string (buffer + 274, 3, country_of_origin);
383 put_string (buffer + 277, 32, publisher);
384 put_string (buffer + 309, 32, editor_name);
385 put_string (buffer + 341, 32, editor_contact_details);
387 ofstream output (file_name.string().c_str());
388 output.write (buffer, 1024);
389 for (auto i: tti_blocks) {
390 output.write (i, 128);