2 Copyright (C) 2014 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>
45 using boost::locale::conv::utf_to_utf;
48 /** Arbitrary number which to divide the screen into rows; e.g.
49 * 64 here would mean that there are 64 addressable vertical positions
50 * on the screen, each 1/64th of the screen height tall.
52 * The magic 23 makes our output agree more closely with
53 * AnnotationEdit, which makes life easier when testing.
55 static int const ROWS = 23;
58 put_string (char* p, string s)
60 memcpy (p, s.c_str (), s.length ());
64 put_string (char* p, unsigned int n, string s)
66 SUB_ASSERT (s.length() <= n);
68 memcpy (p, s.c_str (), s.length ());
69 memset (p + s.length(), ' ', n - s.length ());
73 put_int_as_string (char* p, int v, unsigned int n)
75 locked_stringstream s;
76 /* Be careful to ensure we get no thousands separators */
77 s.imbue (std::locale::classic ());
78 s << setw (n) << setfill ('0');
80 SUB_ASSERT (s.str().length() == n);
81 put_string (p, s.str ());
85 put_int_as_int (char* p, int v, unsigned int n)
87 for (unsigned int i = 0; i < n; ++i) {
88 *p++ = (v & ((1 << ((i + 1) * 8)) - 1)) >> (i * 8);
92 /** @param language ISO 3-character country code for the language of the subtitles */
94 sub::write_stl_binary (
95 list<Subtitle> subtitles,
96 float frames_per_second,
98 string original_programme_title,
99 string original_episode_title,
100 string translated_programme_title,
101 string translated_episode_title,
102 string translator_name,
103 string translator_contact_details,
104 string creation_date,
105 string revision_date,
107 string country_of_origin,
110 string editor_contact_details,
111 boost::filesystem::path file_name
114 SUB_ASSERT (original_programme_title.size() <= 32);
115 SUB_ASSERT (original_episode_title.size() <= 32);
116 SUB_ASSERT (translated_programme_title.size() <= 32);
117 SUB_ASSERT (translated_episode_title.size() <= 32);
118 SUB_ASSERT (translator_name.size() <= 32);
119 SUB_ASSERT (translator_contact_details.size() <= 32);
120 SUB_ASSERT (creation_date.size() == 6);
121 SUB_ASSERT (revision_date.size() == 6);
122 SUB_ASSERT (revision_number <= 99);
123 SUB_ASSERT (country_of_origin.size() == 3);
124 SUB_ASSERT (publisher.size() <= 32);
125 SUB_ASSERT (editor_name.size() <= 32);
126 SUB_ASSERT (editor_contact_details.size() <= 32);
128 char* buffer = new char[1024];
129 memset (buffer, 0, 1024);
130 ofstream output (file_name.string().c_str ());
131 STLBinaryTables tables;
133 /* Find the longest subtitle in characters and the number of lines */
138 for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
139 for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
141 for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
142 t += k->text.size ();
144 longest = max (longest, t);
150 put_string (buffer + 0, "850");
151 /* Disk format code */
152 put_string (buffer + 3, stl_frame_rate_to_dfc (frames_per_second));
153 /* Display standard code: open subtitling */
154 put_string (buffer + 11, "0");
155 /* Character code table: Latin (ISO 6937) */
156 put_string (buffer + 12, "00");
157 put_string (buffer + 14, tables.language_enum_to_file (language));
158 put_string (buffer + 16, 32, original_programme_title);
159 put_string (buffer + 48, 32, original_episode_title);
160 put_string (buffer + 80, 32, translated_programme_title);
161 put_string (buffer + 112, 32, translated_episode_title);
162 put_string (buffer + 144, 32, translator_name);
163 put_string (buffer + 176, 32, translator_contact_details);
164 /* Subtitle list reference code */
165 put_string (buffer + 208, "0000000000000000");
166 put_string (buffer + 224, creation_date);
167 put_string (buffer + 230, revision_date);
168 put_int_as_string (buffer + 236, revision_number, 2);
170 put_int_as_string (buffer + 238, lines, 5);
171 /* Total number of subtitles */
172 put_int_as_string (buffer + 243, lines, 5);
173 /* Total number of subtitle groups */
174 put_string (buffer + 248, "001");
175 /* Maximum number of displayable characters in any text row */
176 put_int_as_string (buffer + 251, longest, 2);
177 /* Maximum number of displayable rows */
178 put_int_as_string (buffer + 253, ROWS, 2);
179 /* Time code status */
180 put_string (buffer + 255, "1");
181 /* Start-of-programme time code */
182 put_string (buffer + 256, "00000000");
183 /* First-in-cue time code */
184 put_string (buffer + 264, "00000000");
185 /* Total number of disks */
186 put_string (buffer + 272, "1");
187 /* Disk sequence number */
188 put_string (buffer + 273, "1");
189 put_string (buffer + 274, 3, country_of_origin);
190 put_string (buffer + 277, 32, publisher);
191 put_string (buffer + 309, 32, editor_name);
192 put_string (buffer + 341, 32, editor_contact_details);
194 output.write (buffer, 1024);
196 for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
198 for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
200 memset (buffer, 0, 1024);
202 /* Subtitle group number */
203 put_int_as_int (buffer + 0, 1, 1);
204 /* Subtitle number */
205 put_int_as_int (buffer + 1, N, 2);
206 /* Extension block number. Use 0xff here to indicate that it is the last TTI
207 block in this subtitle "set", as we only ever use one.
209 put_int_as_int (buffer + 3, 255, 1);
210 /* Cumulative status */
211 put_int_as_int (buffer + 4, tables.cumulative_status_enum_to_file (CUMULATIVE_STATUS_NOT_CUMULATIVE), 1);
213 put_int_as_int (buffer + 5, i->from.hours (), 1);
214 put_int_as_int (buffer + 6, i->from.minutes (), 1);
215 put_int_as_int (buffer + 7, i->from.seconds (), 1);
216 put_int_as_int (buffer + 8, i->from.frames_at (sub::Rational (frames_per_second * 1000, 1000)), 1);
218 put_int_as_int (buffer + 9, i->to.hours (), 1);
219 put_int_as_int (buffer + 10, i->to.minutes (), 1);
220 put_int_as_int (buffer + 11, i->to.seconds (), 1);
221 put_int_as_int (buffer + 12, i->to.frames_at (sub::Rational (frames_per_second * 1000, 1000)), 1);
222 /* Vertical position */
224 if (j->vertical_position.proportional) {
225 switch (j->vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
227 vp = rint (j->vertical_position.proportional.get() * ROWS);
229 case CENTRE_OF_SCREEN:
230 vp = rint (j->vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
232 case BOTTOM_OF_SCREEN:
233 vp = rint (ROWS - (j->vertical_position.proportional.get() * ROWS));
238 } else if (j->vertical_position.line) {
239 float const prop = float (j->vertical_position.line.get()) / j->vertical_position.lines.get ();
240 switch (j->vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
244 case CENTRE_OF_SCREEN:
245 vp = (prop + 0.5) * ROWS;
247 case BOTTOM_OF_SCREEN:
248 vp = (1 - prop) * ROWS;
254 put_int_as_int (buffer + 13, vp, 1);
255 /* Justification code */
257 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_NONE), 1);
259 put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
264 bool underline = false;
266 for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
267 if (k->underline && !underline) {
270 } else if (underline && !k->underline) {
274 if (k->italic && !italic) {
277 } else if (italic && !k->italic) {
282 text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k->text));
287 if (text.length() > 111) {
288 text = text.substr (111);
291 while (text.length() < 112) {
295 put_string (buffer + 16, text);
296 output.write (buffer, 128);