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")));
174 /* Check that the CPL's hash corresponds to the PKL */
175 BOOST_FOREACH (shared_ptr<PKL> i, dcp->pkls()) {
176 optional<string> h = i->hash(cpl->id());
177 if (h && make_digest(Data(*cpl->file())) != *h) {
178 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::CPL_HASH_INCORRECT));
182 BOOST_FOREACH (shared_ptr<Reel> reel, cpl->reels()) {
183 stage ("Checking reel", optional<boost::filesystem::path>());
184 if (reel->main_picture()) {
185 /* Check reel stuff */
186 Fraction const frame_rate = reel->main_picture()->frame_rate();
187 if (frame_rate.denominator != 1 ||
188 (frame_rate.numerator != 24 &&
189 frame_rate.numerator != 25 &&
190 frame_rate.numerator != 30 &&
191 frame_rate.numerator != 48 &&
192 frame_rate.numerator != 50 &&
193 frame_rate.numerator != 60 &&
194 frame_rate.numerator != 96)) {
195 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::INVALID_PICTURE_FRAME_RATE));
198 if (reel->main_picture()->asset_ref().resolved()) {
199 stage ("Checking picture asset hash", reel->main_picture()->asset()->file());
200 Result const r = verify_asset (dcp, reel->main_picture(), progress);
205 VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_HASH_INCORRECT, *reel->main_picture()->asset()->file()
209 case RESULT_CPL_PKL_DIFFER:
210 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE));
217 if (reel->main_sound() && reel->main_sound()->asset_ref().resolved()) {
218 stage ("Checking sound asset hash", reel->main_sound()->asset()->file());
219 Result const r = verify_asset (dcp, reel->main_sound(), progress);
224 VerificationNote::VERIFY_ERROR, VerificationNote::SOUND_HASH_INCORRECT, *reel->main_sound()->asset()->file()
228 case RESULT_CPL_PKL_DIFFER:
229 notes.push_back (VerificationNote (VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE));
243 dcp::note_to_string (dcp::VerificationNote note)
245 switch (note.code()) {
246 case dcp::VerificationNote::GENERAL_READ:
248 case dcp::VerificationNote::CPL_HASH_INCORRECT:
249 return "The hash of the CPL in the PKL does not agree with the CPL file";
250 case dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE:
251 return "The picture in a reel has an invalid frame rate";
252 case dcp::VerificationNote::PICTURE_HASH_INCORRECT:
253 return dcp::String::compose("The hash of the picture asset %1 does not agree with the PKL file", note.file()->filename());
254 case dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE:
255 return "The PKL and CPL hashes disagree for a picture asset.";
256 case dcp::VerificationNote::SOUND_HASH_INCORRECT:
257 return dcp::String::compose("The hash of the sound asset %1 does not agree with the PKL file", note.file()->filename());
258 case dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE:
259 return "The PKL and CPL hashes disagree for a sound asset.";
260 case dcp::VerificationNote::EMPTY_ASSET_PATH:
261 return "The asset map contains an empty asset path.";
262 case dcp::VerificationNote::MISSING_ASSET:
263 return "The file for an asset in the asset map cannot be found.";
264 case dcp::VerificationNote::MISMATCHED_STANDARD:
265 return "The DCP contains both SMPTE and Interop parts.";
266 case dcp::VerificationNote::BAD_URN_UUID:
267 return "There is a badly-formed urn:uuid.";
268 case dcp::VerificationNote::BAD_DATE:
269 return "There is a badly-formed date.";