2 Copyright (C) 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 /** @file src/verify_j2k.cc
36 * @brief Verification that JPEG2000 files meet requirements
40 #include "compose.hpp"
42 #include "raw_convert.h"
44 #include "verify_j2k.h"
49 using std::shared_ptr;
51 using std::runtime_error;
54 using boost::optional;
55 using dcp::raw_convert;
58 class InvalidCodestream : public runtime_error
61 InvalidCodestream (string note)
68 dcp::verify_j2k (shared_ptr<const Data> j2k, vector<VerificationNote>& notes)
71 auto ptr = j2k->data();
72 auto end = ptr + j2k->size();
74 map<string, uint8_t> markers = {
89 auto marker_name_from_id = [&markers](uint8_t b) -> optional<string> {
90 for (auto const& i: markers) {
98 auto require_marker = [&](string name) {
99 if (ptr == end || *ptr != 0xff) {
100 throw InvalidCodestream ("missing marker start byte");
103 if (ptr == end || *ptr != markers[name]) {
104 throw InvalidCodestream ("missing_marker " + name);
111 throw InvalidCodestream ("unexpected end of file");
116 auto get_16 = [&]() {
117 if (ptr >= (end - 1)) {
118 throw InvalidCodestream ("unexpected end of file");
120 auto const a = *ptr++;
121 auto const b = *ptr++;
125 auto get_32 = [&]() -> uint32_t {
126 if (ptr >= (end - 3)) {
127 throw InvalidCodestream ("unexpected end of file");
129 auto const a = *ptr++;
130 auto const b = *ptr++;
131 auto const c = *ptr++;
132 auto const d = *ptr++;
133 return d | (c << 8) | (b << 16) | (a << 24);
136 auto require_8 = [&](uint8_t value, string note) {
139 throw InvalidCodestream (String::compose(note, v));
143 auto require_16 = [&](uint16_t value, string note) {
146 throw InvalidCodestream (String::compose(note, v));
150 auto require_32 = [&](uint32_t value, string note) {
153 throw InvalidCodestream (String::compose(note, v));
157 require_marker ("SOC");
158 require_marker ("SIZ");
159 auto L_siz = get_16();
161 throw InvalidCodestream("unexpected SIZ size " + raw_convert<string>(L_siz));
164 get_16(); // CA: codestream capabilities
165 auto const image_width = get_32();
166 auto const image_height = get_32();
167 auto const fourk = image_width > 2048;
168 require_32 (0, "invalid top-left image x coordinate %1");
169 require_32 (0, "invalid top-left image y coordinate %1");
170 auto const tile_width = get_32();
171 auto const tile_height = get_32();
172 if (tile_width != image_width || tile_height != image_height) {
173 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_SIZE });
175 require_32 (0, "invalid tile anchor x coordinate %1");
176 require_32 (0, "invalid tile anchor y coordinate %1");
177 require_16 (3, "invalid component count %1");
178 for (auto i = 0; i < 3; ++i) {
179 require_8 (12 - 1, "invalid bit depth %1");
180 require_8 (1, "invalid horizontal subsampling factor %1");
181 require_8 (1, "invalid vertical subsampling factor %1");
186 /** number of POC markers in the main header */
187 auto num_POC_in_main = 0;
188 /** number of POC markers after the main header */
189 auto num_POC_after_main = 0;
190 bool main_header_finished = false;
195 require_8(0xff, "missing marker start byte");
196 auto marker_id = get_8();
197 auto marker_name = marker_name_from_id (marker_id);
200 snprintf (buffer, 16, "%2x", marker_id);
201 throw InvalidCodestream(String::compose("unknown marker %1", buffer));
202 } else if (*marker_name == "SOT") {
203 require_16(10, "invalid SOT size %1");
204 get_16(); // tile index
205 get_32(); // tile part length
206 get_8(); // tile part index
207 auto tile_parts = get_8();
208 if (!fourk && tile_parts != 3) {
209 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_2K, raw_convert<string>(tile_parts) });
211 if (fourk && tile_parts != 6) {
212 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_4K, raw_convert<string>(tile_parts) });
214 main_header_finished = true;
215 } else if (*marker_name == "SOD") {
216 while (ptr < (end - 1) && (ptr[0] != 0xff || ptr[1] < 0x90)) {
219 } else if (*marker_name == "SIZ") {
220 throw InvalidCodestream ("duplicate SIZ marker");
221 } else if (*marker_name == "COD") {
224 /* XXX: I can't find any evidence for this: must the coding style really always be 1? */
225 require_8(1, "invalid COD coding style %1");
226 require_8(4, "invalid progression order %1"); // CPRL
227 require_16(1, "invalid quality layers count %1");
228 require_8(1, "invalid multi-component transform flag %1");
229 require_8(fourk ? 6 : 5, "invalid number of transform levels %1");
230 auto log_code_block_width = get_8();
231 if (log_code_block_width != 3) {
232 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_WIDTH, raw_convert<string>(4 * (2 << log_code_block_width)) });
234 auto log_code_block_height = get_8();
235 if (log_code_block_height != 3) {
236 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_HEIGHT, raw_convert<string>(4 * (2 << log_code_block_height)) });
238 require_8(0, "invalid mode variations");
239 require_8(0, "invalid wavelet transform type %1"); // 9/7 irreversible
240 require_8(0x77, "invalid precinct size %1");
241 require_8(0x88, "invalid precinct size %1");
242 require_8(0x88, "invalid precinct size %1");
243 require_8(0x88, "invalid precinct size %1");
244 require_8(0x88, "invalid precinct size %1");
245 require_8(0x88, "invalid precinct size %1");
247 require_8(0x88, "invalid precinct size %1");
249 } else if (*marker_name == "QCD") {
251 auto const L_qcd = get_16();
252 auto quantization_style = get_8();
253 int guard_bits = (quantization_style >> 5) & 7;
254 if (fourk && guard_bits != 2) {
255 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_4K, raw_convert<string>(guard_bits) });
257 if (!fourk && guard_bits != 1) {
258 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, raw_convert<string>(guard_bits) });
261 } else if (*marker_name == "COC") {
263 require_8(0, "invalid COC component number");
264 /* XXX: I can't find any evidence for this: must the coding style really always be 1? */
265 require_8(1, "invalid COC coding style %1");
266 require_8(5, "invalid number of transform levels %1");
267 require_8(3, "invalid code block width exponent %1");
268 require_8(3, "invalid code block height exponent %1");
269 require_8(0, "invalid mode variations");
270 require_8(0x77, "invalid precinct size %1");
271 require_8(0x88, "invalid precinct size %1");
272 require_8(0x88, "invalid precinct size %1");
273 require_8(0x88, "invalid precinct size %1");
274 require_8(0x88, "invalid precinct size %1");
275 require_8(0x88, "invalid precinct size %1");
276 } else if (*marker_name == "TLM") {
277 auto const len = get_16();
280 } else if (*marker_name == "QCC" || *marker_name == "COM") {
281 auto const len = get_16();
283 } else if (*marker_name == "POC") {
284 if (main_header_finished) {
285 num_POC_after_main++;
290 auto require_8_poc = [&](uint16_t value, string note) {
291 if (get_8() != value) {
292 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER, String::compose(note, value) });
296 auto require_16_poc = [&](uint16_t value, string note) {
297 if (get_16() != value) {
298 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER, String::compose(note, value) });
302 require_16_poc(16, "invalid length %1");
303 require_8_poc(0, "invalid RSpoc %1");
304 require_8_poc(0, "invalid CSpoc %1");
305 require_16_poc(1, "invalid LYEpoc %1");
306 require_8_poc(6, "invalid REpoc %1");
307 require_8_poc(3, "invalid CEpoc %1");
308 require_8_poc(4, "invalid Ppoc %1");
309 require_8_poc(6, "invalid RSpoc %1");
310 require_8_poc(0, "invalid CSpoc %1");
311 require_16_poc(1, "invalid LYEpoc %1");
312 require_8_poc(7, "invalid REpoc %1");
313 require_8_poc(3, "invalid CEpoc %1");
314 require_8_poc(4, "invalid Ppoc %1");
319 throw InvalidCodestream("no COD marker found");
322 throw InvalidCodestream("more than one COD marker found");
325 throw InvalidCodestream("no QCD marker found");
328 throw InvalidCodestream("more than one QCD marker found");
330 if (num_POC_in_main != 0 && !fourk) {
331 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_2K, raw_convert<string>(num_POC_in_main) });
333 if (num_POC_in_main != 1 && fourk) {
334 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_4K, raw_convert<string>(num_POC_in_main) });
336 if (num_POC_after_main != 0) {
337 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_POC_MARKER_LOCATION });
340 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_JPEG200_TLM_MARKER });
343 catch (InvalidCodestream const& e)
345 notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string(e.what()) });