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>
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 put_int_as_string (char* p, int v, unsigned int n)
77 locked_stringstream s;
78 /* Be careful to ensure we get no thousands separators */
79 s.imbue (std::locale::classic ());
80 s << setw (n) << setfill ('0');
82 SUB_ASSERT (s.str().length() == n);
83 put_string (p, s.str ());
87 put_int_as_int (char* p, int v, unsigned int n)
89 for (unsigned int i = 0; i < n; ++i) {
90 *p++ = (v & ((1 << ((i + 1) * 8)) - 1)) >> (i * 8);
95 vertical_position (sub::Line const & line)
98 if (line.vertical_position.proportional) {
99 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
101 vp = rint (line.vertical_position.proportional.get() * ROWS);
103 case VERTICAL_CENTRE_OF_SCREEN:
104 vp = rint (line.vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
106 case BOTTOM_OF_SCREEN:
107 vp = rint (ROWS - (line.vertical_position.proportional.get() * ROWS));
112 } else if (line.vertical_position.line) {
113 float const prop = float (line.vertical_position.line.get()) / line.vertical_position.lines.get ();
114 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
118 case VERTICAL_CENTRE_OF_SCREEN:
119 vp = (prop + 0.5) * ROWS;
121 case BOTTOM_OF_SCREEN:
122 vp = (1 - prop) * ROWS;
132 /** @param language ISO 3-character country code for the language of the subtitles */
134 sub::write_stl_binary (
135 list<Subtitle> subtitles,
136 float frames_per_second,
138 string original_programme_title,
139 string original_episode_title,
140 string translated_programme_title,
141 string translated_episode_title,
142 string translator_name,
143 string translator_contact_details,
144 string creation_date,
145 string revision_date,
147 string country_of_origin,
150 string editor_contact_details,
151 boost::filesystem::path file_name
154 SUB_ASSERT (original_programme_title.size() <= 32);
155 SUB_ASSERT (original_episode_title.size() <= 32);
156 SUB_ASSERT (translated_programme_title.size() <= 32);
157 SUB_ASSERT (translated_episode_title.size() <= 32);
158 SUB_ASSERT (translator_name.size() <= 32);
159 SUB_ASSERT (translator_contact_details.size() <= 32);
160 SUB_ASSERT (creation_date.size() == 6);
161 SUB_ASSERT (revision_date.size() == 6);
162 SUB_ASSERT (revision_number <= 99);
163 SUB_ASSERT (country_of_origin.size() == 3);
164 SUB_ASSERT (publisher.size() <= 32);
165 SUB_ASSERT (editor_name.size() <= 32);
166 SUB_ASSERT (editor_contact_details.size() <= 32);
168 char* buffer = new char[1024];
169 memset (buffer, 0, 1024);
170 ofstream output (file_name.string().c_str ());
171 STLBinaryTables tables;
173 /* Find the longest subtitle in characters and count the number of lines we have */
176 BOOST_FOREACH (Subtitle const& i, subtitles) {
177 BOOST_FOREACH (Line const& j, i.lines) {
179 BOOST_FOREACH (Block const& k, j.blocks) {
182 longest = std::max (longest, t);
188 put_string (buffer + 0, "850");
189 /* Disk format code */
190 put_string (buffer + 3, stl_frame_rate_to_dfc (frames_per_second));
191 /* Display standard code: open subtitling */
192 put_string (buffer + 11, "0");
193 /* Character code table: Latin (ISO 6937) */
194 put_string (buffer + 12, "00");
195 put_string (buffer + 14, tables.language_enum_to_file (language));
196 put_string (buffer + 16, 32, original_programme_title);
197 put_string (buffer + 48, 32, original_episode_title);
198 put_string (buffer + 80, 32, translated_programme_title);
199 put_string (buffer + 112, 32, translated_episode_title);
200 put_string (buffer + 144, 32, translator_name);
201 put_string (buffer + 176, 32, translator_contact_details);
202 /* Subtitle list reference code */
203 put_string (buffer + 208, "0000000000000000");
204 put_string (buffer + 224, creation_date);
205 put_string (buffer + 230, revision_date);
206 put_int_as_string (buffer + 236, revision_number, 2);
208 put_int_as_string (buffer + 238, lines, 5);
209 /* Total number of subtitles */
210 put_int_as_string (buffer + 243, subtitles.size(), 5);
211 /* Total number of subtitle groups */
212 put_string (buffer + 248, "001");
213 /* Maximum number of displayable characters in any text row */
214 put_int_as_string (buffer + 251, longest, 2);
215 /* Maximum number of displayable rows */
216 put_int_as_string (buffer + 253, ROWS, 2);
217 /* Time code status */
218 put_string (buffer + 255, "1");
219 /* Start-of-programme time code */
220 put_string (buffer + 256, "00000000");
221 /* First-in-cue time code */
222 put_string (buffer + 264, "00000000");
223 /* Total number of disks */
224 put_string (buffer + 272, "1");
225 /* Disk sequence number */
226 put_string (buffer + 273, "1");
227 put_string (buffer + 274, 3, country_of_origin);
228 put_string (buffer + 277, 32, publisher);
229 put_string (buffer + 309, 32, editor_name);
230 put_string (buffer + 341, 32, editor_contact_details);
232 output.write (buffer, 1024);
234 BOOST_FOREACH (Subtitle const& i, subtitles) {
237 BOOST_FOREACH (Line const& j, i.lines) {
239 memset (buffer, 0, 128);
241 /* XXX: these should increment, surely! */
242 /* Subtitle group number */
243 put_int_as_int (buffer + 0, 1, 1);
244 /* Subtitle number */
245 put_int_as_int (buffer + 1, 0, 2);
246 /* Extension block number. These are indexed from zero except the last one is 255 */
247 put_int_as_int (buffer + 3, line == (i.lines.size() - 1) ? 255 : line, 1);
248 /* Cumulative status */
249 put_int_as_int (buffer + 4, tables.cumulative_status_enum_to_file (CUMULATIVE_STATUS_NOT_CUMULATIVE), 1);
251 put_int_as_int (buffer + 5, i.from.hours(), 1);
252 put_int_as_int (buffer + 6, i.from.minutes(), 1);
253 put_int_as_int (buffer + 7, i.from.seconds(), 1);
254 put_int_as_int (buffer + 8, i.from.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
256 put_int_as_int (buffer + 9, i.to.hours(), 1);
257 put_int_as_int (buffer + 10, i.to.minutes(), 1);
258 put_int_as_int (buffer + 11, i.to.seconds(), 1);
259 put_int_as_int (buffer + 12, i.to.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
260 /* Vertical position */
261 put_int_as_int (buffer + 13, vertical_position(j), 1);
263 /* Justification code */
264 switch (j.horizontal_position.reference) {
266 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_LEFT), 1);
268 case HORIZONTAL_CENTRE_OF_SCREEN:
269 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_CENTRE), 1);
271 case RIGHT_OF_SCREEN:
272 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_RIGHT), 1);
277 put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
282 bool underline = false;
284 BOOST_FOREACH (Block const& k, j.blocks) {
285 if (k.underline && !underline) {
288 } else if (underline && !k.underline) {
292 if (k.italic && !italic) {
295 } else if (italic && !k.italic) {
300 text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k.text));
303 /* Turn italic/underline off before the end of this subtitle */
313 if (text.length() > 111) {
314 text = text.substr (111);
317 while (text.length() < 112) {
321 put_string (buffer + 16, text);
322 output.write (buffer, 128);