WIP: analyse J2K files. j2k-check
authorCarl Hetherington <cth@carlh.net>
Wed, 27 Jan 2021 00:27:53 +0000 (01:27 +0100)
committerCarl Hetherington <cth@carlh.net>
Sat, 6 Feb 2021 22:05:15 +0000 (23:05 +0100)
src/verify.cc
src/verify.h
src/verify_j2k.cc [new file with mode: 0644]
src/verify_j2k.h [new file with mode: 0644]
src/wscript
test/test.cc
test/test.h
test/verify_test.cc

index e45c381e4f725e22f51c9969848eb682672b2e78..0082cfcdbd8a22191194af58ce12f2a3a175eb5e 100644 (file)
@@ -1491,9 +1491,35 @@ dcp::note_to_string (VerificationNote note)
        case VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT:
                return String::compose("The PKL %1, which has encrypted content, is not signed.", note.note().get());
        case VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL:
-               return String::compose("The PKL %1 has only one CPL but its <AnnotationText> does not match the CPL's <ContentTitleText>", note.note().get());
+               return String::compose("The PKL %1 has only one CPL but its <AnnotationText> does not match the CPL's <ContentTitleText>.", note.note().get());
        case VerificationNote::Code::PARTIALLY_ENCRYPTED:
-               return "Some assets are encrypted but some are not";
+               return "Some assets are encrypted but some are not.";
+       case VerificationNote::Code::INVALID_JPEG2000_CODESTREAM:
+               return String::compose("The JPEG2000 codestream for at least one frame is invalid (%1)", note.note().get());
+       case VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K:
+               return String::compose("The JPEG2000 codestream uses %1 guard bits in a 2K image instead of 1.", note.note().get());
+       case VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_4K:
+               return String::compose("The JPEG2000 codestream uses %1 guard bits in a 4K image instead of 2.", note.note().get());
+       case VerificationNote::Code::INVALID_JPEG2000_TILE_SIZE:
+               return "The JPEG2000 tile size is not the same as the image size.";
+       case VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_WIDTH:
+               return String::compose("The JPEG2000 codestream uses a code block width of %1 instead of 32.", note.note().get());
+       case VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_HEIGHT:
+               return String::compose("The JPEG2000 codestream uses a code block height of %1 instead of 32.", note.note().get());
+       case VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_2K:
+               return String::compose("%1 POC markers found in 2K JPEG2000 codestream instead of 0.", note.note().get());
+       case VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_4K:
+               return String::compose("%1 POC markers found in 4K JPEG2000 codestream instead of 1.", note.note().get());
+       case VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER:
+               return String::compose("Incorrect POC marker content found (%1)", note.note().get());
+       case VerificationNote::Code::INVALID_JPEG2000_POC_MARKER_LOCATION:
+               return "POC marker found outside main header";
+       case VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_2K:
+               return String::compose("The JPEG2000 codestream has %1 tile parts in a 2K image instead of 3.", note.note().get());
+       case VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_4K:
+               return String::compose("The JPEG2000 codestream has %1 tile parts in a 4K image instead of 6.", note.note().get());
+       case VerificationNote::Code::MISSING_JPEG200_TLM_MARKER:
+               return "No TLM marker was found in a JPEG2000 codestream.";
        }
 
        return "";
index 0a8b39ca03e6cbad3d97bccdf1c73b2630f196e0..d57958e24f5f982ea92fbdcdd4d945d20a90bc19 100644 (file)
@@ -318,6 +318,32 @@ public:
                MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL,
                /** If any content is encrypted, everything must be encrypted */
                PARTIALLY_ENCRYPTED,
+               /** General error from our JPEG2000 codestream verification */
+               INVALID_JPEG2000_CODESTREAM,
+               /** Invalid number of guard bits in a 2K JPEG2000 stream (should be 1) Bv2.1_10.2.1 */
+               INVALID_JPEG2000_GUARD_BITS_FOR_2K,
+               /** Invalid number of guard bits in a 4K JPEG2000 stream (should be 2) Bv2.1_10.2.1 */
+               INVALID_JPEG2000_GUARD_BITS_FOR_4K,
+               /** JPEG2000 tile size is not the same as the image size Bv2.1_10.2.1 */
+               INVALID_JPEG2000_TILE_SIZE,
+               /** JPEG2000 code block width is not 32 Bv2.1_10.2.1 */
+               INVALID_JPEG2000_CODE_BLOCK_WIDTH,
+               /** JPEG2000 code block height is not 32 Bv2.1_10.2.1 */
+               INVALID_JPEG2000_CODE_BLOCK_HEIGHT,
+               /** There must be no POC markers in a 2K codestream Bv2.1_10.2.1 */
+               INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_2K,
+               /** There must be exactly one POC marker in a 4K codestream Bv2.1_10.2.1 */
+               INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_4K,
+               /** A 4K POC marker has incorrect content Bv2.1_10.2.1 */
+               INCORRECT_JPEG2000_POC_MARKER,
+               /** A POC marker was found outside the main head Bv2.1_10.2.1 */
+               INVALID_JPEG2000_POC_MARKER_LOCATION,
+               /** Invalid number of tile parts for 2K JPEG2000 stream (should be 3) Bv2.1_10.2.1 */
+               INVALID_JPEG2000_TILE_PARTS_FOR_2K,
+               /** Invalid number of tile parts for 4K JPEG2000 stream (should be 6) Bv2.1_10.2.1 */
+               INVALID_JPEG2000_TILE_PARTS_FOR_4K,
+               /** No TLM marker was found Bv2.1_10.2.1 */
+               MISSING_JPEG200_TLM_MARKER,
        };
 
        VerificationNote (Type type, Code code)
diff --git a/src/verify_j2k.cc b/src/verify_j2k.cc
new file mode 100644 (file)
index 0000000..2e1255e
--- /dev/null
@@ -0,0 +1,346 @@
+/*
+    Copyright (C) 2021 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libdcp is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+/** @file  src/verify_j2k.cc
+ *  @brief Verification that JPEG2000 files meet requirements
+ */
+
+
+#include "compose.hpp"
+#include "data.h"
+#include "raw_convert.h"
+#include "verify.h"
+#include "verify_j2k.h"
+#include <memory>
+#include <vector>
+
+
+using std::shared_ptr;
+using std::map;
+using std::runtime_error;
+using std::string;
+using std::vector;
+using boost::optional;
+using dcp::raw_convert;
+
+
+class InvalidCodestream : public runtime_error
+{
+public:
+       InvalidCodestream (string note)
+               : runtime_error(note)
+       {}
+};
+
+
+void
+dcp::verify_j2k (shared_ptr<const Data> j2k, vector<VerificationNote>& notes)
+{
+       try {
+               auto ptr = j2k->data();
+               auto end = ptr + j2k->size();
+
+               map<string, uint8_t> markers = {
+                       { "SOC", 0x4f },
+                       { "SIZ", 0x51 },
+                       { "COD", 0x52 },
+                       { "COC", 0x53 },
+                       { "TLM", 0x55 },
+                       { "QCD", 0x5c },
+                       { "QCC", 0x5d },
+                       { "POC", 0x5f },
+                       { "COM", 0x64 },
+                       { "SOT", 0x90 },
+                       { "SOD", 0x93 },
+                       { "EOC", 0xd9 },
+               };
+
+               auto marker_name_from_id = [&markers](uint8_t b) -> optional<string> {
+                       for (auto const& i: markers) {
+                               if (i.second == b) {
+                                       return i.first;
+                               }
+                       }
+                       return {};
+               };
+
+               auto require_marker = [&](string name) {
+                       if (ptr == end || *ptr != 0xff) {
+                               throw InvalidCodestream ("missing marker start byte");
+                       }
+                       ++ptr;
+                       if (ptr == end || *ptr != markers[name]) {
+                               throw InvalidCodestream ("missing_marker " + name);
+                       }
+                       ++ptr;
+               };
+
+               auto get_8 = [&]() {
+                       if (ptr >= end) {
+                               throw InvalidCodestream ("unexpected end of file");
+                       }
+                       return *ptr++;
+               };
+
+               auto get_16 = [&]() {
+                       if (ptr >= (end - 1)) {
+                               throw InvalidCodestream ("unexpected end of file");
+                       }
+                       auto const a = *ptr++;
+                       auto const b = *ptr++;
+                       return b | (a << 8);
+               };
+
+               auto get_32 = [&]() -> uint32_t {
+                       if (ptr >= (end - 3)) {
+                               throw InvalidCodestream ("unexpected end of file");
+                       }
+                       auto const a = *ptr++;
+                       auto const b = *ptr++;
+                       auto const c = *ptr++;
+                       auto const d = *ptr++;
+                       return d | (c << 8) | (b << 16) | (a << 24);
+               };
+
+               auto require_8 = [&](uint8_t value, string note) {
+                       auto v = get_8 ();
+                       if (v != value) {
+                               throw InvalidCodestream (String::compose(note, v));
+                       }
+               };
+
+               auto require_16 = [&](uint16_t value, string note) {
+                       auto v = get_16 ();
+                       if (v != value) {
+                               throw InvalidCodestream (String::compose(note, v));
+                       }
+               };
+
+               auto require_32 = [&](uint32_t value, string note) {
+                       auto v = get_32 ();
+                       if (v != value) {
+                               throw InvalidCodestream (String::compose(note, v));
+                       }
+               };
+
+               require_marker ("SOC");
+               require_marker ("SIZ");
+               auto L_siz = get_16();
+               if (L_siz != 47) {
+                       throw InvalidCodestream("unexpected SIZ size " + raw_convert<string>(L_siz));
+               }
+
+               get_16(); // CA: codestream capabilities
+               auto const image_width = get_32();
+               auto const image_height = get_32();
+               auto const fourk = image_width > 2048;
+               require_32 (0, "invalid top-left image x coordinate %1");
+               require_32 (0, "invalid top-left image y coordinate %1");
+               auto const tile_width = get_32();
+               auto const tile_height = get_32();
+               if (tile_width != image_width || tile_height != image_height) {
+                       notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_SIZE });
+               }
+               require_32 (0, "invalid tile anchor x coordinate %1");
+               require_32 (0, "invalid tile anchor y coordinate %1");
+               require_16 (3, "invalid component count %1");
+               for (auto i = 0; i < 3; ++i) {
+                       require_8 (12 - 1, "invalid bit depth %1");
+                       require_8 (1, "invalid horizontal subsampling factor %1");
+                       require_8 (1, "invalid vertical subsampling factor %1");
+               }
+
+               auto num_COD = 0;
+               auto num_QCD = 0;
+               /** number of POC markers in the main header */
+               auto num_POC_in_main = 0;
+               /** number of POC markers after the main header */
+               auto num_POC_after_main = 0;
+               bool main_header_finished = false;
+               bool tlm = false;
+
+               while (ptr < end)
+               {
+                       require_8(0xff, "missing marker start byte");
+                       auto marker_id = get_8();
+                       auto marker_name = marker_name_from_id (marker_id);
+                       if (!marker_name) {
+                               char buffer[16];
+                               snprintf (buffer, 16, "%2x", marker_id);
+                               throw InvalidCodestream(String::compose("unknown marker %1", buffer));
+                       } else if (*marker_name == "SOT") {
+                               require_16(10, "invalid SOT size %1");
+                               get_16(); // tile index
+                               get_32(); // tile part length
+                               get_8(); // tile part index
+                               auto tile_parts = get_8();
+                               if (!fourk && tile_parts != 3) {
+                                       notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_2K, raw_convert<string>(tile_parts) });
+                               }
+                               if (fourk && tile_parts != 6) {
+                                       notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_2K, raw_convert<string>(tile_parts) });
+                               }
+                               main_header_finished = true;
+                       } else if (*marker_name == "SOD") {
+                               while (ptr < (end - 1) && (ptr[0] != 0xff || ptr[1] < 0x90)) {
+                                       ++ptr;
+                               }
+                       } else if (*marker_name == "SIZ") {
+                               throw InvalidCodestream ("duplicate SIZ marker");
+                       } else if (*marker_name == "COD") {
+                               num_COD++;
+                               get_16(); // length
+                               require_8(1, "invalid coding style %1");
+                               require_8(4, "invalid progression order %1"); // CPRL
+                               require_16(1, "invalid quality layers count %1");
+                               require_8(1, "invalid multi-component transform flag %1");
+                               require_8(fourk ? 6 : 5, "invalid number of transform levels %1");
+                               auto log_code_block_width = get_8();
+                               if (log_code_block_width != 3) {
+                                       notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_WIDTH, raw_convert<string>(4 * (2 << log_code_block_width)) });
+                               }
+                               auto log_code_block_height = get_8();
+                               if (log_code_block_height != 3) {
+                                       notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_HEIGHT, raw_convert<string>(4 * (2 << log_code_block_height)) });
+                               }
+                               require_8(0, "invalid mode variations");
+                               require_8(0, "invalid wavelet transform type %1"); // 9/7 irreversible
+                               require_8(0x77, "invalid precinct size %1");
+                               require_8(0x88, "invalid precinct size %1");
+                               require_8(0x88, "invalid precinct size %1");
+                               require_8(0x88, "invalid precinct size %1");
+                               require_8(0x88, "invalid precinct size %1");
+                               require_8(0x88, "invalid precinct size %1");
+                               if (fourk) {
+                                       require_8(0x88, "invalid precinct size %1");
+                               }
+                       } else if (*marker_name == "QCD") {
+                               num_QCD++;
+                               auto const L_qcd = get_16();
+                               auto quantization_style = get_8();
+                               int guard_bits = (quantization_style >> 5) & 7;
+                               if (fourk && guard_bits != 2) {
+                                       notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_4K, raw_convert<string>(guard_bits) });
+                               }
+                               if (!fourk && guard_bits != 1) {
+                                       notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, raw_convert<string>(guard_bits) });
+                               }
+                               ptr += L_qcd - 3;
+                       } else if (*marker_name == "COC") {
+                               get_16(); // length
+                               require_8(0, "invalid COC component number");
+                               require_8(1, "invalid coding style %1");
+                               require_8(5, "invalid number of transform levels %1");
+                               require_8(3, "invalid code block width exponent %1");
+                               require_8(3, "invalid code block height exponent %1");
+                               require_8(0, "invalid mode variations");
+                               require_8(0x77, "invalid precinct size %1");
+                               require_8(0x88, "invalid precinct size %1");
+                               require_8(0x88, "invalid precinct size %1");
+                               require_8(0x88, "invalid precinct size %1");
+                               require_8(0x88, "invalid precinct size %1");
+                               require_8(0x88, "invalid precinct size %1");
+                       } else if (*marker_name == "TLM") {
+                               auto const len = get_16();
+                               ptr += len - 2;
+                               tlm = true;
+                       } else if (*marker_name == "QCC" || *marker_name == "COM") {
+                               auto const len = get_16();
+                               ptr += len - 2;
+                       } else if (*marker_name == "POC") {
+                               if (main_header_finished) {
+                                       num_POC_after_main++;
+                               } else {
+                                       num_POC_in_main++;
+                               }
+
+                               auto require_8_poc = [&](uint16_t value, string note) {
+                                       if (get_8() != value) {
+                                               notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER, String::compose(note, value) });
+                                       }
+                               };
+
+                               auto require_16_poc = [&](uint16_t value, string note) {
+                                       if (get_16() != value) {
+                                               notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER, String::compose(note, value) });
+                                       }
+                               };
+
+                               require_16_poc(16, "invalid length %1");
+                               require_8_poc(0, "invalid RSpoc %1");
+                               require_8_poc(0, "invalid CSpoc %1");
+                               require_16_poc(1, "invalid LYEpoc %1");
+                               require_8_poc(6, "invalid REpoc %1");
+                               require_8_poc(3, "invalid CEpoc %1");
+                               require_8_poc(4, "invalid Ppoc %1");
+                               require_8_poc(6, "invalid RSpoc %1");
+                               require_8_poc(0, "invalid CSpoc %1");
+                               require_16_poc(1, "invalid LYEpoc %1");
+                               require_8_poc(7, "invalid REpoc %1");
+                               require_8_poc(3, "invalid CEpoc %1");
+                               require_8_poc(4, "invalid Ppoc %1");
+                       }
+               }
+
+               if (num_COD == 0) {
+                       throw InvalidCodestream("no COD marker found");
+               }
+               if (num_COD > 1) {
+                       throw InvalidCodestream("more than one COD marker found");
+               }
+               if (num_QCD == 0) {
+                       throw InvalidCodestream("no QCD marker found");
+               }
+               if (num_QCD > 1) {
+                       throw InvalidCodestream("more than one QCD marker found");
+               }
+               if (num_POC_in_main != 0 && !fourk) {
+                       notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_2K, raw_convert<string>(num_POC_in_main) });
+               }
+               if (num_POC_in_main != 1 && fourk) {
+                       notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_4K, raw_convert<string>(num_POC_in_main) });
+               }
+               if (num_POC_after_main != 0) {
+                       notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_POC_MARKER_LOCATION });
+               }
+               if (!tlm) {
+                       notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_JPEG200_TLM_MARKER });
+               }
+       }
+       catch (InvalidCodestream const& e)
+       {
+               notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string(e.what()) });
+       }
+}
+
diff --git a/src/verify_j2k.h b/src/verify_j2k.h
new file mode 100644 (file)
index 0000000..3d9093d
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+    Copyright (C) 2021 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libdcp is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+/** @file  src/verify_j2k.h
+ *  @brief Verification that JPEG2000 files meet requirements
+ */
+
+
+#ifndef LIBDCP_VERIFY_J2K
+#define LIBDCP_VERIFY_J2K
+
+
+#include "verify.h"
+#include <vector>
+
+
+namespace dcp {
+
+
+class Data;
+
+
+void verify_j2k (std::shared_ptr<const Data> data, std::vector<VerificationNote>& notes);
+
+
+}
+
+
+#endif
index 10bb90c7dbd33ba90ac7976b3183e1107fdd2a6a..c5729b93fd2f48d5a95a4ce585b60106dea5151f 100644 (file)
@@ -109,6 +109,7 @@ def build(bld):
              types.cc
              util.cc
              verify.cc
+             verify_j2k.cc
              version.cc
              """
 
@@ -195,6 +196,7 @@ def build(bld):
               types.h
               util.h
               verify.h
+              verify_j2k.h
               version.h
               """
 
index 7ee42cf8835cb2d62e39d3ad1d2acf9be2379487..1f12bee4fcebd8dff0fb3c771f82fb3c5e9a802f 100644 (file)
@@ -515,4 +515,19 @@ black_picture_asset (boost::filesystem::path dir, int frames)
 }
 
 
+boost::filesystem::path
+find_file (boost::filesystem::path dir, string filename_part)
+{
+       boost::optional<boost::filesystem::path> found;
+       for (auto i: boost::filesystem::directory_iterator(dir)) {
+               if (i.path().filename().string().find(filename_part) != string::npos) {
+                       BOOST_REQUIRE (!found);
+                       found = i;
+               }
+       }
+       BOOST_REQUIRE (found);
+       return *found;
+}
+
+
 BOOST_GLOBAL_FIXTURE (TestConfig);
index 155c78981d4a6ad31294bd1d8751e49537bd94c7..57be5d1141cf22da8bebfc2f599c26416e92b7b7 100644 (file)
@@ -54,6 +54,7 @@ extern std::shared_ptr<dcp::DCP> make_simple_with_interop_ccaps (boost::filesyst
 extern std::shared_ptr<dcp::DCP> make_simple_with_smpte_ccaps (boost::filesystem::path path);
 extern std::shared_ptr<dcp::OpenJPEGImage> black_image (dcp::Size size = dcp::Size(1998, 1080));
 extern std::shared_ptr<dcp::ReelAsset> black_picture_asset (boost::filesystem::path dir, int frames = 24);
+extern boost::filesystem::path find_file (boost::filesystem::path dir, std::string filename_part);
 
 /** Creating an object of this class will make asdcplib's random number generation
  *  (more) predictable.
index f5a012a629f93ccd55ad26142640ccd851b3bbb8..e3efb57f7c1104e70974034d6ea2ec873b377e46 100644 (file)
@@ -53,6 +53,7 @@
 #include "test.h"
 #include "util.h"
 #include "verify.h"
+#include "verify_j2k.h"
 #include <boost/test/unit_test.hpp>
 #include <boost/algorithm/string.hpp>
 #include <cstdio>
@@ -2728,3 +2729,40 @@ BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
        check_verify_result ({dir}, {{dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED}});
 }
 
+
+BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
+{
+       vector<dcp::VerificationNote> notes;
+       dcp::MonoPictureAsset picture (find_file(private_test / "data" / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV", "j2c.mxf"));
+       auto reader = picture.start_read ();
+       auto frame = reader->get_frame (0);
+       verify_j2k (frame, notes);
+       dump_notes (notes);
+}
+
+
+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, notes);
+       dump_notes (notes);
+}
+
+
+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 (dcp::Standard::SMPTE);
+       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, notes);
+       dump_notes (notes);
+}
+