49bcc34db001c3a7ebc3bb1bcc030e004d779256
[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 #include "stl_binary_writer.h"
21 #include "subtitle.h"
22 #include "iso6937.h"
23 #include "stl_util.h"
24 #include "compose.hpp"
25 #include <boost/locale.hpp>
26 #include <list>
27 #include <cmath>
28 #include <fstream>
29 #include <iomanip>
30 #include <set>
31
32 using std::list;
33 using std::set;
34 using std::ofstream;
35 using std::string;
36 using std::setw;
37 using std::setfill;
38 using std::max;
39 using std::cout;
40 using boost::locale::conv::utf_to_utf;
41 using namespace sub;
42
43 /** Arbitrary number which to divide the screen into rows; e.g.
44  *  64 here would mean that there are 64 addressable vertical positions
45  *  on the screen, each 1/64th of the screen height tall.
46  *
47  *  The arbitrary 23 makes our output agree more closely with
48  *  AnnotationEdit, which makes life easier when testing.
49  */
50 static int const ROWS = 23;
51
52 static void
53 put_string (char* p, string s)
54 {
55         memcpy (p, s.c_str (), s.length ());
56 }
57
58 static void
59 put_string (char* p, unsigned int n, string s)
60 {
61         assert (s.length() <= n);
62         
63         memcpy (p, s.c_str (), s.length ());
64         memset (p + s.length(), ' ', n - s.length ());
65 }
66
67 static void
68 put_int_as_string (char* p, int v, unsigned int n)
69 {
70         std::stringstream s;
71         /* Be careful to ensure we get no thousands separators */
72         s.imbue (std::locale::classic ());
73         s << setw (n) << setfill ('0');
74         s << v;
75         assert (s.str().length() == n);
76         put_string (p, s.str ());
77 }
78
79 static void
80 put_int_as_int (char* p, int v, unsigned int n)
81 {
82         for (unsigned int i = 0; i < n; ++i) {
83                 *p++ = (v & ((1 << ((i + 1) * 8)) - 1)) >> (i * 8);
84         }
85 }
86
87 /** @param language ISO 3-character country code for the language of the subtitles */
88 void
89 sub::write_stl_binary (
90         list<Subtitle> subtitles,
91         float frames_per_second,
92         Language language,
93         string original_programme_title,
94         string original_episode_title,
95         string translated_programme_title,
96         string translated_episode_title,
97         string translator_name,
98         string translator_contact_details,
99         string creation_date,
100         string revision_date,
101         int revision_number,
102         string country_of_origin,
103         string publisher,
104         string editor_name,
105         string editor_contact_details,
106         boost::filesystem::path file_name
107         )
108 {
109         assert (original_programme_title.size() <= 32);
110         assert (original_episode_title.size() <= 32);
111         assert (translated_programme_title.size() <= 32);
112         assert (translated_episode_title.size() <= 32);
113         assert (translator_name.size() <= 32);
114         assert (translator_contact_details.size() <= 32);
115         assert (creation_date.size() == 6);
116         assert (revision_date.size() == 6);
117         assert (revision_number <= 99);
118         assert (country_of_origin.size() == 3);
119         assert (publisher.size() <= 32);
120         assert (editor_name.size() <= 32);
121         assert (editor_contact_details.size() <= 32);
122         
123         char* buffer = new char[1024];
124         memset (buffer, 0, 1024);
125         ofstream output (file_name.string().c_str ());
126         STLBinaryTables tables;
127
128         /* Find the longest subtitle in characters and the number of lines */
129
130         int longest = 0;
131         int lines = 0;
132
133         for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
134                 for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
135                         int t = 0;
136                         for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
137                                 t += k->text.size ();
138                         }
139                         longest = max (longest, t);
140                         ++lines;
141                 }
142         }
143         
144         /* Code page: 850 */
145         put_string (buffer + 0, "850");
146         /* Disk format code */
147         put_string (buffer + 3, stl_frame_rate_to_dfc (frames_per_second));
148         /* Display standard code: open subtitling */
149         put_string (buffer + 11, "0");
150         /* Character code table: Latin (ISO 6937) */
151         put_string (buffer + 12, "00");
152         put_string (buffer + 14, tables.language_enum_to_file (language));
153         put_string (buffer + 16, 32, original_programme_title);
154         put_string (buffer + 48, 32, original_episode_title);
155         put_string (buffer + 80, 32, translated_programme_title);
156         put_string (buffer + 112, 32, translated_episode_title);
157         put_string (buffer + 144, 32, translator_name);
158         put_string (buffer + 176, 32, translator_contact_details);
159         /* Subtitle list reference code */
160         put_string (buffer + 208, "0000000000000000");
161         put_string (buffer + 224, creation_date);
162         put_string (buffer + 230, revision_date);
163         put_int_as_string (buffer + 236, revision_number, 2);
164         /* TTI blocks */
165         put_int_as_string (buffer + 238, lines, 5);
166         /* Total number of subtitles */
167         put_int_as_string (buffer + 243, lines, 5);
168         /* Total number of subtitle groups */
169         put_string (buffer + 248, "001");
170         /* Maximum number of displayable characters in any text row */
171         put_int_as_string (buffer + 251, longest, 2);
172         /* Maximum number of displayable rows */
173         put_int_as_string (buffer + 253, ROWS, 2);
174         /* Time code status */
175         put_string (buffer + 255, "1");
176         /* Start-of-programme time code */
177         put_string (buffer + 256, "00000000");
178         /* First-in-cue time code */
179         put_string (buffer + 264, "00000000");
180         /* Total number of disks */
181         put_string (buffer + 272, "1");
182         /* Disk sequence number */
183         put_string (buffer + 273, "1");
184         put_string (buffer + 274, 3, country_of_origin);
185         put_string (buffer + 277, 32, publisher);
186         put_string (buffer + 309, 32, editor_name);
187         put_string (buffer + 341, 32, editor_contact_details);
188
189         output.write (buffer, 1024);
190
191         int N = 0;
192         for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
193                 for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
194
195                         memset (buffer, 0, 1024);
196
197                         /* Subtitle group number */
198                         put_int_as_int (buffer + 0, 1, 1);
199                         /* Subtitle number */
200                         put_int_as_int (buffer + 1, N, 2);
201                         /* Extension block number.  Use 0xff here to indicate that it is the last TTI
202                            block in this subtitle "set", as we only ever use one.
203                         */
204                         put_int_as_int (buffer + 3, 255, 1);
205                         /* Cumulative status */
206                         put_int_as_int (buffer + 4, tables.cumulative_status_enum_to_file (CUMULATIVE_STATUS_NOT_CUMULATIVE), 1);
207                         /* Time code in */
208                         put_int_as_int (buffer + 5, i->from.frame(frames_per_second).hours (), 1);
209                         put_int_as_int (buffer + 6, i->from.frame(frames_per_second).minutes (), 1);
210                         put_int_as_int (buffer + 7, i->from.frame(frames_per_second).seconds (), 1);
211                         put_int_as_int (buffer + 8, i->from.frame(frames_per_second).frames (), 1);
212                         /* Time code out */
213                         put_int_as_int (buffer + 9, i->to.frame(frames_per_second).hours (), 1);
214                         put_int_as_int (buffer + 10, i->to.frame(frames_per_second).minutes (), 1);
215                         put_int_as_int (buffer + 11, i->to.frame(frames_per_second).seconds (), 1);
216                         put_int_as_int (buffer + 12, i->to.frame(frames_per_second).frames (), 1);
217                         /* Vertical position */
218                         int vp = 0;
219                         if (j->vertical_position.proportional) {
220                                 switch (j->vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
221                                 case TOP_OF_SCREEN:
222                                         vp = rint (j->vertical_position.proportional.get() * ROWS);
223                                         break;
224                                 case CENTRE_OF_SCREEN:
225                                         vp = rint (j->vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
226                                         break;
227                                 case BOTTOM_OF_SCREEN:
228                                         vp = rint (ROWS - (j->vertical_position.proportional.get() * ROWS));
229                                         break;
230                                 default:
231                                         break;
232                                 }
233                         } else if (j->vertical_position.line) {
234                                 /* XXX: how many lines are there...?  We need to correct for any difference
235                                    between the number of lines and our ROWS.
236                                 */
237                                 switch (j->vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
238                                 case TOP_OF_SCREEN:
239                                         vp = j->vertical_position.line.get();
240                                         break;
241                                 case CENTRE_OF_SCREEN:
242                                         vp = j->vertical_position.line.get() + (ROWS / 2);
243                                         break;
244                                 case BOTTOM_OF_SCREEN:
245                                         vp = ROWS - (j->vertical_position.line.get());
246                                         break;
247                                 default:
248                                         break;
249                                 }
250                         }
251                         put_int_as_int (buffer + 13, vp, 1);
252                         /* Justification code */
253                         /* XXX */
254                         put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_NONE), 1);
255                         /* Comment flag */
256                         put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
257                         
258                         /* Text */
259                         string text;
260                         bool italic = false;
261                         bool underline = false;
262                         
263                         for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
264                                 if (k->underline && !underline) {
265                                         text += "\x82";
266                                         underline = true;
267                                 } else if (underline && !k->underline) {
268                                         text += "\x83";
269                                         underline = false;
270                                 }
271                                 if (k->italic && !italic) {
272                                         text += "\x80";
273                                         italic = true;
274                                 } else if (italic && !k->italic) {
275                                         text += "\x81";
276                                         italic = false;
277                                 }
278                                 
279                                 text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k->text));
280                         }
281                         
282                         text += "\x8A";
283                 
284                         if (text.length() > 111) {
285                                 text = text.substr (111);
286                         }
287                         
288                         while (text.length() < 112) {
289                                 text += "\x8F";
290                         }
291                         
292                         put_string (buffer + 16, text);
293                         output.write (buffer, 128);
294                 }
295         }
296
297         delete[] buffer;
298 }