/*
- Copyright (C) 2018 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2018-2019 Carl Hetherington <cth@carlh.net>
This file is part of libdcp.
#include "reel_picture_asset.h"
#include "reel_sound_asset.h"
#include "exceptions.h"
+#include "compose.hpp"
+#include "raw_convert.h"
#include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/regex.hpp>
#include <list>
#include <vector>
#include <iostream>
};
static Result
-verify_asset (shared_ptr<DCP> dcp, shared_ptr<ReelAsset> reel_asset, function<void (float)> progress)
+verify_asset (shared_ptr<DCP> dcp, shared_ptr<ReelMXF> reel_mxf, function<void (float)> progress)
{
- string const actual_hash = reel_asset->asset_ref()->hash(progress);
+ string const actual_hash = reel_mxf->asset_ref()->hash(progress);
list<shared_ptr<PKL> > pkls = dcp->pkls();
/* We've read this DCP in so it must have at least one PKL */
DCP_ASSERT (!pkls.empty());
- shared_ptr<Asset> asset = reel_asset->asset_ref().asset();
+ shared_ptr<Asset> asset = reel_mxf->asset_ref().asset();
optional<string> pkl_hash;
BOOST_FOREACH (shared_ptr<PKL> i, pkls) {
- pkl_hash = i->hash (reel_asset->asset_ref()->id());
+ pkl_hash = i->hash (reel_mxf->asset_ref()->id());
if (pkl_hash) {
break;
}
DCP_ASSERT (pkl_hash);
- optional<string> cpl_hash = reel_asset->hash();
+ optional<string> cpl_hash = reel_mxf->hash();
if (cpl_hash && *cpl_hash != *pkl_hash) {
return RESULT_CPL_PKL_DIFFER;
}
return RESULT_GOOD;
}
+static
+bool
+good_urn_uuid (string id)
+{
+ boost::regex ex("urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}");
+ return boost::regex_match (id, ex);
+}
+
+static
+bool
+good_date (string date)
+{
+ boost::regex ex("\\d{4}-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})[+-](\\d{2}):(\\d{2})");
+ boost::match_results<string::const_iterator> res;
+ if (!regex_match (date, res, ex, boost::match_default)) {
+ return false;
+ }
+ int const month = dcp::raw_convert<int>(res[1].str());
+ if (month < 1 || month > 12) {
+ return false;
+ }
+ int const day = dcp::raw_convert<int>(res[2].str());
+ if (day < 1 || day > 31) {
+ return false;
+ }
+ if (dcp::raw_convert<int>(res[3].str()) > 23) {
+ return false;
+ }
+ if (dcp::raw_convert<int>(res[4].str()) > 59) {
+ return false;
+ }
+ if (dcp::raw_convert<int>(res[5].str()) > 59) {
+ return false;
+ }
+ if (dcp::raw_convert<int>(res[6].str()) > 23) {
+ return false;
+ }
+ if (dcp::raw_convert<int>(res[7].str()) > 59) {
+ return false;
+ }
+ return true;
+}
+
list<VerificationNote>
dcp::verify (vector<boost::filesystem::path> directories, function<void (string, optional<boost::filesystem::path>)> stage, function<void (float)> progress)
{
BOOST_FOREACH (shared_ptr<DCP> dcp, dcps) {
stage ("Checking DCP", dcp->directory());
- DCP::ReadErrors errors;
try {
- dcp->read (true, &errors);
+ dcp->read (¬es);
} catch (DCPReadError& e) {
- notes.push_back (VerificationNote (VerificationNote::VERIFY_ERROR, e.what ()));
+ notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::Code::GENERAL_READ, string(e.what())));
} catch (XMLError& e) {
- notes.push_back (VerificationNote (VerificationNote::VERIFY_ERROR, e.what ()));
+ notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::Code::GENERAL_READ, string(e.what())));
}
BOOST_FOREACH (shared_ptr<CPL> cpl, dcp->cpls()) {
stage ("Checking CPL", cpl->file());
+ cxml::Document cpl_doc ("CompositionPlaylist");
+ cpl_doc.read_file (cpl->file().get());
+ if (!good_urn_uuid(cpl_doc.string_child("Id"))) {
+ notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::Code::BAD_URN_UUID, string("CPL <Id> is malformed")));
+ }
+ if (!good_date(cpl_doc.string_child("IssueDate"))) {
+ notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::Code::BAD_DATE, string("CPL <IssueDate> is malformed")));
+ }
+ if (cpl->standard() && cpl->standard().get() == SMPTE && !good_urn_uuid(cpl_doc.node_child("ContentVersion")->string_child("Id"))) {
+ notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::Code::BAD_URN_UUID, string("<ContentVersion> <Id> is malformed.")));
+ }
+
/* Check that the CPL's hash corresponds to the PKL */
BOOST_FOREACH (shared_ptr<PKL> i, dcp->pkls()) {
optional<string> h = i->hash(cpl->id());
if (h && make_digest(Data(*cpl->file())) != *h) {
- notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, "CPL hash is incorrect."));
+ notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::CPL_HASH_INCORRECT));
}
}
BOOST_FOREACH (shared_ptr<Reel> reel, cpl->reels()) {
stage ("Checking reel", optional<boost::filesystem::path>());
if (reel->main_picture()) {
- stage ("Checking picture asset hash", reel->main_picture()->asset()->file());
- Result const r = verify_asset (dcp, reel->main_picture(), progress);
- switch (r) {
- case RESULT_BAD:
- notes.push_back (VerificationNote (VerificationNote::VERIFY_ERROR, "Picture asset hash is incorrect."));
- break;
- case RESULT_CPL_PKL_DIFFER:
- notes.push_back (VerificationNote (VerificationNote::VERIFY_ERROR, "PKL and CPL hashes differ for picture asset."));
- break;
- default:
- break;
+ /* Check reel stuff */
+ Fraction const frame_rate = reel->main_picture()->frame_rate();
+ if (frame_rate.denominator != 1 ||
+ (frame_rate.numerator != 24 &&
+ frame_rate.numerator != 25 &&
+ frame_rate.numerator != 30 &&
+ frame_rate.numerator != 48 &&
+ frame_rate.numerator != 50 &&
+ frame_rate.numerator != 60 &&
+ frame_rate.numerator != 96)) {
+ notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::INVALID_PICTURE_FRAME_RATE));
+ }
+ /* Check asset */
+ if (reel->main_picture()->asset_ref().resolved()) {
+ stage ("Checking picture asset hash", reel->main_picture()->asset()->file());
+ Result const r = verify_asset (dcp, reel->main_picture(), progress);
+ switch (r) {
+ case RESULT_BAD:
+ notes.push_back (
+ VerificationNote(
+ VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_HASH_INCORRECT, *reel->main_picture()->asset()->file()
+ )
+ );
+ break;
+ case RESULT_CPL_PKL_DIFFER:
+ notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE));
+ break;
+ default:
+ break;
+ }
}
}
- if (reel->main_sound()) {
+ if (reel->main_sound() && reel->main_sound()->asset_ref().resolved()) {
stage ("Checking sound asset hash", reel->main_sound()->asset()->file());
Result const r = verify_asset (dcp, reel->main_sound(), progress);
switch (r) {
case RESULT_BAD:
- notes.push_back (VerificationNote (VerificationNote::VERIFY_ERROR, "Sound asset hash is incorrect."));
+ notes.push_back (
+ VerificationNote(
+ VerificationNote::VERIFY_ERROR, VerificationNote::SOUND_HASH_INCORRECT, *reel->main_sound()->asset()->file()
+ )
+ );
break;
case RESULT_CPL_PKL_DIFFER:
- notes.push_back (VerificationNote (VerificationNote::VERIFY_ERROR, "PKL and CPL hashes differ for sound asset."));
+ notes.push_back (VerificationNote (VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE));
break;
default:
break;
return notes;
}
+
+string
+dcp::note_to_string (dcp::VerificationNote note)
+{
+ switch (note.code()) {
+ case dcp::VerificationNote::GENERAL_READ:
+ return *note.note();
+ case dcp::VerificationNote::CPL_HASH_INCORRECT:
+ return "The hash of the CPL in the PKL does not agree with the CPL file";
+ case dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE:
+ return "The picture in a reel has an invalid frame rate";
+ case dcp::VerificationNote::PICTURE_HASH_INCORRECT:
+ return dcp::String::compose("The hash of the picture asset %1 does not agree with the PKL file", note.file()->filename());
+ case dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE:
+ return "The PKL and CPL hashes disagree for a picture asset.";
+ case dcp::VerificationNote::SOUND_HASH_INCORRECT:
+ return dcp::String::compose("The hash of the sound asset %1 does not agree with the PKL file", note.file()->filename());
+ case dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE:
+ return "The PKL and CPL hashes disagree for a sound asset.";
+ case dcp::VerificationNote::EMPTY_ASSET_PATH:
+ return "The asset map contains an empty asset path.";
+ case dcp::VerificationNote::MISSING_ASSET:
+ return "The file for an asset in the asset map cannot be found.";
+ case dcp::VerificationNote::MISMATCHED_STANDARD:
+ return "The DCP contains both SMPTE and Interop parts.";
+ case dcp::VerificationNote::BAD_URN_UUID:
+ return "There is a badly-formed urn:uuid.";
+ case dcp::VerificationNote::BAD_DATE:
+ return "There is a badly-formed date.";
+ }
+
+ return "";
+}
+