e93efefb5237912f4f1252dcb1e44e24d9178077
[libdcp.git] / test / smpte_subtitle_test.cc
1 /*
2     Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34
35 #include "smpte_load_font_node.h"
36 #include "smpte_subtitle_asset.h"
37 #include "stream_operators.h"
38 #include "subtitle_image.h"
39 #include "test.h"
40 #include "types.h"
41 #include <boost/optional/optional_io.hpp>
42 #include <boost/test/unit_test.hpp>
43
44
45 using std::make_shared;
46 using std::string;
47 using std::shared_ptr;
48 using std::dynamic_pointer_cast;
49 using std::vector;
50 using boost::optional;
51
52
53 BOOST_AUTO_TEST_CASE (smpte_subtitle_id_test)
54 {
55         dcp::SMPTESubtitleAsset subs;
56         subs.add(
57                 make_shared<dcp::SubtitleString>(
58                         optional<string>(),
59                         false, false, false,
60                         dcp::Colour(),
61                         64,
62                         1,
63                         dcp::Time(0, 1, 2, 3, 24),
64                         dcp::Time(0, 2, 2, 3, 24),
65                         0.5,
66                         dcp::HAlign::CENTER,
67                         0.5,
68                         dcp::VAlign::CENTER,
69                         dcp::Direction::LTR,
70                         "Hello",
71                         dcp::Effect::NONE,
72                         dcp::Colour(),
73                         dcp::Time(0, 0, 0, 0, 24),
74                         dcp::Time(0, 0, 0, 0, 24)
75                         )
76                 );
77         subs.write("build/test/smpte_subtitle_id_test.mxf");
78
79         dcp::SMPTESubtitleAsset check("build/test/smpte_subtitle_id_test.mxf");
80         BOOST_CHECK(check.id() != check.xml_id());
81 }
82
83
84 /** Check reading of a SMPTE subtitle file */
85 BOOST_AUTO_TEST_CASE (read_smpte_subtitle_test)
86 {
87         dcp::SMPTESubtitleAsset sc (
88                 private_test /
89                 "data" /
90                 "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV" /
91                 "8b48f6ae-c74b-4b80-b994-a8236bbbad74_sub.mxf"
92                 );
93
94         BOOST_CHECK_EQUAL (sc.id(), "8b48f6ae-c74b-4b80-b994-a8236bbbad74");
95         BOOST_CHECK_EQUAL (sc.content_title_text(), "Journey to Jah");
96         BOOST_REQUIRE (sc.annotation_text());
97         BOOST_CHECK_EQUAL (sc.annotation_text().get(), "Journey to Jah");
98         BOOST_CHECK_EQUAL (sc.issue_date(), dcp::LocalTime ("2014-02-25T11:22:48.000-00:00"));
99         BOOST_REQUIRE (sc.reel_number());
100         BOOST_CHECK_EQUAL (sc.reel_number().get(), 1);
101         BOOST_REQUIRE (sc.language ());
102         BOOST_CHECK_EQUAL (sc.language().get (), "de");
103         BOOST_CHECK_EQUAL (sc.edit_rate(), dcp::Fraction (25, 1));
104         BOOST_CHECK_EQUAL (sc.time_code_rate(), 25);
105         BOOST_CHECK_EQUAL (sc.start_time(), dcp::Time (0, 0, 0, 0, 25));
106         auto lfn = sc.load_font_nodes ();
107         BOOST_REQUIRE_EQUAL (lfn.size(), 1);
108         shared_ptr<dcp::SMPTELoadFontNode> smpte_lfn = dynamic_pointer_cast<dcp::SMPTELoadFontNode> (lfn.front ());
109         BOOST_REQUIRE (smpte_lfn);
110         BOOST_CHECK_EQUAL (smpte_lfn->id, "theFontId");
111         BOOST_CHECK_EQUAL (smpte_lfn->urn, "9118bbce-4105-4a05-b37c-a5a6f75e1fea");
112         BOOST_REQUIRE_EQUAL (sc.subtitles().size(), 63);
113         BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().front()));
114         BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().front())->text(), "Noch mal.");
115         BOOST_CHECK_EQUAL (sc.subtitles().front()->in(), dcp::Time (0, 0, 25, 12, 25));
116         BOOST_CHECK_EQUAL (sc.subtitles().front()->out(), dcp::Time (0, 0, 26, 4, 25));
117         BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().back()));
118         BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().back())->text(), "Prochainement");
119         BOOST_CHECK_EQUAL (sc.subtitles().back()->in(), dcp::Time (0, 1, 57, 17, 25));
120         BOOST_CHECK_EQUAL (sc.subtitles().back()->out(), dcp::Time (0, 1, 58, 12, 25));
121 }
122
123
124 /** And another one featuring <Font> within <Text> */
125 BOOST_AUTO_TEST_CASE (read_smpte_subtitle_test2)
126 {
127         dcp::SMPTESubtitleAsset sc (private_test / "olsson.xml");
128
129         auto subs = sc.subtitles();
130         BOOST_REQUIRE_EQUAL (subs.size(), 6);
131         auto i = 0;
132         auto is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
133         BOOST_REQUIRE (is);
134         BOOST_CHECK_EQUAL (is->text(), "Testing is ");
135         BOOST_CHECK (!is->italic());
136         ++i;
137         is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
138         BOOST_REQUIRE (is);
139         BOOST_CHECK_EQUAL (is->text(), "really");
140         BOOST_CHECK (is->italic());
141         ++i;
142         is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
143         BOOST_REQUIRE (is);
144         BOOST_CHECK_EQUAL (is->text(), " fun!");
145         BOOST_CHECK (!is->italic());
146         ++i;
147         is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
148         BOOST_REQUIRE (is);
149         BOOST_CHECK_EQUAL (is->text(), "This is the ");
150         BOOST_CHECK (!is->italic());
151         ++i;
152         is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
153         BOOST_REQUIRE (is);
154         BOOST_CHECK_EQUAL (is->text(), "second");
155         BOOST_CHECK (is->italic());
156         ++i;
157         is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
158         BOOST_REQUIRE (is);
159         BOOST_CHECK_EQUAL (is->text(), " line!");
160         BOOST_CHECK (!is->italic());
161 }
162
163
164 /* Write some subtitle content as SMPTE XML and check that it is right */
165 BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test)
166 {
167         dcp::SMPTESubtitleAsset c;
168         c.set_reel_number (1);
169         c.set_language (dcp::LanguageTag("en"));
170         c.set_content_title_text ("Test");
171         c.set_issue_date (dcp::LocalTime ("2016-04-01T03:52:00+00:00"));
172
173         c.add (
174                 make_shared<dcp::SubtitleString> (
175                         string ("Frutiger"),
176                         false,
177                         false,
178                         false,
179                         dcp::Colour (255, 255, 255),
180                         48,
181                         1.0,
182                         dcp::Time (0, 4,  9, 22, 24),
183                         dcp::Time (0, 4, 11, 22, 24),
184                         0,
185                         dcp::HAlign::CENTER,
186                         0.8,
187                         dcp::VAlign::TOP,
188                         dcp::Direction::LTR,
189                         "Hello world",
190                         dcp::Effect::NONE,
191                         dcp::Colour (0, 0, 0),
192                         dcp::Time (0, 0, 0, 0, 24),
193                         dcp::Time (0, 0, 0, 0, 24)
194                         )
195                 );
196
197         c.add (
198                 make_shared<dcp::SubtitleString>(
199                         boost::optional<string> (),
200                         true,
201                         true,
202                         true,
203                         dcp::Colour (128, 0, 64),
204                         91,
205                         1.0,
206                         dcp::Time (5, 41,  0, 21, 24),
207                         dcp::Time (6, 12, 15, 21, 24),
208                         0,
209                         dcp::HAlign::CENTER,
210                         0.4,
211                         dcp::VAlign::BOTTOM,
212                         dcp::Direction::RTL,
213                         "What's going on",
214                         dcp::Effect::BORDER,
215                         dcp::Colour (1, 2, 3),
216                         dcp::Time (1, 2, 3, 4, 24),
217                         dcp::Time (5, 6, 7, 8, 24)
218                         )
219                 );
220
221         c._xml_id = "a6c58cff-3e1e-4b38-acec-a42224475ef6";
222
223         check_xml (
224                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
225                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">"
226                   "<Id>urn:uuid:a6c58cff-3e1e-4b38-acec-a42224475ef6</Id>"
227                   "<ContentTitleText>Test</ContentTitleText>"
228                   "<IssueDate>2016-04-01T03:52:00.000+00:00</IssueDate>"
229                   "<ReelNumber>1</ReelNumber>"
230                   "<Language>en</Language>"
231                   "<EditRate>24 1</EditRate>"
232                   "<TimeCodeRate>24</TimeCodeRate>"
233                   "<SubtitleList>"
234                     "<Font AspectAdjust=\"1.0\" Color=\"FFFFFFFF\" Effect=\"none\" EffectColor=\"FF000000\" ID=\"Frutiger\" Italic=\"no\" Script=\"normal\" Size=\"48\" Underline=\"no\" Weight=\"normal\">"
235                       "<Subtitle SpotNumber=\"1\" TimeIn=\"00:04:09:22\" TimeOut=\"00:04:11:22\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
236                         "<Text Valign=\"top\" Vposition=\"80\">Hello world</Text>"
237                       "</Subtitle>"
238                     "</Font>"
239                     "<Font AspectAdjust=\"1.0\" Color=\"FF800040\" Effect=\"border\" EffectColor=\"FF010203\" Italic=\"yes\" Script=\"normal\" Size=\"91\" Underline=\"yes\" Weight=\"bold\">"
240                       "<Subtitle SpotNumber=\"2\" TimeIn=\"05:41:00:21\" TimeOut=\"06:12:15:21\" FadeUpTime=\"01:02:03:04\" FadeDownTime=\"05:06:07:08\">"
241                         "<Text Valign=\"bottom\" Vposition=\"40\" Direction=\"rtl\">What's going on</Text>"
242                       "</Subtitle>"
243                     "</Font>"
244                   "</SubtitleList>"
245                 "</SubtitleReel>",
246                 c.xml_as_string (),
247                 vector<string>()
248                 );
249 }
250
251 /* Write some subtitle content as SMPTE XML and check that it is right.
252    This includes in-line font changes.
253 */
254 BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test2)
255 {
256         dcp::SMPTESubtitleAsset c;
257         c.set_reel_number (1);
258         c.set_language (dcp::LanguageTag("en"));
259         c.set_content_title_text ("Test");
260         c.set_issue_date (dcp::LocalTime ("2016-04-01T03:52:00+00:00"));
261
262         c.add (
263                 make_shared<dcp::SubtitleString>(
264                         string ("Arial"),
265                         false,
266                         false,
267                         false,
268                         dcp::Colour (255, 255, 255),
269                         48,
270                         1.0,
271                         dcp::Time (0, 0, 1, 0, 24),
272                         dcp::Time (0, 0, 9, 0, 24),
273                         0,
274                         dcp::HAlign::CENTER,
275                         0.8,
276                         dcp::VAlign::TOP,
277                         dcp::Direction::LTR,
278                         "Testing is ",
279                         dcp::Effect::NONE,
280                         dcp::Colour (0, 0, 0),
281                         dcp::Time (0, 0, 0, 0, 24),
282                         dcp::Time (0, 0, 0, 0, 24)
283                         )
284                 );
285
286         c.add (
287                 make_shared<dcp::SubtitleString>(
288                         string ("Arial"),
289                         true,
290                         false,
291                         false,
292                         dcp::Colour (255, 255, 255),
293                         48,
294                         1.0,
295                         dcp::Time (0, 0, 1, 0, 24),
296                         dcp::Time (0, 0, 9, 0, 24),
297                         0,
298                         dcp::HAlign::CENTER,
299                         0.8,
300                         dcp::VAlign::TOP,
301                         dcp::Direction::LTR,
302                         "really",
303                         dcp::Effect::NONE,
304                         dcp::Colour (0, 0, 0),
305                         dcp::Time (0, 0, 0, 0, 24),
306                         dcp::Time (0, 0, 0, 0, 24)
307                         )
308                 );
309
310         c.add (
311                 make_shared<dcp::SubtitleString>(
312                         string ("Arial"),
313                         false,
314                         false,
315                         false,
316                         dcp::Colour (255, 255, 255),
317                         48,
318                         1.0,
319                         dcp::Time (0, 0, 1, 0, 24),
320                         dcp::Time (0, 0, 9, 0, 24),
321                         0,
322                         dcp::HAlign::CENTER,
323                         0.8,
324                         dcp::VAlign::TOP,
325                         dcp::Direction::LTR,
326                         " fun",
327                         dcp::Effect::NONE,
328                         dcp::Colour (0, 0, 0),
329                         dcp::Time (0, 0, 0, 0, 24),
330                         dcp::Time (0, 0, 0, 0, 24)
331                         )
332                 );
333
334         c.add (
335                 make_shared<dcp::SubtitleString>(
336                         string ("Arial"),
337                         false,
338                         false,
339                         false,
340                         dcp::Colour (255, 255, 255),
341                         48,
342                         1.0,
343                         dcp::Time (0, 0, 1, 0, 24),
344                         dcp::Time (0, 0, 9, 0, 24),
345                         0,
346                         dcp::HAlign::CENTER,
347                         0.9,
348                         dcp::VAlign::TOP,
349                         dcp::Direction::LTR,
350                         "This is the ",
351                         dcp::Effect::NONE,
352                         dcp::Colour (0, 0, 0),
353                         dcp::Time (0, 0, 0, 0, 24),
354                         dcp::Time (0, 0, 0, 0, 24)
355                         )
356                 );
357
358         c.add (
359                 make_shared<dcp::SubtitleString>(
360                         string ("Arial"),
361                         true,
362                         false,
363                         false,
364                         dcp::Colour (255, 255, 255),
365                         48,
366                         1.0,
367                         dcp::Time (0, 0, 1, 0, 24),
368                         dcp::Time (0, 0, 9, 0, 24),
369                         0,
370                         dcp::HAlign::CENTER,
371                         0.9,
372                         dcp::VAlign::TOP,
373                         dcp::Direction::LTR,
374                         "second",
375                         dcp::Effect::NONE,
376                         dcp::Colour (0, 0, 0),
377                         dcp::Time (0, 0, 0, 0, 24),
378                         dcp::Time (0, 0, 0, 0, 24)
379                         )
380                 );
381
382         c.add (
383                 make_shared<dcp::SubtitleString>(
384                         string ("Arial"),
385                         false,
386                         false,
387                         false,
388                         dcp::Colour (255, 255, 255),
389                         48,
390                         1.0,
391                         dcp::Time (0, 0, 1, 0, 24),
392                         dcp::Time (0, 0, 9, 0, 24),
393                         0,
394                         dcp::HAlign::CENTER,
395                         0.9,
396                         dcp::VAlign::TOP,
397                         dcp::Direction::LTR,
398                         " line",
399                         dcp::Effect::NONE,
400                         dcp::Colour (0, 0, 0),
401                         dcp::Time (0, 0, 0, 0, 24),
402                         dcp::Time (0, 0, 0, 0, 24)
403                         )
404                 );
405
406         c._xml_id = "a6c58cff-3e1e-4b38-acec-a42224475ef6";
407
408         check_xml (
409                 c.xml_as_string(),
410                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
411                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">"
412                   "<Id>urn:uuid:a6c58cff-3e1e-4b38-acec-a42224475ef6</Id>"
413                   "<ContentTitleText>Test</ContentTitleText>"
414                   "<IssueDate>2016-04-01T03:52:00.000+00:00</IssueDate>"
415                   "<ReelNumber>1</ReelNumber>"
416                   "<Language>en</Language>"
417                   "<EditRate>24 1</EditRate>"
418                   "<TimeCodeRate>24</TimeCodeRate>"
419                   "<SubtitleList>"
420                     "<Font AspectAdjust=\"1.0\" Color=\"FFFFFFFF\" Effect=\"none\" EffectColor=\"FF000000\" ID=\"Arial\" Script=\"normal\" Size=\"48\" Underline=\"no\" Weight=\"normal\">"
421                       "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:01:00\" TimeOut=\"00:00:09:00\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
422                         "<Text Valign=\"top\" Vposition=\"80\">"
423                           "<Font Italic=\"no\">Testing is </Font>"
424                           "<Font Italic=\"yes\">really</Font>"
425                           "<Font Italic=\"no\"> fun</Font>"
426                         "</Text>"
427                         "<Text Valign=\"top\" Vposition=\"90\">"
428                           "<Font Italic=\"no\">This is the </Font>"
429                           "<Font Italic=\"yes\">second</Font>"
430                           "<Font Italic=\"no\"> line</Font>"
431                         "</Text>"
432                       "</Subtitle>"
433                     "</Font>"
434                   "</SubtitleList>"
435                 "</SubtitleReel>",
436                 vector<string>()
437                 );
438 }
439
440 /* Write some subtitle content as SMPTE using bitmaps and check that it is right */
441 BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test3)
442 {
443         dcp::SMPTESubtitleAsset c;
444         c.set_reel_number (1);
445         c.set_language (dcp::LanguageTag("en"));
446         c.set_content_title_text ("Test");
447         c.set_start_time (dcp::Time());
448
449         boost::filesystem::path const sub_image = "test/data/sub.png";
450
451         c.add (
452                 make_shared<dcp::SubtitleImage>(
453                         dcp::ArrayData(sub_image),
454                         dcp::Time (0, 4,  9, 22, 24),
455                         dcp::Time (0, 4, 11, 22, 24),
456                         0,
457                         dcp::HAlign::CENTER,
458                         0.8,
459                         dcp::VAlign::TOP,
460                         dcp::Time (0, 0, 0, 0, 24),
461                         dcp::Time (0, 0, 0, 0, 24)
462                         )
463               );
464
465         c._id = "a6c58cff-3e1e-4b38-acec-a42224475ef6";
466
467         boost::filesystem::path path = "build/test/write_smpte_subtitle_test3";
468         boost::filesystem::create_directories (path);
469         c.write (path / "subs.mxf");
470
471         dcp::SMPTESubtitleAsset read_back (path / "subs.mxf");
472         auto subs = read_back.subtitles ();
473         BOOST_REQUIRE_EQUAL (subs.size(), 1U);
474         auto image = dynamic_pointer_cast<const dcp::SubtitleImage>(subs[0]);
475         BOOST_REQUIRE (image);
476
477         BOOST_CHECK (image->png_image() == dcp::ArrayData(sub_image));
478         BOOST_CHECK (image->in() == dcp::Time(0, 4, 9, 22, 24));
479         BOOST_CHECK (image->out() == dcp::Time(0, 4, 11, 22, 24));
480         BOOST_CHECK_CLOSE (image->h_position(), 0.0, 1);
481         BOOST_CHECK (image->h_align() == dcp::HAlign::CENTER);
482         BOOST_CHECK_CLOSE (image->v_position(), 0.8, 1);
483         BOOST_CHECK (image->v_align() == dcp::VAlign::TOP);
484         BOOST_CHECK (image->fade_up_time() == dcp::Time(0, 0, 0, 0, 24));
485         BOOST_CHECK (image->fade_down_time() == dcp::Time(0, 0, 0, 0, 24));
486 }