2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
35 #include "smpte_load_font_node.h"
36 #include "smpte_subtitle_asset.h"
37 #include "stream_operators.h"
38 #include "subtitle_image.h"
41 #include <boost/optional/optional_io.hpp>
42 #include <boost/test/unit_test.hpp>
45 using std::make_shared;
47 using std::shared_ptr;
48 using std::dynamic_pointer_cast;
50 using boost::optional;
53 BOOST_AUTO_TEST_CASE (smpte_subtitle_id_test)
55 dcp::SMPTESubtitleAsset subs;
57 make_shared<dcp::SubtitleString>(
63 dcp::Time(0, 1, 2, 3, 24),
64 dcp::Time(0, 2, 2, 3, 24),
74 dcp::Time(0, 0, 0, 0, 24),
75 dcp::Time(0, 0, 0, 0, 24),
79 subs.write("build/test/smpte_subtitle_id_test.mxf");
81 dcp::SMPTESubtitleAsset check("build/test/smpte_subtitle_id_test.mxf");
82 BOOST_CHECK(check.id() != check.xml_id());
86 /** Check reading of a SMPTE subtitle file */
87 BOOST_AUTO_TEST_CASE (read_smpte_subtitle_test)
89 dcp::SMPTESubtitleAsset sc (
92 "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV" /
93 "8b48f6ae-c74b-4b80-b994-a8236bbbad74_sub.mxf"
96 BOOST_CHECK_EQUAL (sc.id(), "8b48f6ae-c74b-4b80-b994-a8236bbbad74");
97 BOOST_CHECK_EQUAL (sc.content_title_text(), "Journey to Jah");
98 BOOST_REQUIRE (sc.annotation_text());
99 BOOST_CHECK_EQUAL (sc.annotation_text().get(), "Journey to Jah");
100 BOOST_CHECK_EQUAL (sc.issue_date(), dcp::LocalTime ("2014-02-25T11:22:48.000-00:00"));
101 BOOST_REQUIRE (sc.reel_number());
102 BOOST_CHECK_EQUAL (sc.reel_number().get(), 1);
103 BOOST_REQUIRE (sc.language ());
104 BOOST_CHECK_EQUAL (sc.language().get (), "de");
105 BOOST_CHECK_EQUAL (sc.edit_rate(), dcp::Fraction (25, 1));
106 BOOST_CHECK_EQUAL (sc.time_code_rate(), 25);
107 BOOST_CHECK_EQUAL (sc.start_time(), dcp::Time (0, 0, 0, 0, 25));
108 auto lfn = sc.load_font_nodes ();
109 BOOST_REQUIRE_EQUAL (lfn.size(), 1U);
110 shared_ptr<dcp::SMPTELoadFontNode> smpte_lfn = dynamic_pointer_cast<dcp::SMPTELoadFontNode> (lfn.front ());
111 BOOST_REQUIRE (smpte_lfn);
112 BOOST_CHECK_EQUAL (smpte_lfn->id, "theFontId");
113 BOOST_CHECK_EQUAL (smpte_lfn->urn, "9118bbce-4105-4a05-b37c-a5a6f75e1fea");
114 BOOST_REQUIRE_EQUAL (sc.subtitles().size(), 63U);
115 BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().front()));
116 BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().front())->text(), "Noch mal.");
117 BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().front())->space_before(), 0.0f);
118 BOOST_CHECK_EQUAL (sc.subtitles().front()->in(), dcp::Time (0, 0, 25, 12, 25));
119 BOOST_CHECK_EQUAL (sc.subtitles().front()->out(), dcp::Time (0, 0, 26, 4, 25));
120 BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().back()));
121 BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().back())->text(), "Prochainement");
122 BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().back())->space_before(), 0.0f);
123 BOOST_CHECK_EQUAL (sc.subtitles().back()->in(), dcp::Time (0, 1, 57, 17, 25));
124 BOOST_CHECK_EQUAL (sc.subtitles().back()->out(), dcp::Time (0, 1, 58, 12, 25));
128 /** And another one featuring <Font> within <Text> and some <Space> */
129 BOOST_AUTO_TEST_CASE (read_smpte_subtitle_test2)
131 dcp::SMPTESubtitleAsset sc (private_test / "olsson.xml");
133 auto subs = sc.subtitles();
134 BOOST_REQUIRE_EQUAL (subs.size(), 6U);
136 auto is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
138 BOOST_CHECK_EQUAL (is->text(), "Testing is ");
139 BOOST_CHECK (!is->italic());
140 BOOST_CHECK_CLOSE (is->space_before(), 0, 0.1);
142 is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
144 BOOST_CHECK_EQUAL (is->text(), "really");
145 BOOST_CHECK (is->italic());
146 BOOST_CHECK_CLOSE (is->space_before(), 0, 0.1);
148 is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
150 BOOST_CHECK_EQUAL (is->text(), " fun!");
151 BOOST_CHECK (!is->italic());
152 BOOST_CHECK_CLOSE (is->space_before(), 5, 0.1);
154 is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
156 BOOST_CHECK_EQUAL (is->text(), "This is the ");
157 BOOST_CHECK (!is->italic());
158 BOOST_CHECK_CLOSE (is->space_before(), 0, 0.1);
160 is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
162 BOOST_CHECK_EQUAL (is->text(), "second");
163 BOOST_CHECK (is->italic());
164 BOOST_CHECK_CLOSE (is->space_before(), 0, 0.1);
166 is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
168 BOOST_CHECK_EQUAL (is->text(), " line!");
169 BOOST_CHECK (!is->italic());
170 BOOST_CHECK_CLOSE (is->space_before(), 0, 0.1);
174 /* Write some subtitle content as SMPTE XML and check that it is right */
175 BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test)
177 dcp::SMPTESubtitleAsset c;
178 c.set_reel_number (1);
179 c.set_language (dcp::LanguageTag("en"));
180 c.set_content_title_text ("Test");
181 c.set_issue_date (dcp::LocalTime ("2016-04-01T03:52:00+00:00"));
184 make_shared<dcp::SubtitleString> (
189 dcp::Colour (255, 255, 255),
192 dcp::Time (0, 4, 9, 22, 24),
193 dcp::Time (0, 4, 11, 22, 24),
202 dcp::Colour (0, 0, 0),
203 dcp::Time (0, 0, 0, 0, 24),
204 dcp::Time (0, 0, 0, 0, 24),
210 make_shared<dcp::SubtitleString>(
211 boost::optional<string> (),
215 dcp::Colour (128, 0, 64),
218 dcp::Time (5, 41, 0, 21, 24),
219 dcp::Time (6, 12, 15, 21, 24),
228 dcp::Colour (1, 2, 3),
229 dcp::Time (1, 2, 3, 4, 24),
230 dcp::Time (5, 6, 7, 8, 24),
236 make_shared<dcp::SubtitleString>(
237 boost::optional<string> (),
241 dcp::Colour (128, 0, 64),
244 dcp::Time (5, 41, 0, 21, 24),
245 dcp::Time (6, 12, 15, 21, 24),
254 dcp::Colour (1, 2, 3),
255 dcp::Time (1, 2, 3, 4, 24),
256 dcp::Time (5, 6, 7, 8, 24),
261 c._xml_id = "a6c58cff-3e1e-4b38-acec-a42224475ef6";
264 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
265 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n"
266 " <Id>urn:uuid:a6c58cff-3e1e-4b38-acec-a42224475ef6</Id>\n"
267 " <ContentTitleText>Test</ContentTitleText>\n"
268 " <IssueDate>2016-04-01T03:52:00.000+00:00</IssueDate>\n"
269 " <ReelNumber>1</ReelNumber>\n"
270 " <Language>en</Language>\n"
271 " <EditRate>24 1</EditRate>\n"
272 " <TimeCodeRate>24</TimeCodeRate>\n"
274 " <Font AspectAdjust=\"1.0\" Color=\"FFFFFFFF\" Effect=\"none\" EffectColor=\"FF000000\" ID=\"Frutiger\" Italic=\"no\" Script=\"normal\" Size=\"48\" Underline=\"no\" Weight=\"normal\">\n"
275 " <Subtitle SpotNumber=\"1\" TimeIn=\"00:04:09:22\" TimeOut=\"00:04:11:22\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">\n"
276 " <Text Valign=\"top\" Vposition=\"80\" Zposition=\"30\">Hello world</Text>\n"
279 " <Font AspectAdjust=\"1.0\" Color=\"FF800040\" Effect=\"border\" EffectColor=\"FF010203\" Italic=\"yes\" Script=\"normal\" Size=\"91\" Underline=\"yes\" Weight=\"bold\">\n"
280 " <Subtitle SpotNumber=\"2\" TimeIn=\"05:41:00:21\" TimeOut=\"06:12:15:21\" FadeUpTime=\"01:02:03:04\" FadeDownTime=\"05:06:07:08\">\n"
281 " <Text Valign=\"bottom\" Vposition=\"40\" Direction=\"rtl\">What's going <Space Size=\"4.2\"/>on</Text>\n"
291 /* Write some subtitle content as SMPTE XML and check that it is right.
292 This includes in-line font changes.
294 BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test2)
296 dcp::SMPTESubtitleAsset c;
297 c.set_reel_number (1);
298 c.set_language (dcp::LanguageTag("en"));
299 c.set_content_title_text ("Test");
300 c.set_issue_date (dcp::LocalTime ("2016-04-01T03:52:00+00:00"));
303 make_shared<dcp::SubtitleString>(
308 dcp::Colour (255, 255, 255),
311 dcp::Time (0, 0, 1, 0, 24),
312 dcp::Time (0, 0, 9, 0, 24),
321 dcp::Colour (0, 0, 0),
322 dcp::Time (0, 0, 0, 0, 24),
323 dcp::Time (0, 0, 0, 0, 24),
329 make_shared<dcp::SubtitleString>(
334 dcp::Colour (255, 255, 255),
337 dcp::Time (0, 0, 1, 0, 24),
338 dcp::Time (0, 0, 9, 0, 24),
347 dcp::Colour (0, 0, 0),
348 dcp::Time (0, 0, 0, 0, 24),
349 dcp::Time (0, 0, 0, 0, 24),
355 make_shared<dcp::SubtitleString>(
360 dcp::Colour (255, 255, 255),
363 dcp::Time (0, 0, 1, 0, 24),
364 dcp::Time (0, 0, 9, 0, 24),
373 dcp::Colour (0, 0, 0),
374 dcp::Time (0, 0, 0, 0, 24),
375 dcp::Time (0, 0, 0, 0, 24),
381 make_shared<dcp::SubtitleString>(
386 dcp::Colour (255, 255, 255),
389 dcp::Time (0, 0, 1, 0, 24),
390 dcp::Time (0, 0, 9, 0, 24),
399 dcp::Colour (0, 0, 0),
400 dcp::Time (0, 0, 0, 0, 24),
401 dcp::Time (0, 0, 0, 0, 24),
407 make_shared<dcp::SubtitleString>(
412 dcp::Colour (255, 255, 255),
415 dcp::Time (0, 0, 1, 0, 24),
416 dcp::Time (0, 0, 9, 0, 24),
425 dcp::Colour (0, 0, 0),
426 dcp::Time (0, 0, 0, 0, 24),
427 dcp::Time (0, 0, 0, 0, 24),
433 make_shared<dcp::SubtitleString>(
438 dcp::Colour (255, 255, 255),
441 dcp::Time (0, 0, 1, 0, 24),
442 dcp::Time (0, 0, 9, 0, 24),
451 dcp::Colour (0, 0, 0),
452 dcp::Time (0, 0, 0, 0, 24),
453 dcp::Time (0, 0, 0, 0, 24),
458 c._xml_id = "a6c58cff-3e1e-4b38-acec-a42224475ef6";
462 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
463 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n"
464 " <Id>urn:uuid:a6c58cff-3e1e-4b38-acec-a42224475ef6</Id>\n"
465 " <ContentTitleText>Test</ContentTitleText>\n"
466 " <IssueDate>2016-04-01T03:52:00.000+00:00</IssueDate>\n"
467 " <ReelNumber>1</ReelNumber>\n"
468 " <Language>en</Language>\n"
469 " <EditRate>24 1</EditRate>\n"
470 " <TimeCodeRate>24</TimeCodeRate>\n"
472 " <Font AspectAdjust=\"1.0\" Color=\"FFFFFFFF\" Effect=\"none\" EffectColor=\"FF000000\" ID=\"Arial\" Script=\"normal\" Size=\"48\" Underline=\"no\" Weight=\"normal\">\n"
473 " <Subtitle SpotNumber=\"1\" TimeIn=\"00:00:01:00\" TimeOut=\"00:00:09:00\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">\n"
474 " <Text Valign=\"top\" Vposition=\"80\"><Font Italic=\"no\">Testing is </Font><Font Italic=\"yes\">really</Font><Font Italic=\"no\"> fun</Font></Text>\n"
475 " <Text Valign=\"top\" Vposition=\"90\"><Font Italic=\"no\">This is the </Font><Font Italic=\"yes\">second</Font><Font Italic=\"no\"> line</Font></Text>\n"
484 /* Write some subtitle content as SMPTE using bitmaps and check that it is right */
485 BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test3)
487 dcp::SMPTESubtitleAsset c;
488 c.set_reel_number (1);
489 c.set_language (dcp::LanguageTag("en"));
490 c.set_content_title_text ("Test");
491 c.set_start_time (dcp::Time());
493 boost::filesystem::path const sub_image = "test/data/sub.png";
496 make_shared<dcp::SubtitleImage>(
497 dcp::ArrayData(sub_image),
498 dcp::Time (0, 4, 9, 22, 24),
499 dcp::Time (0, 4, 11, 22, 24),
505 dcp::Time (0, 0, 0, 0, 24),
506 dcp::Time (0, 0, 0, 0, 24)
510 c._id = "a6c58cff-3e1e-4b38-acec-a42224475ef6";
512 boost::filesystem::path path = "build/test/write_smpte_subtitle_test3";
513 boost::filesystem::create_directories (path);
514 c.write (path / "subs.mxf");
516 dcp::SMPTESubtitleAsset read_back (path / "subs.mxf");
517 auto subs = read_back.subtitles ();
518 BOOST_REQUIRE_EQUAL (subs.size(), 1U);
519 auto image = dynamic_pointer_cast<const dcp::SubtitleImage>(subs[0]);
520 BOOST_REQUIRE (image);
522 BOOST_CHECK (image->png_image() == dcp::ArrayData(sub_image));
523 BOOST_CHECK (image->in() == dcp::Time(0, 4, 9, 22, 24));
524 BOOST_CHECK (image->out() == dcp::Time(0, 4, 11, 22, 24));
525 BOOST_CHECK_CLOSE (image->h_position(), 0.0, 1);
526 BOOST_CHECK (image->h_align() == dcp::HAlign::CENTER);
527 BOOST_CHECK_CLOSE (image->v_position(), 0.8, 1);
528 BOOST_CHECK (image->v_align() == dcp::VAlign::TOP);
529 BOOST_CHECK_EQUAL(image->z_position(), -88);
530 BOOST_CHECK (image->fade_up_time() == dcp::Time(0, 0, 0, 0, 24));
531 BOOST_CHECK (image->fade_down_time() == dcp::Time(0, 0, 0, 0, 24));
535 /* Some closed caption systems require the <Text> elements to be written in order of their
536 * vertical position (see DoM bug #2106).
538 BOOST_AUTO_TEST_CASE (write_subtitles_in_vertical_order_with_top_alignment)
540 dcp::SMPTESubtitleAsset c;
541 c.set_reel_number (1);
542 c.set_language (dcp::LanguageTag("en"));
543 c.set_content_title_text ("Test");
544 c.set_issue_date (dcp::LocalTime ("2016-04-01T03:52:00+00:00"));
547 make_shared<dcp::SubtitleString>(
552 dcp::Colour (255, 255, 255),
555 dcp::Time (0, 0, 1, 0, 24),
556 dcp::Time (0, 0, 9, 0, 24),
565 dcp::Colour (0, 0, 0),
566 dcp::Time (0, 0, 0, 0, 24),
567 dcp::Time (0, 0, 0, 0, 24),
573 make_shared<dcp::SubtitleString>(
578 dcp::Colour (255, 255, 255),
581 dcp::Time (0, 0, 1, 0, 24),
582 dcp::Time (0, 0, 9, 0, 24),
591 dcp::Colour (0, 0, 0),
592 dcp::Time (0, 0, 0, 0, 24),
593 dcp::Time (0, 0, 0, 0, 24),
598 c._xml_id = "a6c58cff-3e1e-4b38-acec-a42224475ef6";
602 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
603 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n"
604 " <Id>urn:uuid:a6c58cff-3e1e-4b38-acec-a42224475ef6</Id>\n"
605 " <ContentTitleText>Test</ContentTitleText>\n"
606 " <IssueDate>2016-04-01T03:52:00.000+00:00</IssueDate>\n"
607 " <ReelNumber>1</ReelNumber>\n"
608 " <Language>en</Language>\n"
609 " <EditRate>24 1</EditRate>\n"
610 " <TimeCodeRate>24</TimeCodeRate>\n"
612 " <Font AspectAdjust=\"1.0\" Color=\"FFFFFFFF\" Effect=\"none\" EffectColor=\"FF000000\" ID=\"Arial\" Italic=\"no\" Script=\"normal\" Size=\"48\" Underline=\"no\" Weight=\"normal\">\n"
613 " <Subtitle SpotNumber=\"1\" TimeIn=\"00:00:01:00\" TimeOut=\"00:00:09:00\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">\n"
614 " <Text Valign=\"top\" Vposition=\"80\">Top line</Text>\n"
615 " <Text Valign=\"top\" Vposition=\"90\">Bottom line</Text>\n"
625 /* See the test above */
626 BOOST_AUTO_TEST_CASE (write_subtitles_in_vertical_order_with_bottom_alignment)
628 dcp::SMPTESubtitleAsset c;
629 c.set_reel_number (1);
630 c.set_language (dcp::LanguageTag("en"));
631 c.set_content_title_text ("Test");
632 c.set_issue_date (dcp::LocalTime ("2016-04-01T03:52:00+00:00"));
635 make_shared<dcp::SubtitleString>(
640 dcp::Colour (255, 255, 255),
643 dcp::Time (0, 0, 1, 0, 24),
644 dcp::Time (0, 0, 9, 0, 24),
653 dcp::Colour (0, 0, 0),
654 dcp::Time (0, 0, 0, 0, 24),
655 dcp::Time (0, 0, 0, 0, 24),
661 make_shared<dcp::SubtitleString>(
666 dcp::Colour (255, 255, 255),
669 dcp::Time (0, 0, 1, 0, 24),
670 dcp::Time (0, 0, 9, 0, 24),
679 dcp::Colour (0, 0, 0),
680 dcp::Time (0, 0, 0, 0, 24),
681 dcp::Time (0, 0, 0, 0, 24),
686 c._xml_id = "a6c58cff-3e1e-4b38-acec-a42224475ef6";
690 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
691 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n"
692 " <Id>urn:uuid:a6c58cff-3e1e-4b38-acec-a42224475ef6</Id>\n"
693 " <ContentTitleText>Test</ContentTitleText>\n"
694 " <IssueDate>2016-04-01T03:52:00.000+00:00</IssueDate>\n"
695 " <ReelNumber>1</ReelNumber>\n"
696 " <Language>en</Language>\n"
697 " <EditRate>24 1</EditRate>\n"
698 " <TimeCodeRate>24</TimeCodeRate>\n"
700 " <Font AspectAdjust=\"1.0\" Color=\"FFFFFFFF\" Effect=\"none\" EffectColor=\"FF000000\" ID=\"Arial\" Italic=\"no\" Script=\"normal\" Size=\"48\" Underline=\"no\" Weight=\"normal\">\n"
701 " <Subtitle SpotNumber=\"1\" TimeIn=\"00:00:01:00\" TimeOut=\"00:00:09:00\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">\n"
702 " <Text Valign=\"bottom\" Vposition=\"80\">Top line</Text>\n"
703 " <Text Valign=\"bottom\" Vposition=\"70\">Bottom line</Text>\n"