+
+BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
+{
+ vector<dcp::VerificationNote> notes;
+ dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
+ auto reader = picture.start_read ();
+ auto frame = reader->get_frame (0);
+ verify_j2k(frame, 0, 0, 24, notes);
+ BOOST_REQUIRE_EQUAL (notes.size(), 0U);
+}
+
+
+BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
+{
+ boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
+ prepare_directory (dir);
+ auto dcp = make_simple (dir);
+ dcp->write_xml ();
+ vector<dcp::VerificationNote> notes;
+ dcp::MonoPictureAsset picture (find_file(dir, "video"));
+ auto reader = picture.start_read ();
+ auto frame = reader->get_frame (0);
+ verify_j2k(frame, 0, 0, 24, notes);
+ BOOST_REQUIRE_EQUAL (notes.size(), 0U);
+}
+
+
+/** Check that ResourceID and the XML ID being different is spotted */
+BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
+{
+ boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
+ prepare_directory (dir);
+
+ ASDCP::WriterInfo writer_info;
+ writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
+
+ unsigned int c;
+ auto mxf_id = dcp::make_uuid ();
+ Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
+ BOOST_REQUIRE (c == Kumu::UUID_Length);
+
+ auto resource_id = dcp::make_uuid ();
+ ASDCP::TimedText::TimedTextDescriptor descriptor;
+ Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
+ DCP_ASSERT (c == Kumu::UUID_Length);
+
+ auto xml_id = dcp::make_uuid ();
+ ASDCP::TimedText::MXFWriter writer;
+ auto subs_mxf = dir / "subs.mxf";
+ auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
+ BOOST_REQUIRE (ASDCP_SUCCESS(r));
+ writer.WriteTimedTextResource (dcp::String::compose(
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
+ "<Id>urn:uuid:%1</Id>"
+ "<ContentTitleText>Content</ContentTitleText>"
+ "<AnnotationText>Annotation</AnnotationText>"
+ "<IssueDate>2018-10-02T12:25:14</IssueDate>"
+ "<ReelNumber>1</ReelNumber>"
+ "<Language>en-US</Language>"
+ "<EditRate>25 1</EditRate>"
+ "<TimeCodeRate>25</TimeCodeRate>"
+ "<StartTime>00:00:00:00</StartTime>"
+ "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
+ "<SubtitleList>"
+ "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
+ "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
+ "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
+ "</Subtitle>"
+ "</Font>"
+ "</SubtitleList>"
+ "</SubtitleReel>",
+ xml_id).c_str());
+
+ writer.Finalize();
+
+ auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
+ auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
+
+ auto cpl = write_dcp_with_single_asset (dir, subs_reel);
+
+ check_verify_result (
+ { dir },
+ {},
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf)
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
+ ).set_cpl_id(cpl->id())
+ });
+}
+
+
+/** Check that ResourceID and the MXF ID being the same is spotted */
+BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
+{
+ boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
+ prepare_directory (dir);
+
+ ASDCP::WriterInfo writer_info;
+ writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
+
+ unsigned int c;
+ auto mxf_id = dcp::make_uuid ();
+ Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
+ BOOST_REQUIRE (c == Kumu::UUID_Length);
+
+ auto resource_id = mxf_id;
+ ASDCP::TimedText::TimedTextDescriptor descriptor;
+ Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
+ DCP_ASSERT (c == Kumu::UUID_Length);
+
+ auto xml_id = resource_id;
+ ASDCP::TimedText::MXFWriter writer;
+ auto subs_mxf = dir / "subs.mxf";
+ auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
+ BOOST_REQUIRE (ASDCP_SUCCESS(r));
+ writer.WriteTimedTextResource (dcp::String::compose(
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
+ "<Id>urn:uuid:%1</Id>"
+ "<ContentTitleText>Content</ContentTitleText>"
+ "<AnnotationText>Annotation</AnnotationText>"
+ "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
+ "<ReelNumber>1</ReelNumber>"
+ "<Language>en-US</Language>"
+ "<EditRate>25 1</EditRate>"
+ "<TimeCodeRate>25</TimeCodeRate>"
+ "<StartTime>00:00:00:00</StartTime>"
+ "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
+ "<SubtitleList>"
+ "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
+ "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
+ "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
+ "</Subtitle>"
+ "</Font>"
+ "</SubtitleList>"
+ "</SubtitleReel>",
+ xml_id).c_str());
+
+ writer.Finalize();
+
+ auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
+ auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
+
+ auto cpl = write_dcp_with_single_asset (dir, subs_reel);
+
+ check_verify_result (
+ { dir },
+ {},
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf)
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"}
+ ).set_cpl_id(cpl->id())
+ });
+}
+
+
+/** Check a DCP with a 3D asset marked as 2D */
+BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
+{
+ auto const path = private_test / "data" / "xm";
+
+ check_verify_result (
+ { path },
+ {},
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::WARNING,
+ dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(path, "j2c"))
+ ),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR,
+ dcp::VerificationNote::Code::INVALID_STANDARD
+ )
+ });
+
+}
+
+
+BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
+{
+ path dir = "build/test/verify_unexpected_things_in_main_markers";
+ prepare_directory (dir);
+ auto dcp = make_simple (dir, 1, 24);
+ dcp->write_xml();
+
+ HashCalculator calc(find_cpl(dir));
+
+ {
+ Editor e (find_cpl(dir));
+ e.insert(
+ " <IntrinsicDuration>24</IntrinsicDuration>",
+ "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
+ );
+ }
+
+ dcp::CPL cpl (find_cpl(dir));
+
+ check_verify_result (
+ { dir },
+ {},
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
+ ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT
+ ).set_cpl_id(cpl.id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION
+ ).set_cpl_id(cpl.id())
+ });
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
+{
+ path dir = "build/test/verify_invalid_content_kind";
+ prepare_directory (dir);
+ auto dcp = make_simple (dir, 1, 24);
+ dcp->write_xml();
+
+ HashCalculator calc(find_cpl(dir));
+
+ {
+ Editor e(find_cpl(dir));
+ e.replace("trailer", "trip");
+ }
+
+ dcp::CPL cpl (find_cpl(dir));
+
+ check_verify_result (
+ { dir },
+ {},
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
+ ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip")
+ ).set_cpl_id(cpl.id()),
+ });
+
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
+{
+ path dir = "build/test/verify_valid_content_kind";
+ prepare_directory (dir);
+ auto dcp = make_simple (dir, 1, 24);
+ dcp->write_xml();
+
+ HashCalculator calc(find_cpl(dir));
+
+ {
+ Editor e(find_cpl(dir));
+ e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
+ }
+
+ dcp::CPL cpl (find_cpl(dir));
+
+ check_verify_result (
+ { dir },
+ {},
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
+ ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
+ });
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
+{
+ path dir = "build/test/verify_invalid_main_picture_active_area_1";
+ prepare_directory(dir);
+ auto dcp = make_simple(dir, 1, 24);
+ dcp->write_xml();
+
+ auto constexpr area = "<meta:MainPictureActiveArea>";
+
+ HashCalculator calc(find_cpl(dir));
+
+ {
+ Editor e(find_cpl(dir));
+ e.delete_lines_after(area, 2);
+ e.insert(area, "<meta:Height>4080</meta:Height>");
+ e.insert(area, "<meta:Width>1997</meta:Width>");
+ }
+
+ dcp::PKL pkl(find_pkl(dir));
+ dcp::CPL cpl(find_cpl(dir));
+
+ check_verify_result(
+ { dir },
+ {},
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
+ ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir))
+ ).set_cpl_id(cpl.id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 4080 is bigger than the asset height 1080", canonical(find_cpl(dir))
+ ).set_cpl_id(cpl.id()),
+ });
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
+{
+ path dir = "build/test/verify_invalid_main_picture_active_area_2";
+ prepare_directory(dir);
+ auto dcp = make_simple(dir, 1, 24);
+ dcp->write_xml();
+
+ auto constexpr area = "<meta:MainPictureActiveArea>";
+
+ HashCalculator calc(find_cpl(dir));
+
+ {
+ Editor e(find_cpl(dir));
+ e.delete_lines_after(area, 2);
+ e.insert(area, "<meta:Height>5125</meta:Height>");
+ e.insert(area, "<meta:Width>9900</meta:Width>");
+ }
+
+ dcp::PKL pkl(find_pkl(dir));
+ dcp::CPL cpl(find_cpl(dir));
+
+ check_verify_result(
+ { dir },
+ {},
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, canonical(find_cpl(dir))
+ ).set_cpl_id(cpl.id()).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir))
+ ).set_cpl_id(cpl.id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 9900 is bigger than the asset width 1998", canonical(find_cpl(dir))
+ ).set_cpl_id(cpl.id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is bigger than the asset height 1080", canonical(find_cpl(dir))
+ ).set_cpl_id(cpl.id())
+ });
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
+{
+ RNGFixer rg;
+
+ path dir = "build/test/verify_duplicate_pkl_asset_ids";
+ prepare_directory(dir);
+ auto dcp = make_simple(dir, 1, 24);
+ dcp->write_xml();
+
+ {
+ Editor e(find_pkl(dir));
+ e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
+ }
+
+ dcp::PKL pkl(find_pkl(dir));
+
+ check_verify_result(
+ { dir },
+ {},
+ {
+ { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
+ });
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
+{
+ RNGFixer rg;
+
+ path dir = "build/test/verify_duplicate_assetmap_asset_ids";
+ prepare_directory(dir);
+ auto dcp = make_simple(dir, 1, 24);
+ dcp->write_xml();
+
+ {
+ Editor e(find_asset_map(dir));
+ e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
+ }
+
+ dcp::PKL pkl(find_pkl(dir));
+ dcp::AssetMap asset_map(find_asset_map(dir));
+ dcp::CPL cpl(find_cpl(dir));
+
+ check_verify_result(
+ { dir },
+ {},
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir))
+ ),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54")
+ )
+ });
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
+{
+ boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
+
+ dcp::MXFMetadata mxf_meta;
+ mxf_meta.company_name = "OpenDCP";
+ mxf_meta.product_name = "OpenDCP";
+ mxf_meta.product_version = "0.0.25";
+
+ auto constexpr sample_rate = 48000;
+ auto constexpr frames = 240;
+
+ boost::filesystem::remove_all(path);
+ boost::filesystem::create_directories(path);
+ auto dcp = make_shared<dcp::DCP>(path);
+ auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
+ cpl->set_annotation_text("hello");
+ cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
+ cpl->set_main_sound_sample_rate(sample_rate);
+ cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
+ cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
+ cpl->set_version_number(1);
+
+ {
+
+ /* Reel with 2 channels of audio */
+
+ auto mp = simple_picture(path, "1", frames, {});
+ auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
+
+ auto reel = make_shared<dcp::Reel>(
+ std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
+ std::make_shared<dcp::ReelSoundAsset>(ms, 0)
+ );
+
+ auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
+ markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
+ reel->add(markers);
+
+ cpl->add(reel);
+ }
+
+ {
+ /* Reel with 6 channels of audio */
+
+ auto mp = simple_picture(path, "2", frames, {});
+ auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
+
+ auto reel = make_shared<dcp::Reel>(
+ std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
+ std::make_shared<dcp::ReelSoundAsset>(ms, 0)
+ );
+
+ auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
+ markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
+ reel->add(markers);
+
+ cpl->add(reel);
+ }
+
+ dcp->add(cpl);
+ dcp->set_annotation_text("hello");
+ dcp->write_xml();
+
+ check_verify_result(
+ { path },
+ {},
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2"))
+ ).set_cpl_id(cpl->id())
+ });
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
+{
+ boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
+
+ dcp::MXFMetadata mxf_meta;
+ mxf_meta.company_name = "OpenDCP";
+ mxf_meta.product_name = "OpenDCP";
+ mxf_meta.product_version = "0.0.25";
+
+ auto constexpr sample_rate = 48000;
+ auto constexpr frames = 240;
+
+ boost::filesystem::remove_all(path);
+ boost::filesystem::create_directories(path);
+ auto dcp = make_shared<dcp::DCP>(path);
+ auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
+ cpl->set_annotation_text("hello");
+ cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
+ cpl->set_main_sound_sample_rate(sample_rate);
+ cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
+ cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
+ cpl->set_version_number(1);
+
+ auto mp = simple_picture(path, "1", frames, {});
+ auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
+
+ auto reel = make_shared<dcp::Reel>(
+ std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
+ std::make_shared<dcp::ReelSoundAsset>(ms, 0)
+ );
+
+ auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
+ markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
+ markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
+ reel->add(markers);
+
+ cpl->add(reel);
+
+ dcp->add(cpl);
+ dcp->set_annotation_text("hello");
+ dcp->write_xml();
+
+ check_verify_result(
+ { path },
+ {},
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION, std::string{"MainSoundConfiguration has 6 channels but sound assets have 2"}, canonical(find_cpl(path))
+ ).set_cpl_id(cpl->id())
+ });
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
+{
+ boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
+ auto constexpr video_frames = 24;
+ auto constexpr sample_rate = 48000;
+
+ boost::filesystem::remove_all(path);
+ boost::filesystem::create_directories(path);
+
+ auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
+ auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
+
+ dcp::Size const size(1998, 1080);
+ auto image = make_shared<dcp::OpenJPEGImage>(size);
+ boost::random::mt19937 rng(1);
+ boost::random::uniform_int_distribution<> dist(0, 4095);
+ for (int c = 0; c < 3; ++c) {
+ for (int p = 0; p < (1998 * 1080); ++p) {
+ image->data(c)[p] = dist(rng);
+ }
+ }
+ auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
+ for (int i = 0; i < 24; ++i) {
+ picture_writer->write(j2c.data(), j2c.size());
+ }
+ picture_writer->finalize();
+
+ auto dcp = make_shared<dcp::DCP>(path);
+ auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
+ cpl->set_content_version(
+ dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
+ );
+ cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
+ cpl->set_main_sound_sample_rate(sample_rate);
+ cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
+ cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
+ cpl->set_version_number(1);
+
+ auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
+
+ auto reel = make_shared<dcp::Reel>(
+ make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
+ make_shared<dcp::ReelSoundAsset>(ms, 0)
+ );
+
+ cpl->add(reel);
+ dcp->add(cpl);
+ dcp->set_annotation_text("A Test DCP");
+ dcp->write_xml();
+
+ vector<dcp::VerificationNote> expected;
+
+ for (auto frame = 0; frame < 24; frame++) {
+ expected.push_back(
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf")
+ ).set_frame(frame).set_frame_rate(24).set_cpl_id(cpl->id())
+ );
+ }
+
+ int component_sizes[] = {
+ 1321816,
+ 1294414,
+ 1289881,
+ };
+
+ for (auto frame = 0; frame < 24; frame++) {
+ for (auto component = 0; component < 3; component++) {
+ expected.push_back(
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE
+ ).set_frame(frame).set_component(component).set_size(component_sizes[component]).set_cpl_id(cpl->id())
+ );
+ }
+ }
+
+ expected.push_back(
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC
+ ).set_cpl_id(cpl->id())
+ );
+
+ expected.push_back(
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC
+ ).set_cpl_id(cpl->id())
+ );
+
+ check_verify_result({ path }, {}, expected);
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
+{
+ boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
+ dcp::DCP dcp(dir);
+ dcp.read();
+ BOOST_REQUIRE(!dcp.cpls().empty());
+ auto cpl = dcp.cpls()[0];
+
+ check_verify_result(
+ { dir },
+ {},
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_"))
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(find_file(dir, "cpl_"))
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"}
+ ).set_cpl_id(cpl->id())
+ });
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
+{
+ path const dir("build/test/verify_missing_load_font");
+ prepare_directory (dir);
+ copy_file ("test/data/subs1.xml", dir / "subs.xml");
+ {
+ Editor editor(dir / "subs.xml");
+ editor.delete_first_line_containing("LoadFont");
+ }
+ auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
+ auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
+ auto cpl = write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
+
+ check_verify_result (
+ {dir},
+ {},
+ {
+ { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
+ dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId").set_cpl_id(cpl->id())
+ });
+
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_missing_load_font)
+{
+ boost::filesystem::path const dir = "build/test/verify_missing_load_font";
+ prepare_directory(dir);
+ auto dcp = make_simple (dir, 1, 202);
+
+ string const xml =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
+ "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
+ "<ContentTitleText>Content</ContentTitleText>"
+ "<AnnotationText>Annotation</AnnotationText>"
+ "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
+ "<ReelNumber>1</ReelNumber>"
+ "<EditRate>24 1</EditRate>"
+ "<TimeCodeRate>24</TimeCodeRate>"
+ "<StartTime>00:00:00:00</StartTime>"
+ "<Language>de-DE</Language>"
+ "<SubtitleList>"
+ "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
+ "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
+ "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
+ "</Subtitle>"
+ "</Font>"
+ "</SubtitleList>"
+ "</SubtitleReel>";
+
+ dcp::File xml_file(dir / "subs.xml", "w");
+ BOOST_REQUIRE(xml_file);
+ xml_file.write(xml.c_str(), xml.size(), 1);
+ xml_file.close();
+ auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
+ subs->write(dir / "subs.mxf");
+
+ auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
+ auto cpl = dcp->cpls()[0];
+ cpl->reels()[0]->add(reel_subs);
+ dcp->write_xml();
+
+ check_verify_result (
+ { dir },
+ {},
+ {
+ dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id()).set_cpl_id(cpl->id())
+ });
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
+{
+ boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
+ boost::filesystem::remove_all(dir);
+
+ auto dcp1 = make_simple(dir / "1");
+ dcp1->write_xml();
+
+ auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
+
+ auto dcp2 = make_simple(dir / "2");
+ dcp2->write_xml();
+ auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
+
+ boost::filesystem::remove(dir / "1" / "video.mxf");
+ boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
+
+ check_verify_result(
+ {dir / "1"},
+ {},
+ {
+ dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2)
+ });
+}
+
+
+BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty)
+{
+ boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty";
+ boost::filesystem::remove_all(dir);
+
+ auto dcp = make_simple(dir);
+ BOOST_REQUIRE(dcp->cpls().size() == 1);
+ auto cpl = dcp->cpls()[0];
+ cpl->set_content_version(dcp::ContentVersion(""));
+ dcp->write_xml();
+
+ check_verify_result(
+ {dir},
+ {},
+ {
+ dcp::VerificationNote(dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_cpl_id(cpl->id())
+ });
+}
+
+
+/** Check that we don't get any strange errors when verifying encrypted DCPs (DoM #2659) */
+BOOST_AUTO_TEST_CASE(verify_encrypted_smpte_dcp)
+{
+ auto const dir = path("build/test/verify_encrypted_smpte_dcp");
+ dcp::Key key;
+ auto key_id = dcp::make_uuid();
+ auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset>(dir, {{ 4 * 24, 5 * 24 }}, key, key_id);
+
+ dcp::DecryptedKDM kdm(dcp::LocalTime(), dcp::LocalTime(), "", "", "");
+ kdm.add_key(dcp::DecryptedKDMKey(string{"MDIK"}, key_id, key, cpl->id(), dcp::Standard::SMPTE));
+
+ path const pkl_file = find_file(dir, "pkl_");
+ path const cpl_file = find_file(dir, "cpl_");
+
+ check_verify_result(
+ { dir },
+ { kdm },
+ {
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, canonical(cpl_file)
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, canonical(cpl_file)
+ ).set_cpl_id(cpl->id()),
+ dcp::VerificationNote(
+ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, filename_to_id(pkl_file.filename()), canonical(pkl_file)
+ )
+ });
+}
+
+