Add missing files.
[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 static void
44 put_string (char* p, string s)
45 {
46         memcpy (p, s.c_str (), s.length ());
47 }
48
49 static void
50 put_string (char* p, unsigned int n, string s)
51 {
52         assert (s.length() <= n);
53         
54         memcpy (p, s.c_str (), s.length ());
55         memset (p + s.length(), ' ', n - s.length ());
56 }
57
58 static void
59 put_int_as_string (char* p, int v, unsigned int n)
60 {
61         std::stringstream s;
62         /* Be careful to ensure we get no thousands separators */
63         s.imbue (std::locale::classic ());
64         s << setw (n) << setfill ('0');
65         s << v;
66         assert (s.str().length() == n);
67         put_string (p, s.str ());
68 }
69
70 static void
71 put_int_as_int (char* p, int v, unsigned int n)
72 {
73         for (unsigned int i = 0; i < n; ++i) {
74                 *p++ = (v & ((1 << ((i + 1) * 8)) - 1)) >> (i * 8);
75         }
76 }
77
78 /** @param language ISO 3-character country code for the language of the subtitles */
79 void
80 sub::write_stl_binary (
81         list<Subtitle> subtitles,
82         float frames_per_second,
83         Language language,
84         string original_programme_title,
85         string original_episode_title,
86         string translated_programme_title,
87         string translated_episode_title,
88         string translator_name,
89         string translator_contact_details,
90         string creation_date,
91         string revision_date,
92         int revision_number,
93         string country_of_origin,
94         string publisher,
95         string editor_name,
96         string editor_contact_details,
97         boost::filesystem::path file_name
98         )
99 {
100         assert (original_programme_title.size() <= 32);
101         assert (original_episode_title.size() <= 32);
102         assert (translated_programme_title.size() <= 32);
103         assert (translated_episode_title.size() <= 32);
104         assert (translator_name.size() <= 32);
105         assert (translator_contact_details.size() <= 32);
106         assert (creation_date.size() == 6);
107         assert (revision_date.size() == 6);
108         assert (revision_number <= 99);
109         assert (country_of_origin.size() == 3);
110         assert (publisher.size() <= 32);
111         assert (editor_name.size() <= 32);
112         assert (editor_contact_details.size() <= 32);
113         
114         char* buffer = new char[1024];
115         memset (buffer, 0, 1024);
116         ofstream output (file_name.string().c_str ());
117         STLBinaryTables tables;
118
119         /* Find the longest subtitle in characters and the number of rows */
120
121         int longest = 0;
122
123         set<float> check_top;
124         set<float> check_centre;
125         set<float> check_bottom;
126         set<int> check_rows;
127         
128         for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
129                 for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
130                         int t = 0;
131                         for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
132                                 t += k->text.size ();
133                         }
134                         longest = max (longest, t);
135                         
136                         if (j->vertical_position.proportional) {
137                                 switch (j->vertical_position.reference.get ()) {
138                                 case TOP:
139                                         check_top.insert (j->vertical_position.proportional.get ());
140                                         break;
141                                 case CENTRE:
142                                         check_centre.insert (j->vertical_position.proportional.get ());
143                                         break;
144                                 case BOTTOM:
145                                         check_bottom.insert (j->vertical_position.proportional.get ());
146                                         break;
147                                 }
148                         } else {
149                                 check_rows.insert (j->vertical_position.line.get ());
150                         }
151                 }
152         }
153
154         int const rows = check_top.size() + check_centre.size() + check_bottom.size() + check_rows.size();
155         
156         /* Code page: 850 */
157         put_string (buffer + 0, "850");
158         /* Disk format code */
159         put_string (buffer + 3, stl_frame_rate_to_dfc (frames_per_second));
160         /* Display standard code: open subtitling */
161         put_string (buffer + 11, "0");
162         /* Character code table: Latin (ISO 6937) */
163         put_string (buffer + 12, "00");
164         put_string (buffer + 14, tables.language_enum_to_file (language));
165         put_string (buffer + 16, 32, original_programme_title);
166         put_string (buffer + 48, 32, original_episode_title);
167         put_string (buffer + 80, 32, translated_programme_title);
168         put_string (buffer + 112, 32, translated_episode_title);
169         put_string (buffer + 144, 32, translator_name);
170         put_string (buffer + 176, 32, translator_contact_details);
171         /* Subtitle list reference code */
172         put_string (buffer + 208, "0000000000000000");
173         put_string (buffer + 224, creation_date);
174         put_string (buffer + 230, revision_date);
175         put_int_as_string (buffer + 236, revision_number, 2);
176         /* TTI blocks */
177         put_int_as_string (buffer + 238, subtitles.size (), 5);
178         /* Total number of subtitles */
179         put_int_as_string (buffer + 243, subtitles.size (), 5);
180         /* Total number of subtitle groups */
181         put_string (buffer + 248, "001");
182         /* Maximum number of displayable characters in any text row */
183         put_int_as_string (buffer + 251, longest, 2);
184         /* Maximum number of displayable rows */
185         put_int_as_string (buffer + 253, rows, 2);
186         /* Time code status */
187         put_string (buffer + 255, "1");
188         /* Start-of-programme time code */
189         put_string (buffer + 256, "00000000");
190         /* First-in-cue time code */
191         put_string (buffer + 264, "00000000");
192         /* Total number of disks */
193         put_string (buffer + 272, "1");
194         /* Disk sequence number */
195         put_string (buffer + 273, "1");
196         put_string (buffer + 274, 3, country_of_origin);
197         put_string (buffer + 277, 32, publisher);
198         put_string (buffer + 309, 32, editor_name);
199         put_string (buffer + 341, 32, editor_contact_details);
200
201         output.write (buffer, 1024);
202
203         int N = 0;
204         for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
205
206                 memset (buffer, 0, 1024);
207
208                 /* Subtitle group number */
209                 put_int_as_int (buffer + 0, 1, 1);
210                 /* Subtitle number */
211                 put_int_as_int (buffer + 1, N, 2);
212                 /* Extension block number.  Use 0xff here to indicate that it is the last TTI
213                    block in this subtitle "set", as we only ever use one.
214                 */
215                 put_int_as_int (buffer + 3, 255, 1);
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.frame(frames_per_second).hours (), 1);
220                 put_int_as_int (buffer + 6, i->from.frame(frames_per_second).minutes (), 1);
221                 put_int_as_int (buffer + 7, i->from.frame(frames_per_second).seconds (), 1);
222                 put_int_as_int (buffer + 8, i->from.frame(frames_per_second).frames (), 1);
223                 /* Time code out */
224                 put_int_as_int (buffer + 9, i->to.frame(frames_per_second).hours (), 1);
225                 put_int_as_int (buffer + 10, i->to.frame(frames_per_second).minutes (), 1);
226                 put_int_as_int (buffer + 11, i->to.frame(frames_per_second).seconds (), 1);
227                 put_int_as_int (buffer + 12, i->to.frame(frames_per_second).frames (), 1);
228                 /* Vertical position */
229                 /* XXX */
230                 put_int_as_int (buffer + 13, 0, 1);
231                 /* Justification code */
232                 /* XXX */
233                 put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_NONE), 1);
234                 /* Comment flag */
235                 put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
236
237                 /* Text */
238                 string text;
239                 bool italic = false;
240                 bool underline = false;
241                 
242                 for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
243                         for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
244                                 if (k->underline && !underline) {
245                                         text += "\x82";
246                                         underline = true;
247                                 } else if (underline && !k->underline) {
248                                         text += "\x83";
249                                         underline = false;
250                                 }
251                                 if (k->italic && !italic) {
252                                         text += "\x80";
253                                         italic = true;
254                                 } else if (italic && !k->italic) {
255                                         text += "\x81";
256                                         italic = false;
257                                 }
258
259                                 text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k->text));
260                         }
261
262                         text += "\x8A";
263                 }
264
265                 if (text.length() > 111) {
266                         text = text.substr (111);
267                 }
268
269                 while (text.length() < 112) {
270                         text += "\x8F";
271                 }
272
273                 put_string (buffer + 16, text);
274                 output.write (buffer, 128);
275         }
276
277         delete[] buffer;
278 }