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/foreach.hpp>
48 using boost::locale::conv::utf_to_utf;
49 using boost::optional;
52 /** Arbitrary number which to divide the screen into rows; e.g.
53 * 64 here would mean that there are 64 addressable vertical positions
54 * on the screen, each 1/64th of the screen height tall.
56 * The magic 23 makes our output agree more closely with
57 * AnnotationEdit, which makes life easier when testing.
59 static int const ROWS = 23;
62 put_string (char* p, string s)
64 memcpy (p, s.c_str (), s.length ());
68 put_string (char* p, unsigned int n, string s)
70 SUB_ASSERT (s.length() <= n);
72 memcpy (p, s.c_str (), s.length ());
73 memset (p + s.length(), ' ', n - s.length ());
77 put_int_as_string (char* p, int v, unsigned int n)
79 locked_stringstream s;
80 /* Be careful to ensure we get no thousands separators */
81 s.imbue (std::locale::classic ());
82 s << setw (n) << setfill ('0');
84 SUB_ASSERT (s.str().length() == n);
85 put_string (p, s.str ());
89 put_int_as_int (char* p, int v, unsigned int n)
91 for (unsigned int i = 0; i < n; ++i) {
92 *p++ = (v & ((1 << ((i + 1) * 8)) - 1)) >> (i * 8);
97 vertical_position (sub::Line const & line)
100 if (line.vertical_position.proportional) {
101 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
103 vp = rint (line.vertical_position.proportional.get() * ROWS);
105 case VERTICAL_CENTRE_OF_SCREEN:
106 vp = rint (line.vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
108 case BOTTOM_OF_SCREEN:
109 vp = rint (ROWS - (line.vertical_position.proportional.get() * ROWS));
114 } else if (line.vertical_position.line) {
115 float const prop = float (line.vertical_position.line.get()) / line.vertical_position.lines.get ();
116 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
120 case VERTICAL_CENTRE_OF_SCREEN:
121 vp = (prop + 0.5) * ROWS;
123 case BOTTOM_OF_SCREEN:
124 vp = (1 - prop) * ROWS;
135 make_tti_blocks (list<Subtitle> const& subtitles, STLBinaryTables const& tables, float frames_per_second)
137 static int const tti_size = 128;
140 /* Buffer to build the TTI blocks in */
141 char buffer[tti_size];
143 BOOST_FOREACH (Subtitle const& i, subtitles) {
145 /* Find the top vertical position of this subtitle */
147 BOOST_FOREACH (Line const& j, i.lines) {
148 int const vp = vertical_position (j);
149 if (!top || vp < top.get ()) {
154 /* Work out the text */
157 bool underline = false;
158 optional<int> last_vp;
160 BOOST_FOREACH (Line const& j, i.lines) {
162 /* CR/LF down to this line */
163 int const vp = vertical_position (j);
166 for (int k = last_vp.get(); k < vp; ++k) {
173 BOOST_FOREACH (Block const& k, j.blocks) {
174 if (k.underline && !underline) {
177 } else if (underline && !k.underline) {
181 if (k.italic && !italic) {
184 } else if (italic && !k.italic) {
189 text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k.text));
193 /* Turn italic/underline off before the end of this subtitle */
201 /* Make sure there's at least one end-of-line */
204 /* Now write this text in 112 byte chunks (TTI blocks). Only the first TTI
205 block's cumulative status, timecodes, vertical position, justification code
206 and comment flag are taken into account by the reader.
209 /* Set up the first part of the block */
211 /* XXX: these should increment, surely! */
212 /* Subtitle group number */
213 put_int_as_int (buffer + 0, 1, 1);
214 /* Subtitle number */
215 put_int_as_int (buffer + 1, 0, 2);
216 /* Cumulative status */
217 put_int_as_int (buffer + 4, tables.cumulative_status_enum_to_file (CUMULATIVE_STATUS_NOT_CUMULATIVE), 1);
219 put_int_as_int (buffer + 5, i.from.hours(), 1);
220 put_int_as_int (buffer + 6, i.from.minutes(), 1);
221 put_int_as_int (buffer + 7, i.from.seconds(), 1);
222 put_int_as_int (buffer + 8, i.from.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
224 put_int_as_int (buffer + 9, i.to.hours(), 1);
225 put_int_as_int (buffer + 10, i.to.minutes(), 1);
226 put_int_as_int (buffer + 11, i.to.seconds(), 1);
227 put_int_as_int (buffer + 12, i.to.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
228 /* Vertical position */
229 put_int_as_int (buffer + 13, top.get(), 1);
231 /* Justification code */
232 /* XXX: this assumes the first line has the right value */
233 switch (i.lines.front().horizontal_position.reference) {
235 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_LEFT), 1);
237 case HORIZONTAL_CENTRE_OF_SCREEN:
238 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_CENTRE), 1);
240 case RIGHT_OF_SCREEN:
241 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_RIGHT), 1);
246 put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
248 /* Now make as many blocks as are needed to add all the text */
249 size_t const block_size = 112;
251 int block_number = 0;
252 while (offset < text.length()) {
253 size_t this_time = std::min(block_size, text.length() - offset);
254 put_string (buffer + 16, text.substr(offset, this_time) + string(block_size - this_time, '\x8f'));
257 /* Extension block number. Count up from 0 but use 0xff for the last one */
258 put_int_as_int (buffer + 3, offset == text.length() ? 0xff : block_number, 1);
261 char* finished = new char[tti_size];
262 memcpy (finished, buffer, tti_size);
263 tti.push_back (finished);
272 /** @param language ISO 3-character country code for the language of the subtitles */
274 sub::write_stl_binary (
275 list<Subtitle> subtitles,
276 float frames_per_second,
278 string original_programme_title,
279 string original_episode_title,
280 string translated_programme_title,
281 string translated_episode_title,
282 string translator_name,
283 string translator_contact_details,
284 string creation_date,
285 string revision_date,
287 string country_of_origin,
290 string editor_contact_details,
291 boost::filesystem::path file_name
294 SUB_ASSERT (original_programme_title.size() <= 32);
295 SUB_ASSERT (original_episode_title.size() <= 32);
296 SUB_ASSERT (translated_programme_title.size() <= 32);
297 SUB_ASSERT (translated_episode_title.size() <= 32);
298 SUB_ASSERT (translator_name.size() <= 32);
299 SUB_ASSERT (translator_contact_details.size() <= 32);
300 SUB_ASSERT (creation_date.size() == 6);
301 SUB_ASSERT (revision_date.size() == 6);
302 SUB_ASSERT (revision_number <= 99);
303 SUB_ASSERT (country_of_origin.size() == 3);
304 SUB_ASSERT (publisher.size() <= 32);
305 SUB_ASSERT (editor_name.size() <= 32);
306 SUB_ASSERT (editor_contact_details.size() <= 32);
309 memset (buffer, 0, 1024);
310 STLBinaryTables tables;
312 /* Find the longest subtitle in characters */
316 BOOST_FOREACH (Subtitle const& i, subtitles) {
317 BOOST_FOREACH (Line const& j, i.lines) {
319 BOOST_FOREACH (Block const& k, j.blocks) {
322 longest = std::max (longest, t);
326 vector<char*> tti_blocks = make_tti_blocks (subtitles, tables, frames_per_second);
329 put_string (buffer + 0, "850");
330 /* Disk format code */
331 put_string (buffer + 3, stl_frame_rate_to_dfc (frames_per_second));
332 /* Display standard code: open subtitling */
333 put_string (buffer + 11, "0");
334 /* Character code table: Latin (ISO 6937) */
335 put_string (buffer + 12, "00");
336 put_string (buffer + 14, tables.language_enum_to_file (language));
337 put_string (buffer + 16, 32, original_programme_title);
338 put_string (buffer + 48, 32, original_episode_title);
339 put_string (buffer + 80, 32, translated_programme_title);
340 put_string (buffer + 112, 32, translated_episode_title);
341 put_string (buffer + 144, 32, translator_name);
342 put_string (buffer + 176, 32, translator_contact_details);
343 /* Subtitle list reference code */
344 put_string (buffer + 208, "0000000000000000");
345 put_string (buffer + 224, creation_date);
346 put_string (buffer + 230, revision_date);
347 put_int_as_string (buffer + 236, revision_number, 2);
349 put_int_as_string (buffer + 238, tti_blocks.size(), 5);
350 /* Total number of subtitles */
351 put_int_as_string (buffer + 243, subtitles.size(), 5);
352 /* Total number of subtitle groups */
353 put_string (buffer + 248, "001");
354 /* Maximum number of displayable characters in any text row */
355 put_int_as_string (buffer + 251, longest, 2);
356 /* Maximum number of displayable rows */
357 put_int_as_string (buffer + 253, ROWS, 2);
358 /* Time code status */
359 put_string (buffer + 255, "1");
360 /* Start-of-programme time code */
361 put_string (buffer + 256, "00000000");
362 /* First-in-cue time code */
363 put_string (buffer + 264, "00000000");
364 /* Total number of disks */
365 put_string (buffer + 272, "1");
366 /* Disk sequence number */
367 put_string (buffer + 273, "1");
368 put_string (buffer + 274, 3, country_of_origin);
369 put_string (buffer + 277, 32, publisher);
370 put_string (buffer + 309, 32, editor_name);
371 put_string (buffer + 341, 32, editor_contact_details);
373 ofstream output (file_name.string().c_str());
374 output.write (buffer, 1024);
375 BOOST_FOREACH (char* i, tti_blocks) {
376 output.write (i, 128);