Bump libdcp for waf bump.
[libsub.git] / src / stl_binary_writer.cc
1 /*
2     Copyright (C) 2014-2020 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/foreach.hpp>
32 #include <list>
33 #include <cmath>
34 #include <fstream>
35 #include <vector>
36 #include <iomanip>
37 #include <set>
38
39 using std::list;
40 using std::set;
41 using std::ofstream;
42 using std::string;
43 using std::setw;
44 using std::setfill;
45 using std::max;
46 using std::cout;
47 using std::vector;
48 using boost::locale::conv::utf_to_utf;
49 using boost::optional;
50 using namespace sub;
51
52 /** Arbitrary number which to divide the screen into rows; e.g.
53  *  64 here would mean that there are 64 addressable vertical positions
54  *  on the screen, each 1/64th of the screen height tall.
55  *
56  *  The magic 23 makes our output agree more closely with
57  *  AnnotationEdit, which makes life easier when testing.
58  */
59 static int const ROWS = 23;
60
61 static void
62 put_string (char* p, string s)
63 {
64         memcpy (p, s.c_str (), s.length ());
65 }
66
67 static void
68 put_string (char* p, unsigned int n, string s)
69 {
70         SUB_ASSERT (s.length() <= n);
71
72         memcpy (p, s.c_str (), s.length ());
73         memset (p + s.length(), ' ', n - s.length ());
74 }
75
76 static void
77 put_int_as_string (char* p, int v, unsigned int n)
78 {
79         locked_stringstream s;
80         /* Be careful to ensure we get no thousands separators */
81         s.imbue (std::locale::classic ());
82         s << setw (n) << setfill ('0');
83         s << v;
84         SUB_ASSERT (s.str().length() == n);
85         put_string (p, s.str ());
86 }
87
88 static void
89 put_int_as_int (char* p, int v, unsigned int n)
90 {
91         for (unsigned int i = 0; i < n; ++i) {
92                 *p++ = (v & ((1 << ((i + 1) * 8)) - 1)) >> (i * 8);
93         }
94 }
95
96 static int
97 vertical_position (sub::Line const & line)
98 {
99         int vp = 0;
100         if (line.vertical_position.proportional) {
101                 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
102                 case TOP_OF_SCREEN:
103                         vp = rint (line.vertical_position.proportional.get() * ROWS);
104                         break;
105                 case VERTICAL_CENTRE_OF_SCREEN:
106                         vp = rint (line.vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
107                         break;
108                 case BOTTOM_OF_SCREEN:
109                         vp = rint (ROWS - (line.vertical_position.proportional.get() * ROWS));
110                         break;
111                 default:
112                         break;
113                 }
114         } else if (line.vertical_position.line) {
115                 float const prop = float (line.vertical_position.line.get()) / line.vertical_position.lines.get ();
116                 switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
117                 case TOP_OF_SCREEN:
118                         vp = prop * ROWS;
119                         break;
120                 case VERTICAL_CENTRE_OF_SCREEN:
121                         vp = (prop + 0.5) * ROWS;
122                         break;
123                 case BOTTOM_OF_SCREEN:
124                         vp = (1 - prop) * ROWS;
125                         break;
126                 default:
127                         break;
128                 }
129         }
130
131         return vp;
132 }
133
134 vector<char*>
135 make_tti_blocks (list<Subtitle> const& subtitles, STLBinaryTables const& tables, float frames_per_second)
136 {
137         static int const tti_size = 128;
138         vector<char*> tti;
139
140         /* Buffer to build the TTI blocks in */
141         char buffer[tti_size];
142
143         BOOST_FOREACH (Subtitle const& i, subtitles) {
144
145                 /* Find the top vertical position of this subtitle */
146                 optional<int> top;
147                 BOOST_FOREACH (Line const& j, i.lines) {
148                         int const vp = vertical_position (j);
149                         if (!top || vp < top.get ()) {
150                                 top = vp;
151                         }
152                 }
153
154                 /* Work out the text */
155                 string text;
156                 bool italic = false;
157                 bool underline = false;
158                 optional<int> last_vp;
159
160                 BOOST_FOREACH (Line const& j, i.lines) {
161
162                         /* CR/LF down to this line */
163                         int const vp = vertical_position (j);
164
165                         if (last_vp) {
166                                 for (int k = last_vp.get(); k < vp; ++k) {
167                                         text += "\x8A";
168                                 }
169                         }
170
171                         last_vp = vp;
172
173                         BOOST_FOREACH (Block const& k, j.blocks) {
174                                 if (k.underline && !underline) {
175                                         text += "\x82";
176                                         underline = true;
177                                 } else if (underline && !k.underline) {
178                                         text += "\x83";
179                                         underline = false;
180                                 }
181                                 if (k.italic && !italic) {
182                                         text += "\x80";
183                                         italic = true;
184                                 } else if (italic && !k.italic) {
185                                         text += "\x81";
186                                         italic = false;
187                                 }
188
189                                 text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k.text));
190                         }
191                 }
192
193                 /* Turn italic/underline off before the end of this subtitle */
194                 if (underline) {
195                         text += "\x83";
196                 }
197                 if (italic) {
198                         text += "\x81";
199                 }
200
201                 /* Make sure there's at least one end-of-line */
202                 text += "\x8F";
203
204                 /* Now write this text in 112 byte chunks (TTI blocks).  Only the first TTI
205                    block's cumulative status, timecodes, vertical position, justification code
206                    and comment flag are taken into account by the reader.
207                    */
208
209                 /* Set up the first part of the block */
210
211                 /* XXX: these should increment, surely! */
212                 /* Subtitle group number */
213                 put_int_as_int (buffer + 0, 1, 1);
214                 /* Subtitle number */
215                 put_int_as_int (buffer + 1, 0, 2);
216                 /* Cumulative status */
217                 put_int_as_int (buffer + 4, tables.cumulative_status_enum_to_file (CUMULATIVE_STATUS_NOT_CUMULATIVE), 1);
218                 /* Time code in */
219                 put_int_as_int (buffer + 5, i.from.hours(), 1);
220                 put_int_as_int (buffer + 6, i.from.minutes(), 1);
221                 put_int_as_int (buffer + 7, i.from.seconds(), 1);
222                 put_int_as_int (buffer + 8, i.from.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
223                 /* Time code out */
224                 put_int_as_int (buffer + 9, i.to.hours(), 1);
225                 put_int_as_int (buffer + 10, i.to.minutes(), 1);
226                 put_int_as_int (buffer + 11, i.to.seconds(), 1);
227                 put_int_as_int (buffer + 12, i.to.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
228                 /* Vertical position */
229                 put_int_as_int (buffer + 13, top.get(), 1);
230
231                 /* Justification code */
232                 /* XXX: this assumes the first line has the right value */
233                 switch (i.lines.front().horizontal_position.reference) {
234                         case LEFT_OF_SCREEN:
235                                 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_LEFT), 1);
236                                 break;
237                         case HORIZONTAL_CENTRE_OF_SCREEN:
238                                 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_CENTRE), 1);
239                                 break;
240                         case RIGHT_OF_SCREEN:
241                                 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_RIGHT), 1);
242                                 break;
243                 }
244
245                 /* Comment flag */
246                 put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
247
248                 /* Now make as many blocks as are needed to add all the text */
249                 size_t const block_size = 112;
250                 size_t offset = 0;
251                 int block_number = 0;
252                 while (offset < text.length()) {
253                         size_t this_time = std::min(block_size, text.length() - offset);
254                         put_string (buffer + 16, text.substr(offset, this_time) + string(block_size - this_time, '\x8f'));
255                         offset += this_time;
256
257                         /* Extension block number.  Count up from 0 but use 0xff for the last one */
258                         put_int_as_int (buffer + 3, offset == text.length() ? 0xff : block_number, 1);
259                         ++block_number;
260
261                         char* finished = new char[tti_size];
262                         memcpy (finished, buffer, tti_size);
263                         tti.push_back (finished);
264                 }
265         }
266
267         return tti;
268 }
269
270
271
272 /** @param language ISO 3-character country code for the language of the subtitles */
273         void
274 sub::write_stl_binary (
275                 list<Subtitle> subtitles,
276                 float frames_per_second,
277                 Language language,
278                 string original_programme_title,
279                 string original_episode_title,
280                 string translated_programme_title,
281                 string translated_episode_title,
282                 string translator_name,
283                 string translator_contact_details,
284                 string creation_date,
285                 string revision_date,
286                 int revision_number,
287                 string country_of_origin,
288                 string publisher,
289                 string editor_name,
290                 string editor_contact_details,
291                 boost::filesystem::path file_name
292                 )
293 {
294         SUB_ASSERT (original_programme_title.size() <= 32);
295         SUB_ASSERT (original_episode_title.size() <= 32);
296         SUB_ASSERT (translated_programme_title.size() <= 32);
297         SUB_ASSERT (translated_episode_title.size() <= 32);
298         SUB_ASSERT (translator_name.size() <= 32);
299         SUB_ASSERT (translator_contact_details.size() <= 32);
300         SUB_ASSERT (creation_date.size() == 6);
301         SUB_ASSERT (revision_date.size() == 6);
302         SUB_ASSERT (revision_number <= 99);
303         SUB_ASSERT (country_of_origin.size() == 3);
304         SUB_ASSERT (publisher.size() <= 32);
305         SUB_ASSERT (editor_name.size() <= 32);
306         SUB_ASSERT (editor_contact_details.size() <= 32);
307
308         char buffer[1024];
309         memset (buffer, 0, 1024);
310         STLBinaryTables tables;
311
312         /* Find the longest subtitle in characters */
313
314         int longest = 0;
315
316         BOOST_FOREACH (Subtitle const& i, subtitles) {
317                 BOOST_FOREACH (Line const& j, i.lines) {
318                         int t = 0;
319                         BOOST_FOREACH (Block const& k, j.blocks) {
320                                 t += k.text.size ();
321                         }
322                         longest = std::max (longest, t);
323                 }
324         }
325
326         vector<char*> tti_blocks = make_tti_blocks (subtitles, tables, frames_per_second);
327
328         /* Code page: 850 */
329         put_string (buffer + 0, "850");
330         /* Disk format code */
331         put_string (buffer + 3, stl_frame_rate_to_dfc (frames_per_second));
332         /* Display standard code: open subtitling */
333         put_string (buffer + 11, "0");
334         /* Character code table: Latin (ISO 6937) */
335         put_string (buffer + 12, "00");
336         put_string (buffer + 14, tables.language_enum_to_file (language));
337         put_string (buffer + 16, 32, original_programme_title);
338         put_string (buffer + 48, 32, original_episode_title);
339         put_string (buffer + 80, 32, translated_programme_title);
340         put_string (buffer + 112, 32, translated_episode_title);
341         put_string (buffer + 144, 32, translator_name);
342         put_string (buffer + 176, 32, translator_contact_details);
343         /* Subtitle list reference code */
344         put_string (buffer + 208, "0000000000000000");
345         put_string (buffer + 224, creation_date);
346         put_string (buffer + 230, revision_date);
347         put_int_as_string (buffer + 236, revision_number, 2);
348         /* TTI blocks */
349         put_int_as_string (buffer + 238, tti_blocks.size(), 5);
350         /* Total number of subtitles */
351         put_int_as_string (buffer + 243, subtitles.size(), 5);
352         /* Total number of subtitle groups */
353         put_string (buffer + 248, "001");
354         /* Maximum number of displayable characters in any text row */
355         put_int_as_string (buffer + 251, longest, 2);
356         /* Maximum number of displayable rows */
357         put_int_as_string (buffer + 253, ROWS, 2);
358         /* Time code status */
359         put_string (buffer + 255, "1");
360         /* Start-of-programme time code */
361         put_string (buffer + 256, "00000000");
362         /* First-in-cue time code */
363         put_string (buffer + 264, "00000000");
364         /* Total number of disks */
365         put_string (buffer + 272, "1");
366         /* Disk sequence number */
367         put_string (buffer + 273, "1");
368         put_string (buffer + 274, 3, country_of_origin);
369         put_string (buffer + 277, 32, publisher);
370         put_string (buffer + 309, 32, editor_name);
371         put_string (buffer + 341, 32, editor_contact_details);
372
373         ofstream output (file_name.string().c_str());
374         output.write (buffer, 1024);
375         BOOST_FOREACH (char* i, tti_blocks) {
376                 output.write (i, 128);
377                 delete[] i;
378         }
379 }