Give a more informative exception on SubRip parse failures.
[libsub.git] / test / ssa_reader_test.cc
1 /*
2     Copyright (C) 2016 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 "test.h"
21 #include "ssa_reader.h"
22 #include "collect.h"
23 #include "subtitle.h"
24 #include <boost/test/unit_test.hpp>
25 #include <boost/filesystem.hpp>
26 #include <boost/foreach.hpp>
27 #include <cstdio>
28 #include <cmath>
29 #include <iostream>
30
31 using std::list;
32 using std::fabs;
33
34 BOOST_AUTO_TEST_CASE (ssa_reader_test)
35 {
36         boost::filesystem::path p = private_test / "example.ssa";
37         FILE* f = fopen (p.string().c_str(), "r");
38         sub::SSAReader reader (f);
39         fclose (f);
40         list<sub::Subtitle> subs = sub::collect<std::list<sub::Subtitle> > (reader.subtitles ());
41
42         list<sub::Subtitle>::iterator i = subs.begin ();
43
44         BOOST_REQUIRE (i != subs.end ());
45         BOOST_CHECK_EQUAL (i->from, sub::Time::from_hms (0, 2, 40, 650));
46         BOOST_CHECK_EQUAL (i->to, sub::Time::from_hms (0, 2, 41, 790));
47         list<sub::Line>::iterator j = i->lines.begin();
48         BOOST_REQUIRE (j != i->lines.end ());
49         BOOST_REQUIRE_EQUAL (j->blocks.size(), 1);
50         sub::Block b = j->blocks.front ();
51         BOOST_CHECK_EQUAL (b.text, "Et les enregistrements de ses ondes delta ?");
52         BOOST_CHECK_EQUAL (b.font.get(), "Wolf_Rain");
53         BOOST_CHECK_EQUAL (b.font_size.points().get(), 56);
54         BOOST_CHECK_EQUAL (b.bold, false);
55         BOOST_CHECK_EQUAL (b.italic, false);
56         BOOST_CHECK_EQUAL (b.underline, false);
57         ++i;
58
59         BOOST_REQUIRE (i != subs.end ());
60         BOOST_CHECK_EQUAL (i->from, sub::Time::from_hms (0, 2, 42, 420));
61         BOOST_CHECK_EQUAL (i->to, sub::Time::from_hms (0, 2, 44, 150));
62         j = i->lines.begin();
63         BOOST_REQUIRE (j != i->lines.end ());
64         BOOST_REQUIRE_EQUAL (j->blocks.size(), 1);
65         b = j->blocks.front ();
66         BOOST_CHECK_EQUAL (b.text, "Toujours rien.");
67         BOOST_CHECK_EQUAL (b.font.get(), "Wolf_Rain");
68         BOOST_CHECK_EQUAL (b.font_size.points().get(), 56);
69         BOOST_CHECK_EQUAL (b.bold, false);
70         BOOST_CHECK_EQUAL (b.italic, false);
71         BOOST_CHECK_EQUAL (b.underline, false);
72         ++i;
73
74         BOOST_CHECK (i == subs.end());
75 }
76
77 BOOST_AUTO_TEST_CASE (ssa_reader_line_test1)
78 {
79         sub::RawSubtitle base;
80         list<sub::RawSubtitle> r = sub::SSAReader::parse_line (base, "This is a line with some {\\i1}italics{\\i0} and then\\nthere is a new line.");
81
82         list<sub::RawSubtitle>::const_iterator i = r.begin ();
83         BOOST_CHECK_EQUAL (i->text, "This is a line with some ");
84         BOOST_CHECK_EQUAL (i->italic, false);
85         ++i;
86         BOOST_REQUIRE (i != r.end ());
87
88         BOOST_CHECK_EQUAL (i->text, "italics");
89         BOOST_CHECK_EQUAL (i->italic, true);
90         ++i;
91         BOOST_REQUIRE (i != r.end ());
92
93         BOOST_CHECK_EQUAL (i->text, " and then");
94         BOOST_CHECK_EQUAL (i->italic, false);
95         ++i;
96         BOOST_REQUIRE (i != r.end ());
97
98         BOOST_CHECK_EQUAL (i->text, "there is a new line.");
99         ++i;
100         BOOST_REQUIRE (i == r.end ());
101 }
102
103 BOOST_AUTO_TEST_CASE (ssa_reader_line_test2)
104 {
105         sub::RawSubtitle base;
106         list<sub::RawSubtitle> r = sub::SSAReader::parse_line (base, "{\\i1}It's all just italics{\\i0}");
107
108         list<sub::RawSubtitle>::const_iterator i = r.begin ();
109         BOOST_CHECK_EQUAL (i->text, "It's all just italics");
110         BOOST_CHECK_EQUAL (i->italic, true);
111         ++i;
112         BOOST_REQUIRE (i == r.end ());
113
114         r = sub::SSAReader::parse_line (base, "{\\i1}Italic{\\i0}\\Nand new line");
115         i = r.begin ();
116         BOOST_CHECK_EQUAL (i->text, "Italic");
117         BOOST_CHECK_EQUAL (i->italic, true);
118         BOOST_CHECK (fabs ((72.0 * 1.2 / 792) - i->vertical_position.proportional.get()) < 1e-5);
119         ++i;
120         BOOST_CHECK_EQUAL (i->text, "and new line");
121         BOOST_CHECK_EQUAL (i->italic, false);
122         BOOST_CHECK (i->vertical_position.proportional.get() < 1e-5);
123 }
124
125 static void
126 test (boost::filesystem::path p)
127 {
128         p = private_test / p;
129         FILE* f = fopen (p.string().c_str(), "r");
130         BOOST_REQUIRE (f);
131         sub::SSAReader r (f);
132         fclose (f);
133 }
134
135 /** Test of reading some typical .ssa files */
136 BOOST_AUTO_TEST_CASE (ssa_reader_test2)
137 {
138         test ("DKH_UT_EN20160601def.ssa");
139         test ("dcpsubtest-en.ssa");
140 }
141
142 #define SUB_START(f, t) \
143         BOOST_REQUIRE (i != subs.end ()); \
144         BOOST_CHECK_EQUAL (i->from, f); \
145         BOOST_CHECK_EQUAL (i->to, t); \
146         j = i->lines.begin ();
147
148 #define LINE(p, r)                                                      \
149         BOOST_REQUIRE (j != i->lines.end ()); \
150         BOOST_CHECK (j->vertical_position.proportional); \
151         BOOST_CHECK (fabs (j->vertical_position.proportional.get() - p) < 1e-5); \
152         BOOST_CHECK (j->vertical_position.reference); \
153         BOOST_CHECK_EQUAL (j->vertical_position.reference.get(), r); \
154         k = j->blocks.begin (); \
155         ++j;
156
157 #define BLOCK(t, f, s, b, i, u) \
158         BOOST_REQUIRE (k != j->blocks.end ()); \
159         BOOST_CHECK_EQUAL (k->text, t); \
160         BOOST_CHECK_EQUAL (k->font.get(), f); \
161         BOOST_CHECK_EQUAL (k->font_size.points().get(), s); \
162         BOOST_CHECK_EQUAL (k->bold, b); \
163         BOOST_CHECK_EQUAL (k->italic, i); \
164         BOOST_CHECK_EQUAL (k->underline, u); \
165         ++k;
166
167 #define SUB_END() \
168         ++i;
169
170 /** Test reading of a file within the libsub tree which exercises the parser */
171 BOOST_AUTO_TEST_CASE (ssa_reader_test3)
172 {
173         boost::filesystem::path p = "test/data/test.ssa";
174         FILE* f = fopen (p.string().c_str(), "r");
175         sub::SSAReader reader (f);
176         fclose (f);
177         list<sub::Subtitle> subs = sub::collect<std::list<sub::Subtitle> > (reader.subtitles ());
178
179         list<sub::Subtitle>::iterator i = subs.begin ();
180         list<sub::Line>::iterator j;
181         list<sub::Block>::iterator k;
182
183         /* Hello world */
184         SUB_START (sub::Time::from_hms (0, 0, 1, 230), sub::Time::from_hms (0, 0, 4, 550));
185         LINE (0, sub::BOTTOM_OF_SCREEN);
186         BLOCK ("Hello world", "Arial", 20, false, false, false);
187         SUB_END();
188
189         /* This is vertically moved\nand has two lines. */
190         SUB_START (sub::Time::from_hms (0, 0, 5, 740), sub::Time::from_hms (0, 0, 11, 0));
191         /* The first line should be 900 pixels and one line (20
192            points, 1.2 times spaced, as a proportion of the total
193            screen height 729 points) up.
194         */
195         LINE((900.0 / 1080) - (20.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
196         BLOCK("This is vertically moved", "Arial", 20, false, false, false);
197         LINE((900.0 / 1080), sub::BOTTOM_OF_SCREEN);
198         BLOCK("and has two lines.", "Arial", 20, false, false, false);
199         SUB_END();
200
201         /* Some {\i1}italics{\i} are here. */
202         SUB_START (sub::Time::from_hms (0, 0, 7, 740), sub::Time::from_hms (0, 0, 9, 0));
203         LINE(0, sub::BOTTOM_OF_SCREEN);
204         BLOCK("Some ", "Arial", 20, false, false, false);
205         BLOCK("italics", "Arial", 20, false, true, false);
206         BLOCK(" are here.", "Arial", 20, false, false, false);
207         SUB_END();
208
209         /* Alignments */
210
211         SUB_START (sub::Time::from_hms (0, 0, 9, 230), sub::Time::from_hms (0, 0, 11, 560));
212         LINE (0, sub::BOTTOM_OF_SCREEN);
213         BLOCK("bottom left", "Arial", 20, false, false, false);
214         SUB_END ();
215
216         SUB_START (sub::Time::from_hms (0, 0, 9, 240), sub::Time::from_hms (0, 0, 11, 560));
217         LINE (0, sub::BOTTOM_OF_SCREEN);
218         BLOCK("bottom centre", "Arial", 20, false, false, false);
219         SUB_END ();
220
221         SUB_START (sub::Time::from_hms (0, 0, 9, 250), sub::Time::from_hms (0, 0, 11, 560));
222         LINE (0, sub::BOTTOM_OF_SCREEN);
223         BLOCK("bottom right", "Arial", 20, false, false, false);
224         SUB_END ();
225
226         SUB_START (sub::Time::from_hms (0, 0, 9, 260), sub::Time::from_hms (0, 0, 11, 560));
227         LINE (0, sub::CENTRE_OF_SCREEN);
228         BLOCK("middle left", "Arial", 20, false, false, false);
229         SUB_END ();
230
231         SUB_START (sub::Time::from_hms (0, 0, 9, 270), sub::Time::from_hms (0, 0, 11, 560));
232         LINE (0, sub::CENTRE_OF_SCREEN);
233         BLOCK("middle centre", "Arial", 20, false, false, false);
234         SUB_END ();
235
236         SUB_START (sub::Time::from_hms (0, 0, 9, 280), sub::Time::from_hms (0, 0, 11, 560));
237         LINE (0, sub::CENTRE_OF_SCREEN);
238         BLOCK("middle right", "Arial", 20, false, false, false);
239         SUB_END ();
240
241         SUB_START (sub::Time::from_hms (0, 0, 9, 290), sub::Time::from_hms (0, 0, 11, 560));
242         LINE (0, sub::TOP_OF_SCREEN);
243         BLOCK("top left", "Arial", 20, false, false, false);
244         SUB_END ();
245
246         SUB_START (sub::Time::from_hms (0, 0, 9, 300), sub::Time::from_hms (0, 0, 11, 560));
247         LINE (0, sub::TOP_OF_SCREEN);
248         BLOCK("top centre", "Arial", 20, false, false, false);
249         SUB_END ();
250
251         SUB_START (sub::Time::from_hms (0, 0, 9, 310), sub::Time::from_hms (0, 0, 11, 560));
252         LINE (0, sub::TOP_OF_SCREEN);
253         BLOCK("top right", "Arial", 20, false, false, false);
254         SUB_END ();
255
256         BOOST_REQUIRE (i == subs.end ());
257 }
258
259 /** Test reading of a file within the libsub-test-private tree which exercises the parser */
260 BOOST_AUTO_TEST_CASE (ssa_reader_test4)
261 {
262         boost::filesystem::path p = private_test / "dcpsubtest2-en.ssa";
263         FILE* f = fopen (p.string().c_str(), "r");
264         sub::SSAReader reader (f);
265         fclose (f);
266         list<sub::Subtitle> subs = sub::collect<std::list<sub::Subtitle> > (reader.subtitles ());
267
268         list<sub::Subtitle>::iterator i = subs.begin ();
269         list<sub::Line>::iterator j;
270         list<sub::Block>::iterator k;
271
272         BOOST_REQUIRE (i != subs.end ());
273
274         SUB_START (sub::Time::from_hms (0, 0, 1, 0), sub::Time::from_hms (0, 0, 3, 0));
275         /* The first line should be one line (50 points, 1.2 times
276            spaced, as a proportion of the total screen height 729
277            points) up.
278         */
279         LINE ((50.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
280         BLOCK ("1st line: This is normal", "Verdana", 50, false, false, false);
281         LINE (0, sub::BOTTOM_OF_SCREEN);
282         BLOCK ("2d line: this is bold", "Verdana", 50, true, false, false);
283         SUB_END ();
284
285         SUB_START (sub::Time::from_hms (0, 0, 3, 100), sub::Time::from_hms (0, 0, 5, 100));
286         LINE ((50.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
287         BLOCK ("1st line: this is bold", "Verdana", 50, true, false, false);
288         LINE (0, sub::BOTTOM_OF_SCREEN);
289         BLOCK ("2nd line: This is normal", "Verdana", 50, false, false, false);
290         SUB_END ();
291
292         SUB_START (sub::Time::from_hms (0, 0, 5, 200), sub::Time::from_hms (0, 0, 7, 200));
293         LINE ((50.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
294         BLOCK ("1st line: this is bold", "Verdana", 50, true, false, false);
295         LINE (0, sub::BOTTOM_OF_SCREEN);
296         BLOCK ("2nd line: this is italics", "Verdana", 50, false, true, false);
297         SUB_END ();
298
299         SUB_START (sub::Time::from_hms (0, 0, 7, 300), sub::Time::from_hms (0, 0, 9, 300));
300         LINE ((50.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
301         BLOCK ("1st line: this is italics", "Verdana", 50, false, true, false);
302         LINE (0, sub::BOTTOM_OF_SCREEN);
303         BLOCK ("2nd line: this is bold", "Verdana", 50, true, false, false);
304         SUB_END ();
305 }
306
307 /** Test reading of a .ass file */
308 BOOST_AUTO_TEST_CASE (ssa_reader_test5)
309 {
310         boost::filesystem::path p = private_test / "dcpsubtest-en.ass";
311         FILE* f = fopen (p.string().c_str(), "r");
312         sub::SSAReader reader (f);
313         fclose (f);
314         list<sub::Subtitle> subs = sub::collect<std::list<sub::Subtitle> > (reader.subtitles ());
315
316         list<sub::Subtitle>::iterator i = subs.begin ();
317         list<sub::Line>::iterator j;
318         list<sub::Block>::iterator k;
319
320         BOOST_REQUIRE (i != subs.end ());
321
322         SUB_START (sub::Time::from_hms (0, 0, 1, 0), sub::Time::from_hms (0, 0, 3, 0));
323         /* The first line should be one line (26 points, 1.2 times
324            spaced, as a proportion of the total screen height 729
325            points) up.
326         */
327         LINE ((26.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
328         BLOCK ("1st subtitle, 1st line", "arial", 26, true, false, false);
329         LINE (0, sub::BOTTOM_OF_SCREEN);
330         BLOCK ("2nd subtitle, 2nd line", "arial", 26, true, false, false);
331         SUB_END ();
332
333         SUB_START (sub::Time::from_hms (0, 0, 3, 100), sub::Time::from_hms (0, 0, 5, 100));
334         LINE ((26.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
335         BLOCK ("2nd subtitle, 1st line", "arial", 26, true, false, false);
336         LINE (0, sub::BOTTOM_OF_SCREEN);
337         BLOCK ("2nd subtitle, 2nd line", "arial", 26, true, false, false);
338         SUB_END ();
339
340         SUB_START (sub::Time::from_hms (0, 0, 5, 200), sub::Time::from_hms (0, 0, 7, 200));
341         LINE ((26.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
342         BLOCK ("3rd subtitle, 1st line", "arial", 26, true, false, false);
343         LINE (0, sub::BOTTOM_OF_SCREEN);
344         BLOCK ("3rd subtitle, 2nd line", "arial", 26, true, false, false);
345         SUB_END ();
346
347         SUB_START (sub::Time::from_hms (0, 0, 7, 300), sub::Time::from_hms (0, 0, 9, 300));
348         LINE ((26.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
349         BLOCK ("4th subtitle, 1st line", "arial", 26, true, false, false);
350         LINE (0, sub::BOTTOM_OF_SCREEN);
351         BLOCK ("4th subtitle, 2nd line", "arial", 26, true, false, false);
352         SUB_END ();
353 }
354
355 /** Test reading of another .ass file */
356 BOOST_AUTO_TEST_CASE (ssa_reader_test6)
357 {
358         boost::filesystem::path p = private_test / "DCP-o-matic_test_subs_1.ass";
359         FILE* f = fopen (p.string().c_str(), "r");
360         BOOST_REQUIRE (f);
361         sub::SSAReader reader (f);
362         fclose (f);
363         list<sub::Subtitle> subs = sub::collect<std::list<sub::Subtitle> > (reader.subtitles ());
364
365         list<sub::Subtitle>::iterator i = subs.begin ();
366         list<sub::Line>::iterator j;
367         list<sub::Block>::iterator k;
368
369         BOOST_REQUIRE (i != subs.end ());
370
371         SUB_START (sub::Time::from_hms (0, 0, 0, 70), sub::Time::from_hms (0, 0, 1, 110));
372         /* The first line should be one line (30 points, 1.2 times
373            spaced, as a proportion of the total screen height 729
374            points) up.
375         */
376         LINE ((30.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
377         BLOCK ("This line is normal", "Arial", 30, false, false, false);
378         LINE (0, sub::BOTTOM_OF_SCREEN);
379         BLOCK ("This line is bold", "Arial", 30, true, false, false);
380         SUB_END ();
381
382         SUB_START (sub::Time::from_hms (0, 0, 1, 200), sub::Time::from_hms (0, 0, 2, 240));
383         LINE ((30.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
384         BLOCK ("This line is bold", "Arial", 30, true, false, false);
385         LINE (0, sub::BOTTOM_OF_SCREEN);
386         BLOCK ("This line is normal", "Arial", 30, false, false, false);
387         SUB_END ();
388
389         SUB_START (sub::Time::from_hms (0, 0, 2, 300), sub::Time::from_hms (0, 0, 3, 380));
390         LINE ((30.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
391         BLOCK ("This line is bold", "Arial", 30, true, false, false);
392         LINE (0, sub::BOTTOM_OF_SCREEN);
393         BLOCK ("This line is italic", "Arial", 30, false, true, false);
394         SUB_END ();
395
396         SUB_START (sub::Time::from_hms (0, 0, 3, 400), sub::Time::from_hms (0, 0, 4, 480));
397         LINE ((30.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
398         BLOCK ("This line is italic", "Arial", 30, false, true, false);
399         LINE (0, sub::BOTTOM_OF_SCREEN);
400         BLOCK ("This line is bold", "Arial", 30, true, false, false);
401         SUB_END ();
402
403         SUB_START (sub::Time::from_hms (0, 0, 4, 510), sub::Time::from_hms (0, 0, 5, 600));
404         LINE ((30.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
405         BLOCK ("Last three words are ", "Arial", 30, false, false, false);
406         BLOCK ("bold AND italic", "Arial", 30, true, true, false);
407         LINE (0, sub::BOTTOM_OF_SCREEN);
408         BLOCK ("Last three words are ", "Arial", 30, false, false, false);
409         BLOCK ("italic AND bold", "Arial", 30, true, true, false);
410         SUB_END ();
411
412         SUB_START (sub::Time::from_hms (0, 0, 5, 620), sub::Time::from_hms (0, 0, 6, 710));
413         LINE ((30.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
414         BLOCK ("Last three words are ", "Arial", 30, false, false, false);
415         BLOCK ("bold AND italic", "Arial", 30, true, true, false);
416         LINE (0, sub::BOTTOM_OF_SCREEN);
417         BLOCK ("First three words", "Arial", 30, true, true, false);
418         BLOCK (" are italic AND bold", "Arial", 30, false, false, false);
419         SUB_END ();
420
421         SUB_START (sub::Time::from_hms (0, 0, 6, 730), sub::Time::from_hms (0, 0, 8, 30));
422         LINE ((30.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
423         BLOCK ("Last three words are ", "Arial", 30, false, false, false);
424         BLOCK ("bold AND italic", "Arial", 30, true, true, false);
425         LINE (0, sub::BOTTOM_OF_SCREEN);
426         BLOCK ("This line is normal", "Arial", 30, false, false, false);
427         SUB_END ();
428
429         SUB_START (sub::Time::from_hms (0, 0, 8, 90), sub::Time::from_hms (0, 0, 9, 210));
430         LINE ((30.0 * 1.2 / 792), sub::BOTTOM_OF_SCREEN);
431         BLOCK ("Both lines are bold AND italic", "Arial", 30, true, true, false);
432         LINE (0, sub::BOTTOM_OF_SCREEN);
433         BLOCK ("Both lines are bold AND italic", "Arial", 30, true, true, false);
434         SUB_END ();
435 }