Tweak ISO6937 mapping to put $ sign on 0xa4 (164) (from master).
[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 <list>
32 #include <cmath>
33 #include <fstream>
34 #include <iomanip>
35 #include <set>
36
37 using std::list;
38 using std::set;
39 using std::ofstream;
40 using std::string;
41 using std::setw;
42 using std::setfill;
43 using std::max;
44 using std::cout;
45 using boost::locale::conv::utf_to_utf;
46 using namespace sub;
47
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.
51  *
52  *  The magic 23 makes our output agree more closely with
53  *  AnnotationEdit, which makes life easier when testing.
54  */
55 static int const ROWS = 23;
56
57 static void
58 put_string (char* p, string s)
59 {
60         memcpy (p, s.c_str (), s.length ());
61 }
62
63 static void
64 put_string (char* p, unsigned int n, string s)
65 {
66         SUB_ASSERT (s.length() <= n);
67
68         memcpy (p, s.c_str (), s.length ());
69         memset (p + s.length(), ' ', n - s.length ());
70 }
71
72 static void
73 put_int_as_string (char* p, int v, unsigned int n)
74 {
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');
79         s << v;
80         SUB_ASSERT (s.str().length() == n);
81         put_string (p, s.str ());
82 }
83
84 static void
85 put_int_as_int (char* p, int v, unsigned int n)
86 {
87         for (unsigned int i = 0; i < n; ++i) {
88                 *p++ = (v & ((1 << ((i + 1) * 8)) - 1)) >> (i * 8);
89         }
90 }
91
92 /** @param language ISO 3-character country code for the language of the subtitles */
93 void
94 sub::write_stl_binary (
95         list<Subtitle> subtitles,
96         float frames_per_second,
97         Language language,
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,
106         int revision_number,
107         string country_of_origin,
108         string publisher,
109         string editor_name,
110         string editor_contact_details,
111         boost::filesystem::path file_name
112         )
113 {
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);
127
128         char* buffer = new char[1024];
129         memset (buffer, 0, 1024);
130         ofstream output (file_name.string().c_str ());
131         STLBinaryTables tables;
132
133         /* Find the longest subtitle in characters and the number of lines */
134
135         int longest = 0;
136         int lines = 0;
137
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) {
140                         int t = 0;
141                         for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
142                                 t += k->text.size ();
143                         }
144                         longest = max (longest, t);
145                         ++lines;
146                 }
147         }
148
149         /* Code page: 850 */
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);
169         /* TTI blocks */
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);
193
194         output.write (buffer, 1024);
195
196         for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
197                 int N = 0;
198                 for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
199
200                         memset (buffer, 0, 1024);
201
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.
208                         */
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);
212                         /* Time code in */
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);
217                         /* Time code out */
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 */
223                         int vp = 0;
224                         if (j->vertical_position.proportional) {
225                                 switch (j->vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
226                                 case TOP_OF_SCREEN:
227                                         vp = rint (j->vertical_position.proportional.get() * ROWS);
228                                         break;
229                                 case CENTRE_OF_SCREEN:
230                                         vp = rint (j->vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
231                                         break;
232                                 case BOTTOM_OF_SCREEN:
233                                         vp = rint (ROWS - (j->vertical_position.proportional.get() * ROWS));
234                                         break;
235                                 default:
236                                         break;
237                                 }
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)) {
241                                 case TOP_OF_SCREEN:
242                                         vp = prop * ROWS;
243                                         break;
244                                 case CENTRE_OF_SCREEN:
245                                         vp = (prop + 0.5) * ROWS;
246                                         break;
247                                 case BOTTOM_OF_SCREEN:
248                                         vp = (1 - prop) * ROWS;
249                                         break;
250                                 default:
251                                         break;
252                                 }
253                         }
254                         put_int_as_int (buffer + 13, vp, 1);
255                         /* Justification code */
256                         /* XXX */
257                         put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_NONE), 1);
258                         /* Comment flag */
259                         put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
260
261                         /* Text */
262                         string text;
263                         bool italic = false;
264                         bool underline = false;
265
266                         for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
267                                 if (k->underline && !underline) {
268                                         text += "\x82";
269                                         underline = true;
270                                 } else if (underline && !k->underline) {
271                                         text += "\x83";
272                                         underline = false;
273                                 }
274                                 if (k->italic && !italic) {
275                                         text += "\x80";
276                                         italic = true;
277                                 } else if (italic && !k->italic) {
278                                         text += "\x81";
279                                         italic = false;
280                                 }
281
282                                 text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k->text));
283                         }
284
285                         text += "\x8A";
286
287                         if (text.length() > 111) {
288                                 text = text.substr (111);
289                         }
290
291                         while (text.length() < 112) {
292                                 text += "\x8F";
293                         }
294
295                         put_string (buffer + 16, text);
296                         output.write (buffer, 128);
297
298                         ++N;
299                 }
300         }
301
302         delete[] buffer;
303 }