2 Copyright (C) 2018-2019 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.
38 #include "reel_picture_asset.h"
39 #include "reel_sound_asset.h"
40 #include "exceptions.h"
41 #include "compose.hpp"
42 #include "raw_convert.h"
43 #include <boost/foreach.hpp>
44 #include <boost/algorithm/string.hpp>
45 #include <boost/regex.hpp>
54 using boost::shared_ptr;
55 using boost::optional;
56 using boost::function;
62 RESULT_CPL_PKL_DIFFER,
67 verify_asset (shared_ptr<DCP> dcp, shared_ptr<ReelMXF> reel_mxf, function<void (float)> progress)
69 string const actual_hash = reel_mxf->asset_ref()->hash(progress);
71 list<shared_ptr<PKL> > pkls = dcp->pkls();
72 /* We've read this DCP in so it must have at least one PKL */
73 DCP_ASSERT (!pkls.empty());
75 shared_ptr<Asset> asset = reel_mxf->asset_ref().asset();
77 optional<string> pkl_hash;
78 BOOST_FOREACH (shared_ptr<PKL> i, pkls) {
79 pkl_hash = i->hash (reel_mxf->asset_ref()->id());
85 DCP_ASSERT (pkl_hash);
87 optional<string> cpl_hash = reel_mxf->hash();
88 if (cpl_hash && *cpl_hash != *pkl_hash) {
89 return RESULT_CPL_PKL_DIFFER;
92 if (actual_hash != *pkl_hash) {
101 good_urn_uuid (string id)
103 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}");
104 return boost::regex_match (id, ex);
109 good_date (string date)
111 boost::regex ex("\\d{4}-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})[+-](\\d{2}):(\\d{2})");
112 boost::match_results<string::const_iterator> res;
113 if (!regex_match (date, res, ex, boost::match_default)) {
116 int const month = dcp::raw_convert<int>(res[1].str());
117 if (month < 1 || month > 12) {
120 int const day = dcp::raw_convert<int>(res[2].str());
121 if (day < 1 || day > 31) {
124 if (dcp::raw_convert<int>(res[3].str()) > 23) {
127 if (dcp::raw_convert<int>(res[4].str()) > 59) {
130 if (dcp::raw_convert<int>(res[5].str()) > 59) {
133 if (dcp::raw_convert<int>(res[6].str()) > 23) {
136 if (dcp::raw_convert<int>(res[7].str()) > 59) {
142 list<VerificationNote>
143 dcp::verify (vector<boost::filesystem::path> directories, function<void (string, optional<boost::filesystem::path>)> stage, function<void (float)> progress)
145 list<VerificationNote> notes;
147 list<shared_ptr<DCP> > dcps;
148 BOOST_FOREACH (boost::filesystem::path i, directories) {
149 dcps.push_back (shared_ptr<DCP> (new DCP (i)));
152 BOOST_FOREACH (shared_ptr<DCP> dcp, dcps) {
153 stage ("Checking DCP", dcp->directory());
156 } catch (DCPReadError& e) {
157 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::Code::GENERAL_READ, string(e.what())));
158 } catch (XMLError& e) {
159 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::Code::GENERAL_READ, string(e.what())));
162 BOOST_FOREACH (shared_ptr<CPL> cpl, dcp->cpls()) {
163 stage ("Checking CPL", cpl->file());
165 cxml::Document cpl_doc ("CompositionPlaylist");
166 cpl_doc.read_file (cpl->file().get());
167 if (!good_urn_uuid(cpl_doc.string_child("Id"))) {
168 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::Code::BAD_URN_UUID, string("CPL <Id> is malformed")));
170 if (!good_date(cpl_doc.string_child("IssueDate"))) {
171 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::Code::BAD_DATE, string("CPL <IssueDate> is malformed")));
173 if (cpl->standard() && cpl->standard().get() == SMPTE && !good_urn_uuid(cpl_doc.node_child("ContentVersion")->string_child("Id"))) {
174 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::Code::BAD_URN_UUID, string("<ContentVersion> <Id> is malformed.")));
177 /* Check that the CPL's hash corresponds to the PKL */
178 BOOST_FOREACH (shared_ptr<PKL> i, dcp->pkls()) {
179 optional<string> h = i->hash(cpl->id());
180 if (h && make_digest(Data(*cpl->file())) != *h) {
181 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::CPL_HASH_INCORRECT));
185 BOOST_FOREACH (shared_ptr<Reel> reel, cpl->reels()) {
186 stage ("Checking reel", optional<boost::filesystem::path>());
187 if (reel->main_picture()) {
188 /* Check reel stuff */
189 Fraction const frame_rate = reel->main_picture()->frame_rate();
190 if (frame_rate.denominator != 1 ||
191 (frame_rate.numerator != 24 &&
192 frame_rate.numerator != 25 &&
193 frame_rate.numerator != 30 &&
194 frame_rate.numerator != 48 &&
195 frame_rate.numerator != 50 &&
196 frame_rate.numerator != 60 &&
197 frame_rate.numerator != 96)) {
198 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::INVALID_PICTURE_FRAME_RATE));
201 if (reel->main_picture()->asset_ref().resolved()) {
202 stage ("Checking picture asset hash", reel->main_picture()->asset()->file());
203 Result const r = verify_asset (dcp, reel->main_picture(), progress);
208 VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_HASH_INCORRECT, *reel->main_picture()->asset()->file()
212 case RESULT_CPL_PKL_DIFFER:
213 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE));
220 if (reel->main_sound() && reel->main_sound()->asset_ref().resolved()) {
221 stage ("Checking sound asset hash", reel->main_sound()->asset()->file());
222 Result const r = verify_asset (dcp, reel->main_sound(), progress);
227 VerificationNote::VERIFY_ERROR, VerificationNote::SOUND_HASH_INCORRECT, *reel->main_sound()->asset()->file()
231 case RESULT_CPL_PKL_DIFFER:
232 notes.push_back (VerificationNote (VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE));
246 dcp::note_to_string (dcp::VerificationNote note)
248 switch (note.code()) {
249 case dcp::VerificationNote::GENERAL_READ:
251 case dcp::VerificationNote::CPL_HASH_INCORRECT:
252 return "The hash of the CPL in the PKL does not agree with the CPL file";
253 case dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE:
254 return "The picture in a reel has an invalid frame rate";
255 case dcp::VerificationNote::PICTURE_HASH_INCORRECT:
256 return dcp::String::compose("The hash of the picture asset %1 does not agree with the PKL file", note.file()->filename());
257 case dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE:
258 return "The PKL and CPL hashes disagree for a picture asset.";
259 case dcp::VerificationNote::SOUND_HASH_INCORRECT:
260 return dcp::String::compose("The hash of the sound asset %1 does not agree with the PKL file", note.file()->filename());
261 case dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE:
262 return "The PKL and CPL hashes disagree for a sound asset.";
263 case dcp::VerificationNote::EMPTY_ASSET_PATH:
264 return "The asset map contains an empty asset path.";
265 case dcp::VerificationNote::MISSING_ASSET:
266 return "The file for an asset in the asset map cannot be found.";
267 case dcp::VerificationNote::MISMATCHED_STANDARD:
268 return "The DCP contains both SMPTE and Interop parts.";
269 case dcp::VerificationNote::BAD_URN_UUID:
270 return "There is a badly-formed urn:uuid.";
271 case dcp::VerificationNote::BAD_DATE:
272 return "There is a badly-formed date.";