From be71939e858b00d42239a608d9f97918d4d014f6 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Thu, 7 May 2020 01:08:51 +0200 Subject: [PATCH 1/1] Check that JPEG2000 frames aren't too big (i.e. too many bytes). --- src/verify.cc | 101 +++++++++++++++++++++++++++++++++++++-- src/verify.h | 6 ++- test/verify_test.cc | 114 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 4 deletions(-) diff --git a/src/verify.cc b/src/verify.cc index 152f3dc2..b10f25c1 100644 --- a/src/verify.cc +++ b/src/verify.cc @@ -37,6 +37,10 @@ #include "reel.h" #include "reel_picture_asset.h" #include "reel_sound_asset.h" +#include "mono_picture_asset.h" +#include "mono_picture_frame.h" +#include "stereo_picture_asset.h" +#include "stereo_picture_frame.h" #include "exceptions.h" #include "compose.hpp" #include "raw_convert.h" @@ -70,9 +74,11 @@ using std::vector; using std::string; using std::cout; using std::map; +using std::max; using boost::shared_ptr; using boost::optional; using boost::function; +using boost::dynamic_pointer_cast; using namespace dcp; using namespace xercesc; @@ -332,6 +338,70 @@ verify_asset (shared_ptr dcp, shared_ptr reel_mxf, function frame) +{ + return frame->j2k_size (); +} + +int +biggest_frame_size (shared_ptr frame) +{ + return max(frame->left_j2k_size(), frame->right_j2k_size()); +} + + +template +optional +verify_picture_asset_type (shared_ptr reel_mxf, function progress) +{ + shared_ptr asset = dynamic_pointer_cast(reel_mxf->asset_ref().asset()); + if (!asset) { + return optional(); + } + + int biggest_frame = 0; + shared_ptr reader = asset->start_read (); + int64_t const duration = asset->intrinsic_duration (); + for (int64_t i = 0; i < duration; ++i) { + shared_ptr frame = reader->get_frame (i); + biggest_frame = max(biggest_frame, biggest_frame_size(frame)); + progress (float(i) / duration); + } + + static const int max_frame = rint(250 * 1000000 / (8 * asset->edit_rate().as_float())); + static const int risky_frame = rint(230 * 1000000 / (8 * asset->edit_rate().as_float())); + if (biggest_frame > max_frame) { + return VERIFY_PICTURE_ASSET_RESULT_BAD; + } else if (biggest_frame > risky_frame) { + return VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_BIG; + } + + return VERIFY_PICTURE_ASSET_RESULT_GOOD; +} + + +static VerifyPictureAssetResult +verify_picture_asset (shared_ptr reel_mxf, function progress) +{ + optional r = verify_picture_asset_type(reel_mxf, progress); + if (!r) { + r = verify_picture_asset_type(reel_mxf, progress); + } + + DCP_ASSERT (r); + return *r; +} + + list dcp::verify ( vector directories, @@ -398,20 +468,41 @@ dcp::verify ( } /* Check asset */ if (reel->main_picture()->asset_ref().resolved()) { - stage ("Checking picture asset hash", reel->main_picture()->asset()->file()); + boost::filesystem::path const file = *reel->main_picture()->asset()->file(); + stage ("Checking picture asset hash", file); VerifyAssetResult const r = verify_asset (dcp, reel->main_picture(), progress); switch (r) { case VERIFY_ASSET_RESULT_BAD: notes.push_back ( VerificationNote( - VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_HASH_INCORRECT, *reel->main_picture()->asset()->file() + VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_HASH_INCORRECT, file ) ); break; case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER: notes.push_back ( VerificationNote( - VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE, *reel->main_picture()->asset()->file() + VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE, file + ) + ); + break; + default: + break; + } + stage ("Checking picture frame sizes", reel->main_picture()->asset()->file()); + VerifyPictureAssetResult const pr = verify_picture_asset (reel->main_picture(), progress); + switch (pr) { + case VERIFY_PICTURE_ASSET_RESULT_BAD: + notes.push_back ( + VerificationNote( + VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_FRAME_TOO_LARGE, file + ) + ); + break; + case VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_BIG: + notes.push_back ( + VerificationNote( + VerificationNote::VERIFY_WARNING, VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE, file ) ); break; @@ -493,6 +584,10 @@ dcp::note_to_string (dcp::VerificationNote note) return String::compose("The intrinsic duration of an asset is less than 1 second long: %1", note.note().get()); case dcp::VerificationNote::DURATION_TOO_SMALL: return String::compose("The duration of an asset is less than 1 second long: %1", note.note().get()); + case dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE: + return String::compose("The instantaneous bit rate of the picture asset %1 is larger than the limit of 250Mbit/s in at least one place", note.file()->filename()); + case dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE: + return String::compose("The instantaneous bit rate of the picture asset %1 is close to the limit of 250Mbit/s in at least one place", note.file()->filename()); } return ""; diff --git a/src/verify.h b/src/verify.h index 8d29df6b..4bd91f55 100644 --- a/src/verify.h +++ b/src/verify.h @@ -82,7 +82,11 @@ public: /** An asset's IntrinsicDuration is less than 1 second */ INTRINSIC_DURATION_TOO_SMALL, /** An asset's Duration is less than 1 second */ - DURATION_TOO_SMALL + DURATION_TOO_SMALL, + /** The JPEG2000 data in at least one picture frame is larger than the equivalent of 250Mbit/s */ + PICTURE_FRAME_TOO_LARGE, + /** The JPEG2000 data in at least one picture frame is larger than the equivalent of 230Mbit/s */ + PICTURE_FRAME_NEARLY_TOO_LARGE, }; VerificationNote (Type type, Code code) diff --git a/test/verify_test.cc b/test/verify_test.cc index 2ff66f69..7a553d07 100644 --- a/test/verify_test.cc +++ b/test/verify_test.cc @@ -33,6 +33,14 @@ #include "verify.h" #include "util.h" +#include "j2k.h" +#include "reel.h" +#include "reel_mono_picture_asset.h" +#include "cpl.h" +#include "dcp.h" +#include "openjpeg_image.h" +#include "mono_picture_asset.h" +#include "mono_picture_asset_writer.h" #include "compose.hpp" #include #include @@ -46,6 +54,8 @@ using std::string; using std::vector; using std::make_pair; using boost::optional; +using boost::shared_ptr; + static list > > stages; static int next_verify_test_number = 1; @@ -77,6 +87,10 @@ setup (int reference_number, int verify_test_number) } + +/** Class that can alter a file by searching and replacing strings within it. + * On destruction modifies the file whose name was given to the constructor. + */ class Editor { public: @@ -140,6 +154,10 @@ BOOST_AUTO_TEST_CASE (verify_test1) BOOST_REQUIRE (st->second); BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/video.mxf", next_verify_test_number))); ++st; + BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes"); + BOOST_REQUIRE (st->second); + BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/video.mxf", next_verify_test_number))); + ++st; BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash"); BOOST_REQUIRE (st->second); BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/audio.mxf", next_verify_test_number))); @@ -451,6 +469,10 @@ BOOST_AUTO_TEST_CASE (verify_test13) BOOST_REQUIRE (st->second); BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf", next_verify_test_number))); ++st; + BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes"); + BOOST_REQUIRE (st->second); + BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf", next_verify_test_number))); + ++st; BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash"); BOOST_REQUIRE (st->second); BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf", next_verify_test_number))); @@ -493,3 +515,95 @@ BOOST_AUTO_TEST_CASE (verify_test14) next_verify_test_number++; } + +static +shared_ptr +random_image () +{ + shared_ptr image(new dcp::OpenJPEGImage(dcp::Size(1998, 1080))); + int const pixels = 1998 * 1080; + for (int i = 0; i < 3; ++i) { + int* p = image->data(i); + for (int j = 0; j < pixels; ++j) { + *p++ = rand(); + } + } + return image; +} + + +static +void +dcp_from_frame (dcp::Data const& frame, boost::filesystem::path dir) +{ + shared_ptr asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE)); + boost::filesystem::create_directories (dir); + shared_ptr writer = asset->start_write (dir / "pic.mxf", true); + for (int i = 0; i < 24; ++i) { + writer->write (frame.data().get(), frame.size()); + } + writer->finalize (); + + shared_ptr reel_asset(new dcp::ReelMonoPictureAsset(asset, 0)); + shared_ptr reel(new dcp::Reel()); + reel->add (reel_asset); + shared_ptr cpl(new dcp::CPL("hello", dcp::FEATURE)); + cpl->add (reel); + shared_ptr dcp(new dcp::DCP(dir)); + dcp->add (cpl); + dcp->write_xml (dcp::SMPTE); +} + + +/* DCP with an over-sized JPEG2000 frame */ +BOOST_AUTO_TEST_CASE (verify_test15) +{ + /* Compress a random image with a bandwidth of 500Mbit/s */ + shared_ptr image = random_image (); + dcp::Data frame = dcp::compress_j2k (image, 500000000, 24, false, false); + + boost::filesystem::path const dir("build/test/verify_test15"); + dcp_from_frame (frame, dir); + + vector dirs; + dirs.push_back (dir); + list notes = dcp::verify (dirs, &stage, &progress, "xsd"); + BOOST_REQUIRE_EQUAL (notes.size(), 1); + BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE); +} + + +/* DCP with a nearly over-sized JPEG2000 frame */ +BOOST_AUTO_TEST_CASE (verify_test16) +{ + /* Compress a random image with a bandwidth of 500Mbit/s */ + shared_ptr image = random_image (); + dcp::Data frame = dcp::compress_j2k (image, 240000000, 24, false, false); + + boost::filesystem::path const dir("build/test/verify_test16"); + dcp_from_frame (frame, dir); + + vector dirs; + dirs.push_back (dir); + list notes = dcp::verify (dirs, &stage, &progress, "xsd"); + BOOST_REQUIRE_EQUAL (notes.size(), 1); + BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE); +} + + +/* DCP with a within-range JPEG2000 frame */ +BOOST_AUTO_TEST_CASE (verify_test17) +{ + /* Compress a random image with a bandwidth of 500Mbit/s */ + shared_ptr image = random_image (); + dcp::Data frame = dcp::compress_j2k (image, 100000000, 24, false, false); + + boost::filesystem::path const dir("build/test/verify_test17"); + dcp_from_frame (frame, dir); + + vector dirs; + dirs.push_back (dir); + list notes = dcp::verify (dirs, &stage, &progress, "xsd"); + BOOST_REQUIRE_EQUAL (notes.size(), 0); +} + -- 2.30.2