biggest_frame = max(biggest_frame, frame->size());
if (!mono_asset->encrypted() || mono_asset->key()) {
vector<VerificationNote> j2k_notes;
- verify_j2k (frame, j2k_notes);
+ verify_j2k(frame, i, mono_asset->frame_rate().numerator, j2k_notes);
check_and_add (j2k_notes);
}
progress (float(i) / duration);
biggest_frame = max(biggest_frame, max(frame->left()->size(), frame->right()->size()));
if (!stereo_asset->encrypted() || stereo_asset->key()) {
vector<VerificationNote> j2k_notes;
- verify_j2k (frame->left(), j2k_notes);
- verify_j2k (frame->right(), j2k_notes);
+ verify_j2k(frame->left(), i, stereo_asset->frame_rate().numerator, j2k_notes);
+ verify_j2k(frame->right(), i, stereo_asset->frame_rate().numerator, j2k_notes);
check_and_add (j2k_notes);
}
progress (float(i) / duration);
return String::compose("<MainSoundConfiguration> has an invalid value: %1", note.note().get());
case VerificationNote::Code::MISSING_FONT:
return String::compose("The font file for font ID \"%1\" was not found, or was not referred to in the ASSETMAP.", note.note().get());
+ case VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE:
+ return String::compose(
+ "Frame %1 has an image component that is too large (component %2 is %3 bytes in size).",
+ note.frame().get(), note.component().get(), note.size().get()
+ );
}
return "";
/** An interop subtitle file has a <LoadFont> node which refers to a font file that is not found.
* note contains the <LoadFont> ID
*/
- MISSING_FONT
+ MISSING_FONT,
+ /** A tile part in a JPEG2000 frame is too big.
+ * frame contains the frame index (counted from 0)
+ * component contains the component index (0, 1 or 2)
+ * size contains the invalid size in bytes.
+ */
+ INVALID_JPEG2000_TILE_PART_SIZE,
};
VerificationNote (Type type, Code code)
private:
enum class Data {
- NOTE, ///< further information about the error
- FILE, ///< path of file containing the error
- LINE ///< error line number within the FILE
+ NOTE, ///< further information about the error
+ FILE, ///< path of file containing the error
+ LINE, ///< error line number within the FILE
+ FRAME,
+ COMPONENT,
+ SIZE,
};
template <class T>
return data<uint64_t>(Data::LINE);
}
+ VerificationNote& set_frame(int frame) {
+ _data[Data::FRAME] = frame;
+ return *this;
+ }
+
+ boost::optional<int> frame() const {
+ return data<int>(Data::FRAME);
+ }
+
+ VerificationNote& set_component(int component) {
+ _data[Data::COMPONENT] = component;
+ return *this;
+ }
+
+ boost::optional<int> component() const {
+ return data<int>(Data::COMPONENT);
+ }
+
+ VerificationNote& set_size(int size) {
+ _data[Data::SIZE] = size;
+ return *this;
+ }
+
+ boost::optional<int> size() const {
+ return data<int>(Data::SIZE);
+ }
+
private:
Type _type;
Code _code;
void
-dcp::verify_j2k (shared_ptr<const Data> j2k, vector<VerificationNote>& notes)
+dcp::verify_j2k(shared_ptr<const Data> j2k, int frame_index, int frame_rate, vector<VerificationNote>& notes)
{
+ /* See ITU-T T800 (visible on https://github.com/Ymagis/ClairMeta/issues/130) */
+ unsigned int const max_tile_part_size = std::floor(200e6 / (8 * frame_rate));
+
try {
auto ptr = j2k->data();
auto end = ptr + j2k->size();
} 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 const tile_part_length = get_32();
+ auto const tile_part_index = get_8();
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_4K, raw_convert<string>(tile_parts) });
}
+ if (tile_part_length > max_tile_part_size) {
+ VerificationNote note{VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE};
+ note.set_frame(frame_index);
+ note.set_component(tile_part_index);
+ note.set_size(tile_part_length);
+ notes.push_back(note);
+ }
main_header_finished = true;
} else if (*marker_name == "SOD") {
while (ptr < (end - 1) && (ptr[0] != 0xff || ptr[1] < 0x90)) {
class Data;
-void verify_j2k (std::shared_ptr<const Data> data, std::vector<VerificationNote>& notes);
+/** @param frame_index Video frame index, so that notes can say which frame contains the problem.
+ * @param frame_rate Video frame rate (in frames per second) to calculate how big the tile parts
+ * can be.
+ */
+void verify_j2k(std::shared_ptr<const Data> data, int frame_index, int frame_rate, std::vector<VerificationNote>& notes);
}
#include "util.h"
#include "verify.h"
#include "verify_j2k.h"
-#include <boost/test/unit_test.hpp>
#include <boost/algorithm/string.hpp>
+#include <boost/random.hpp>
+#include <boost/test/unit_test.hpp>
#include <cstdio>
#include <iostream>
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);
+ verify_j2k(frame, 0, 24, notes);
BOOST_REQUIRE_EQUAL (notes.size(), 0U);
}
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);
+ verify_j2k(frame, 0, 24, notes);
BOOST_REQUIRE_EQUAL (notes.size(), 0U);
}
dcp::MonoPictureAsset picture (find_file(dir, "video"));
auto reader = picture.start_read ();
auto frame = reader->get_frame (0);
- verify_j2k (frame, notes);
+ verify_j2k(frame, 0, 24, notes);
BOOST_REQUIRE_EQUAL (notes.size(), 0U);
}
});
}
+
+BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
+{
+ boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
+ auto constexpr video_frames = 24;
+ auto constexpr sample_rate = 48000;
+
+ boost::filesystem::remove_all(path);
+ boost::filesystem::create_directories(path);
+
+ auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
+ auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
+
+ dcp::Size const size(1998, 1080);
+ auto image = make_shared<dcp::OpenJPEGImage>(size);
+ boost::random::mt19937 rng(1);
+ boost::random::uniform_int_distribution<> dist(0, 4095);
+ for (int c = 0; c < 3; ++c) {
+ for (int p = 0; p < (1998 * 1080); ++p) {
+ image->data(c)[p] = dist(rng);
+ }
+ }
+ auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
+ for (int i = 0; i < 24; ++i) {
+ picture_writer->write(j2c.data(), j2c.size());
+ }
+ picture_writer->finalize();
+
+ auto dcp = make_shared<dcp::DCP>(path);
+ auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
+ cpl->set_content_version(
+ dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
+ );
+ cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
+ cpl->set_main_sound_sample_rate(sample_rate);
+ cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
+ cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
+ cpl->set_version_number(1);
+
+ auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
+
+ auto reel = make_shared<dcp::Reel>(
+ make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
+ make_shared<dcp::ReelSoundAsset>(ms, 0)
+ );
+
+ cpl->add(reel);
+ dcp->add(cpl);
+ dcp->set_annotation_text("A Test DCP");
+ dcp->write_xml();
+
+ check_verify_result(
+ { path },
+ {
+ dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE).set_frame(0).set_component(0).set_size(1321721),
+ { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") },
+ { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
+ { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
+ });
+}
+