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>
49 using boost::locale::conv::utf_to_utf;
50 using boost::optional;
53 /** Arbitrary number which to divide the screen into rows; e.g.
54 * 64 here would mean that there are 64 addressable vertical positions
55 * on the screen, each 1/64th of the screen height tall.
57 * The magic 23 makes our output agree more closely with
58 * AnnotationEdit, which makes life easier when testing.
60 static int const ROWS = 23;
63 put_string (char* p, string s)
65 memcpy (p, s.c_str (), s.length ());
69 put_string (char* p, unsigned int n, string s)
71 SUB_ASSERT (s.length() <= n);
73 memcpy (p, s.c_str (), s.length ());
74 memset (p + s.length(), ' ', n - s.length ());
78 * @param n Width to zero-pad v to.
81 put_int_as_string (char* p, int v, unsigned int n)
87 snprintf (buffer, sizeof(buffer), "%02d", v);
90 snprintf (buffer, sizeof(buffer), "%05d", v);
98 struct lconv* lc = localeconv ();
99 boost::algorithm::replace_all (s, lc->thousands_sep, "");
100 boost::algorithm::replace_all (s, lc->decimal_point, ".");
106 put_int_as_int (char* p, int v, unsigned int n)
108 for (unsigned int i = 0; i < n; ++i) {
109 *p++ = (v & ((1 << ((i + 1) * 8)) - 1)) >> (i * 8);
114 vertical_position (sub::Line const & line)
117 if (line.vertical_position.proportional) {
118 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
120 vp = rint (line.vertical_position.proportional.get() * ROWS);
122 case VERTICAL_CENTRE_OF_SCREEN:
123 vp = rint (line.vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
125 case BOTTOM_OF_SCREEN:
126 vp = rint (ROWS - (line.vertical_position.proportional.get() * ROWS));
131 } else if (line.vertical_position.line) {
132 float const prop = float (line.vertical_position.line.get()) / line.vertical_position.lines.get ();
133 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
137 case VERTICAL_CENTRE_OF_SCREEN:
138 vp = (prop + 0.5) * ROWS;
140 case BOTTOM_OF_SCREEN:
141 vp = (1 - prop) * ROWS;
152 make_tti_blocks (list<Subtitle> const& subtitles, STLBinaryTables const& tables, float frames_per_second)
154 static int const tti_size = 128;
157 /* Buffer to build the TTI blocks in */
158 char buffer[tti_size];
160 BOOST_FOREACH (Subtitle const& i, subtitles) {
162 /* Find the top vertical position of this subtitle */
164 BOOST_FOREACH (Line const& j, i.lines) {
165 int const vp = vertical_position (j);
166 if (!top || vp < top.get ()) {
171 /* Work out the text */
174 bool underline = false;
175 optional<int> last_vp;
177 BOOST_FOREACH (Line const& j, i.lines) {
179 /* CR/LF down to this line */
180 int const vp = vertical_position (j);
183 for (int k = last_vp.get(); k < vp; ++k) {
190 BOOST_FOREACH (Block const& k, j.blocks) {
191 if (k.underline && !underline) {
194 } else if (underline && !k.underline) {
198 if (k.italic && !italic) {
201 } else if (italic && !k.italic) {
206 text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k.text));
210 /* Turn italic/underline off before the end of this subtitle */
218 /* Make sure there's at least one end-of-line */
221 /* Now write this text in 112 byte chunks (TTI blocks). Only the first TTI
222 block's cumulative status, timecodes, vertical position, justification code
223 and comment flag are taken into account by the reader.
226 /* Set up the first part of the block */
228 /* XXX: these should increment, surely! */
229 /* Subtitle group number */
230 put_int_as_int (buffer + 0, 1, 1);
231 /* Subtitle number */
232 put_int_as_int (buffer + 1, 0, 2);
233 /* Cumulative status */
234 put_int_as_int (buffer + 4, tables.cumulative_status_enum_to_file (CUMULATIVE_STATUS_NOT_CUMULATIVE), 1);
236 put_int_as_int (buffer + 5, i.from.hours(), 1);
237 put_int_as_int (buffer + 6, i.from.minutes(), 1);
238 put_int_as_int (buffer + 7, i.from.seconds(), 1);
239 put_int_as_int (buffer + 8, i.from.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
241 put_int_as_int (buffer + 9, i.to.hours(), 1);
242 put_int_as_int (buffer + 10, i.to.minutes(), 1);
243 put_int_as_int (buffer + 11, i.to.seconds(), 1);
244 put_int_as_int (buffer + 12, i.to.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
245 /* Vertical position */
246 put_int_as_int (buffer + 13, top.get(), 1);
248 /* Justification code */
249 /* XXX: this assumes the first line has the right value */
250 switch (i.lines.front().horizontal_position.reference) {
252 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_LEFT), 1);
254 case HORIZONTAL_CENTRE_OF_SCREEN:
255 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_CENTRE), 1);
257 case RIGHT_OF_SCREEN:
258 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_RIGHT), 1);
263 put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
265 /* Now make as many blocks as are needed to add all the text */
266 size_t const block_size = 112;
268 int block_number = 0;
269 while (offset < text.length()) {
270 size_t this_time = std::min(block_size, text.length() - offset);
271 put_string (buffer + 16, text.substr(offset, this_time) + string(block_size - this_time, '\x8f'));
274 /* Extension block number. Count up from 0 but use 0xff for the last one */
275 put_int_as_int (buffer + 3, offset == text.length() ? 0xff : block_number, 1);
278 char* finished = new char[tti_size];
279 memcpy (finished, buffer, tti_size);
280 tti.push_back (finished);
289 /** @param language ISO 3-character country code for the language of the subtitles */
291 sub::write_stl_binary (
292 list<Subtitle> subtitles,
293 float frames_per_second,
295 string original_programme_title,
296 string original_episode_title,
297 string translated_programme_title,
298 string translated_episode_title,
299 string translator_name,
300 string translator_contact_details,
301 string creation_date,
302 string revision_date,
304 string country_of_origin,
307 string editor_contact_details,
308 boost::filesystem::path file_name
311 SUB_ASSERT (original_programme_title.size() <= 32);
312 SUB_ASSERT (original_episode_title.size() <= 32);
313 SUB_ASSERT (translated_programme_title.size() <= 32);
314 SUB_ASSERT (translated_episode_title.size() <= 32);
315 SUB_ASSERT (translator_name.size() <= 32);
316 SUB_ASSERT (translator_contact_details.size() <= 32);
317 SUB_ASSERT (creation_date.size() == 6);
318 SUB_ASSERT (revision_date.size() == 6);
319 SUB_ASSERT (revision_number <= 99);
320 SUB_ASSERT (country_of_origin.size() == 3);
321 SUB_ASSERT (publisher.size() <= 32);
322 SUB_ASSERT (editor_name.size() <= 32);
323 SUB_ASSERT (editor_contact_details.size() <= 32);
326 memset (buffer, 0, 1024);
327 STLBinaryTables tables;
329 /* Find the longest subtitle in characters */
333 BOOST_FOREACH (Subtitle const& i, subtitles) {
334 BOOST_FOREACH (Line const& j, i.lines) {
336 BOOST_FOREACH (Block const& k, j.blocks) {
339 longest = std::max (longest, t);
343 vector<char*> tti_blocks = make_tti_blocks (subtitles, tables, frames_per_second);
346 put_string (buffer + 0, "850");
347 /* Disk format code */
348 put_string (buffer + 3, stl_frame_rate_to_dfc (frames_per_second));
349 /* Display standard code: open subtitling */
350 put_string (buffer + 11, "0");
351 /* Character code table: Latin (ISO 6937) */
352 put_string (buffer + 12, "00");
353 put_string (buffer + 14, tables.language_enum_to_file (language));
354 put_string (buffer + 16, 32, original_programme_title);
355 put_string (buffer + 48, 32, original_episode_title);
356 put_string (buffer + 80, 32, translated_programme_title);
357 put_string (buffer + 112, 32, translated_episode_title);
358 put_string (buffer + 144, 32, translator_name);
359 put_string (buffer + 176, 32, translator_contact_details);
360 /* Subtitle list reference code */
361 put_string (buffer + 208, "0000000000000000");
362 put_string (buffer + 224, creation_date);
363 put_string (buffer + 230, revision_date);
364 put_int_as_string (buffer + 236, revision_number, 2);
366 put_int_as_string (buffer + 238, tti_blocks.size(), 5);
367 /* Total number of subtitles */
368 put_int_as_string (buffer + 243, subtitles.size(), 5);
369 /* Total number of subtitle groups */
370 put_string (buffer + 248, "001");
371 /* Maximum number of displayable characters in any text row */
372 put_int_as_string (buffer + 251, longest, 2);
373 /* Maximum number of displayable rows */
374 put_int_as_string (buffer + 253, ROWS, 2);
375 /* Time code status */
376 put_string (buffer + 255, "1");
377 /* Start-of-programme time code */
378 put_string (buffer + 256, "00000000");
379 /* First-in-cue time code */
380 put_string (buffer + 264, "00000000");
381 /* Total number of disks */
382 put_string (buffer + 272, "1");
383 /* Disk sequence number */
384 put_string (buffer + 273, "1");
385 put_string (buffer + 274, 3, country_of_origin);
386 put_string (buffer + 277, 32, publisher);
387 put_string (buffer + 309, 32, editor_name);
388 put_string (buffer + 341, 32, editor_contact_details);
390 ofstream output (file_name.string().c_str());
391 output.write (buffer, 1024);
392 BOOST_FOREACH (char* i, tti_blocks) {
393 output.write (i, 128);