diff options
| -rw-r--r-- | src/verify.cc | 30 | ||||
| -rw-r--r-- | src/verify.h | 26 | ||||
| -rw-r--r-- | src/verify_j2k.cc | 346 | ||||
| -rw-r--r-- | src/verify_j2k.h | 60 | ||||
| -rw-r--r-- | src/wscript | 2 | ||||
| -rw-r--r-- | test/test.cc | 15 | ||||
| -rw-r--r-- | test/test.h | 1 | ||||
| -rw-r--r-- | test/verify_test.cc | 38 |
8 files changed, 516 insertions, 2 deletions
diff --git a/src/verify.cc b/src/verify.cc index e45c381e..0082cfcd 100644 --- a/src/verify.cc +++ b/src/verify.cc @@ -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 ""; diff --git a/src/verify.h b/src/verify.h index 0a8b39ca..d57958e2 100644 --- a/src/verify.h +++ b/src/verify.h @@ -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 index 00000000..2e1255ea --- /dev/null +++ b/src/verify_j2k.cc @@ -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 index 00000000..3d9093dd --- /dev/null +++ b/src/verify_j2k.h @@ -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 diff --git a/src/wscript b/src/wscript index 10bb90c7..c5729b93 100644 --- a/src/wscript +++ b/src/wscript @@ -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 """ diff --git a/test/test.cc b/test/test.cc index 7ee42cf8..1f12bee4 100644 --- a/test/test.cc +++ b/test/test.cc @@ -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); diff --git a/test/test.h b/test/test.h index 155c7898..57be5d11 100644 --- a/test/test.h +++ b/test/test.h @@ -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. diff --git a/test/verify_test.cc b/test/verify_test.cc index f5a012a6..e3efb57f 100644 --- a/test/verify_test.cc +++ b/test/verify_test.cc @@ -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); +} + |
