Fix line numbers in binary STL files.
[libsub.git] / src / stl_binary_reader.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 #include "stl_binary_reader.h"
21 #include "exceptions.h"
22 #include "iso6937.h"
23 #include "stl_util.h"
24 #include "compose.hpp"
25 #include <boost/lexical_cast.hpp>
26 #include <boost/algorithm/string.hpp>
27 #include <boost/locale.hpp>
28 #include <iostream>
29
30 using std::map;
31 using std::vector;
32 using std::cout;
33 using std::string;
34 using std::istream;
35 using boost::lexical_cast;
36 using boost::algorithm::replace_all;
37 using boost::is_any_of;
38 using boost::locale::conv::utf_to_utf;
39 using std::shared_ptr;
40 using namespace sub;
41
42 namespace sub {
43
44 class InputReader : public boost::noncopyable
45 {
46 public:
47         InputReader ()
48                 : _buffer (new unsigned char[1024])
49         {
50
51         }
52
53         virtual ~InputReader ()
54         {
55                 delete[] _buffer;
56         }
57
58         virtual void read (int size, string what) = 0;
59
60         string get_string (int offset, int length) const
61         {
62                 string s;
63                 for (int i = 0; i < length; ++i) {
64                         s += _buffer[offset + i];
65                 }
66
67                 return s;
68         }
69
70         int get_int (int offset, int length) const
71         {
72                 int v = 0;
73                 for (int i = 0; i < length; ++i) {
74                         v |= _buffer[offset + i] << (8 * i);
75                 }
76
77                 return v;
78         }
79
80         Time get_timecode (int offset, int frame_rate) const
81         {
82                 return Time::from_hmsf (_buffer[offset], _buffer[offset + 1], _buffer[offset + 2], _buffer[offset + 3], Rational (frame_rate, 1));
83         }
84
85 protected:
86         unsigned char* _buffer;
87 };
88
89
90 class StreamInputReader : public InputReader
91 {
92 public:
93         StreamInputReader (istream& in)
94                 : _in (in)
95         {
96
97         }
98
99         void read (int size, string what)
100         {
101                 _in.read (reinterpret_cast<char *>(_buffer), size);
102                 if (_in.gcount() != size) {
103                         throw STLError (String::compose("Could not read %1 block from binary STL file", what));
104                 }
105         }
106
107 private:
108         std::istream& _in;
109 };
110
111 class FILEInputReader : public InputReader
112 {
113 public:
114         FILEInputReader (FILE* in)
115                 : _in (in)
116         {
117
118         }
119
120         void read (int size, string what)
121         {
122                 size_t const N = fread (_buffer, 1, size, _in);
123                 if (static_cast<int>(N) != size) {
124                         throw STLError (String::compose("Could not read %1 block from binary STL file", what));
125                 }
126         }
127
128 private:
129         FILE* _in;
130 };
131
132 }
133
134 STLBinaryReader::STLBinaryReader (istream& in)
135 {
136         read (shared_ptr<InputReader>(new StreamInputReader(in)));
137 }
138
139 STLBinaryReader::STLBinaryReader (FILE* in)
140 {
141         read (shared_ptr<InputReader>(new FILEInputReader(in)));
142 }
143
144 void STLBinaryReader::read (shared_ptr<InputReader> reader)
145 {
146         reader->read (1024, "GSI");
147
148         code_page_number = atoi (reader->get_string(0, 3).c_str());
149         frame_rate = stl_dfc_to_frame_rate (reader->get_string(3, 8));
150         display_standard = _tables.display_standard_file_to_enum (reader->get_string(11, 1));
151         language_group = _tables.language_group_file_to_enum (reader->get_string(12, 2));
152         language = _tables.language_file_to_enum (reader->get_string(14, 2));
153         original_programme_title = reader->get_string(16, 32);
154         original_episode_title = reader->get_string(48, 32);
155         translated_programme_title = reader->get_string(80, 32);
156         translated_episode_title = reader->get_string(112, 32);
157         translator_name = reader->get_string(144, 32);
158         translator_contact_details = reader->get_string(176, 32);
159         subtitle_list_reference_code = reader->get_string(208, 16);
160         creation_date = reader->get_string(224, 6);
161         revision_date = reader->get_string(230, 6);
162         revision_number = reader->get_string(236, 2);
163
164         tti_blocks = atoi (reader->get_string(238, 5).c_str());
165         number_of_subtitles = atoi (reader->get_string(243, 5).c_str());
166         subtitle_groups = atoi (reader->get_string(248, 3).c_str());
167         maximum_characters = atoi (reader->get_string(251, 2).c_str());
168         maximum_rows = atoi (reader->get_string(253, 2).c_str());
169
170         if (maximum_rows == 99) {
171                 /* https://tech.ebu.ch/docs/tech/tech3360.pdf says
172                    "It is recommended that for files with a large MNR value (e.g. '99') the
173                    font size (height) should be defined as ~ 1/15 of the 'Subtitle Safe Area'
174                    and a lineHeight of 120% is used to achieve a row height of ~ 1/12 of the height
175                    of the 'Subtitle Safe Area'.
176                 */
177                 maximum_rows = 12;
178         }
179
180         timecode_status = _tables.timecode_status_file_to_enum (reader->get_string(255, 1));
181         start_of_programme = reader->get_string(256, 8);
182         first_in_cue = reader->get_string(264, 8);
183         disks = atoi (reader->get_string(272, 1).c_str());
184         disk_sequence_number = atoi (reader->get_string(273, 1).c_str());
185         country_of_origin = reader->get_string(274, 3);
186         publisher = reader->get_string(277, 32);
187         editor_name = reader->get_string(309, 32);
188         editor_contact_details = reader->get_string(341, 32);
189
190         int highest_line = 0;
191         for (int i = 0; i < tti_blocks; ++i) {
192
193                 reader->read (128, "TTI");
194
195                 if (_tables.comment_file_to_enum (reader->get_int(15, 1)) == COMMENT_YES) {
196                         continue;
197                 }
198
199                 string const whole = reader->get_string(16, 112);
200
201                 /* Split the text up into lines (8Ah is a new line) */
202                 vector<string> lines;
203                 split (lines, whole, is_any_of ("\x8a"));
204
205                 /* Italic / underline specifications can span lines, so we need to track them
206                    outside the lines loop.
207                 */
208                 bool italic = false;
209                 bool underline = false;
210
211                 for (size_t j = 0; j < lines.size(); ++j) {
212                         RawSubtitle sub;
213                         sub.from = reader->get_timecode(5, frame_rate);
214                         sub.to = reader->get_timecode(9, frame_rate);
215                         /* XXX: vertical position of TTI extension blocks should be ignored (spec page 10) so this
216                          * is wrong if the EBN of this TTI block is not 255 (I think).
217                          */
218                         sub.vertical_position.line = reader->get_int(13, 1) + j;
219                         highest_line = std::max(highest_line, *sub.vertical_position.line);
220                         sub.vertical_position.lines = maximum_rows;
221                         sub.vertical_position.reference = TOP_OF_SCREEN;
222                         sub.italic = italic;
223                         sub.underline = underline;
224
225                         /* XXX: not sure what to do with JC = 0, "unchanged presentation" */
226                         int const h = reader->get_int(14, 1);
227                         switch (h) {
228                         case 0:
229                         case 2:
230                                 sub.horizontal_position.reference = HORIZONTAL_CENTRE_OF_SCREEN;
231                                 break;
232                         case 1:
233                                 sub.horizontal_position.reference = LEFT_OF_SCREEN;
234                                 break;
235                         case 3:
236                                 sub.horizontal_position.reference = RIGHT_OF_SCREEN;
237                                 break;
238                         }
239
240                         /* Loop over characters */
241                         string text;
242                         for (size_t k = 0; k < lines[j].size(); ++k) {
243
244                                 unsigned char const c = static_cast<unsigned char> (lines[j][k]);
245
246                                 if (c == 0x8f) {
247                                         /* Unused space i.e. end of line */
248                                         break;
249                                 }
250
251                                 if (c >= 0x80 && c <= 0x83) {
252                                         /* Italic or underline control code */
253                                         sub.text = utf_to_utf<char> (iso6937_to_utf16 (text.c_str()));
254                                         _subs.push_back (sub);
255                                         text.clear ();
256                                 }
257
258                                 switch (c) {
259                                 case 0x80:
260                                         italic = true;
261                                         break;
262                                 case 0x81:
263                                         italic = false;
264                                         break;
265                                 case 0x82:
266                                         underline = true;
267                                         break;
268                                 case 0x83:
269                                         underline = false;
270                                         break;
271                                 default:
272                                         text += lines[j][k];
273                                         break;
274                                 }
275
276                                 sub.italic = italic;
277                                 sub.underline = underline;
278                         }
279
280                         if (!text.empty ()) {
281                                 sub.text = utf_to_utf<char> (iso6937_to_utf16 (text.c_str()));
282                                 _subs.push_back (sub);
283                         }
284
285                         /* XXX: justification */
286                 }
287         }
288
289         /* Fix line numbers so they don't go off the bottom of the screen */
290         if (highest_line > maximum_rows) {
291                 int correction = highest_line - maximum_rows;
292                 for (auto& i: _subs) {
293                         *i.vertical_position.line -= correction;
294                 }
295         }
296 }
297
298 map<string, string>
299 STLBinaryReader::metadata () const
300 {
301         map<string, string> m;
302
303         m["Code page number"] = lexical_cast<string> (code_page_number);
304         m["Frame rate"] = lexical_cast<string> (frame_rate);
305         m["Display standard"] = _tables.display_standard_enum_to_description (display_standard);
306         m["Language group"] = _tables.language_group_enum_to_description (language_group);
307         m["Language"] = _tables.language_enum_to_description (language);
308         m["Original programme title"] = original_programme_title;
309         m["Original episode title"] = original_episode_title;
310         m["Translated programme title"] = translated_programme_title;
311         m["Translated episode title"] = translated_episode_title;
312         m["Translator name"] = translator_name;
313         m["Translator contact details"] = translator_contact_details;
314         m["Subtitle list reference code"] = subtitle_list_reference_code;
315         m["Creation date"] = creation_date;
316         m["Revision date"] = revision_date;
317         m["Revision number"] = revision_number;
318         m["TTI blocks"] = lexical_cast<string> (tti_blocks);
319         m["Number of subtitles"] = lexical_cast<string> (number_of_subtitles);
320         m["Subtitle groups"] = lexical_cast<string> (subtitle_groups);
321         m["Maximum characters"] = lexical_cast<string> (maximum_characters);
322         m["Maximum rows"] = lexical_cast<string> (maximum_rows);
323         m["Timecode status"] = _tables.timecode_status_enum_to_description (timecode_status);
324         m["Start of programme"] = start_of_programme;
325         m["First in cue"] = first_in_cue;
326         m["Disks"] = lexical_cast<string> (disks);
327         m["Disk sequence number"] = lexical_cast<string> (disk_sequence_number);
328         m["Country of origin"] = country_of_origin;
329         m["Publisher"] = publisher;
330         m["Editor name"] = editor_name;
331         m["Editor contact details"] = editor_contact_details;
332
333         return m;
334 }