Fix build.
[libsub.git] / src / stl_binary_writer.cc
1 /*
2     Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
3
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.
8
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.
13
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.
17
18 */
19
20 /** @file  src/stl_binary_writer.cc
21  *  @brief Writer for STL binary files.
22  */
23
24 #include "stl_binary_writer.h"
25 #include "subtitle.h"
26 #include "iso6937.h"
27 #include "stl_util.h"
28 #include "compose.hpp"
29 #include "sub_assert.h"
30 #include <boost/locale.hpp>
31 #include <boost/algorithm/string.hpp>
32 #include <list>
33 #include <cmath>
34 #include <fstream>
35 #include <iomanip>
36 #include <set>
37
38 using std::list;
39 using std::set;
40 using std::ofstream;
41 using std::string;
42 using std::setw;
43 using std::setfill;
44 using std::max;
45 using std::cout;
46 using boost::locale::conv::utf_to_utf;
47 using boost::optional;
48 using namespace sub;
49
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.
53  *
54  *  The magic 23 makes our output agree more closely with
55  *  AnnotationEdit, which makes life easier when testing.
56  */
57 static int const ROWS = 23;
58
59 static void
60 put_string (char* p, string s)
61 {
62         memcpy (p, s.c_str (), s.length ());
63 }
64
65 static void
66 put_string (char* p, unsigned int n, string s)
67 {
68         SUB_ASSERT (s.length() <= n);
69
70         memcpy (p, s.c_str (), s.length ());
71         memset (p + s.length(), ' ', n - s.length ());
72 }
73
74 /** @param v Value
75  *  @param n Width to zero-pad v to.
76  */
77 static void
78 put_int_as_string (char* p, int v, unsigned int n)
79 {
80         char buffer[64];
81
82         switch (n) {
83         case 2:
84                 snprintf (buffer, sizeof(buffer), "%02d", v);
85                 break;
86         case 5:
87                 snprintf (buffer, sizeof(buffer), "%05d", v);
88                 break;
89         default:
90                 SUB_ASSERT (false);
91         }
92
93         string s = buffer;
94
95         struct lconv* lc = localeconv ();
96         boost::algorithm::replace_all (s, lc->thousands_sep, "");
97         boost::algorithm::replace_all (s, lc->decimal_point, ".");
98
99         put_string (p, s);
100 }
101
102 static void
103 put_int_as_int (char* p, int v, unsigned int n)
104 {
105         for (unsigned int i = 0; i < n; ++i) {
106                 *p++ = (v & ((1 << ((i + 1) * 8)) - 1)) >> (i * 8);
107         }
108 }
109
110 static int
111 vertical_position (sub::Line const & line)
112 {
113         int vp = 0;
114         if (line.vertical_position.proportional) {
115                 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
116                 case TOP_OF_SCREEN:
117                         vp = rint (line.vertical_position.proportional.get() * ROWS);
118                         break;
119                 case VERTICAL_CENTRE_OF_SCREEN:
120                         vp = rint (line.vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
121                         break;
122                 case BOTTOM_OF_SCREEN:
123                         vp = rint (ROWS - (line.vertical_position.proportional.get() * ROWS));
124                         break;
125                 default:
126                         break;
127                 }
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)) {
131                 case TOP_OF_SCREEN:
132                         vp = prop * ROWS;
133                         break;
134                 case VERTICAL_CENTRE_OF_SCREEN:
135                         vp = (prop + 0.5) * ROWS;
136                         break;
137                 case BOTTOM_OF_SCREEN:
138                         vp = (1 - prop) * ROWS;
139                         break;
140                 default:
141                         break;
142                 }
143         }
144
145         return vp;
146 }
147
148 /** @param language ISO 3-character country code for the language of the subtitles */
149 void
150 sub::write_stl_binary (
151         list<Subtitle> subtitles,
152         float frames_per_second,
153         Language language,
154         string original_programme_title,
155         string original_episode_title,
156         string translated_programme_title,
157         string translated_episode_title,
158         string translator_name,
159         string translator_contact_details,
160         string creation_date,
161         string revision_date,
162         int revision_number,
163         string country_of_origin,
164         string publisher,
165         string editor_name,
166         string editor_contact_details,
167         boost::filesystem::path file_name
168         )
169 {
170         SUB_ASSERT (original_programme_title.size() <= 32);
171         SUB_ASSERT (original_episode_title.size() <= 32);
172         SUB_ASSERT (translated_programme_title.size() <= 32);
173         SUB_ASSERT (translated_episode_title.size() <= 32);
174         SUB_ASSERT (translator_name.size() <= 32);
175         SUB_ASSERT (translator_contact_details.size() <= 32);
176         SUB_ASSERT (creation_date.size() == 6);
177         SUB_ASSERT (revision_date.size() == 6);
178         SUB_ASSERT (revision_number <= 99);
179         SUB_ASSERT (country_of_origin.size() == 3);
180         SUB_ASSERT (publisher.size() <= 32);
181         SUB_ASSERT (editor_name.size() <= 32);
182         SUB_ASSERT (editor_contact_details.size() <= 32);
183
184         char* buffer = new char[1024];
185         memset (buffer, 0, 1024);
186         ofstream output (file_name.string().c_str ());
187         STLBinaryTables tables;
188
189         /* Find the longest subtitle in characters */
190
191         int longest = 0;
192
193         for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
194                 for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
195                         int t = 0;
196                         for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
197                                 t += k->text.size ();
198                         }
199                         longest = std::max (longest, t);
200                 }
201         }
202
203         /* Code page: 850 */
204         put_string (buffer + 0, "850");
205         /* Disk format code */
206         put_string (buffer + 3, stl_frame_rate_to_dfc (frames_per_second));
207         /* Display standard code: open subtitling */
208         put_string (buffer + 11, "0");
209         /* Character code table: Latin (ISO 6937) */
210         put_string (buffer + 12, "00");
211         put_string (buffer + 14, tables.language_enum_to_file (language));
212         put_string (buffer + 16, 32, original_programme_title);
213         put_string (buffer + 48, 32, original_episode_title);
214         put_string (buffer + 80, 32, translated_programme_title);
215         put_string (buffer + 112, 32, translated_episode_title);
216         put_string (buffer + 144, 32, translator_name);
217         put_string (buffer + 176, 32, translator_contact_details);
218         /* Subtitle list reference code */
219         put_string (buffer + 208, "0000000000000000");
220         put_string (buffer + 224, creation_date);
221         put_string (buffer + 230, revision_date);
222         put_int_as_string (buffer + 236, revision_number, 2);
223         /* TTI blocks */
224         put_int_as_string (buffer + 238, subtitles.size(), 5);
225         /* Total number of subtitles */
226         put_int_as_string (buffer + 243, subtitles.size(), 5);
227         /* Total number of subtitle groups */
228         put_string (buffer + 248, "001");
229         /* Maximum number of displayable characters in any text row */
230         put_int_as_string (buffer + 251, longest, 2);
231         /* Maximum number of displayable rows */
232         put_int_as_string (buffer + 253, ROWS, 2);
233         /* Time code status */
234         put_string (buffer + 255, "1");
235         /* Start-of-programme time code */
236         put_string (buffer + 256, "00000000");
237         /* First-in-cue time code */
238         put_string (buffer + 264, "00000000");
239         /* Total number of disks */
240         put_string (buffer + 272, "1");
241         /* Disk sequence number */
242         put_string (buffer + 273, "1");
243         put_string (buffer + 274, 3, country_of_origin);
244         put_string (buffer + 277, 32, publisher);
245         put_string (buffer + 309, 32, editor_name);
246         put_string (buffer + 341, 32, editor_contact_details);
247
248         output.write (buffer, 1024);
249
250         for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
251
252                 /* Find the top vertical position of this subtitle */
253                 optional<int> top;
254                 for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
255                         int const vp = vertical_position (*j);
256                         if (!top || vp < top.get ()) {
257                                 top = vp;
258                         }
259                 }
260
261                 memset (buffer, 0, 1024);
262
263                 /* Subtitle group number */
264                 put_int_as_int (buffer + 0, 1, 1);
265                 /* Subtitle number */
266                 put_int_as_int (buffer + 1, 0, 2);
267                 /* Extension block number.  Use 0xff here to indicate that it is the last TTI
268                    block in this subtitle "set", as we only ever use one.
269                 */
270                 put_int_as_int (buffer + 3, 255, 1);
271                 /* Cumulative status */
272                 put_int_as_int (buffer + 4, tables.cumulative_status_enum_to_file (CUMULATIVE_STATUS_NOT_CUMULATIVE), 1);
273                 /* Time code in */
274                 put_int_as_int (buffer + 5, i->from.hours(), 1);
275                 put_int_as_int (buffer + 6, i->from.minutes(), 1);
276                 put_int_as_int (buffer + 7, i->from.seconds(), 1);
277                 put_int_as_int (buffer + 8, i->from.frames_at(sub::Rational (frames_per_second * 1000, 1000)), 1);
278                 /* Time code out */
279                 put_int_as_int (buffer + 9, i->to.hours(), 1);
280                 put_int_as_int (buffer + 10, i->to.minutes(), 1);
281                 put_int_as_int (buffer + 11, i->to.seconds(), 1);
282                 put_int_as_int (buffer + 12, i->to.frames_at(sub::Rational (frames_per_second * 1000, 1000)), 1);
283                 /* Vertical position */
284                 put_int_as_int (buffer + 13, top.get(), 1);
285
286                 /* Justification code */
287                 /* XXX: this assumes the first line has the right value */
288                 switch (i->lines.front().horizontal_position.reference) {
289                 case LEFT_OF_SCREEN:
290                         put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_LEFT), 1);
291                         break;
292                 case HORIZONTAL_CENTRE_OF_SCREEN:
293                         put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_CENTRE), 1);
294                         break;
295                 case RIGHT_OF_SCREEN:
296                         put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_RIGHT), 1);
297                         break;
298                 }
299
300                 /* Comment flag */
301                 put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
302
303                 /* Text */
304                 string text;
305                 bool italic = false;
306                 bool underline = false;
307                 optional<int> last_vp;
308
309                 for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
310
311                         /* CR/LF down to this line */
312                         int const vp = vertical_position (*j);
313
314                         if (last_vp) {
315                                 for (int i = last_vp.get(); i < vp; ++i) {
316                                         text += "\x8A";
317                                 }
318                         }
319
320                         last_vp = vp;
321
322                         for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
323                                 if (k->underline && !underline) {
324                                         text += "\x82";
325                                         underline = true;
326                                 } else if (underline && !k->underline) {
327                                         text += "\x83";
328                                         underline = false;
329                                 }
330                                 if (k->italic && !italic) {
331                                         text += "\x80";
332                                         italic = true;
333                                 } else if (italic && !k->italic) {
334                                         text += "\x81";
335                                         italic = false;
336                                 }
337
338                                 text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k->text));
339                         }
340                 }
341
342                 /* Turn italic/underline off before the end of this subtitle */
343
344                 if (underline) {
345                         text += "\x83";
346                 }
347
348                 if (italic) {
349                         text += "\x81";
350                 }
351
352                 if (text.length() > 111) {
353                         text = text.substr (111);
354                 }
355
356                 while (text.length() < 112) {
357                         text += "\x8F";
358                 }
359
360                 put_string (buffer + 16, text);
361                 output.write (buffer, 128);
362         }
363
364         delete[] buffer;
365 }